From 15c1837d846f63b76f1320a5fe55b974c3d0a2de Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Fri, 12 Feb 2021 12:10:56 +0100 Subject: [PATCH] Add tx signing metrics (#1659) Monitor the rate at which we sign channel txs and the duration of the signing operations. --- .../fr/acinq/eclair/crypto/Monitoring.scala | 11 +++++ .../crypto/keymanager/ChannelKeyManager.scala | 17 +++----- .../keymanager/LocalChannelKeyManager.scala | 43 ++++++++++++------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Monitoring.scala index 38bbcb272a..28fdcc8042 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Monitoring.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Monitoring.scala @@ -22,10 +22,21 @@ object Monitoring { object Metrics { val OnionPayloadFormat = Kamon.counter("crypto.sphinx.onion-payload-format") + val SignTxCount = Kamon.counter("crypto.keymanager.sign.count") + val SignTxDuration = Kamon.timer("crypto.keymanager.sign.duration") } object Tags { val LegacyOnion = "legacy" + val TxOwner = "txOwner" + val TxType = "txType" + + object TxTypes { + val CommitTx = "commit" + val HtlcTx = "htlc" + val RevokedTx = "revoked" + } + } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala index 4ad6ab3afe..91071bd8e1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala @@ -16,9 +16,6 @@ package fr.acinq.eclair.crypto.keymanager -import java.io.ByteArrayInputStream -import java.nio.ByteOrder - import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey import fr.acinq.bitcoin.{ByteVector64, Crypto, DeterministicWallet, Protocol} @@ -26,6 +23,9 @@ import fr.acinq.eclair.channel.{ChannelVersion, LocalParams} import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, TransactionWithInputInfo, TxOwner} import scodec.bits.ByteVector +import java.io.ByteArrayInputStream +import java.nio.ByteOrder + trait ChannelKeyManager { def fundingPublicKey(keyPath: DeterministicWallet.KeyPath): ExtendedPublicKey @@ -62,8 +62,7 @@ trait ChannelKeyManager { * @param publicKey extended public key * @param txOwner owner of the transaction (local/remote) * @param commitmentFormat format of the commitment tx - * @return a signature generated with the private key that matches the input - * extended public key + * @return a signature generated with the private key that matches the input extended public key */ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 @@ -75,8 +74,7 @@ trait ChannelKeyManager { * @param remotePoint remote point * @param txOwner owner of the transaction (local/remote) * @param commitmentFormat format of the commitment tx - * @return a signature generated with a private key generated from the input keys's matching - * private key and the remote point. + * @return a signature generated with a private key generated from the input key's matching private key and the remote point. */ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 @@ -88,8 +86,7 @@ trait ChannelKeyManager { * @param remoteSecret remote secret * @param txOwner owner of the transaction (local/remote) * @param commitmentFormat format of the commitment tx - * @return a signature generated with a private key generated from the input keys's matching - * private key and the remote secret. + * @return a signature generated with a private key generated from the input key's matching private key and the remote secret. */ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 @@ -105,7 +102,7 @@ trait ChannelKeyManager { object ChannelKeyManager { /** * Create a BIP32 path from a public key. This path will be used to derive channel keys. - * Having channel keys derived from the funding public keys makes it very easy to retrieve your funds when've you've lost your data: + * Having channel keys derived from the funding public keys makes it very easy to retrieve your funds when you've lost your data: * - connect to your peer and use DLP to get them to publish their remote commit tx * - retrieve the commit tx from the bitcoin network, extract your funding pubkey from its witness data * - recompute your channel keys and spend your output diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala index 42b3526132..f8545420df 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala @@ -21,11 +21,13 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.DeterministicWallet.{derivePrivateKey, _} import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet} import fr.acinq.eclair.crypto.Generators +import fr.acinq.eclair.crypto.Monitoring.{Metrics, Tags} import fr.acinq.eclair.router.Announcements -import fr.acinq.eclair.secureRandom import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, TransactionWithInputInfo, TxOwner} +import fr.acinq.eclair.{KamonExt, secureRandom} import grizzled.slf4j.Logging +import kamon.tag.TagSet import scodec.bits.ByteVector object LocalChannelKeyManager { @@ -97,12 +99,16 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: ByteVector32) extends * @param publicKey extended public key * @param txOwner owner of the transaction (local/remote) * @param commitmentFormat format of the commitment tx - * @return a signature generated with the private key that matches the input - * extended public key + * @return a signature generated with the private key that matches the input extended public key */ override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = { - val privateKey = privateKeys.get(publicKey.path) - Transactions.sign(tx, privateKey.privateKey, txOwner, commitmentFormat) + // NB: not all those transactions are actually commit txs (especially during closing), but this is good enough for monitoring purposes + val tags = TagSet.Empty.withTag(Tags.TxOwner, txOwner.toString).withTag(Tags.TxType, Tags.TxTypes.CommitTx) + Metrics.SignTxCount.withTags(tags).increment() + KamonExt.time(Metrics.SignTxDuration.withTags(tags)) { + val privateKey = privateKeys.get(publicKey.path) + Transactions.sign(tx, privateKey.privateKey, txOwner, commitmentFormat) + } } /** @@ -113,13 +119,17 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: ByteVector32) extends * @param remotePoint remote point * @param txOwner owner of the transaction (local/remote) * @param commitmentFormat format of the commitment tx - * @return a signature generated with a private key generated from the input keys's matching - * private key and the remote point. + * @return a signature generated with a private key generated from the input key's matching private key and the remote point. */ override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = { - val privateKey = privateKeys.get(publicKey.path) - val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint) - Transactions.sign(tx, currentKey, txOwner, commitmentFormat) + // NB: not all those transactions are actually htlc txs (especially during closing), but this is good enough for monitoring purposes + val tags = TagSet.Empty.withTag(Tags.TxOwner, txOwner.toString).withTag(Tags.TxType, Tags.TxTypes.HtlcTx) + Metrics.SignTxCount.withTags(tags).increment() + KamonExt.time(Metrics.SignTxDuration.withTags(tags)) { + val privateKey = privateKeys.get(publicKey.path) + val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint) + Transactions.sign(tx, currentKey, txOwner, commitmentFormat) + } } /** @@ -130,13 +140,16 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: ByteVector32) extends * @param remoteSecret remote secret * @param txOwner owner of the transaction (local/remote) * @param commitmentFormat format of the commitment tx - * @return a signature generated with a private key generated from the input keys's matching - * private key and the remote secret. + * @return a signature generated with a private key generated from the input key's matching private key and the remote secret. */ override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = { - val privateKey = privateKeys.get(publicKey.path) - val currentKey = Generators.revocationPrivKey(privateKey.privateKey, remoteSecret) - Transactions.sign(tx, currentKey, txOwner, commitmentFormat) + val tags = TagSet.Empty.withTag(Tags.TxOwner, txOwner.toString).withTag(Tags.TxType, Tags.TxTypes.RevokedTx) + Metrics.SignTxCount.withTags(tags).increment() + KamonExt.time(Metrics.SignTxDuration.withTags(tags)) { + val privateKey = privateKeys.get(publicKey.path) + val currentKey = Generators.revocationPrivKey(privateKey.privateKey, remoteSecret) + Transactions.sign(tx, currentKey, txOwner, commitmentFormat) + } } override def signChannelAnnouncement(witness: ByteVector, fundingKeyPath: KeyPath): ByteVector64 =