diff --git a/dagger/bitswap/engine/payments.nim b/dagger/bitswap/engine/payments.nim new file mode 100644 index 00000000..6417d7ac --- /dev/null +++ b/dagger/bitswap/engine/payments.nim @@ -0,0 +1,40 @@ +import std/math +import pkg/nitro +import pkg/questionable/results +import ../peercontext + +export nitro +export results +export peercontext + +push: {.upraises: [].} + +const ChainId = 0.u256 # invalid chain id for now +const AmountPerChannel = (10^18).u256 # 1 asset, ERC20 default is 18 decimals + +func openLedgerChannel*(wallet: var Wallet, + hub: EthAddress, + asset: EthAddress): ?!ChannelId = + wallet.openLedgerChannel(hub, ChainId, asset, AmountPerChannel) + +func getOrOpenChannel(wallet: var Wallet, peer: BitswapPeerCtx): ?!ChannelId = + if channel =? peer.paymentChannel: + channel.success + elif pricing =? peer.pricing: + let channel = ?wallet.openLedgerChannel(pricing.address, pricing.asset) + peer.paymentChannel = channel.some + channel.success + else: + ChannelId.failure "no pricing set for peer" + +func pay*(wallet: var Wallet, + peer: BitswapPeerCtx, + amountOfBytes: int): ?!SignedState = + if pricing =? peer.pricing: + let amount = amountOfBytes.u256 * pricing.price + let asset = pricing.asset + let receiver = pricing.address + let channel = ?wallet.getOrOpenChannel(peer) + wallet.pay(channel, asset, receiver, amount) + else: + SignedState.failure "no pricing set for peer" diff --git a/dagger/bitswap/peercontext.nim b/dagger/bitswap/peercontext.nim index 5685688d..b57a82e4 100644 --- a/dagger/bitswap/peercontext.nim +++ b/dagger/bitswap/peercontext.nim @@ -1,20 +1,25 @@ import std/sequtils import pkg/libp2p import pkg/chronos +import pkg/nitro import pkg/questionable import ./protobuf/bitswap import ./protobuf/payments +export payments +export nitro + type BitswapPeerCtx* = ref object of RootObj id*: PeerID - peerHave*: seq[Cid] # remote peers have lists - peerWants*: seq[Entry] # remote peers want lists - bytesSent*: int # bytes sent to remote - bytesRecv*: int # bytes received from remote - exchanged*: int # times peer has exchanged with us - lastExchange*: Moment # last time peer has exchanged with us - pricing*: ?Pricing # optional bandwidth price for this peer + peerHave*: seq[Cid] # remote peers have lists + peerWants*: seq[Entry] # remote peers want lists + bytesSent*: int # bytes sent to remote + bytesRecv*: int # bytes received from remote + exchanged*: int # times peer has exchanged with us + lastExchange*: Moment # last time peer has exchanged with us + pricing*: ?Pricing # optional bandwidth price for this peer + paymentChannel*: ?ChannelId # payment channel id proc contains*(a: openArray[BitswapPeerCtx], b: PeerID): bool = ## Convenience method to check for peer prepense diff --git a/dagger/bitswap/protobuf/payments.nim b/dagger/bitswap/protobuf/payments.nim index 045dbb1e..5fca5aa2 100644 --- a/dagger/bitswap/protobuf/payments.nim +++ b/dagger/bitswap/protobuf/payments.nim @@ -1,5 +1,6 @@ import pkg/protobuf_serialization import pkg/stew/byteutils +import pkg/stint import pkg/nitro import pkg/questionable import pkg/upraises @@ -8,6 +9,7 @@ import ./bitswap export PricingMessage export StateChannelUpdate +export stint export nitro push: {.upraises: [].} diff --git a/tests/dagger/bitswap/engine/testpayments.nim b/tests/dagger/bitswap/engine/testpayments.nim new file mode 100644 index 00000000..c66a7e74 --- /dev/null +++ b/tests/dagger/bitswap/engine/testpayments.nim @@ -0,0 +1,32 @@ +import std/unittest +import pkg/dagger/bitswap/engine/payments +import ../../examples + +suite "engine payments": + + let amountOfBytes = 42 + + var wallet: Wallet + var peer: BitswapPeerCtx + + setup: + wallet = Wallet.example + peer = BitswapPeerCtx.example + peer.pricing = Pricing.example.some + + test "pays for received bytes": + let payment = wallet.pay(peer, amountOfBytes).get + let pricing = peer.pricing.get + let balances = payment.state.outcome.balances(pricing.asset) + let destination = pricing.address.toDestination + check balances[destination].get == amountOfBytes.u256 * pricing.price + + test "no payment when no price is set": + peer.pricing = Pricing.none + check wallet.pay(peer, amountOfBytes).isErr + + test "uses same channel for consecutive payments": + let payment1, payment2 = wallet.pay(peer, amountOfBytes) + let channel1 = payment1.?state.?channel.?getChannelId + let channel2 = payment2.?state.?channel.?getChannelId + check channel1 == channel2 diff --git a/tests/dagger/examples.nim b/tests/dagger/examples.nim index b4ae1d99..948a88ee 100644 --- a/tests/dagger/examples.nim +++ b/tests/dagger/examples.nim @@ -1,6 +1,11 @@ import std/random +import std/sequtils +import pkg/libp2p import pkg/nitro +import pkg/dagger/p2p/rng import pkg/dagger/bitswap/protobuf/payments +import pkg/dagger/bitswap/peercontext +import pkg/dagger/blocktype proc example*(_: type EthAddress): EthAddress = EthPrivateKey.random().toPublicKey.toAddress @@ -30,5 +35,17 @@ proc example*(_: type Pricing): Pricing = Pricing( address: EthAddress.example, asset: EthAddress.example, - price: UInt256.example() + price: uint32.example.u256 ) + +proc example*(_: type Block): Block = + let length = rand(4096) + let bytes = newSeqWith(length, rand(uint8)) + Block.new(bytes) + +proc example*(_: type PeerId): PeerID = + let key = PrivateKey.random(Rng.instance[]).get + PeerId.init(key.getKey().get).get + +proc example*(_: type BitswapPeerCtx): BitswapPeerCtx = + BitswapPeerCtx(id: PeerID.example) diff --git a/tests/testAll.nim b/tests/testAll.nim index 4e007243..2b03c4a7 100644 --- a/tests/testAll.nim +++ b/tests/testAll.nim @@ -1,7 +1,8 @@ import ./dagger/bitswap/testbitswap import ./dagger/bitswap/testengine import ./dagger/bitswap/testnetwork -import ./dagger/bitswap/protobuf/testpayments +import ./dagger/bitswap/protobuf/testpayments as testprotobufpayments +import ./dagger/bitswap/engine/testpayments as testenginepayments import ./dagger/testasyncheapqueue import ./dagger/testblockstore import ./dagger/testchunking