Accept incoming channel

This commit is contained in:
Mark Spanbroek 2021-03-16 12:50:46 +01:00
parent 48e27a9bd1
commit 2737732e6d
4 changed files with 113 additions and 35 deletions

20
nitro/channelupdate.nim Normal file
View File

@ -0,0 +1,20 @@
import ./basics
import ./protocol
include questionable/errorban
type
ChannelUpdate* = object
state*: State
signatures*: seq[(EthAddress, Signature)]
proc participants*(update: ChannelUpdate): seq[EthAddress] =
update.state.channel.participants
proc verifySignatures*(update: ChannelUpdate): bool =
for (participant, signature) in update.signatures:
if not update.participants.contains(participant):
return false
if not signature.verify(update.state, participant):
return false
true

20
nitro/ledger.nim Normal file
View File

@ -0,0 +1,20 @@
import ./basics
import ./channelupdate
import ./protocol
proc startLedger*(me: EthAddress,
hub: EthAddress,
chainId: UInt256,
nonce: UInt48,
asset: EthAddress,
amount: UInt256): ChannelUpdate =
ChannelUpdate(
state: State(
channel: ChannelDefinition(
chainId: chainId,
participants: @[me, hub],
nonce: nonce
),
outcome: Outcome.init(asset, {me.toDestination: amount})
)
)

View File

@ -1,21 +1,21 @@
import ./basics
import ./keys
import ./protocol
import ./channelupdate
import ./ledger
include questionable/errorban
export basics
export keys
export channelupdate
type
Wallet* = object
key: PrivateKey
channels*: seq[Channel]
Channel* = object
latest*, upcoming*: ?ChannelUpdate
ChannelUpdate* = object
state*: State
signatures*: seq[(EthAddress, Signature)]
latest*: ChannelUpdate
proc init*(_: type Wallet, key: PrivateKey): Wallet =
result.key = key
@ -23,25 +23,31 @@ proc init*(_: type Wallet, key: PrivateKey): Wallet =
proc address*(wallet: Wallet): EthAddress =
wallet.key.toPublicKey.toAddress
proc sign(wallet: Wallet, update: ChannelUpdate): ChannelUpdate =
var signed = update
signed.signatures &= @{wallet.address: wallet.key.sign(update.state)}
signed
proc createChannel(wallet: var Wallet, update: ChannelUpdate): Channel =
let signed = wallet.sign(update)
let channel = Channel(latest: signed)
wallet.channels.add(channel)
channel
proc openLedgerChannel*(wallet: var Wallet,
hub: EthAddress,
chainId: UInt256,
nonce: UInt48,
asset: EthAddress,
amount: UInt256): Channel =
let state = State(
channel: ChannelDefinition(
chainId: chainId,
participants: @[wallet.address, hub],
nonce: nonce
),
outcome: Outcome.init(asset, {wallet.address.toDestination: amount})
)
let channel = Channel(
upcoming: ChannelUpdate(
state: state,
signatures: @{wallet.address: wallet.key.sign(state)}
).some
)
wallet.channels.add(channel)
channel
let update = startLedger(wallet.address, hub, chainId, nonce, asset, amount)
wallet.createChannel(update)
proc acceptChannel*(wallet: var Wallet, update: ChannelUpdate): ?!Channel =
if not update.participants.contains(wallet.address):
return Channel.failure "wallet owner is not a participant"
if not verifySignatures(update):
return Channel.failure "incorrect signatures"
wallet.createChannel(update).success

View File

@ -23,30 +23,62 @@ suite "wallet: opening ledger channel":
wallet = Wallet.init(key)
channel = wallet.openLedgerChannel(hub, chainId, nonce, asset, amount)
test "creates a new upcoming state":
check channel.latest.isNone
check channel.upcoming.isSome
test "sets correct channel definition":
let definition = channel.upcoming?.state?.channel
check definition?.chainId == chainId.some
check definition?.nonce == nonce.some
check definition?.participants == @[wallet.address, hub].some
let definition = channel.latest.state.channel
check definition.chainId == chainId
check definition.nonce == nonce
check definition.participants == @[wallet.address, hub]
test "provides correct outcome":
let outcome = channel.upcoming?.state?.outcome
let outcome = channel.latest.state.outcome
let destination = wallet.address.toDestination
check outcome == Outcome.init(asset, {destination: amount}).some
check outcome == Outcome.init(asset, {destination: amount})
test "signs the upcoming state":
let state = channel.upcoming?.state
let signatures = channel.upcoming?.signatures
check signatures == @{wallet.address: key.sign(state.get)}.some
let state = channel.latest.state
let signatures = channel.latest.signatures
check signatures == @{wallet.address: key.sign(state)}
test "sets app definition and app data to zero":
check channel.upcoming?.state?.appDefinition == EthAddress.zero.some
check channel.upcoming?.state?.appData?.len == 0.some
check channel.latest.state.appDefinition == EthAddress.zero
check channel.latest.state.appData.len == 0
test "updates the list of channels":
check wallet.channels == @[channel]
suite "wallet: accepting incoming channel":
let key = PrivateKey.random()
var wallet: Wallet
var update: ChannelUpdate
setup:
wallet = Wallet.init(key)
update = ChannelUpdate(state: State.example)
update.state.channel.participants &= @[wallet.address]
test "returns the new channel instance":
let channel = wallet.acceptChannel(update).get
check channel.latest.state == update.state
test "updates the list of channels":
let channel = wallet.acceptChannel(update).get
check wallet.channels == @[channel]
test "signs the channel state":
let channel = wallet.acceptChannel(update).get
let expectedSignatures = @{wallet.address: key.sign(update.state)}
check channel.latest.signatures == expectedSignatures
test "fails when wallet address is not a participant":
let wrongParticipants = seq[EthAddress].example
update.state.channel.participants = wrongParticipants
check wallet.acceptChannel(update).isErr
test "fails when signatures are incorrect":
let otherKey = PrivateKey.random()
let otherWallet = Wallet.init(otherKey)
let wrongAddress = EthAddress.example
update.state.channel.participants &= @[otherWallet.address]
update.signatures = @{wrongAddress: otherKey.sign(update.state)}
check wallet.acceptChannel(update).isErr