Skip to content

Commit

Permalink
Refactor channel test helpers (#1682)
Browse files Browse the repository at this point in the history
It's useful to separate channel state test methods in a dedicated trait
instead of always bundling it with `FixtureTestSuite`.

In particular, it was previously impossible to use both `BitcoindService`
and `StateTestsHelperMethods` because `BitcoindService` doesn't work with
fixtures (it leverages `beforeAll` and `afterAll` instead because launching
one bitcoind instance per-test would be too expensive and useless).
  • Loading branch information
t-bast authored Feb 4, 2021
1 parent d053188 commit 4902362
Show file tree
Hide file tree
Showing 21 changed files with 147 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import akka.testkit.TestKit
import org.scalatest.{BeforeAndAfterAll, TestSuite}

/**
* This base class kills all actor between each tests.
* This base class ensures the actor system is shutdown after the test suite ends.
* Created by PM on 06/09/2016.
*/
abstract class TestKitBaseClass extends TestKit(ActorSystem("test")) with TestSuite with BeforeAndAfterAll {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import fr.acinq.eclair.TestConstants.TestFeeEstimator
import fr.acinq.eclair.blockchain.fee.{FeeTargets, FeeratePerKw, FeerateTolerance, OnChainFeeConf}
import fr.acinq.eclair.channel.Commitments._
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
Expand All @@ -35,7 +35,7 @@ import scodec.bits.ByteVector
import scala.concurrent.duration._
import scala.util.Random

class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

type FixtureParam = SetupFixture

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,28 @@

package fr.acinq.eclair.channel

import java.util.UUID
import java.util.concurrent.CountDownLatch

import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.payment.OutgoingPacket.Upstream
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceivePayment
import fr.acinq.eclair.payment.receive.PaymentHandler
import fr.acinq.eclair.payment.relay.Relayer
import fr.acinq.eclair.payment.OutgoingPacket.Upstream
import fr.acinq.eclair.router.Router.ChannelHop
import fr.acinq.eclair.wire.Onion.FinalLegacyPayload
import fr.acinq.eclair.wire._
import grizzled.slf4j.Logging
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}

import java.util.UUID
import java.util.concurrent.CountDownLatch
import scala.collection.immutable.Nil
import scala.concurrent.duration._
import scala.util.Random
Expand All @@ -47,7 +46,7 @@ import scala.util.Random
* Created by PM on 05/07/2016.
*/

class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods with Logging {
class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase with Logging {

case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], pipe: ActorRef, relayerA: ActorRef, relayerB: ActorRef, paymentHandlerA: ActorRef, paymentHandlerB: ActorRef)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.blockchain.WatchEventSpent
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager
import fr.acinq.eclair.transactions.Scripts
Expand All @@ -17,7 +17,7 @@ import org.scalatest.funsuite.FixtureAnyFunSuiteLike

import scala.concurrent.duration._

class RecoverySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class RecoverySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

type FixtureParam = SetupFixture

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.fee.FeeTargets
import fr.acinq.eclair.channel._
import fr.acinq.eclair.io.Peer
import fr.acinq.eclair.payment.OutgoingPacket
import fr.acinq.eclair.payment.OutgoingPacket.Upstream
import fr.acinq.eclair.router.Router.ChannelHop
Expand All @@ -39,7 +38,29 @@ import scala.concurrent.duration._
/**
* Created by PM on 23/08/2016.
*/
trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with ParallelTestExecution {
trait StateTestsBase extends StateTestsHelperMethods with FixtureTestSuite with ParallelTestExecution {

implicit class ChannelWithTestFeeConf(a: TestFSMRef[State, Data, Channel]) {
// @formatter:off
def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets
// @formatter:on
}

}

object StateTestsTags {
/** If set, channels will use option_static_remotekey. */
val StaticRemoteKey = "static_remotekey"
/** If set, channels will use option_anchor_outputs. */
val AnchorOutputs = "anchor_outputs"
/** If set, channels will be public (otherwise we don't announce them by default). */
val ChannelsPublic = "channels_public"
/** If set, no amount will be pushed when opening a channel (by default we push a small amount). */
val NoPushMsat = "no_push_msat"
}

trait StateTestsHelperMethods extends TestKitBase {

case class SetupFixture(alice: TestFSMRef[State, Data, Channel],
bob: TestFSMRef[State, Data, Channel],
Expand Down Expand Up @@ -77,12 +98,12 @@ trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with Par

def reachNormal(setup: SetupFixture, tags: Set[String] = Set.empty): Unit = {
import setup._
val channelFlags = if (tags.contains("channels_public")) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
val pushMsat = if (tags.contains("no_push_msat")) 0.msat else TestConstants.pushMsat
val (aliceParams, bobParams, channelVersion) = if (tags.contains("anchor_outputs")) {
val channelFlags = if (tags.contains(StateTestsTags.ChannelsPublic)) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
val pushMsat = if (tags.contains(StateTestsTags.NoPushMsat)) 0.msat else TestConstants.pushMsat
val (aliceParams, bobParams, channelVersion) = if (tags.contains(StateTestsTags.AnchorOutputs)) {
val features = Features(Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Mandatory), ActivatedFeature(Features.AnchorOutputs, FeatureSupport.Optional)))
(Alice.channelParams.copy(features = features), Bob.channelParams.copy(features = features), ChannelVersion.ANCHOR_OUTPUTS)
} else if (tags.contains("static_remotekey")) {
} else if (tags.contains(StateTestsTags.StaticRemoteKey)) {
val features = Features(Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Optional)))
val aliceParams = Alice.channelParams.copy(features = features, walletStaticPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet)))
val bobParams = Bob.channelParams.copy(features = features, walletStaticPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet)))
Expand Down Expand Up @@ -240,19 +261,19 @@ trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with Par
s2blockchain.expectMsgAllOf(localCommitPublished.claimHtlcDelayedTxs.map(PublishAsap): _*)

// we watch the confirmation of the "final" transactions that send funds to our wallets (main delayed output and 2nd stage htlc transactions)
assert(s2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(commitTx))
localCommitPublished.claimMainDelayedOutputTx.foreach(tx => assert(s2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx)))
assert(localCommitPublished.claimHtlcDelayedTxs.map(_ => s2blockchain.expectMsgType[WatchConfirmed].event).toSet === localCommitPublished.claimHtlcDelayedTxs.map(BITCOIN_TX_CONFIRMED).toSet)
assert(s2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(commitTx))
localCommitPublished.claimMainDelayedOutputTx.foreach(tx => assert(s2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(tx)))
assert(localCommitPublished.claimHtlcDelayedTxs.map(_ => s2blockchain.expectMsgType[WatchConfirmed].event).toSet == localCommitPublished.claimHtlcDelayedTxs.map(BITCOIN_TX_CONFIRMED).toSet)

// we watch outputs of the commitment tx that both parties may spend
val htlcOutputIndexes = (localCommitPublished.htlcSuccessTxs ++ localCommitPublished.htlcTimeoutTxs).map(tx => tx.txIn.head.outPoint.index)
val spentWatches = htlcOutputIndexes.map(_ => s2blockchain.expectMsgType[WatchSpent])
spentWatches.foreach(ws => assert(ws.event === BITCOIN_OUTPUT_SPENT))
spentWatches.foreach(ws => assert(ws.txId === commitTx.txid))
assert(spentWatches.map(_.outputIndex).toSet === htlcOutputIndexes.toSet)
spentWatches.foreach(ws => assert(ws.event == BITCOIN_OUTPUT_SPENT))
spentWatches.foreach(ws => assert(ws.txId == commitTx.txid))
assert(spentWatches.map(_.outputIndex).toSet == htlcOutputIndexes.toSet)
s2blockchain.expectNoMsg(1 second)

// s is now in CLOSING state with txes pending for confirmation before going in CLOSED state
// s is now in CLOSING state with txs pending for confirmation before going in CLOSED state
s.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get
}

Expand All @@ -274,38 +295,26 @@ trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with Par
s2blockchain.expectMsg(PublishAsap(tx))
})
// all htlcs success/timeout should be claimed
val claimHtlcTxes = remoteCommitPublished.claimHtlcSuccessTxs ++ remoteCommitPublished.claimHtlcTimeoutTxs
claimHtlcTxes.foreach(tx => Transaction.correctlySpends(tx, rCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
s2blockchain.expectMsgAllOf(claimHtlcTxes.map(PublishAsap): _*)
val claimHtlcTxs = remoteCommitPublished.claimHtlcSuccessTxs ++ remoteCommitPublished.claimHtlcTimeoutTxs
claimHtlcTxs.foreach(tx => Transaction.correctlySpends(tx, rCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
s2blockchain.expectMsgAllOf(claimHtlcTxs.map(PublishAsap): _*)

// we watch the confirmation of the "final" transactions that send funds to our wallets (main delayed output and 2nd stage htlc transactions)
assert(s2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(rCommitTx))
remoteCommitPublished.claimMainOutputTx.foreach(tx => assert(s2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx)))
assert(s2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(rCommitTx))
remoteCommitPublished.claimMainOutputTx.foreach(tx => assert(s2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(tx)))

// we watch outputs of the commitment tx that both parties may spend
val htlcOutputIndexes = claimHtlcTxes.map(tx => tx.txIn.head.outPoint.index)
val htlcOutputIndexes = claimHtlcTxs.map(tx => tx.txIn.head.outPoint.index)
val spentWatches = htlcOutputIndexes.map(_ => s2blockchain.expectMsgType[WatchSpent])
spentWatches.foreach(ws => assert(ws.event === BITCOIN_OUTPUT_SPENT))
spentWatches.foreach(ws => assert(ws.txId === rCommitTx.txid))
assert(spentWatches.map(_.outputIndex).toSet === htlcOutputIndexes.toSet)
spentWatches.foreach(ws => assert(ws.event == BITCOIN_OUTPUT_SPENT))
spentWatches.foreach(ws => assert(ws.txId == rCommitTx.txid))
assert(spentWatches.map(_.outputIndex).toSet == htlcOutputIndexes.toSet)
s2blockchain.expectNoMsg(1 second)

// s is now in CLOSING state with txes pending for confirmation before going in CLOSED state
// s is now in CLOSING state with txs pending for confirmation before going in CLOSED state
getRemoteCommitPublished(s.stateData.asInstanceOf[DATA_CLOSING]).get
}

def channelId(a: TestFSMRef[State, Data, Channel]): ByteVector32 = a.stateData.channelId

// @formatter:off
implicit class ChannelWithTestFeeConf(a: TestFSMRef[State, Data, Channel]) {
def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets
}

implicit class PeerWithTestFeeConf(a: TestFSMRef[Peer.State, Peer.Data, Peer]) {
def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets
}
// @formatter:on

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet}
import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.wire.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream}
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, Features, TestConstants, TestKitBaseClass}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
Expand All @@ -39,7 +39,7 @@ import scala.concurrent.{Future, Promise}
* Created by PM on 05/07/2016.
*/

class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import fr.acinq.eclair.Features.Wumbo
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.wire.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream}
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
Expand All @@ -36,7 +36,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/

class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestConstants, TestKitBaseClass}
import org.scalatest.Outcome
Expand All @@ -33,16 +33,16 @@ import scala.concurrent.duration._
import scala.concurrent.{Future, Promise}

/**
* Created by PM on 05/07/2016.
*/
* Created by PM on 05/07/2016.
*/

class WaitForFundingCreatedInternalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class WaitForFundingCreatedInternalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)

override def withFixture(test: OneArgTest): Outcome = {
val noopWallet = new TestWallet {
override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed
override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed
}
val setup = init(wallet = noopWallet)
import setup._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.{ByteVector32, SatoshiLong}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
Expand All @@ -35,7 +35,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/

class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestKitBaseClass}
import org.scalatest.Outcome
Expand All @@ -31,10 +31,10 @@ import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scala.concurrent.duration._

/**
* Created by PM on 05/07/2016.
*/
* Created by PM on 05/07/2016.
*/

class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.{ByteVector32, SatoshiLong, Script, Transaction}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.states.StateTestsBase
import fr.acinq.eclair.transactions.Scripts.multiSig2of2
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestKitBaseClass, randomKey}
Expand All @@ -34,7 +34,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/

class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsHelperMethods {
class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTestsBase {

case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)

Expand Down
Loading

0 comments on commit 4902362

Please sign in to comment.