2020-11-13 06:02:17 +00:00
|
|
|
## SWAP implements Accounting for Waku. See
|
|
|
|
## https://github.com/vacp2p/specs/issues/24 for more.
|
|
|
|
##
|
|
|
|
## This is based on the SWAP based approach researched by the Swarm team, and
|
|
|
|
## can be thought of as an economic extension to Bittorrent's tit-for-tat
|
|
|
|
## economics.
|
|
|
|
##
|
|
|
|
## It is quite suitable for accounting for imbalances between peers, and
|
|
|
|
## specifically for something like the Store protocol.
|
|
|
|
##
|
|
|
|
## It is structured as follows:
|
|
|
|
##
|
|
|
|
## 1) First a handshake is made, where terms are agreed upon
|
|
|
|
##
|
|
|
|
## 2) Then operation occurs as normal with HistoryRequest, HistoryResponse etc
|
|
|
|
## through store protocol (or otherwise)
|
|
|
|
##
|
|
|
|
## 3) When payment threshhold is met, a cheque is sent. This acts as promise to
|
|
|
|
## pay. Right now it is best thought of as karma points.
|
|
|
|
##
|
|
|
|
## Things like settlement is for future work.
|
|
|
|
##
|
|
|
|
|
|
|
|
import
|
2020-11-20 13:59:29 +00:00
|
|
|
std/[tables, options],
|
2020-11-13 06:02:17 +00:00
|
|
|
bearssl,
|
2020-11-20 13:59:29 +00:00
|
|
|
chronos, chronicles, metrics, stew/results,
|
2020-11-13 06:02:17 +00:00
|
|
|
libp2p/switch,
|
|
|
|
libp2p/crypto/crypto,
|
|
|
|
libp2p/protocols/protocol,
|
|
|
|
libp2p/protobuf/minprotobuf,
|
|
|
|
libp2p/stream/connection,
|
2020-11-23 02:27:45 +00:00
|
|
|
../message_notifier,
|
2020-11-24 04:34:32 +00:00
|
|
|
./waku_swap_types
|
2020-11-13 06:02:17 +00:00
|
|
|
|
2020-11-23 02:27:45 +00:00
|
|
|
export waku_swap_types
|
2020-11-13 06:02:17 +00:00
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "wakuswap"
|
|
|
|
|
|
|
|
const WakuSwapCodec* = "/vac/waku/swap/2.0.0-alpha1"
|
|
|
|
|
2020-11-24 04:53:42 +00:00
|
|
|
# Serialization
|
|
|
|
# -------------------------------------------------------------------------------
|
2020-11-13 06:02:17 +00:00
|
|
|
proc encode*(handshake: Handshake): ProtoBuffer =
|
|
|
|
result = initProtoBuffer()
|
|
|
|
result.write(1, handshake.beneficiary)
|
|
|
|
|
|
|
|
proc encode*(cheque: Cheque): ProtoBuffer =
|
|
|
|
result = initProtoBuffer()
|
|
|
|
result.write(1, cheque.beneficiary)
|
|
|
|
result.write(2, cheque.date)
|
|
|
|
result.write(3, cheque.amount)
|
|
|
|
|
|
|
|
proc init*(T: type Handshake, buffer: seq[byte]): ProtoResult[T] =
|
|
|
|
var beneficiary: seq[byte]
|
|
|
|
var handshake = Handshake()
|
|
|
|
let pb = initProtoBuffer(buffer)
|
|
|
|
|
|
|
|
discard ? pb.getField(1, handshake.beneficiary)
|
|
|
|
|
|
|
|
ok(handshake)
|
|
|
|
|
|
|
|
proc init*(T: type Cheque, buffer: seq[byte]): ProtoResult[T] =
|
|
|
|
var beneficiary: seq[byte]
|
|
|
|
var date: uint32
|
|
|
|
var amount: uint32
|
|
|
|
var cheque = Cheque()
|
|
|
|
let pb = initProtoBuffer(buffer)
|
|
|
|
|
|
|
|
discard ? pb.getField(1, cheque.beneficiary)
|
|
|
|
discard ? pb.getField(2, cheque.date)
|
|
|
|
discard ? pb.getField(3, cheque.amount)
|
|
|
|
|
|
|
|
ok(cheque)
|
2020-11-16 09:55:49 +00:00
|
|
|
|
|
|
|
# Accounting
|
2020-11-24 04:53:42 +00:00
|
|
|
# -------------------------------------------------------------------------------
|
2020-11-16 09:55:49 +00:00
|
|
|
#
|
2020-11-24 04:53:42 +00:00
|
|
|
# We credit and debits peers based on what for now is a form of Karma asset.
|
|
|
|
|
|
|
|
# TODO Test for credit/debit operations in succession
|
2020-11-16 09:55:49 +00:00
|
|
|
|
2020-11-26 10:02:10 +00:00
|
|
|
proc sendCheque*(ws: WakuSwap) {.async.} =
|
|
|
|
# TODO Better peer selection, for now using hardcoded peer
|
|
|
|
let peer = ws.peers[0]
|
|
|
|
let conn = await ws.switch.dial(peer.peerInfo.peerId, peer.peerInfo.addrs, WakuSwapCodec)
|
|
|
|
|
|
|
|
info "sendCheque"
|
|
|
|
|
|
|
|
# TODO Add beneficiary, etc
|
|
|
|
# XXX Hardcoded amount for now
|
|
|
|
await conn.writeLP(Cheque(amount: 1).encode().buffer)
|
|
|
|
|
|
|
|
# Set new balance
|
|
|
|
# XXX Assume peerId is first peer
|
|
|
|
let peerId = ws.peers[0].peerInfo.peerId
|
|
|
|
ws.accounting[peerId] -= 1
|
|
|
|
info "New accounting state", accounting = ws.accounting[peerId]
|
|
|
|
|
|
|
|
# TODO Authenticate cheque, check beneficiary etc
|
|
|
|
proc handleCheque*(ws: WakuSwap, cheque: Cheque) =
|
|
|
|
info "handle incoming cheque"
|
|
|
|
# XXX Assume peerId is first peer
|
|
|
|
let peerId = ws.peers[0].peerInfo.peerId
|
|
|
|
ws.accounting[peerId] += int(cheque.amount)
|
|
|
|
info "New accounting state", accounting = ws.accounting[peerId]
|
|
|
|
|
2020-11-18 12:45:51 +00:00
|
|
|
proc init*(wakuSwap: WakuSwap) =
|
|
|
|
info "wakuSwap init 1"
|
|
|
|
proc handle(conn: Connection, proto: string) {.async, gcsafe, closure.} =
|
2020-11-26 10:02:10 +00:00
|
|
|
info "swap handle incoming connection"
|
|
|
|
var message = await conn.readLp(64*1024)
|
|
|
|
# XXX This can be handshake, etc
|
|
|
|
var res = Cheque.init(message)
|
|
|
|
if res.isErr:
|
|
|
|
error "failed to decode rpc"
|
|
|
|
return
|
|
|
|
|
|
|
|
info "received cheque", value=res.value
|
|
|
|
wakuSwap.handleCheque(res.value)
|
2020-11-18 12:45:51 +00:00
|
|
|
|
2020-11-24 04:53:42 +00:00
|
|
|
proc credit(peerId: PeerId, n: int) {.gcsafe, closure.} =
|
|
|
|
info "Crediting peer for", peerId, n
|
|
|
|
if wakuSwap.accounting.hasKey(peerId):
|
|
|
|
wakuSwap.accounting[peerId] -= n
|
|
|
|
else:
|
|
|
|
wakuSwap.accounting[peerId] = -n
|
|
|
|
info "Accounting state", accounting = wakuSwap.accounting[peerId]
|
|
|
|
|
2020-11-26 10:02:10 +00:00
|
|
|
# TODO Isolate to policy function
|
|
|
|
# TODO Tunable disconnect threshhold, hard code for PoC
|
|
|
|
let disconnectThreshhold = 2
|
|
|
|
if wakuSwap.accounting[peerId] >= disconnectThreshhold:
|
|
|
|
info "Disconnect threshhold hit, disconnect peer"
|
|
|
|
else:
|
|
|
|
info "Disconnect threshhold not hit"
|
|
|
|
|
2020-11-24 04:53:42 +00:00
|
|
|
# TODO Debit and credit here for Karma asset
|
|
|
|
proc debit(peerId: PeerId, n: int) {.gcsafe, closure.} =
|
|
|
|
info "Debiting peer for", peerId, n
|
2020-11-18 12:45:51 +00:00
|
|
|
if wakuSwap.accounting.hasKey(peerId):
|
|
|
|
wakuSwap.accounting[peerId] += n
|
|
|
|
else:
|
|
|
|
wakuSwap.accounting[peerId] = n
|
|
|
|
info "Accounting state", accounting = wakuSwap.accounting[peerId]
|
|
|
|
|
2020-11-26 10:02:10 +00:00
|
|
|
# TODO Isolate to policy function
|
|
|
|
# TODO Tunable payment threshhold, hard code for PoC
|
|
|
|
let paymentThreshhold = 1
|
|
|
|
if wakuSwap.accounting[peerId] >= paymentThreshhold:
|
|
|
|
info "Payment threshhold hit, send cheque"
|
|
|
|
discard wakuSwap.sendCheque()
|
|
|
|
else:
|
|
|
|
info "Payment threshhold not hit"
|
|
|
|
|
2020-11-18 12:45:51 +00:00
|
|
|
wakuSwap.handler = handle
|
|
|
|
wakuSwap.codec = WakuSwapCodec
|
2020-11-24 04:53:42 +00:00
|
|
|
wakuSwap.credit = credit
|
|
|
|
wakuSwap.debit = debit
|
2020-11-18 12:45:51 +00:00
|
|
|
|
2020-11-24 04:53:42 +00:00
|
|
|
# TODO Expression return?
|
2020-11-18 12:45:51 +00:00
|
|
|
proc init*(T: type WakuSwap, switch: Switch, rng: ref BrHmacDrbgContext): T =
|
|
|
|
info "wakuSwap init 2"
|
|
|
|
new result
|
|
|
|
result.rng = rng
|
|
|
|
result.switch = switch
|
|
|
|
result.accounting = initTable[PeerId, int]()
|
|
|
|
result.text = "test"
|
|
|
|
result.init()
|
2020-11-16 09:55:49 +00:00
|
|
|
|
2020-11-26 10:02:10 +00:00
|
|
|
proc setPeer*(ws: WakuSwap, peer: PeerInfo) =
|
|
|
|
ws.peers.add(SwapPeer(peerInfo: peer))
|
|
|
|
|
2020-11-16 09:55:49 +00:00
|
|
|
# TODO End to end communication
|
2020-11-26 10:02:10 +00:00
|
|
|
|