1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-21 22:11:46 +01:00

added a NodeParams class

This commit is contained in:
pm47 2017-02-26 16:06:41 +01:00
parent 810aed301d
commit 89db03fe91
16 changed files with 72 additions and 68 deletions

View file

@ -43,7 +43,7 @@ object Boot extends App with Logging {
class Setup() extends Logging {
logger.info(s"hello!")
logger.info(s"nodeid=${Globals.Node.publicKey.toBin} alias=${Globals.Node.alias}")
logger.info(s"nodeid=${Globals.nodeParams.privateKey.publicKey.toBin} alias=${Globals.nodeParams.alias}")
val config = ConfigFactory.load()
implicit lazy val system = ActorSystem()
@ -89,9 +89,9 @@ class Setup() extends Logging {
case "noop" => system.actorOf(Props[NoopPaymentHandler], name = "payment-handler")
}
val register = system.actorOf(Props(new Register), name = "register")
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler), name = "relayer")
val relayer = system.actorOf(Relayer.props(Globals.nodeParams.privateKey, paymentHandler), name = "relayer")
val router = system.actorOf(Router.props(watcher), name = "router")
val paymentInitiator = system.actorOf(PaymentInitiator.props(Globals.Node.publicKey, router), "payment-initiator")
val paymentInitiator = system.actorOf(PaymentInitiator.props(Globals.nodeParams.privateKey.publicKey, router), "payment-initiator")
val switchboard = system.actorOf(Switchboard.props(watcher, router, relayer, finalScriptPubKey), name = "switchboard")
val server = system.actorOf(Server.props(switchboard, new InetSocketAddress(config.getString("eclair.server.host"), config.getInt("eclair.server.port"))), "server")

View file

@ -4,11 +4,8 @@ import java.net.InetSocketAddress
import java.util.concurrent.atomic.AtomicLong
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet}
import fr.acinq.eclair.router.Router
import scala.compat.Platform
import scala.concurrent.duration._
/**
@ -17,34 +14,25 @@ import scala.concurrent.duration._
object Globals {
val config = ConfigFactory.load().getConfig("eclair")
object Node {
val seed: BinaryData = config.getString("node.seed")
val master = DeterministicWallet.generate(seed)
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
val privateKey = extendedPrivateKey.privateKey
val extendedPublicKey = DeterministicWallet.publicKey(extendedPrivateKey)
val publicKey = extendedPublicKey.publicKey
val id = publicKey.toBin.toString()
val alias = config.getString("node.alias").take(32)
val color: (Byte, Byte, Byte) = (config.getInt("node.color.r").toByte, config.getInt("node.color.g").toByte, config.getInt("node.color.b").toByte)
val address = new InetSocketAddress(config.getString("server.host"), config.getInt("server.port"))
}
val seed: BinaryData = config.getString("node.seed")
val master = DeterministicWallet.generate(seed)
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
val global_features = BinaryData("")
val local_features = BinaryData("05") // channels_public and initial_routing_sync
val expiry_delta_blocks = config.getInt("expiry-delta-blocks")
val htlc_minimum_msat = config.getInt("htlc-minimum-msat")
val delay_blocks = config.getInt("delay-blocks")
val mindepth_blocks = config.getInt("mindepth-blocks")
val feeratePerKw = 10000
val fee_base_msat = config.getInt("fee-base-msat")
val fee_proportional_millionth = config.getInt("fee-proportional-millionth")
val default_anchor_amount = 1000000
// channel reserve can't be more than 5% of the funding amount (recommended: 1%)
val max_reserve_to_funding_ratio = 0.05
val nodeParams = NodeParams(privateKey = extendedPrivateKey.privateKey,
alias = config.getString("node.alias").take(32),
color = (config.getInt("node.color.r").toByte, config.getInt("node.color.g").toByte, config.getInt("node.color.b").toByte),
address = new InetSocketAddress(config.getString("server.host"), config.getInt("server.port")),
globalFeatures = BinaryData(""),
localFeatures = BinaryData("05"), // channels_public and initial_routing_sync
expiryDeltaBlocks = config.getInt("expiry-delta-blocks"),
htlcMinimumMsat = config.getInt("htlc-minimum-msat"),
delayBlocks = config.getInt("delay-blocks"),
minDepthBlocks = config.getInt("mindepth-blocks"),
feeratePerKw = 10000,
feeBaseMsat = config.getInt("fee-base-msat"),
feeProportionalMillionth = config.getInt("fee-proportional-millionth"),
maxReserveToFundingRatio = 0.05 // channel reserve can't be more than 5% of the funding amount (recommended: 1%)
)
/**
* This counter holds the current blockchain height.
@ -53,3 +41,18 @@ object Globals {
*/
val blockCount = new AtomicLong(0)
}
case class NodeParams(privateKey: PrivateKey,
alias: String,
color: (Byte, Byte, Byte),
address: InetSocketAddress,
globalFeatures: BinaryData,
localFeatures: BinaryData,
expiryDeltaBlocks: Int,
htlcMinimumMsat: Int,
delayBlocks: Int,
minDepthBlocks: Int,
feeratePerKw: Int,
feeBaseMsat: Int,
feeProportionalMillionth: Int,
maxReserveToFundingRatio: Double)

View file

@ -108,7 +108,7 @@ class Channel(val r: ActorRef, val blockchain: ActorRef, router: ActorRef, relay
case Success(_) =>
context.system.eventStream.publish(ChannelCreated(open.temporaryChannelId, context.parent, self, localParams, remoteNodeId))
// TODO: maybe also check uniqueness of temporary channel id
val minimumDepth = Globals.mindepth_blocks
val minimumDepth = Globals.nodeParams.minDepthBlocks
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
dustLimitSatoshis = localParams.dustLimitSatoshis,
@ -334,7 +334,7 @@ class Channel(val r: ActorRef, val blockchain: ActorRef, router: ActorRef, relay
// this clock will be used to detect htlc timeouts
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
if (Funding.announceChannel(params.localParams.localFeatures, params.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.Node.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.nodeParams.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val annSignatures = AnnouncementSignatures(d.channelId, localNodeSig, localBitcoinSig)
remote ! annSignatures
goto(WAIT_FOR_ANN_SIGNATURES) using DATA_WAIT_FOR_ANN_SIGNATURES(params, commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), annSignatures)
@ -355,10 +355,10 @@ class Channel(val r: ActorRef, val blockchain: ActorRef, router: ActorRef, relay
when(WAIT_FOR_ANN_SIGNATURES)(handleExceptions {
case Event(AnnouncementSignatures(_, remoteNodeSig, remoteBitcoinSig), d@DATA_WAIT_FOR_ANN_SIGNATURES(params, commitments, _)) =>
log.info(s"announcing channel ${d.channelId} on the network")
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.Node.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val channelAnn = Announcements.makeChannelAnnouncement(d.channelId, Globals.Node.publicKey, remoteNodeId, d.params.localParams.fundingPrivKey.publicKey, d.params.remoteParams.fundingPubKey, localNodeSig, remoteNodeSig, localBitcoinSig, remoteBitcoinSig)
val nodeAnn = Announcements.makeNodeAnnouncement(Globals.Node.privateKey, Globals.Node.alias, Globals.Node.color, Globals.Node.address :: Nil, Platform.currentTime / 1000)
val channelUpdate = Announcements.makeChannelUpdate(Globals.Node.privateKey, remoteNodeId, d.commitments.channelId, Globals.expiry_delta_blocks, Globals.htlc_minimum_msat, Globals.fee_base_msat, Globals.fee_proportional_millionth, Platform.currentTime / 1000)
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.nodeParams.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val channelAnn = Announcements.makeChannelAnnouncement(d.channelId, Globals.nodeParams.privateKey.publicKey, remoteNodeId, d.params.localParams.fundingPrivKey.publicKey, d.params.remoteParams.fundingPubKey, localNodeSig, remoteNodeSig, localBitcoinSig, remoteBitcoinSig)
val nodeAnn = Announcements.makeNodeAnnouncement(Globals.nodeParams.privateKey, Globals.nodeParams.alias, Globals.nodeParams.color, Globals.nodeParams.address :: Nil, Platform.currentTime / 1000)
val channelUpdate = Announcements.makeChannelUpdate(Globals.nodeParams.privateKey, remoteNodeId, d.commitments.channelId, Globals.nodeParams.expiryDeltaBlocks, Globals.nodeParams.htlcMinimumMsat, Globals.nodeParams.feeBaseMsat, Globals.nodeParams.feeProportionalMillionth, Platform.currentTime / 1000)
router ! channelAnn
router ! nodeAnn
router ! channelUpdate
@ -807,7 +807,7 @@ class Channel(val r: ActorRef, val blockchain: ActorRef, router: ActorRef, relay
remote = r
// this is a brand new channel
if (Funding.announceChannel(d.params.localParams.localFeatures, d.params.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.Node.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.nodeParams.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val annSignatures = AnnouncementSignatures(d.channelId, localNodeSig, localBitcoinSig)
remote ! annSignatures
} else {

View file

@ -24,7 +24,7 @@ object Helpers {
def validateParams(channelReserveSatoshis: Long, fundingSatoshis: Long): Unit = {
val reserveToFundingRatio = channelReserveSatoshis.toDouble / fundingSatoshis
require(reserveToFundingRatio <= Globals.max_reserve_to_funding_ratio, s"channelReserveSatoshis too high: ratio=$reserveToFundingRatio max=${Globals.max_reserve_to_funding_ratio}")
require(reserveToFundingRatio <= Globals.nodeParams.maxReserveToFundingRatio, s"channelReserveSatoshis too high: ratio=$reserveToFundingRatio max=${Globals.nodeParams.maxReserveToFundingRatio}")
}
def makeFundingInputInfo(fundingTxId: BinaryData, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo = {

View file

@ -27,7 +27,7 @@ class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Set
}
val graph = new SimpleGraph[BinaryData, NamedEdge](classOf[NamedEdge])
graph.addVertex(Globals.Node.publicKey)
graph.addVertex(Globals.nodeParams.privateKey.publicKey)
def receive: Receive = main(Map())

View file

@ -65,7 +65,7 @@ class Handlers(setup: Setup) extends Logging {
(paymentHandler ? 'genh).mapTo[BinaryData].map { h =>
Platform.runLater(new Runnable() {
override def run = {
textarea.setText(s"${Globals.Node.id}:$amountMsat:${h.toString()}")
textarea.setText(s"${Globals.nodeParams.privateKey.publicKey}:$amountMsat:${h.toString()}")
textarea.requestFocus
textarea.selectAll
}

View file

@ -89,7 +89,7 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup,
initNotifs
// init status bar
labelNodeId.setText(s"${Globals.Node.id}")
labelNodeId.setText(s"${Globals.nodeParams.privateKey.publicKey}")
labelApi.setText(s"${setup.config.getInt("eclair.api.port")}")
labelServer.setText(s"${setup.config.getInt("eclair.server.port")}")
bitcoinVersion.setText(s"v${setup.bitcoinVersion}")
@ -98,8 +98,8 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup,
// init context
contextMenu = ContextMenuUtils.buildCopyContext(List(
new CopyAction("Copy Pubkey", Globals.Node.id),
new CopyAction("Copy URI", s"${Globals.Node.id}@${Globals.Node.address.getHostString}:${Globals.Node.address.getPort}" )))
new CopyAction("Copy Pubkey", s"${Globals.nodeParams.privateKey.publicKey}"),
new CopyAction("Copy URI", s"${Globals.nodeParams.privateKey.publicKey}@${Globals.nodeParams.address.getHostString}:${Globals.nodeParams.address.getPort}" )))
// init channels tab
if (channelBox.getChildren.size() > 0) {

View file

@ -31,7 +31,7 @@ class Client(switchboard: ActorRef, address: InetSocketAddress, remoteNodeId: Pu
val connection = sender
val transport = context.actorOf(Props(
new TransportHandler[LightningMessage](
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
KeyPair(Globals.nodeParams.privateKey.publicKey.toBin, Globals.nodeParams.privateKey.toBin),
Some(remoteNodeId),
connection = connection,
serializer = LightningMessageSerializer)))

View file

@ -57,7 +57,7 @@ class Peer(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress], watc
log.info(s"registering as a listener to $transport")
transport ! Listener(self)
context watch transport
transport ! Init(globalFeatures = Globals.global_features, localFeatures = Globals.local_features)
transport ! Init(globalFeatures = Globals.nodeParams.globalFeatures, localFeatures = Globals.nodeParams.localFeatures)
goto(INITIALIZING) using InitializingData(transport, channels)
}
@ -68,7 +68,7 @@ class Peer(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress], watc
case Event(remoteInit: Init, InitializingData(transport, offlineChannels)) =>
import fr.acinq.eclair.Features._
log.info(s"$remoteNodeId has features: channelPublic=${channelPublic(remoteInit.localFeatures)} initialRoutingSync=${initialRoutingSync(remoteInit.localFeatures)}")
if (Features.areFeaturesCompatible(Globals.local_features, remoteInit.localFeatures)) {
if (Features.areFeaturesCompatible(Globals.nodeParams.localFeatures, remoteInit.localFeatures)) {
if (Features.initialRoutingSync(remoteInit.localFeatures) != Unset) {
router ! SendRoutingState(transport)
}
@ -162,7 +162,7 @@ object Peer {
def props(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress], watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: BinaryData) = Props(classOf[Peer], remoteNodeId, address_opt, watcher, router, relayer, defaultFinalScriptPubKey)
def generateKey(keyPath: Seq[Long]): PrivateKey = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, keyPath).privateKey
def generateKey(keyPath: Seq[Long]): PrivateKey = DeterministicWallet.derivePrivateKey(Globals.extendedPrivateKey, keyPath).privateKey
def makeChannelParams(keyIndex: Long, defaultFinalScriptPubKey: BinaryData, isFunder: Boolean): LocalParams =
LocalParams(
@ -170,18 +170,18 @@ object Peer {
maxHtlcValueInFlightMsat = Long.MaxValue,
channelReserveSatoshis = 0,
htlcMinimumMsat = 0,
feeratePerKw = Globals.feeratePerKw,
toSelfDelay = Globals.delay_blocks,
feeratePerKw = Globals.nodeParams.feeratePerKw,
toSelfDelay = Globals.nodeParams.delayBlocks,
maxAcceptedHtlcs = 100,
fundingPrivKey = generateKey(keyIndex :: 0L :: Nil),
revocationSecret = generateKey(keyIndex :: 1L :: Nil),
paymentKey = generateKey(keyIndex :: 2L :: Nil),
delayedPaymentKey = generateKey(keyIndex :: 3L :: Nil),
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
shaSeed = Globals.Node.seed,
shaSeed = Globals.seed, // TODO: we should use a different seed
isFunder = isFunder,
globalFeatures = Globals.global_features,
localFeatures = Globals.local_features
globalFeatures = Globals.nodeParams.globalFeatures,
localFeatures = Globals.nodeParams.localFeatures
)
}

View file

@ -35,7 +35,7 @@ class Server(switchboard: ActorRef, address: InetSocketAddress) extends Actor wi
val connection = sender
val transport = context.actorOf(Props(
new TransportHandler[LightningMessage](
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
KeyPair(Globals.nodeParams.privateKey.publicKey.toBin, Globals.nodeParams.privateKey.toBin),
None,
connection = connection,
serializer = LightningMessageSerializer)))

View file

@ -20,7 +20,7 @@ class Switchboard(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaul
def main(peers: Map[PublicKey, ActorRef], connections: Map[PublicKey, ActorRef]): Receive = {
case NewConnection(Globals.Node.publicKey, _, _) =>
case NewConnection(publicKey, _, _) if publicKey == Globals.nodeParams.privateKey.publicKey =>
sender ! Status.Failure(new RuntimeException("cannot open connection with oneself"))
case NewConnection(remoteNodeId, address, newChannel_opt) =>

View file

@ -2,11 +2,12 @@ package fr.acinq.eclair.payment
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.PublicKey
/**
* Created by PM on 29/08/2016.
*/
class PaymentInitiator(sourceNodeId: BinaryData, router: ActorRef) extends Actor with ActorLogging {
class PaymentInitiator(sourceNodeId: PublicKey, router: ActorRef) extends Actor with ActorLogging {
override def receive: Receive = {
case c: CreatePayment =>
@ -17,5 +18,5 @@ class PaymentInitiator(sourceNodeId: BinaryData, router: ActorRef) extends Actor
}
object PaymentInitiator {
def props(sourceNodeId: BinaryData, router: ActorRef) = Props(classOf[PaymentInitiator], sourceNodeId, router)
def props(sourceNodeId: PublicKey, router: ActorRef) = Props(classOf[PaymentInitiator], sourceNodeId, router)
}

View file

@ -30,7 +30,7 @@ case object WAITING_FOR_PAYMENT_COMPLETE extends State
/**
* Created by PM on 26/08/2016.
*/
class PaymentLifecycle(sourceNodeId: BinaryData, router: ActorRef) extends LoggingFSM[State, Data] {
class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef) extends LoggingFSM[State, Data] {
import PaymentLifecycle._
@ -76,7 +76,7 @@ class PaymentLifecycle(sourceNodeId: BinaryData, router: ActorRef) extends Loggi
object PaymentLifecycle {
def props(sourceNodeId: BinaryData, router: ActorRef) = Props(classOf[PaymentLifecycle], sourceNodeId, router)
def props(sourceNodeId: PublicKey, router: ActorRef) = Props(classOf[PaymentLifecycle], sourceNodeId, router)
/**
*

View file

@ -52,7 +52,7 @@ class ThroughputSpec extends FunSuite {
context.become(run(h2r - htlc.paymentHash))
}
}), "payment-handler")
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler))
val relayer = system.actorOf(Relayer.props(Globals.nodeParams.privateKey, paymentHandler))
val alice = system.actorOf(Channel.props(pipe, blockchain, ???, relayer), "a")
val bob = system.actorOf(Channel.props(pipe, blockchain, ???, relayer), "b")
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)

View file

@ -32,8 +32,8 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods {
val bob2blockchain = TestProbe()
val paymentHandlerA = system.actorOf(Props(new LocalPaymentHandler()), name = "payment-handler-a")
val paymentHandlerB = system.actorOf(Props(new LocalPaymentHandler()), name = "payment-handler-b")
val relayerA = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandlerA), "relayer-a")
val relayerB = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandlerB), "relayer-b")
val relayerA = system.actorOf(Relayer.props(Globals.nodeParams.privateKey, paymentHandlerA), "relayer-a")
val relayerB = system.actorOf(Relayer.props(Globals.nodeParams.privateKey, paymentHandlerB), "relayer-b")
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, alice2blockchain.ref, router.ref, relayerA))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, bob2blockchain.ref, router.ref, relayerB))
@ -65,7 +65,7 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods {
def buildCmdAdd(paymentHash: BinaryData) = {
val channelUpdate_ab = ChannelUpdate("00" * 64, 0, 0, "0000", cltvExpiryDelta = 4, feeBaseMsat = 642000, feeProportionalMillionths = 7, htlcMinimumMsat = 0)
val hops = Hop(Globals.Node.publicKey, Globals.Node.publicKey, channelUpdate_ab) :: Nil
val hops = Hop(Globals.nodeParams.privateKey.publicKey, Globals.nodeParams.privateKey.publicKey, channelUpdate_ab) :: Nil
// we don't want to be below htlcMinimumMsat
val amount = Random.nextInt(1000000) + 1000
PaymentLifecycle.buildCommand(amount, paymentHash, hops, 444000)

View file

@ -25,14 +25,14 @@ class AnnouncementsSpec extends FunSuite {
test("create valid signed node announcement") {
val key = randomKey
val ann = makeNodeAnnouncement(key, Globals.Node.alias, Globals.Node.color, Globals.Node.address :: Nil, Platform.currentTime / 1000)
val ann = makeNodeAnnouncement(key, Globals.nodeParams.alias, Globals.nodeParams.color, Globals.nodeParams.address :: Nil, Platform.currentTime / 1000)
assert(checkSig(ann))
assert(checkSig(ann.copy(timestamp = 153)) === false)
}
test("create valid signed channel update announcement") {
val key = randomKey
val ann = makeChannelUpdate(key, randomKey.publicKey, 45561, Globals.expiry_delta_blocks, Globals.htlc_minimum_msat, Globals.fee_base_msat, Globals.fee_proportional_millionth, Platform.currentTime / 1000)
val ann = makeChannelUpdate(key, randomKey.publicKey, 45561, Globals.nodeParams.expiryDeltaBlocks, Globals.nodeParams.htlcMinimumMsat, Globals.nodeParams.feeBaseMsat, Globals.nodeParams.feeProportionalMillionth, Platform.currentTime / 1000)
assert(checkSig(ann, key.publicKey))
assert(checkSig(ann, randomKey.publicKey) === false)
}