2022-05-19 22:28:53 +00:00
|
|
|
## Nim-Codex
|
|
|
|
## Copyright (c) 2021 Status Research & Development GmbH
|
|
|
|
## Licensed under either of
|
|
|
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
## at your option.
|
|
|
|
## This file may not be copied, modified, or distributed except according to
|
|
|
|
## those terms.
|
|
|
|
|
|
|
|
import std/tables
|
|
|
|
import std/sequtils
|
|
|
|
|
|
|
|
import pkg/chronos
|
|
|
|
|
|
|
|
import pkg/libp2p
|
2022-07-29 16:19:34 +00:00
|
|
|
import pkg/libp2p/utils/semaphore
|
2022-05-19 22:28:53 +00:00
|
|
|
import pkg/questionable
|
|
|
|
import pkg/questionable/results
|
|
|
|
|
|
|
|
import ../../blocktype as bt
|
feat: create logging proxy (#663)
* implement a logging proxy
The logging proxy:
- prevents the need to import chronicles (as well as export except toJson),
- prevents the need to override `writeValue` or use or import nim-json-seralization elsewhere in the codebase, allowing for sole use of utils/json for de/serialization,
- and handles json formatting correctly in chronicles json sinks
* Rename logging -> logutils to avoid ambiguity with common names
* clean up
* add setProperty for JsonRecord, remove nim-json-serialization conflict
* Allow specifying textlines and json format separately
Not specifying a LogFormat will apply the formatting to both textlines and json sinks.
Specifying a LogFormat will apply the formatting to only that sink.
* remove unneeded usages of std/json
We only need to import utils/json instead of std/json
* move serialization from rest/json to utils/json so it can be shared
* fix NoColors ambiguity
Was causing unit tests to fail on Windows.
* Remove nre usage to fix Windows error
Windows was erroring with `could not load: pcre64.dll`. Instead of fixing that error, remove the pcre usage :)
* Add logutils module doc
* Shorten logutils.formatIt for `NBytes`
Both json and textlines formatIt were not needed, and could be combined into one formatIt
* remove debug integration test config
debug output and logformat of json for integration test logs
* Use ## module doc to support docgen
* bump nim-poseidon2 to export fromBytes
Before the changes in this branch, fromBytes was likely being resolved by nim-stew, or other dependency. With the changes in this branch, that dependency was removed and fromBytes could no longer be resolved. By exporting fromBytes from nim-poseidon, the correct resolution is now happening.
* fixes to get compiling after rebasing master
* Add support for Result types being logged using formatIt
2024-01-23 07:35:03 +00:00
|
|
|
import ../../logutils
|
2022-05-19 22:28:53 +00:00
|
|
|
import ../protobuf/blockexc as pb
|
|
|
|
import ../protobuf/payments
|
|
|
|
|
|
|
|
import ./networkpeer
|
|
|
|
|
|
|
|
export network, payments
|
|
|
|
|
|
|
|
logScope:
|
2022-11-15 15:46:21 +00:00
|
|
|
topics = "codex blockexcnetwork"
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
const
|
|
|
|
Codec* = "/codex/blockexc/1.0.0"
|
|
|
|
MaxInflight* = 100
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
type
|
2023-11-14 12:02:17 +00:00
|
|
|
WantListHandler* = proc(peer: PeerId, wantList: WantList): Future[void] {.gcsafe.}
|
|
|
|
BlocksDeliveryHandler* = proc(peer: PeerId, blocks: seq[BlockDelivery]): Future[void] {.gcsafe.}
|
2023-03-10 07:02:54 +00:00
|
|
|
BlockPresenceHandler* = proc(peer: PeerId, precense: seq[BlockPresence]): Future[void] {.gcsafe.}
|
|
|
|
AccountHandler* = proc(peer: PeerId, account: Account): Future[void] {.gcsafe.}
|
|
|
|
PaymentHandler* = proc(peer: PeerId, payment: SignedState): Future[void] {.gcsafe.}
|
2022-07-29 16:19:34 +00:00
|
|
|
WantListSender* = proc(
|
2023-03-10 07:02:54 +00:00
|
|
|
id: PeerId,
|
2023-11-14 12:02:17 +00:00
|
|
|
addresses: seq[BlockAddress],
|
2022-07-29 16:19:34 +00:00
|
|
|
priority: int32 = 0,
|
|
|
|
cancel: bool = false,
|
2022-11-15 15:46:21 +00:00
|
|
|
wantType: WantType = WantType.WantHave,
|
2022-07-29 16:19:34 +00:00
|
|
|
full: bool = false,
|
|
|
|
sendDontHave: bool = false): Future[void] {.gcsafe.}
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
BlockExcHandlers* = object
|
|
|
|
onWantList*: WantListHandler
|
2023-11-14 12:02:17 +00:00
|
|
|
onBlocksDelivery*: BlocksDeliveryHandler
|
2022-05-19 22:28:53 +00:00
|
|
|
onPresence*: BlockPresenceHandler
|
|
|
|
onAccount*: AccountHandler
|
|
|
|
onPayment*: PaymentHandler
|
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
BlocksDeliverySender* = proc(peer: PeerId, blocksDelivery: seq[BlockDelivery]): Future[void] {.gcsafe.}
|
2023-03-10 07:02:54 +00:00
|
|
|
PresenceSender* = proc(peer: PeerId, presence: seq[BlockPresence]): Future[void] {.gcsafe.}
|
|
|
|
AccountSender* = proc(peer: PeerId, account: Account): Future[void] {.gcsafe.}
|
|
|
|
PaymentSender* = proc(peer: PeerId, payment: SignedState): Future[void] {.gcsafe.}
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
BlockExcRequest* = object
|
2022-07-29 16:19:34 +00:00
|
|
|
sendWantList*: WantListSender
|
2023-11-14 12:02:17 +00:00
|
|
|
sendBlocksDelivery*: BlocksDeliverySender
|
2022-07-29 16:19:34 +00:00
|
|
|
sendPresence*: PresenceSender
|
|
|
|
sendAccount*: AccountSender
|
|
|
|
sendPayment*: PaymentSender
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
BlockExcNetwork* = ref object of LPProtocol
|
2023-03-10 07:02:54 +00:00
|
|
|
peers*: Table[PeerId, NetworkPeer]
|
2022-05-19 22:28:53 +00:00
|
|
|
switch*: Switch
|
|
|
|
handlers*: BlockExcHandlers
|
|
|
|
request*: BlockExcRequest
|
|
|
|
getConn: ConnProvider
|
2022-07-29 16:19:34 +00:00
|
|
|
inflightSema: AsyncSemaphore
|
|
|
|
|
2023-11-27 18:25:53 +00:00
|
|
|
proc peerId*(b: BlockExcNetwork): PeerId =
|
|
|
|
## Return peer id
|
|
|
|
##
|
|
|
|
|
|
|
|
return b.switch.peerInfo.peerId
|
|
|
|
|
|
|
|
proc isSelf*(b: BlockExcNetwork, peer: PeerId): bool =
|
|
|
|
## Check if peer is self
|
|
|
|
##
|
|
|
|
|
|
|
|
return b.peerId == peer
|
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
proc send*(b: BlockExcNetwork, id: PeerId, msg: pb.Message) {.async.} =
|
|
|
|
## Send message to peer
|
|
|
|
##
|
|
|
|
|
|
|
|
b.peers.withValue(id, peer):
|
|
|
|
try:
|
|
|
|
await b.inflightSema.acquire()
|
|
|
|
trace "Sending message to peer", peer = id
|
|
|
|
await peer[].send(msg)
|
2023-07-19 13:10:14 +00:00
|
|
|
except CatchableError as err:
|
|
|
|
error "Error sending message", peer = id, msg = err.msg
|
2022-07-29 16:19:34 +00:00
|
|
|
finally:
|
|
|
|
b.inflightSema.release()
|
|
|
|
do:
|
|
|
|
trace "Unable to send, peer not found", peerId = id
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
proc handleWantList(
|
|
|
|
b: BlockExcNetwork,
|
|
|
|
peer: NetworkPeer,
|
2023-11-14 12:02:17 +00:00
|
|
|
list: WantList) {.async.} =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Handle incoming want list
|
|
|
|
##
|
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
if not b.handlers.onWantList.isNil:
|
|
|
|
trace "Handling want list for peer", peer = peer.id, items = list.entries.len
|
|
|
|
await b.handlers.onWantList(peer.id, list)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
proc sendWantList*(
|
2023-11-27 18:25:53 +00:00
|
|
|
b: BlockExcNetwork,
|
|
|
|
id: PeerId,
|
|
|
|
addresses: seq[BlockAddress],
|
|
|
|
priority: int32 = 0,
|
|
|
|
cancel: bool = false,
|
|
|
|
wantType: WantType = WantType.WantHave,
|
|
|
|
full: bool = false,
|
|
|
|
sendDontHave: bool = false): Future[void] =
|
2022-07-29 16:19:34 +00:00
|
|
|
## Send a want message to peer
|
2022-05-19 22:28:53 +00:00
|
|
|
##
|
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
trace "Sending want list to peer", peer = id, `type` = $wantType, items = addresses.len
|
|
|
|
let msg = WantList(
|
|
|
|
entries: addresses.mapIt(
|
|
|
|
WantListEntry(
|
|
|
|
address: it,
|
|
|
|
priority: priority,
|
|
|
|
cancel: cancel,
|
|
|
|
wantType: wantType,
|
|
|
|
sendDontHave: sendDontHave) ),
|
|
|
|
full: full)
|
2023-11-27 18:25:53 +00:00
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
b.send(id, Message(wantlist: msg))
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
proc handleBlocksDelivery(
|
2023-11-27 18:25:53 +00:00
|
|
|
b: BlockExcNetwork,
|
|
|
|
peer: NetworkPeer,
|
|
|
|
blocksDelivery: seq[BlockDelivery]) {.async.} =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Handle incoming blocks
|
|
|
|
##
|
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
if not b.handlers.onBlocksDelivery.isNil:
|
|
|
|
trace "Handling blocks for peer", peer = peer.id, items = blocksDelivery.len
|
|
|
|
await b.handlers.onBlocksDelivery(peer.id, blocksDelivery)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
proc sendBlocksDelivery*(
|
2022-05-19 22:28:53 +00:00
|
|
|
b: BlockExcNetwork,
|
2023-03-10 07:02:54 +00:00
|
|
|
id: PeerId,
|
2023-11-14 12:02:17 +00:00
|
|
|
blocksDelivery: seq[BlockDelivery]): Future[void] =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Send blocks to remote
|
|
|
|
##
|
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
b.send(id, pb.Message(payload: blocksDelivery))
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
proc handleBlockPresence(
|
|
|
|
b: BlockExcNetwork,
|
|
|
|
peer: NetworkPeer,
|
2022-07-29 16:19:34 +00:00
|
|
|
presence: seq[BlockPresence]) {.async.} =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Handle block presence
|
|
|
|
##
|
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
if not b.handlers.onPresence.isNil:
|
|
|
|
trace "Handling block presence for peer", peer = peer.id, items = presence.len
|
|
|
|
await b.handlers.onPresence(peer.id, presence)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
proc sendBlockPresence*(
|
2022-05-19 22:28:53 +00:00
|
|
|
b: BlockExcNetwork,
|
2023-03-10 07:02:54 +00:00
|
|
|
id: PeerId,
|
2022-07-29 16:19:34 +00:00
|
|
|
presence: seq[BlockPresence]): Future[void] =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Send presence to remote
|
|
|
|
##
|
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
b.send(id, Message(blockPresences: @presence))
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
proc handleAccount(
|
2023-11-27 18:25:53 +00:00
|
|
|
network: BlockExcNetwork,
|
|
|
|
peer: NetworkPeer,
|
|
|
|
account: Account) {.async.} =
|
2022-07-29 16:19:34 +00:00
|
|
|
## Handle account info
|
|
|
|
##
|
|
|
|
|
|
|
|
if not network.handlers.onAccount.isNil:
|
|
|
|
await network.handlers.onAccount(peer.id, account)
|
|
|
|
|
|
|
|
proc sendAccount*(
|
2023-11-27 18:25:53 +00:00
|
|
|
b: BlockExcNetwork,
|
|
|
|
id: PeerId,
|
|
|
|
account: Account): Future[void] =
|
2022-07-29 16:19:34 +00:00
|
|
|
## Send account info to remote
|
|
|
|
##
|
|
|
|
|
|
|
|
b.send(id, Message(account: AccountMessage.init(account)))
|
|
|
|
|
|
|
|
proc sendPayment*(
|
2023-11-27 18:25:53 +00:00
|
|
|
b: BlockExcNetwork,
|
|
|
|
id: PeerId,
|
|
|
|
payment: SignedState): Future[void] =
|
2022-07-29 16:19:34 +00:00
|
|
|
## Send payment to remote
|
|
|
|
##
|
|
|
|
|
|
|
|
b.send(id, Message(payment: StateChannelUpdate.init(payment)))
|
|
|
|
|
|
|
|
proc handlePayment(
|
2023-11-27 18:25:53 +00:00
|
|
|
network: BlockExcNetwork,
|
|
|
|
peer: NetworkPeer,
|
|
|
|
payment: SignedState) {.async.} =
|
2022-07-29 16:19:34 +00:00
|
|
|
## Handle payment
|
|
|
|
##
|
|
|
|
|
|
|
|
if not network.handlers.onPayment.isNil:
|
|
|
|
await network.handlers.onPayment(peer.id, payment)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2023-06-22 15:11:18 +00:00
|
|
|
proc rpcHandler(
|
2023-11-27 18:25:53 +00:00
|
|
|
b: BlockExcNetwork,
|
|
|
|
peer: NetworkPeer,
|
|
|
|
msg: Message) {.async.} =
|
2023-06-22 15:11:18 +00:00
|
|
|
## handle rpc messages
|
2023-11-27 18:25:53 +00:00
|
|
|
##
|
2022-05-19 22:28:53 +00:00
|
|
|
try:
|
2023-11-14 12:02:17 +00:00
|
|
|
if msg.wantList.entries.len > 0:
|
|
|
|
asyncSpawn b.handleWantList(peer, msg.wantList)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
if msg.payload.len > 0:
|
2023-11-14 12:02:17 +00:00
|
|
|
asyncSpawn b.handleBlocksDelivery(peer, msg.payload)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
if msg.blockPresences.len > 0:
|
|
|
|
asyncSpawn b.handleBlockPresence(peer, msg.blockPresences)
|
|
|
|
|
|
|
|
if account =? Account.init(msg.account):
|
|
|
|
asyncSpawn b.handleAccount(peer, account)
|
|
|
|
|
|
|
|
if payment =? SignedState.init(msg.payment):
|
|
|
|
asyncSpawn b.handlePayment(peer, payment)
|
|
|
|
|
|
|
|
except CatchableError as exc:
|
|
|
|
trace "Exception in blockexc rpc handler", exc = exc.msg
|
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc getOrCreatePeer(b: BlockExcNetwork, peer: PeerId): NetworkPeer =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Creates or retrieves a BlockExcNetwork Peer
|
|
|
|
##
|
|
|
|
|
|
|
|
if peer in b.peers:
|
|
|
|
return b.peers.getOrDefault(peer, nil)
|
|
|
|
|
2023-08-01 23:47:57 +00:00
|
|
|
var getConn: ConnProvider = proc(): Future[Connection] {.async, gcsafe, closure.} =
|
2022-05-19 22:28:53 +00:00
|
|
|
try:
|
|
|
|
return await b.switch.dial(peer, Codec)
|
|
|
|
except CatchableError as exc:
|
|
|
|
trace "Unable to connect to blockexc peer", exc = exc.msg
|
|
|
|
|
|
|
|
if not isNil(b.getConn):
|
|
|
|
getConn = b.getConn
|
|
|
|
|
|
|
|
let rpcHandler = proc (p: NetworkPeer, msg: Message): Future[void] =
|
|
|
|
b.rpcHandler(p, msg)
|
|
|
|
|
|
|
|
# create new pubsub peer
|
|
|
|
let blockExcPeer = NetworkPeer.new(peer, getConn, rpcHandler)
|
|
|
|
debug "Created new blockexc peer", peer
|
|
|
|
|
|
|
|
b.peers[peer] = blockExcPeer
|
|
|
|
|
|
|
|
return blockExcPeer
|
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc setupPeer*(b: BlockExcNetwork, peer: PeerId) =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Perform initial setup, such as want
|
|
|
|
## list exchange
|
|
|
|
##
|
|
|
|
|
|
|
|
discard b.getOrCreatePeer(peer)
|
|
|
|
|
|
|
|
proc dialPeer*(b: BlockExcNetwork, peer: PeerRecord) {.async.} =
|
2023-11-27 18:25:53 +00:00
|
|
|
## Dial a peer
|
|
|
|
##
|
|
|
|
|
|
|
|
if b.isSelf(peer.peerId):
|
|
|
|
trace "Skipping dialing self", peer = peer.peerId
|
|
|
|
return
|
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
await b.switch.connect(peer.peerId, peer.addresses.mapIt(it.address))
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc dropPeer*(b: BlockExcNetwork, peer: PeerId) =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Cleanup disconnected peer
|
|
|
|
##
|
|
|
|
|
|
|
|
b.peers.del(peer)
|
|
|
|
|
|
|
|
method init*(b: BlockExcNetwork) =
|
|
|
|
## Perform protocol initialization
|
|
|
|
##
|
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc peerEventHandler(peerId: PeerId, event: PeerEvent) {.async.} =
|
2022-05-19 22:28:53 +00:00
|
|
|
if event.kind == PeerEventKind.Joined:
|
|
|
|
b.setupPeer(peerId)
|
|
|
|
else:
|
|
|
|
b.dropPeer(peerId)
|
|
|
|
|
|
|
|
b.switch.addPeerEventHandler(peerEventHandler, PeerEventKind.Joined)
|
|
|
|
b.switch.addPeerEventHandler(peerEventHandler, PeerEventKind.Left)
|
|
|
|
|
|
|
|
proc handle(conn: Connection, proto: string) {.async, gcsafe, closure.} =
|
|
|
|
let peerId = conn.peerId
|
|
|
|
let blockexcPeer = b.getOrCreatePeer(peerId)
|
|
|
|
await blockexcPeer.readLoop(conn) # attach read loop
|
|
|
|
|
|
|
|
b.handler = handle
|
|
|
|
b.codec = Codec
|
|
|
|
|
|
|
|
proc new*(
|
2023-11-27 18:25:53 +00:00
|
|
|
T: type BlockExcNetwork,
|
|
|
|
switch: Switch,
|
|
|
|
connProvider: ConnProvider = nil,
|
|
|
|
maxInflight = MaxInflight): BlockExcNetwork =
|
2022-05-19 22:28:53 +00:00
|
|
|
## Create a new BlockExcNetwork instance
|
|
|
|
##
|
|
|
|
|
2022-11-15 15:46:21 +00:00
|
|
|
let
|
|
|
|
self = BlockExcNetwork(
|
|
|
|
switch: switch,
|
|
|
|
getConn: connProvider,
|
|
|
|
inflightSema: newAsyncSemaphore(maxInflight))
|
2022-05-19 22:28:53 +00:00
|
|
|
|
|
|
|
proc sendWantList(
|
2023-03-10 07:02:54 +00:00
|
|
|
id: PeerId,
|
2023-11-14 12:02:17 +00:00
|
|
|
cids: seq[BlockAddress],
|
2022-05-19 22:28:53 +00:00
|
|
|
priority: int32 = 0,
|
|
|
|
cancel: bool = false,
|
2022-11-15 15:46:21 +00:00
|
|
|
wantType: WantType = WantType.WantHave,
|
2022-05-19 22:28:53 +00:00
|
|
|
full: bool = false,
|
2022-07-29 16:19:34 +00:00
|
|
|
sendDontHave: bool = false): Future[void] {.gcsafe.} =
|
|
|
|
self.sendWantList(
|
2022-05-19 22:28:53 +00:00
|
|
|
id, cids, priority, cancel,
|
|
|
|
wantType, full, sendDontHave)
|
|
|
|
|
2023-11-14 12:02:17 +00:00
|
|
|
proc sendBlocksDelivery(id: PeerId, blocksDelivery: seq[BlockDelivery]): Future[void] {.gcsafe.} =
|
|
|
|
self.sendBlocksDelivery(id, blocksDelivery)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc sendPresence(id: PeerId, presence: seq[BlockPresence]): Future[void] {.gcsafe.} =
|
2022-07-29 16:19:34 +00:00
|
|
|
self.sendBlockPresence(id, presence)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc sendAccount(id: PeerId, account: Account): Future[void] {.gcsafe.} =
|
2022-07-29 16:19:34 +00:00
|
|
|
self.sendAccount(id, account)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2023-03-10 07:02:54 +00:00
|
|
|
proc sendPayment(id: PeerId, payment: SignedState): Future[void] {.gcsafe.} =
|
2022-07-29 16:19:34 +00:00
|
|
|
self.sendPayment(id, payment)
|
2022-05-19 22:28:53 +00:00
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
self.request = BlockExcRequest(
|
2022-05-19 22:28:53 +00:00
|
|
|
sendWantList: sendWantList,
|
2023-11-14 12:02:17 +00:00
|
|
|
sendBlocksDelivery: sendBlocksDelivery,
|
2022-05-19 22:28:53 +00:00
|
|
|
sendPresence: sendPresence,
|
|
|
|
sendAccount: sendAccount,
|
|
|
|
sendPayment: sendPayment)
|
|
|
|
|
2022-07-29 16:19:34 +00:00
|
|
|
self.init()
|
|
|
|
return self
|