diff --git a/.gitmodules b/.gitmodules
index 13b69f4e43..3d50c2b6e2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -199,6 +199,13 @@
path = vendor/nim-toml-serialization
url = https://github.com/status-im/nim-toml-serialization.git
ignore = untracked
+[submodule "vendor/DOtherSide"]
+ path = vendor/DOtherSide
+ url = https://github.com/filcuc/DOtherSide.git
+ branch = master
+[submodule "vendor/nimqml"]
+ path = vendor/nimqml
+ url = https://github.com/status-im/nimqml.git
branch = master
[submodule "vendor/gnosis-chain-configs"]
path = vendor/gnosis-chain-configs
diff --git a/Makefile b/Makefile
index e0faa59f23..e244fbfad5 100644
--- a/Makefile
+++ b/Makefile
@@ -485,6 +485,10 @@ force_build_alone_tools: | $(FORCE_BUILD_ALONE_TOOLS_DEPS)
# https://www.gnu.org/software/make/manual/html_node/Multiple-Rules.html#Multiple-Rules
# Already defined as a reult
nimbus_beacon_node: force_build_alone_tools
+ngui/ngui: | build deps
+ + echo -e $(BUILD_MSG) "build/$@" && \
+ MAKE="$(MAKE)" V="$(V)" $(ENV_SCRIPT) scripts/compile_nim_program.sh $@ "ngui.ngui.nim" $(NIM_PARAMS) && \
+ echo -e $(BUILD_END_MSG) "ngui/ngui"
GOERLI_TESTNETS_PARAMS := \
--tcp-port=$$(( $(BASE_PORT) + $(NODE_ID) )) \
diff --git a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim
index b5526c7923..251d7250b9 100644
--- a/beacon_chain/spec/eth2_apis/rest_validator_calls.nim
+++ b/beacon_chain/spec/eth2_apis/rest_validator_calls.nim
@@ -13,6 +13,14 @@ import
export client, rest_types, eth2_rest_serialization
+proc getAttesterDuties*(
+ epoch: Epoch,
+ body: seq[ValidatorIndex]
+ ): RestResponse[GetAttesterDutiesResponse] {.
+ rest, endpoint: "/eth/v1/validator/duties/attester/{epoch}",
+ meth: MethodPost.}
+ ## https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties
+
proc getAttesterDutiesPlain*(
epoch: Epoch,
body: seq[ValidatorIndex]
@@ -21,6 +29,13 @@ proc getAttesterDutiesPlain*(
meth: MethodPost.}
## https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties
+proc getProposerDuties*(
+ epoch: Epoch
+ ): RestResponse[GetProposerDutiesResponse] {.
+ rest, endpoint: "/eth/v1/validator/duties/proposer/{epoch}",
+ meth: MethodGet.}
+ ## https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties
+
proc getProposerDutiesPlain*(
epoch: Epoch
): RestPlainResponse {.
@@ -28,6 +43,14 @@ proc getProposerDutiesPlain*(
meth: MethodGet.}
## https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties
+proc getSyncCommitteeDuties*(
+ epoch: Epoch,
+ body: seq[ValidatorIndex]
+ ): RestResponse[GetSyncCommitteeDutiesResponse] {.
+ rest, endpoint: "/eth/v1/validator/duties/sync/{epoch}",
+ meth: MethodPost.}
+ ## https://ethereum.github.io/beacon-APIs/#/Validator/getSyncCommitteeDuties
+
proc getSyncCommitteeDutiesPlain*(
epoch: Epoch,
body: seq[ValidatorIndex]
diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim
index 01f01a0f88..7cfada9b8b 100644
--- a/beacon_chain/spec/forks.nim
+++ b/beacon_chain/spec/forks.nim
@@ -180,6 +180,10 @@ type
phase0.Attestation |
electra.SingleAttestation
+ ForkyAttesterSlashing* =
+ phase0.AttesterSlashing |
+ electra.AttesterSlashing
+
ForkedAttestation* = object
case kind*: ConsensusFork
of ConsensusFork.Phase0: phase0Data*: phase0.Attestation
@@ -1252,28 +1256,43 @@ template getForkedBlockField*(
of ConsensusFork.Electra: unsafeAddr x.electraData.message.y
of ConsensusFork.Fulu: unsafeAddr x.fuluData.message.y)[]
-template signature*(x: ForkedSignedBeaconBlock |
+template getForkedBodyField*(
+ x: ForkedSignedBeaconBlock |
+ ForkedMsgTrustedSignedBeaconBlock |
+ ForkedTrustedSignedBeaconBlock,
+ y: untyped): untyped =
+ # unsafeAddr avoids a copy of the field in some cases
+ (case x.kind
+ of ConsensusFork.Phase0: unsafeAddr x.phase0Data.message.body.y
+ of ConsensusFork.Altair: unsafeAddr x.altairData.message.body.y
+ of ConsensusFork.Bellatrix: unsafeAddr x.bellatrixData.message.body.y
+ of ConsensusFork.Capella: unsafeAddr x.capellaData.message.body.y
+ of ConsensusFork.Deneb: unsafeAddr x.denebData.message.body.y
+ of ConsensusFork.Electra: unsafeAddr x.electraData.message.body.y
+ of ConsensusFork.Fulu: unsafeAddr x.fuluData.message.body.y)[]
+
+func signature*(x: ForkedSignedBeaconBlock |
ForkedMsgTrustedSignedBeaconBlock |
ForkedSignedBlindedBeaconBlock): ValidatorSig =
withBlck(x): forkyBlck.signature
-template signature*(x: ForkedTrustedSignedBeaconBlock): TrustedSig =
+func signature*(x: ForkedTrustedSignedBeaconBlock): TrustedSig =
withBlck(x): forkyBlck.signature
-template root*(x: ForkedSignedBeaconBlock |
+func root*(x: ForkedSignedBeaconBlock |
ForkedMsgTrustedSignedBeaconBlock |
ForkedTrustedSignedBeaconBlock): Eth2Digest =
withBlck(x): forkyBlck.root
-template slot*(x: ForkedSignedBeaconBlock |
+func slot*(x: ForkedSignedBeaconBlock |
ForkedMsgTrustedSignedBeaconBlock |
ForkedTrustedSignedBeaconBlock): Slot =
withBlck(x): forkyBlck.message.slot
-template shortLog*(x: ForkedBeaconBlock | ForkedBlindedBeaconBlock): auto =
+func shortLog*(x: ForkedBeaconBlock | ForkedBlindedBeaconBlock): auto =
withBlck(x): shortLog(forkyBlck)
-template shortLog*(x: ForkedSignedBeaconBlock |
+func shortLog*(x: ForkedSignedBeaconBlock |
ForkedMsgTrustedSignedBeaconBlock |
ForkedTrustedSignedBeaconBlock |
ForkedSignedBlindedBeaconBlock): auto =
diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim
index 7f3280991f..9228b72800 100644
--- a/beacon_chain/spec/helpers.nim
+++ b/beacon_chain/spec/helpers.nim
@@ -11,6 +11,7 @@
import
# Status libraries
+ std/times,
stew/[byteutils, endians2, objects],
nimcrypto/sha2,
chronicles,
diff --git a/ngui/.gitignore b/ngui/.gitignore
new file mode 100644
index 0000000000..8d657bc2ad
--- /dev/null
+++ b/ngui/.gitignore
@@ -0,0 +1 @@
+resources.cpp
diff --git a/ngui/attestationlist.nim b/ngui/attestationlist.nim
new file mode 100644
index 0000000000..38624b2b75
--- /dev/null
+++ b/ngui/attestationlist.nim
@@ -0,0 +1,80 @@
+import
+ std/[sequtils, tables],
+ NimQml,
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ ../beacon_chain/spec/[eth2_merkleization, helpers],
+ ./objecttablemodel,
+ ./utils
+
+type AttestationInfo* = object
+ slot*: int
+ index*: int
+ beacon_block_root*: string
+ source_epoch*: int
+ source_root*: string
+ target_epoch*: int
+ target_root*: string
+ aggregation_bits*: string
+
+proc toAttestationInfo*(v: phase0.Attestation): AttestationInfo =
+ AttestationInfo(
+ slot: v.data.slot.int,
+ index: v.data.index.int,
+ beacon_block_root: toBlockLink(v.data.beacon_block_root),
+ source_epoch: v.data.source.epoch.int,
+ source_root: toBlockLink(v.data.source.root),
+ target_epoch: v.data.target.epoch.int,
+ target_root: toBlockLink(v.data.target.root),
+ aggregation_bits: $v.aggregation_bits,
+ )
+
+proc toAttestationInfo*(v: electra.Attestation): AttestationInfo =
+ AttestationInfo(
+ slot: v.data.slot.int,
+ index: 0.int,
+ beacon_block_root: toBlockLink(v.data.beacon_block_root),
+ source_epoch: v.data.source.epoch.int,
+ source_root: toBlockLink(v.data.source.root),
+ target_epoch: v.data.target.epoch.int,
+ target_root: toBlockLink(v.data.target.root),
+ aggregation_bits: $v.aggregation_bits,
+ )
+
+QtObject:
+ type AttestationList* = ref object of QAbstractTableModel
+ # TODO this could be a generic ObjectTableModel, except generics + method don't work..
+ data: ObjectTableModelImpl[AttestationInfo]
+
+ proc setup(self: AttestationList) =
+ self.QAbstractTableModel.setup
+
+ proc delete(self: AttestationList) =
+ self.QAbstractTableModel.delete
+
+ proc newAttestationList*(data: seq[AttestationInfo]): AttestationList =
+ new(result, delete)
+ result.data = ObjectTableModelImpl[AttestationInfo](items: data)
+ result.setup
+
+ method rowCount(self: AttestationList, index: QModelIndex = nil): int =
+ self.data.rowCount(index)
+
+ method columnCount(self: AttestationList, index: QModelIndex = nil): int =
+ self.data.columnCount(index)
+
+ method headerData*(
+ self: AttestationList, section: int, orientation: QtOrientation, role: int
+ ): QVariant =
+ self.data.headerData(section, orientation, role)
+
+ method data(self: AttestationList, index: QModelIndex, role: int): QVariant =
+ self.data.data(index, role)
+
+ method roleNames(self: AttestationList): Table[int, string] =
+ self.data.roleNames()
+
+ proc setNewData*(self: AttestationList, v: seq[AttestationInfo]) =
+ self.data.setNewData(self, v)
+
+ proc sort*(self: AttestationList, section: int) {.slot.} =
+ self.data.sort(self, section)
diff --git a/ngui/attesterslashinglist.nim b/ngui/attesterslashinglist.nim
new file mode 100644
index 0000000000..1b8b7e4f8a
--- /dev/null
+++ b/ngui/attesterslashinglist.nim
@@ -0,0 +1,56 @@
+import
+ std/[sequtils, tables],
+ NimQml,
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ ../beacon_chain/spec/[helpers],
+ ./objecttablemodel,
+ ./utils
+
+type AttesterSlashingInfo* = object
+ info*: string
+
+proc toAttesterSlashingInfo*(v: ForkyAttesterSlashing): AttesterSlashingInfo =
+ AttesterSlashingInfo(info: $v)
+
+QtObject:
+ type AttesterSlashingList* = ref object of QAbstractTableModel
+ # TODO this could be a generic ObjectTableModel, except generics + method don't work..
+ data: ObjectTableModelImpl[AttesterSlashingInfo]
+
+ proc setup(self: AttesterSlashingList) =
+ self.QAbstractTableModel.setup
+
+ proc delete(self: AttesterSlashingList) =
+ self.QAbstractTableModel.delete
+
+ proc newAttesterSlashingList*(
+ data: openArray[ForkyAttesterSlashing]
+ ): AttesterSlashingList =
+ new(result, delete)
+ result.data = ObjectTableModelImpl[AttesterSlashingInfo](
+ items: data.mapIt(it.toAttesterSlashingInfo())
+ )
+ result.setup
+
+ method rowCount(self: AttesterSlashingList, index: QModelIndex = nil): int =
+ self.data.rowCount(index)
+
+ method columnCount(self: AttesterSlashingList, index: QModelIndex = nil): int =
+ self.data.columnCount(index)
+
+ method headerData*(
+ self: AttesterSlashingList, section: int, orientation: QtOrientation, role: int
+ ): QVariant =
+ self.data.headerData(section, orientation, role)
+
+ method data(self: AttesterSlashingList, index: QModelIndex, role: int): QVariant =
+ self.data.data(index, role)
+
+ method roleNames(self: AttesterSlashingList): Table[int, string] =
+ self.data.roleNames()
+
+ proc setNewData*(self: AttesterSlashingList, v: openArray[ForkyAttesterSlashing]) =
+ self.data.setNewData(self, v.mapIt(it.toAttesterSlashingInfo()))
+
+ proc sort*(self: AttesterSlashingList, section: int) {.slot.} =
+ self.data.sort(self, section)
diff --git a/ngui/blockmodel.nim b/ngui/blockmodel.nim
new file mode 100644
index 0000000000..451e0c2a70
--- /dev/null
+++ b/ngui/blockmodel.nim
@@ -0,0 +1,154 @@
+import
+ std/[sequtils, times],
+ NimQml,
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ "."/[
+ attestationlist, depositlist, attesterslashinglist, proposerslashinglist,
+ voluntaryexitlist, utils,
+ ]
+
+QtObject:
+ type BlockModel* = ref object of QObject
+ blck: ForkedSignedBeaconBlock
+ attestationsx: AttestationList
+ depositsx: DepositList
+ attester_slashingsx: AttesterSlashingList
+ proposer_slashingsx: ProposerSlashingList
+ voluntary_exitsx: VoluntaryExitList
+ genesis_time*: uint64
+
+ proc delete*(self: BlockModel) =
+ self.QObject.delete
+
+ proc setup*(self: BlockModel) =
+ self.QObject.setup
+
+ proc newBlockModel*(
+ forked: ForkedSignedBeaconBlock, genesis_time: uint64
+ ): BlockModel =
+ let res = withBlck(forked):
+ BlockModel(
+ blck: forked,
+ attestationsx: newAttestationList(
+ forkyBlck.message.body.attestations.asSeq().mapIt(it.toAttestationInfo())
+ ),
+ depositsx:
+ newDepositList(forkyBlck.message.body.deposits.mapIt(it.toDepositInfo())),
+ attester_slashingsx:
+ newAttesterSlashingList(forkyBlck.message.body.attester_slashings.asSeq()),
+ proposer_slashingsx:
+ newProposerSlashingList(forkyBlck.message.body.proposer_slashings.asSeq()),
+ voluntary_exitsx:
+ newVoluntaryExitList(forkyBlck.message.body.voluntary_exits.asSeq()),
+ genesis_time: genesis_time,
+ )
+ res.setup()
+ res
+
+ proc `blck=`*(self: BlockModel, forked: ForkedSignedBeaconBlock) =
+ self.blck = forked
+ withBlck(forked):
+ self.attestationsx.setNewData(
+ forkyBlck.message.body.attestations.asSeq().mapIt(it.toAttestationInfo())
+ )
+ self.depositsx.setNewData(
+ forkyBlck.message.body.deposits.mapIt(it.toDepositInfo())
+ )
+ self.attester_slashingsx.setNewData(
+ forkyBlck.message.body.attester_slashings.asSeq()
+ )
+ self.proposer_slashingsx.setNewData(
+ forkyBlck.message.body.proposer_slashings.asSeq()
+ )
+ self.voluntary_exitsx.setNewData(forkyBlck.message.body.voluntary_exits.asSeq())
+
+ proc slot*(self: BlockModel): int {.slot.} =
+ getForkedBlockField(self.blck, slot).int
+
+ QtProperty[int] slot:
+ read = slot
+
+ proc time*(self: BlockModel): string {.slot.} =
+ let t = self.genesis_time + getForkedBlockField(self.blck, slot) * SECONDS_PER_SLOT
+ $fromUnix(t.int64).utc
+
+ QtProperty[string] time:
+ read = time
+
+ proc root*(self: BlockModel): string {.slot.} =
+ toDisplayHex(self.blck.root.data)
+
+ QtProperty[string] root:
+ read = root
+
+ proc proposer_index*(self: BlockModel): int {.slot.} =
+ getForkedBlockField(self.blck, proposer_index).int
+
+ QtProperty[int] proposer_index:
+ read = proposer_index
+
+ proc parent_root*(self: BlockModel): string {.slot.} =
+ toBlockLink(getForkedBlockField(self.blck, parent_root))
+
+ QtProperty[string] parent_root:
+ read = parent_root
+
+ proc state_root*(self: BlockModel): string {.slot.} =
+ toDisplayHex(getForkedBlockField(self.blck, state_root).data)
+
+ QtProperty[string] state_root:
+ read = state_root
+
+ proc randao_reveal*(self: BlockModel): string {.slot.} =
+ toDisplayHex(getForkedBodyField(self.blck, randao_reveal))
+
+ QtProperty[string] randao_reveal:
+ read = randao_reveal
+
+ proc eth1_data*(self: BlockModel): string {.slot.} =
+ RestJson.encode(getForkedBodyField(self.blck, eth1_data), pretty = true)
+
+ QtProperty[string] eth1_data:
+ read = eth1_data
+
+ proc graffiti*(self: BlockModel): string {.slot.} =
+ $getForkedBodyField(self.blck, graffiti)
+
+ QtProperty[string] graffiti:
+ read = graffiti
+
+ proc proposer_slashings*(self: BlockModel): QVariant {.slot.} =
+ newQVariant(self.proposer_slashingsx)
+
+ QtProperty[QVariant] proposer_slashings:
+ read = proposer_slashings
+
+ proc attester_slashings*(self: BlockModel): QVariant {.slot.} =
+ newQVariant(self.attester_slashingsx)
+
+ QtProperty[QVariant] attester_slashings:
+ read = attester_slashings
+
+ proc attestations*(self: BlockModel): QVariant {.slot.} =
+ newQVariant(self.attestationsx)
+
+ QtProperty[QVariant] attestations:
+ read = attestations
+
+ proc deposits*(self: BlockModel): QVariant {.slot.} =
+ newQVariant(self.depositsx)
+
+ QtProperty[QVariant] deposits:
+ read = deposits
+
+ proc voluntary_exits*(self: BlockModel): QVariant {.slot.} =
+ newQVariant(self.voluntary_exitsx)
+
+ QtProperty[QVariant] voluntary_exits:
+ read = voluntary_exits
+
+ proc signature*(self: BlockModel): string {.slot.} =
+ toDisplayHex(self.blck.signature)
+
+ QtProperty[string] signature:
+ read = signature
diff --git a/ngui/depositlist.nim b/ngui/depositlist.nim
new file mode 100644
index 0000000000..883fa69d70
--- /dev/null
+++ b/ngui/depositlist.nim
@@ -0,0 +1,55 @@
+import
+ std/[tables], NimQml, ../beacon_chain/spec/datatypes/base, ./objecttablemodel, ./utils
+
+type DepositInfo* = object
+ pubkey*: string
+ withdrawal_credentials*: string
+ amount*: Gwei
+ signature*: string
+
+proc toDepositInfo*(v: Deposit): DepositInfo =
+ DepositInfo(
+ pubkey: toDisplayHex(v.data.pubkey.toRaw()),
+ withdrawal_credentials: toDisplayHex(v.data.withdrawal_credentials),
+ amount: v.data.amount,
+ signature: toDisplayHex(v.data.signature),
+ )
+
+QtObject:
+ type DepositList* = ref object of QAbstractTableModel
+ # TODO this could be a generic ObjectTableModel, except generics + method don't work..
+ data: ObjectTableModelImpl[DepositInfo]
+
+ proc setup(self: DepositList) =
+ self.QAbstractTableModel.setup
+
+ proc delete(self: DepositList) =
+ self.QAbstractTableModel.delete
+
+ proc newDepositList*(data: seq[DepositInfo]): DepositList =
+ new(result, delete)
+ result.data = ObjectTableModelImpl[DepositInfo](items: data)
+ result.setup
+
+ method rowCount(self: DepositList, index: QModelIndex = nil): int =
+ self.data.rowCount(index)
+
+ method columnCount(self: DepositList, index: QModelIndex = nil): int =
+ self.data.columnCount(index)
+
+ method headerData*(
+ self: DepositList, section: int, orientation: QtOrientation, role: int
+ ): QVariant =
+ self.data.headerData(section, orientation, role)
+
+ method data(self: DepositList, index: QModelIndex, role: int): QVariant =
+ self.data.data(index, role)
+
+ method roleNames(self: DepositList): Table[int, string] =
+ self.data.roleNames()
+
+ proc setNewData*(self: DepositList, v: seq[DepositInfo]) =
+ self.data.setNewData(self, v)
+
+ proc sort*(self: DepositList, section: int) {.slot.} =
+ self.data.sort(self, section)
diff --git a/ngui/epochmodel.nim b/ngui/epochmodel.nim
new file mode 100644
index 0000000000..142aab7aac
--- /dev/null
+++ b/ngui/epochmodel.nim
@@ -0,0 +1,54 @@
+import NimQml
+
+import ../beacon_chain/spec/eth2_apis/rest_beacon_client, ./slotlist
+
+QtObject:
+ type EpochModel* = ref object of QObject
+ client: RestClientRef
+ epoch: int
+ slotList: SlotList
+
+ proc delete*(self: EpochModel) =
+ self.QObject.delete
+
+ proc setup*(self: EpochModel) =
+ self.QObject.setup
+
+ proc newEpochModel*(client: RestClientRef, epoch: int): EpochModel =
+ let data = client.loadSlots(epoch.Epoch)
+ let res = EpochModel(client: client, epoch: epoch, slotList: newSlotList(data))
+ res.setup()
+ res
+
+ proc epoch*(self: EpochModel): int {.slot.} =
+ self.epoch
+
+ proc epochChanged*(self: EpochModel, v: int) {.signal.}
+ QtProperty[int] epoch:
+ read = epoch
+ notify = epochChanged
+
+ proc getSlotList*(self: EpochModel): QVariant {.slot.} =
+ newQVariant(self.slotList)
+
+ QtProperty[QVariant] slotList:
+ read = getSlotList
+
+ proc setNewData*(self: EpochModel, epoch: int, data: seq[SlotInfo]) =
+ self.epoch = epoch
+ self.epochChanged(epoch)
+
+ self.slotList.setNewData(data)
+
+ proc reload(self: EpochModel) {.slot.} =
+ self.slotList.setNewData(self.client.loadSlots(self.epoch.Epoch))
+
+ proc next(self: EpochModel) {.slot.} =
+ self.epoch = self.epoch + 1
+ self.epochChanged(self.epoch)
+ self.reload() # TODO listen to epochchanged
+
+ proc prev(self: EpochModel) {.slot.} =
+ self.epoch = self.epoch - 1
+ self.epochChanged(self.epoch)
+ self.reload() # TODO listen to epochchanged
diff --git a/ngui/footermodel.nim b/ngui/footermodel.nim
new file mode 100644
index 0000000000..c8c54d577f
--- /dev/null
+++ b/ngui/footermodel.nim
@@ -0,0 +1,54 @@
+import NimQml
+
+QtObject:
+ type FooterModel* = ref object of QObject
+ finalized: string
+ head: string
+ syncing: string
+
+ proc delete*(self: FooterModel) =
+ self.QObject.delete
+
+ proc setup*(self: FooterModel) =
+ self.QObject.setup
+
+ proc newFooterModel*(): FooterModel =
+ let res = FooterModel()
+ res.setup()
+ res
+
+ proc finalized*(self: FooterModel): string {.slot.} =
+ self.finalized
+
+ proc finalizedChanged*(self: FooterModel, v: string) {.signal.}
+ proc `finalized=`*(self: FooterModel, v: string) =
+ self.finalized = v
+ self.finalizedChanged(v)
+
+ QtProperty[string] finalized:
+ read = finalized
+ notify = finalizedChanged
+
+ proc head*(self: FooterModel): string {.slot.} =
+ self.head
+
+ proc headChanged*(self: FooterModel, v: string) {.signal.}
+ proc `head=`*(self: FooterModel, v: string) =
+ self.head = v
+ self.headChanged(v)
+
+ QtProperty[string] head:
+ read = head
+ notify = headChanged
+
+ proc syncing*(self: FooterModel): string {.slot.} =
+ self.syncing
+
+ proc syncingChanged*(self: FooterModel, v: string) {.signal.}
+ proc `syncing=`*(self: FooterModel, v: string) =
+ self.syncing = v
+ self.syncingChanged(v)
+
+ QtProperty[string] syncing:
+ read = syncing
+ notify = syncingChanged
diff --git a/ngui/mainmodel.nim b/ngui/mainmodel.nim
new file mode 100644
index 0000000000..016e391417
--- /dev/null
+++ b/ngui/mainmodel.nim
@@ -0,0 +1,162 @@
+import
+ NimQml,
+ "."/[blockmodel, footermodel, epochmodel, peerlist, slotlist, nodemodel, poolmodel]
+
+import
+ std/[os, strutils],
+ chronos,
+ metrics,
+
+ # Local modules
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ ../beacon_chain/spec/datatypes/[phase0, altair],
+ ../beacon_chain/spec/[eth2_merkleization, helpers]
+
+QtObject:
+ type MainModel* = ref object of QObject
+ app: QApplication
+ cfg: RuntimeConfig
+ blck: BlockModel
+ footer: FooterModel
+ client: RestClientRef
+ peerList: PeerList
+ epochModel: EpochModel
+ nodeModel: NodeModel
+ poolModel: PoolModel
+
+ genesis: RestGenesis
+ currentIndex: int
+
+ proc delete*(self: MainModel) =
+ self.QObject.delete
+ self.blck.delete
+
+ proc setup(self: MainModel) =
+ self.QObject.setup
+ self.blck.setup
+
+ proc newMainModel*(app: QApplication, url: string, cfg: RuntimeConfig): MainModel =
+ let client = RestClientRef.new(url).get()
+
+ var
+ headBlock =
+ (waitFor client.getBlockV2(BlockIdent.init(BlockIdentType.Head), cfg)).get()
+ epoch = getForkedBlockField(headBlock[], slot).epoch
+ genesis = (waitFor client.getGenesis()).data.data
+ peerList = newPeerList(@[])
+
+ let res = MainModel(
+ app: app,
+ cfg: cfg,
+ blck: newBlockModel(headBlock[], genesis.genesis_time),
+ client: client,
+ footer: newFooterModel(),
+ peerList: peerList,
+ epochModel: newEpochModel(client, epoch.int),
+ nodeModel: newNodeModel(client),
+ poolModel: newPoolModel(client),
+ genesis: genesis,
+ )
+ res.setup()
+ res
+
+ proc onExitTriggered(self: MainModel) {.slot.} =
+ self.app.quit
+
+ proc updateFooter(self: MainModel) {.slot.} =
+ let
+ checkpoints = (
+ waitFor self.client.getStateFinalityCheckpoints(
+ StateIdent.init(StateIdentType.Head)
+ )
+ ).data.data
+ head = (waitFor self.client.getBlockHeader(BlockIdent.init(BlockIdentType.Head))).valueOr(
+ default(GetBlockHeaderResponse)
+ ).data
+ syncing = (waitFor self.client.getSyncingStatus()).data.data
+
+ self.footer.finalized = $shortLog(checkpoints.finalized)
+ self.footer.head = $shortLog(head.header.message.slot)
+ self.footer.syncing = $syncing
+
+ proc updateSlots(self: MainModel) {.slot.} =
+ let slots = self.client.loadSlots(self.epochModel.epoch.Epoch)
+ self.epochModel.setNewData(self.epochModel.epoch.int, slots)
+
+ proc updatePeers(self: MainModel) {.slot.} =
+ try:
+ self.peerList.setNewData(waitFor(self.client.getPeers(@[], @[])).data.data)
+ except CatchableError as exc:
+ echo exc.msg
+
+ proc getPeerList*(self: MainModel): QVariant {.slot.} =
+ newQVariant(self.peerList)
+
+ QtProperty[QVariant] peerList:
+ read = getPeerList
+
+ proc getFooter*(self: MainModel): QVariant {.slot.} =
+ newQVariant(self.footer)
+
+ QtProperty[QVariant] footer:
+ read = getFooter
+
+ proc getEpochModel*(self: MainModel): QVariant {.slot.} =
+ newQVariant(self.epochModel)
+
+ QtProperty[QVariant] epochModel:
+ read = getEpochModel
+
+ proc getBlck(self: MainModel): QVariant {.slot.} =
+ newQVariant(self.blck)
+
+ proc blckChanged*(self: MainModel, blck: QVariant) {.signal.}
+ proc setBlck(self: MainModel, blck: ForkedSignedBeaconBlock) =
+ self.blck.blck = blck
+ self.blckChanged(newQVariant(self.blck))
+
+ QtProperty[QVariant] blck:
+ read = getBlck
+ write = setBlck
+ notify = blckChanged
+
+ proc getCurrentIndex(self: MainModel): int {.slot.} =
+ self.currentIndex
+
+ proc currentIndexChanged*(self: MainModel, v: int) {.signal.}
+ proc setCurrentIndex(self: MainModel, v: int) =
+ self.currentIndex = v
+ self.currentIndexChanged(v)
+
+ QtProperty[int] currentIndex:
+ read = getCurrentIndex
+ write = setCurrentIndex
+ notify = currentIndexChanged
+
+ proc getNodeModel(self: MainModel): QVariant {.slot.} =
+ newQVariant(self.nodeModel)
+
+ QtProperty[QVariant] nodeModel:
+ read = getNodeModel
+
+ proc getPoolModel(self: MainModel): QVariant {.slot.} =
+ newQVariant(self.poolModel)
+
+ QtProperty[QVariant] poolModel:
+ read = getPoolModel
+
+ proc onLoadBlock(self: MainModel, root: string) {.slot.} =
+ try:
+ var blck = waitFor(
+ self.client.getBlockV2(BlockIdent.decodeString(root).tryGet(), self.cfg)
+ )
+ if blck.isSome():
+ self.setBlck(blck.get()[])
+ except CatchableError as exc:
+ echo exc.msg
+ discard
+
+ proc openUrl(self: MainModel, url: string) {.slot.} =
+ if url.startsWith("block://"):
+ self.onLoadBlock(url[8 ..^ 1])
+ self.setCurrentIndex(1)
diff --git a/ngui/ngui.nim b/ngui/ngui.nim
new file mode 100644
index 0000000000..008941f042
--- /dev/null
+++ b/ngui/ngui.nim
@@ -0,0 +1,66 @@
+import std/os
+
+import confutils
+import ../beacon_chain/networking/network_metadata
+
+import NimQml
+import mainmodel
+
+const
+ dothersideDir = currentSourcePath.parentDir & "/../vendor/DOtherSide/"
+ corePrivate =
+ gorge("pkg-config --variable=includedir Qt5Core") & "/QtCore/" &
+ gorge("pkg-config --modversion Qt5Core")
+ cflags =
+ gorge(
+ "pkg-config --cflags --static Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets"
+ ) & " -I" & dothersideDir & "lib/include -I" & corePrivate & " -I" & corePrivate &
+ "/QtCore"
+ ldflags = gorge "pkg-config --libs --static Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets"
+
+{.compile(dothersideDir & "lib/src/DOtherSide.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosQMetaObject.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosQDeclarative.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosQObject.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DOtherSideTypesCpp.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosQObjectImpl.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosQAbstractItemModel.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosQQuickImageProvider.cpp", cflags).}
+{.compile(dothersideDir & "lib/src/DosLambdaInvoker.cpp", cflags).}
+
+{.passl: ldflags.}
+
+static:
+ discard staticExec(
+ "rcc " & currentSourcePath.parentDir & "/resources.qrc -o " &
+ currentSourcePath.parentDir & "/resources.cpp"
+ )
+{.compile(currentSourcePath.parentDir & "/resources.cpp", cflags).}
+
+proc mainProc(url, network: string) =
+ let app = newQApplication()
+ defer:
+ app.delete
+ let cfg = getMetadataForNetwork(network).cfg
+ let main = newMainModel(app, url, cfg)
+ defer:
+ main.delete
+
+ let engine = newQQmlApplicationEngine()
+ defer:
+ engine.delete
+
+ let mainVariant = newQVariant(main)
+ defer:
+ mainVariant.delete
+
+ engine.setRootContextProperty("main", mainVariant)
+
+ engine.addImportPath("qrc:/")
+ engine.load(newQUrl("qrc:/ui/main.qml"))
+ app.exec()
+
+when isMainModule:
+ cli do(url = "http://localhost:5052", network = "mainnet"):
+ mainProc(url, network)
+ GC_fullcollect()
diff --git a/ngui/nim.cfg b/ngui/nim.cfg
new file mode 100644
index 0000000000..0709b5f2ce
--- /dev/null
+++ b/ngui/nim.cfg
@@ -0,0 +1,4 @@
+--path:"../vendor/nimqml/src"
+-d:"libp2p_pki_schemes=secp256k1"
+--dynliboverrideall
+gcc.linkerexe="g++"
diff --git a/ngui/nodemodel.nim b/ngui/nodemodel.nim
new file mode 100644
index 0000000000..ab513bbb32
--- /dev/null
+++ b/ngui/nodemodel.nim
@@ -0,0 +1,110 @@
+import NimQml
+
+import
+ std/[sequtils, json, times],
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ ./attestationlist,
+ ./utils
+template xxx(body): string =
+ try:
+ $(body)
+ except CatchableError as exc:
+ exc.msg
+
+QtObject:
+ type NodeModel* = ref object of QObject
+ client: RestClientRef
+ genesis: string
+ heads: string
+ identity: string
+ version: string
+ health: string
+
+ proc delete*(self: NodeModel) =
+ self.QObject.delete
+
+ proc setup*(self: NodeModel) =
+ self.QObject.setup
+
+ proc newNodeModel*(client: RestClientRef): NodeModel =
+ let res = NodeModel(client: client)
+ res.setup()
+ res
+
+ proc getgenesis*(self: NodeModel): string {.slot.} =
+ self.genesis
+
+ proc genesisChanged*(self: NodeModel, v: string) {.signal.}
+ proc setgenesis*(self: NodeModel, v: string) =
+ self.genesis = v
+ self.genesisChanged(v)
+
+ QtProperty[string] genesis:
+ read = getgenesis
+ notify = genesisChanged
+ write = setgenesis
+
+ proc getheads*(self: NodeModel): string {.slot.} =
+ self.heads
+
+ proc headsChanged*(self: NodeModel, v: string) {.signal.}
+ proc setheads*(self: NodeModel, v: string) =
+ self.heads = v
+ self.headsChanged(v)
+
+ QtProperty[string] heads:
+ read = getheads
+ notify = headsChanged
+ write = setheads
+
+ proc getidentity*(self: NodeModel): string {.slot.} =
+ self.identity
+
+ proc identityChanged*(self: NodeModel, v: string) {.signal.}
+ proc setidentity*(self: NodeModel, v: string) =
+ self.identity = v
+ self.identityChanged(v)
+
+ QtProperty[string] identity:
+ read = getidentity
+ notify = identityChanged
+ write = setidentity
+
+ proc getversion*(self: NodeModel): string {.slot.} =
+ self.version
+
+ proc versionChanged*(self: NodeModel, v: string) {.signal.}
+ proc setversion*(self: NodeModel, v: string) =
+ self.version = v
+ self.versionChanged(v)
+
+ QtProperty[string] version:
+ read = getversion
+ notify = versionChanged
+ write = setversion
+
+ proc gethealth*(self: NodeModel): string {.slot.} =
+ self.health
+
+ proc healthChanged*(self: NodeModel, v: string) {.signal.}
+ proc sethealth*(self: NodeModel, v: string) =
+ self.health = v
+ self.healthChanged(v)
+
+ QtProperty[string] health:
+ read = gethealth
+ notify = healthChanged
+ write = sethealth
+
+ proc update*(self: NodeModel) {.slot.} =
+ self.setgenesis(xxx(waitFor(self.client.getGenesis()).data.data))
+ self.setheads(
+ xxx(
+ waitFor(self.client.getDebugChainHeadsV2()).data.data
+ .mapIt(toBlockLink(it.root) & " @ " & $it.slot)
+ .join("\n")
+ )
+ )
+ self.setidentity(xxx(waitFor(self.client.getNetworkIdentity()).data.data))
+ self.setversion(xxx(waitFor(self.client.getNodeVersion()).data.data.version))
+ self.sethealth(xxx(waitFor(self.client.getHealth())))
diff --git a/ngui/objecttablemodel.nim b/ngui/objecttablemodel.nim
new file mode 100644
index 0000000000..ce7fbc5f29
--- /dev/null
+++ b/ngui/objecttablemodel.nim
@@ -0,0 +1,86 @@
+{.push raises: [Defect].}
+
+import NimQml
+
+import std/[algorithm, tables, typetraits]
+
+type ObjectTableModelImpl*[T] = object
+ items*: seq[T]
+ sortColumn*: int
+ direction*: bool
+
+func rowCount*(self: ObjectTableModelImpl, index: QModelIndex = nil): int =
+ self.items.len
+
+func columnCount*(self: ObjectTableModelImpl, index: QModelIndex = nil): int =
+ for j in default(type(self.items[0])).fields(): # TODO avoid default
+ result += 1
+
+func headerData*(
+ self: ObjectTableModelImpl, section: int, orientation: QtOrientation, role: int
+): QVariant =
+ ## Returns the data for the given role and section in the header with the specified orientation
+ var i = 0
+ for n, v in default(self.T).fieldPairs(): # TODO avoid default
+ if i == section:
+ return newQVariant(n)
+ i += 1
+
+func data*(self: ObjectTableModelImpl, index: QModelIndex, role: int): QVariant =
+ if not index.isValid:
+ return
+ if index.row < 0 or index.row >= self.items.len:
+ return
+ let peer = self.items[index.row]
+ var i = 0
+ for j in peer.fields():
+ if i == index.column:
+ return newQVariant(distinctBase j)
+ i += 1
+
+func roleNames*(self: ObjectTableModelImpl): Table[int, string] =
+ {0: "display"}.toTable
+
+func doSort(self: var ObjectTableModelImpl) =
+ let
+ column = self.sortColumn
+ dir = self.direction
+ func myCmp(x, y: self.T): int =
+ var i = 0
+ for xv, yv in fields(x, y):
+ if i == column:
+ let c = cmp(xv, yv)
+ return
+ if not dir:
+ c
+ else:
+ -c
+ i += 1
+ 0
+
+ sort(self.items, myCmp)
+
+func setNewData*(
+ self: var ObjectTableModelImpl, model: QAbstractTableModel, items: seq[self.T]
+) =
+ model.beginResetModel()
+ self.items = items
+ self.doSort()
+ model.endResetModel()
+
+func sort*(self: var ObjectTableModelImpl, model: QAbstractTableModel, section: int) =
+ model.beginResetModel()
+ if self.sortColumn == section:
+ self.direction = not self.direction
+ else:
+ self.direction = false
+ self.sortColumn = section
+
+ self.doSort()
+
+ model.endResetModel()
+
+func init*[E](T: type ObjectTableModelImpl[E], items: seq[E]): T =
+ var res = T(items: items)
+ res.doSort()
+ res
diff --git a/ngui/peerlist.nim b/ngui/peerlist.nim
new file mode 100644
index 0000000000..9a1a756a16
--- /dev/null
+++ b/ngui/peerlist.nim
@@ -0,0 +1,40 @@
+import std/tables, NimQml, ../beacon_chain/spec/eth2_apis/rest_types, ./objecttablemodel
+
+QtObject:
+ type PeerList* = ref object of QAbstractTableModel
+ # TODO this could be a generic ObjectTableModel, except generics + method don't work..
+ data: ObjectTableModelImpl[RestNodePeer]
+
+ proc setup(self: PeerList) =
+ self.QAbstractTableModel.setup
+
+ proc delete(self: PeerList) =
+ self.QAbstractTableModel.delete
+
+ proc newPeerList*(items: seq[RestNodePeer]): PeerList =
+ new(result, delete)
+ result.data = ObjectTableModelImpl[RestNodePeer].init(items)
+ result.setup
+
+ method rowCount(self: PeerList, index: QModelIndex = nil): int =
+ self.data.rowCount(index)
+
+ method columnCount(self: PeerList, index: QModelIndex = nil): int =
+ self.data.columnCount(index)
+
+ method headerData*(
+ self: PeerList, section: int, orientation: QtOrientation, role: int
+ ): QVariant =
+ self.data.headerData(section, orientation, role)
+
+ method data(self: PeerList, index: QModelIndex, role: int): QVariant =
+ self.data.data(index, role)
+
+ method roleNames(self: PeerList): Table[int, string] =
+ self.data.roleNames()
+
+ proc setNewData*(self: PeerList, v: seq[RestNodePeer]) =
+ self.data.setNewData(self, v)
+
+ proc sort*(self: PeerList, section: int) {.slot.} =
+ self.data.sort(self, section)
diff --git a/ngui/poolmodel.nim b/ngui/poolmodel.nim
new file mode 100644
index 0000000000..2065f0fc0a
--- /dev/null
+++ b/ngui/poolmodel.nim
@@ -0,0 +1,91 @@
+import NimQml
+
+import
+ std/[sequtils, times],
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ ./attestationlist,
+ ./attesterslashinglist,
+ proposerslashinglist,
+ voluntaryexitlist,
+ ./utils
+
+template xxx(body): untyped =
+ try:
+ body.data.data
+ except CatchableError as exc:
+ debugEcho exc.msg
+ @[]
+
+QtObject:
+ type PoolModel* = ref object of QObject
+ client: RestClientRef
+ attestationsx: AttestationList
+ attesterSlashingsx: AttesterSlashingList
+ proposerSlashingsx: ProposerSlashingList
+ voluntaryExitsx: VoluntaryExitList
+
+ proc delete*(self: PoolModel) =
+ self.QObject.delete
+
+ proc setup*(self: PoolModel) =
+ self.QObject.setup
+
+ proc newPoolModel*(client: RestClientRef): PoolModel =
+ let res = PoolModel(
+ client: client,
+ attestationsx: newAttestationList(default(seq[AttestationInfo])),
+ attesterSlashingsx: newAttesterSlashingList(default(seq[phase0.AttesterSlashing])),
+ proposerSlashingsx: newProposerSlashingList(@[]),
+ voluntaryExitsx: newVoluntaryExitList(@[]),
+ )
+ res.setup()
+ res
+
+ proc attestations*(self: PoolModel): QVariant {.slot.} =
+ newQVariant(self.attestationsx)
+
+ QtProperty[QVariant] attestations:
+ read = attestations
+
+ proc attesterSlashings*(self: PoolModel): QVariant {.slot.} =
+ newQVariant(self.attesterSlashingsx)
+
+ QtProperty[QVariant] attesterSlashings:
+ read = attesterSlashings
+
+ proc proposerSlashings*(self: PoolModel): QVariant {.slot.} =
+ newQVariant(self.proposerSlashingsx)
+
+ QtProperty[QVariant] proposerSlashings:
+ read = proposerSlashings
+
+ proc voluntaryExits*(self: PoolModel): QVariant {.slot.} =
+ newQVariant(self.voluntaryExitsx)
+
+ QtProperty[QVariant] voluntaryExits:
+ read = voluntaryExits
+
+ proc updateAttestations*(self: PoolModel) {.slot.} =
+ self.attestationsx.setNewData(
+ xxx(waitFor self.client.getPoolAttestations(none(Slot), none(CommitteeIndex)))
+ .mapIt(it.toAttestationInfo())
+ )
+
+ proc updateAttesterSlashings*(self: PoolModel) {.slot.} =
+ self.attesterSlashingsx.setNewData(
+ xxx(waitFor self.client.getPoolAttesterSlashings())
+ )
+
+ proc updateProposerSlashings*(self: PoolModel) {.slot.} =
+ self.proposerSlashingsx.setNewData(
+ xxx(waitFor self.client.getPoolProposerSlashings())
+ )
+
+ proc updateVoluntaryExits*(self: PoolModel) {.slot.} =
+ self.voluntaryExitsx.setNewData(xxx(waitFor self.client.getPoolVoluntaryExits()))
+
+ proc update*(self: PoolModel) {.slot.} =
+ self.updateAttestations()
+ self.updateAttesterSlashings()
+ self.updateProposerSlashings()
+ self.updateVoluntaryExits()
diff --git a/ngui/proposerslashinglist.nim b/ngui/proposerslashinglist.nim
new file mode 100644
index 0000000000..056c1a9454
--- /dev/null
+++ b/ngui/proposerslashinglist.nim
@@ -0,0 +1,56 @@
+import
+ std/[sequtils, tables],
+ NimQml,
+ ../beacon_chain/spec/eth2_apis/rest_beacon_client,
+ ../beacon_chain/spec/helpers,
+ ./objecttablemodel,
+ ./utils
+
+type ProposerSlashingInfo* = object
+ info*: string
+
+proc toProposerSlashingInfo*(v: ProposerSlashing): ProposerSlashingInfo =
+ ProposerSlashingInfo(info: $v)
+
+QtObject:
+ type ProposerSlashingList* = ref object of QAbstractTableModel
+ # TODO this could be a generic ObjectTableModel, except generics + method don't work..
+ data: ObjectTableModelImpl[ProposerSlashingInfo]
+
+ proc setup(self: ProposerSlashingList) =
+ self.QAbstractTableModel.setup
+
+ proc delete(self: ProposerSlashingList) =
+ self.QAbstractTableModel.delete
+
+ proc newProposerSlashingList*(
+ data: openArray[ProposerSlashing]
+ ): ProposerSlashingList =
+ new(result, delete)
+ result.data = ObjectTableModelImpl[ProposerSlashingInfo](
+ items: data.mapIt(it.toProposerSlashingInfo())
+ )
+ result.setup
+
+ method rowCount(self: ProposerSlashingList, index: QModelIndex = nil): int =
+ self.data.rowCount(index)
+
+ method columnCount(self: ProposerSlashingList, index: QModelIndex = nil): int =
+ self.data.columnCount(index)
+
+ method headerData*(
+ self: ProposerSlashingList, section: int, orientation: QtOrientation, role: int
+ ): QVariant =
+ self.data.headerData(section, orientation, role)
+
+ method data(self: ProposerSlashingList, index: QModelIndex, role: int): QVariant =
+ self.data.data(index, role)
+
+ method roleNames(self: ProposerSlashingList): Table[int, string] =
+ self.data.roleNames()
+
+ proc setNewData*(self: ProposerSlashingList, v: seq[ProposerSlashing]) =
+ self.data.setNewData(self, v.mapIt(it.toProposerSlashingInfo()))
+
+ proc sort*(self: ProposerSlashingList, section: int) {.slot.} =
+ self.data.sort(self, section)
diff --git a/ngui/resources.qrc b/ngui/resources.qrc
new file mode 100644
index 0000000000..24a946310c
--- /dev/null
+++ b/ngui/resources.qrc
@@ -0,0 +1,12 @@
+
0x" & toHex(v) & "" + +func toDisplayHex*(v: Eth2Digest): string = + toDisplayHex(v.data) +func toDisplayHex*(v: ValidatorSig | TrustedSig): string = + toDisplayHex(toRaw(v)) + +func toBlockLink*(v: Eth2Digest): string = + let + display = toDisplayHex(v) + target = "0x" & toHex(v.data) + + "" & display & "" + +func toValidatorLink*(v: ValidatorIndex): string = + "" & $v & "" diff --git a/ngui/voluntaryexitlist.nim b/ngui/voluntaryexitlist.nim new file mode 100644 index 0000000000..9ed105a084 --- /dev/null +++ b/ngui/voluntaryexitlist.nim @@ -0,0 +1,54 @@ +import + std/[sequtils, tables], + NimQml, + ../beacon_chain/spec/eth2_apis/rest_beacon_client, + ../beacon_chain/spec/helpers, + ./objecttablemodel, + ./utils + +type VoluntaryExitInfo* = object + info*: string + +proc toVoluntaryExitInfo*(v: SignedVoluntaryExit): VoluntaryExitInfo = + VoluntaryExitInfo(info: $v) + +QtObject: + type VoluntaryExitList* = ref object of QAbstractTableModel + # TODO this could be a generic ObjectTableModel, except generics + method don't work.. + data: ObjectTableModelImpl[VoluntaryExitInfo] + + proc setup(self: VoluntaryExitList) = + self.QAbstractTableModel.setup + + proc delete(self: VoluntaryExitList) = + self.QAbstractTableModel.delete + + proc newVoluntaryExitList*(data: openArray[SignedVoluntaryExit]): VoluntaryExitList = + new(result, delete) + result.data = ObjectTableModelImpl[VoluntaryExitInfo]( + items: data.mapIt(it.toVoluntaryExitInfo()) + ) + result.setup + + method rowCount(self: VoluntaryExitList, index: QModelIndex = nil): int = + self.data.rowCount(index) + + method columnCount(self: VoluntaryExitList, index: QModelIndex = nil): int = + self.data.columnCount(index) + + method headerData*( + self: VoluntaryExitList, section: int, orientation: QtOrientation, role: int + ): QVariant = + self.data.headerData(section, orientation, role) + + method data(self: VoluntaryExitList, index: QModelIndex, role: int): QVariant = + self.data.data(index, role) + + method roleNames(self: VoluntaryExitList): Table[int, string] = + self.data.roleNames() + + proc setNewData*(self: VoluntaryExitList, v: openArray[SignedVoluntaryExit]) = + self.data.setNewData(self, v.mapIt(it.toVoluntaryExitInfo())) + + proc sort*(self: VoluntaryExitList, section: int) {.slot.} = + self.data.sort(self, section) diff --git a/tests/test_blockchain_dag.nim b/tests/test_blockchain_dag.nim index 146fd20e24..f7465fb455 100644 --- a/tests/test_blockchain_dag.nim +++ b/tests/test_blockchain_dag.nim @@ -711,7 +711,9 @@ suite "Diverging hardforks": altairRuntimeConfig = defaultRuntimeConfig phase0RuntimeConfig.ALTAIR_FORK_EPOCH = FAR_FUTURE_EPOCH + phase0RuntimeConfig.BELLATRIX_FORK_EPOCH = FAR_FUTURE_EPOCH altairRuntimeConfig.ALTAIR_FORK_EPOCH = 2.Epoch + altairRuntimeConfig.BELLATRIX_FORK_EPOCH = FAR_FUTURE_EPOCH var db = makeTestDB(SLOTS_PER_EPOCH) diff --git a/vendor/DOtherSide b/vendor/DOtherSide new file mode 160000 index 0000000000..7b7c0a91b5 --- /dev/null +++ b/vendor/DOtherSide @@ -0,0 +1 @@ +Subproject commit 7b7c0a91b558b13a968c57b9647bfc15ed962ead diff --git a/vendor/nimqml b/vendor/nimqml new file mode 160000 index 0000000000..9a65a1847e --- /dev/null +++ b/vendor/nimqml @@ -0,0 +1 @@ +Subproject commit 9a65a1847e31f97a6f42dc9624e6d86795fcf491