From 928f73d47026fafe993097c583c44d76311672a5 Mon Sep 17 00:00:00 2001 From: sstone Date: Fri, 14 Dec 2018 15:49:07 +0100 Subject: [PATCH 01/66] integration tests: upgrade to bitcoin 0.17.1 --- eclair-core/pom.xml | 18 +++++++++--------- .../bitcoind/BitcoinCoreWallet.scala | 15 ++++++++++++--- .../test/resources/integration/bitcoin.conf | 4 +++- .../bitcoind/BitcoinCoreWalletSpec.scala | 12 ++++++++---- .../blockchain/bitcoind/BitcoindService.scala | 13 ++++++++++--- 5 files changed, 42 insertions(+), 20 deletions(-) diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index 5bbd7ac664..ee39d48cce 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -79,10 +79,10 @@ true - https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz + https://bitcoin.org/bin/bitcoin-core-0.17.1/bitcoin-0.17.1-x86_64-linux-gnu.tar.gz - c371e383f024c6c45fb255d528a6beec - e6d8ab1f7661a6654fd81e236b9b5fd35a3d4dcb + 724043999e2b5ed0c088e8db34f15d43 + 546ee35d4089c7ccc040a01cdff3362599b8bc53 @@ -93,10 +93,10 @@ - https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-osx64.tar.gz + https://bitcoin.org/bin/bitcoin-core-0.17.1/bitcoin-0.17.1-osx64.tar.gz - bacd87d0c3f65a5acd666e33d094a59e - 62cc5bd9ced610bb9e8d4a854396bfe2139e3d0f + b5a792c6142995faa42b768273a493bd + 8bd51c7024d71de07df381055993e9f472013db8 @@ -107,9 +107,9 @@ - https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-win64.zip - bbde9b1206956d19298034319e9f405e - 85e3dc8a9c6f93b1b20cb79fa5850b5ce81da221 + https://bitcoin.org/bin/bitcoin-core-0.17.1/bitcoin-0.17.1-win64.zip + b0e824e9dd02580b5b01f073f3c89858 + 4e17bad7d08c465b444143a93cd6eb1c95076e3f diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala index d608e92c8f..7843343f60 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala @@ -19,10 +19,12 @@ package fr.acinq.eclair.blockchain.bitcoind import fr.acinq.bitcoin._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinJsonRPCClient, JsonRPCError} +import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinJsonRPCClient, Error, JsonRPCError} import fr.acinq.eclair.transactions.Transactions import grizzled.slf4j.Logging +import org.json4s.DefaultFormats import org.json4s.JsonAST._ +import org.json4s.jackson.Serialization import scala.concurrent.{ExecutionContext, Future} @@ -49,6 +51,13 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC rpcClient.invoke("signrawtransaction", hex).map(json => { val JString(hex) = json \ "hex" val JBool(complete) = json \ "complete" + if (!complete) { + val message = json \ "errors" match { + case value: JValue => Serialization.write(value)(DefaultFormats) + case _ => "signrawtransaction failed" + } + throw new JsonRPCError(Error(-1, message)) + } SignTransactionResponse(Transaction.read(hex), complete) }) @@ -71,7 +80,7 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC private def signTransactionOrUnlock(tx: Transaction): Future[SignTransactionResponse] = { val f = signTransaction(tx) - // if signature fails (e.g. because wallet is uncrypted) we need to unlock the utxos + // if signature fails (e.g. because wallet is encrypted) we need to unlock the utxos f.recoverWith { case _ => unlockOutpoints(tx.txIn.map(_.outPoint)) .recover { case t: Throwable => logger.warn(s"Cannot unlock failed transaction's UTXOs txid=${tx.txid}", t); t } // no-op, just add a log in case of failure @@ -91,7 +100,7 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC // we ask bitcoin core to add inputs to the funding tx, and use the specified change address FundTransactionResponse(unsignedFundingTx, _, fee) <- fundTransaction(partialFundingTx, lockUnspents = true, feeRatePerKw) // now let's sign the funding tx - SignTransactionResponse(fundingTx, _) <- signTransactionOrUnlock(unsignedFundingTx) + SignTransactionResponse(fundingTx, true) <- signTransactionOrUnlock(unsignedFundingTx) // there will probably be a change output, so we need to find which output is ours outputIndex = Transactions.findPubKeyScriptIndex(fundingTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) _ = logger.debug(s"created funding txid=${fundingTx.txid} outputIndex=$outputIndex fee=$fee") diff --git a/eclair-core/src/test/resources/integration/bitcoin.conf b/eclair-core/src/test/resources/integration/bitcoin.conf index da4dd59a07..6ef9572c9d 100644 --- a/eclair-core/src/test/resources/integration/bitcoin.conf +++ b/eclair-core/src/test/resources/integration/bitcoin.conf @@ -1,7 +1,8 @@ regtest=1 +noprinttoconsole=1 server=1 port=28333 -rpcport=28332 +regtest.rpcport=28332 rpcuser=foo rpcpassword=bar txindex=1 @@ -9,3 +10,4 @@ zmqpubrawblock=tcp://127.0.0.1:28334 zmqpubrawtx=tcp://127.0.0.1:28335 rpcworkqueue=64 addresstype=bech32 +deprecatedrpc=signrawtransaction diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index 4acb1255d9..8701639b8a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -141,10 +141,10 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe sender.send(bitcoincli, BitcoinReq("getrawtransaction", fundingTxes(2).txid.toString())) assert(sender.expectMsgType[JString](10 seconds).s === fundingTxes(2).toString()) - // NB: bitcoin core doesn't clear the locks when a tx is published + // NB: bitcoin core before 0.17.0 doesn't clear the locks when a tx is published sender.send(bitcoincli, BitcoinReq("listlockunspent")) - assert(sender.expectMsgType[JValue](10 seconds).children.size === 2) - + val expectedLocks = if (bitcoinVersion >= 170000) 0 else 2 + assert(sender.expectMsgType[JValue](10 seconds).children.size === expectedLocks) } test("encrypt wallet") { @@ -177,7 +177,11 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey.publicKey, randomKey.publicKey))) wallet.makeFundingTx(pubkeyScript, MilliBtc(50), 10000).pipeTo(sender.ref) - assert(sender.expectMsgType[Failure].cause.asInstanceOf[JsonRPCError].error.message.contains("Please enter the wallet passphrase with walletpassphrase first.")) + val error = sender.expectMsgType[Failure].cause.asInstanceOf[JsonRPCError].error + // behaviour has changed, bitcoin core will now return a more generic error + if (bitcoinVersion < 170000) { + assert(error.message.contains("Please enter the wallet passphrase with walletpassphrase first.")) + } sender.send(bitcoincli, BitcoinReq("listlockunspent")) assert(sender.expectMsgType[JValue](10 seconds).children.size === 0) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala index 8c9cc28d2b..7d9971c34b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala @@ -28,7 +28,7 @@ import com.softwaremill.sttp.okhttp.OkHttpFutureBackend import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinJsonRPCClient} import fr.acinq.eclair.integration.IntegrationSpec import grizzled.slf4j.Logging -import org.json4s.JsonAST.JValue +import org.json4s.JsonAST.{JInt, JValue} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -44,12 +44,13 @@ trait BitcoindService extends Logging { val INTEGRATION_TMP_DIR = s"${System.getProperty("buildDirectory")}/integration-${UUID.randomUUID().toString}" logger.info(s"using tmp dir: $INTEGRATION_TMP_DIR") - val PATH_BITCOIND = new File(System.getProperty("buildDirectory"), "bitcoin-0.16.3/bin/bitcoind") + val PATH_BITCOIND = new File(System.getProperty("buildDirectory"), "bitcoin-0.17.1/bin/bitcoind") val PATH_BITCOIND_DATADIR = new File(INTEGRATION_TMP_DIR, "datadir-bitcoin") var bitcoind: Process = null var bitcoinrpcclient: BitcoinJsonRPCClient = null var bitcoincli: ActorRef = null + var bitcoinVersion: Int = _ case class BitcoinReq(method: String, params: Any*) @@ -83,7 +84,13 @@ trait BitcoindService extends Logging { logger.info(s"waiting for bitcoind to initialize...") awaitCond({ sender.send(bitcoincli, BitcoinReq("getnetworkinfo")) - sender.receiveOne(5 second).isInstanceOf[JValue] + sender.receiveOne(5 second) match { + case info: JValue => + val JInt(version) = info \ "version" + bitcoinVersion = version.intValue() + true + case _ => false + } }, max = 30 seconds, interval = 500 millis) logger.info(s"generating initial blocks...") sender.send(bitcoincli, BitcoinReq("generate", 500)) From ae06315c91f4d3df9c386a19ec260300191f9d49 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Feb 2019 16:33:08 +0100 Subject: [PATCH 02/66] Add pushMe output script --- .../fr/acinq/eclair/transactions/Scripts.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index ab1008e679..5cb561a4ee 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.Crypto.{PublicKey, ripemd160} import fr.acinq.bitcoin.Script._ -import fr.acinq.bitcoin.{BinaryData, LexicographicalOrdering, LockTimeThreshold, OP_0, OP_1, OP_1NEGATE, OP_2, OP_2DROP, OP_ADD, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, Satoshi, Script, ScriptElt, ScriptWitness, Transaction, TxIn} +import fr.acinq.bitcoin.{BinaryData, LexicographicalOrdering, LockTimeThreshold, OP_0, OP_1, OP_10, OP_1NEGATE, OP_2, OP_2DROP, OP_ADD, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DEPTH, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, Satoshi, Script, ScriptElt, ScriptWitness, Transaction, TxIn} /** * Created by PM on 02/12/2016. @@ -250,6 +250,20 @@ object Scripts { // @formatter:on } + /** + * Use with `option_simplified_commitment` + */ + def pushMeSimplified(pubkey: PublicKey): List[ScriptElt] = { + // @formatter:off + OP_DEPTH :: + OP_IF :: + OP_PUSHDATA(pubkey) :: OP_CHECKSIG :: + OP_ELSE :: + OP_10 :: OP_CHECKSEQUENCEVERIFY :: + OP_ENDIF :: Nil + // @formatter:on + } + /** * This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcReceived script from commit tx) */ From 0193a2c2a93dabcdbf8bb598bbfc000488220b94 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Feb 2019 16:47:52 +0100 Subject: [PATCH 03/66] Add feature bits --- .../src/main/scala/fr/acinq/eclair/Features.scala | 3 ++- .../src/main/scala/fr/acinq/eclair/io/Peer.scala | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index abecc33e06..2d45fde9d6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -36,12 +36,13 @@ object Features { val CHANNEL_RANGE_QUERIES_BIT_MANDATORY = 6 val CHANNEL_RANGE_QUERIES_BIT_OPTIONAL = 7 + val OPTION_SIMPLIFIED_COMMITMENT_MANDATORY = 8 + val OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL = 9 def hasFeature(features: BitSet, bit: Int): Boolean = features.get(bit) def hasFeature(features: BinaryData, bit: Int): Boolean = hasFeature(BitSet.valueOf(features.reverse.toArray), bit) - /** * Check that the features that we understand are correctly specified, and that there are no mandatory features that * we don't understand (even bits) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index b6ed0fa357..21d3df7b6f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -111,7 +111,15 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor val remoteHasInitialRoutingSync = Features.hasFeature(remoteInit.localFeatures, Features.INITIAL_ROUTING_SYNC_BIT_OPTIONAL) val remoteHasChannelRangeQueriesOptional = Features.hasFeature(remoteInit.localFeatures, Features.CHANNEL_RANGE_QUERIES_BIT_OPTIONAL) val remoteHasChannelRangeQueriesMandatory = Features.hasFeature(remoteInit.localFeatures, Features.CHANNEL_RANGE_QUERIES_BIT_MANDATORY) - log.info(s"$remoteNodeId has features: initialRoutingSync=$remoteHasInitialRoutingSync channelRangeQueriesOptional=$remoteHasChannelRangeQueriesOptional channelRangeQueriesMandatory=$remoteHasChannelRangeQueriesMandatory") + val remoteHasSimplifiedCommitmentOptional = Features.hasFeature(remoteInit.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + val remoteHasSimplifiedCommitmentMandatory = Features.hasFeature(remoteInit.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) + log.info(s"$remoteNodeId has features: " + + s"initialRoutingSync=$remoteHasInitialRoutingSync " + + s"channelRangeQueriesOptional=$remoteHasChannelRangeQueriesOptional " + + s"channelRangeQueriesMandatory=$remoteHasChannelRangeQueriesMandatory" + + s"remoteHasSimplifiedCommitmentOptional=$remoteHasSimplifiedCommitmentOptional" + + s"remoteHasSimplifiedCommitmentMandatory=$remoteHasSimplifiedCommitmentMandatory") + if (Features.areSupported(remoteInit.localFeatures)) { d.origin_opt.foreach(origin => origin ! "connected") From 7ebbf0ecc696f713219f3dcaf0b3d86bb42b199a Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Feb 2019 17:44:56 +0100 Subject: [PATCH 04/66] Add helper to check whether this channel uses option_simplified_commitment --- .../src/main/scala/fr/acinq/eclair/channel/Commitments.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 4b9cc68e54..8342c97609 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -72,6 +72,8 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, def announceChannel: Boolean = (channelFlags & 0x01) != 0 + def isSimplifiedCommitment: Boolean = (channelFlags & 0x02) != 0 // FIXME: the bit hasn't been chosen yet! + def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced).amount else 0 From 65412e63c00eaca16a20e69cb646f0366b219278 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Feb 2019 14:14:21 +0100 Subject: [PATCH 05/66] Read option_simplified_commitment from localParams --- .../src/main/scala/fr/acinq/eclair/channel/Channel.scala | 2 +- .../scala/fr/acinq/eclair/channel/ChannelTypes.scala | 9 +++++++-- .../main/scala/fr/acinq/eclair/channel/Commitments.scala | 4 +--- .../src/main/scala/fr/acinq/eclair/channel/Helpers.scala | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 36f4131c1e..50bd47faaa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -1028,7 +1028,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(c@CurrentFeerates(feerates), d: DATA_SHUTDOWN) => val networkFeeratePerKw = feerates.blocks_2 - d.commitments.localParams.isFunder match { + d.commitments.localParams.isFunder match { // TODO --> "&& !d.commitments.isSimplifiedCommitment" case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) stay diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index b616aedf06..f0b45896aa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -182,6 +182,11 @@ final case class DATA_CLOSING(commitments: Commitments, final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends Data with HasCommitments +trait ParamsWithFeatures { + val globalFeatures: BinaryData + val localFeatures: BinaryData +} + final case class LocalParams(nodeId: PublicKey, channelKeyPath: DeterministicWallet.KeyPath, dustLimitSatoshis: Long, @@ -193,7 +198,7 @@ final case class LocalParams(nodeId: PublicKey, isFunder: Boolean, defaultFinalScriptPubKey: BinaryData, globalFeatures: BinaryData, - localFeatures: BinaryData) + localFeatures: BinaryData) extends ParamsWithFeatures final case class RemoteParams(nodeId: PublicKey, dustLimitSatoshis: Long, @@ -208,7 +213,7 @@ final case class RemoteParams(nodeId: PublicKey, delayedPaymentBasepoint: Point, htlcBasepoint: Point, globalFeatures: BinaryData, - localFeatures: BinaryData) + localFeatures: BinaryData) extends ParamsWithFeatures object ChannelFlags { val AnnounceChannel = 0x01.toByte diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 8342c97609..a4ddaadb60 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, UInt64} +import fr.acinq.eclair.{Features, Globals, UInt64} import scala.util.{Failure, Success} @@ -72,8 +72,6 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, def announceChannel: Boolean = (channelFlags & 0x01) != 0 - def isSimplifiedCommitment: Boolean = (channelFlags & 0x02) != 0 // FIXME: the bit hasn't been chosen yet! - def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced).amount else 0 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 4abd077774..ad7f4a9b47 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -27,8 +27,7 @@ import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId, addressToPublicKeyScript} - +import fr.acinq.eclair._ import scala.concurrent.Await import scala.util.{Failure, Success, Try} @@ -314,6 +313,8 @@ object Helpers { } } + def isSimplifiedCommitment[T <: ParamsWithFeatures](params: T) = Features.hasFeature(params.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + object Closing { From 6ce78a06c3f5ac1c8878310598865f3e82489ad5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Feb 2019 14:39:41 +0100 Subject: [PATCH 06/66] Do not rotate remote_pubkey if option_simplified_commitment is enabled --- .../fr/acinq/eclair/channel/Commitments.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index a4ddaadb60..66a281f687 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter -import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256} +import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi, Satoshi, Transaction} import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment._ @@ -510,7 +510,12 @@ object Commitments { def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) - val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) + + val remotePaymentPubkey = Helpers.isSimplifiedCommitment(localParams) match { + case true => PublicKey(remoteParams.paymentBasepoint) + case false => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) + } + val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) @@ -519,7 +524,10 @@ object Commitments { } def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + val localPaymentPubkey = Helpers.isSimplifiedCommitment(localParams) match { + case true => keyManager.paymentPoint(localParams.channelKeyPath).publicKey + case false => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + } val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) From cc4ed3490b2b8060ced7768d3619a1445e9a39a6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Feb 2019 16:15:00 +0100 Subject: [PATCH 07/66] Create commitTx with pushMe outputs --- .../fr/acinq/eclair/channel/Commitments.scala | 15 ++++++------ .../fr/acinq/eclair/channel/Helpers.scala | 2 +- .../eclair/transactions/Transactions.scala | 23 ++++++++++++------- .../eclair/transactions/TestVectorsSpec.scala | 4 +++- .../transactions/TransactionsSpec.scala | 13 ++++------- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 66a281f687..4ec8c26d48 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -72,9 +72,10 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, def announceChannel: Boolean = (channelFlags & 0x01) != 0 + // TODO subtract the pushMe value from the balance? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced).amount else 0 + val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(localParams)).amount else 0 reduced.toRemoteMsat / 1000 - remoteParams.channelReserveSatoshis - fees } } @@ -143,7 +144,7 @@ object Commitments { // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount else 0 + val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(commitments.localParams)).amount else 0 val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { return Left(InsufficientFunds(commitments.channelId, amountMsat = cmd.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.remoteParams.channelReserveSatoshis, feesSatoshis = fees)) @@ -180,7 +181,7 @@ object Commitments { } // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced).amount + val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(commitments.localParams)).amount val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw InsufficientFunds(commitments.channelId, amountMsat = add.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -302,7 +303,7 @@ object Commitments { // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount + val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, simplifiedCommitment = false).amount // we update the fee only in NON simplified commitment val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(commitments.channelId, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -336,7 +337,7 @@ object Commitments { val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount + val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, simplifiedCommitment = false).amount // we update the fee only in NON simplified val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(commitments.channelId, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -518,7 +519,7 @@ object Commitments { val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(Helpers.isSimplifiedCommitment(localParams), commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } @@ -532,7 +533,7 @@ object Commitments { val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(Helpers.isSimplifiedCommitment(localParams), commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index ad7f4a9b47..462b808416 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -239,7 +239,7 @@ object Helpers { if (!localParams.isFunder) { // they are funder, therefore they pay the fee: we need to make sure they can afford it! val toRemoteMsat = remoteSpec.toLocalMsat - val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec).amount + val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, Helpers.isSimplifiedCommitment(localParams)).amount val missing = toRemoteMsat / 1000 - localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(temporaryChannelId, missingSatoshis = -1 * missing, reserveSatoshis = localParams.channelReserveSatoshis, feesSatoshis = fees) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index ae3073fdef..4ea1bea7a7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -93,8 +93,11 @@ object Transactions { * these values are defined in the RFC */ val commitWeight = 724 + val simplifiedCommitWeight = 1116 val htlcTimeoutWeight = 663 val htlcSuccessWeight = 703 + val simplifiedFeerateKw = 253 + val pushMeValue = Satoshi(1000) /** * these values specific to us and used to estimate fees @@ -132,11 +135,11 @@ object Transactions { .toSeq } - def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec): Satoshi = { + def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec, simplifiedCommitment: Boolean): Satoshi = { val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec) val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec) - val weight = commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size) - weight2fee(spec.feeratePerKw, weight) + val weight = (if(simplifiedCommitment) simplifiedCommitWeight else commitWeight) + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size) + weight2fee(if(simplifiedCommitment) simplifiedFeerateKw else spec.feeratePerKw, weight) } /** @@ -185,13 +188,14 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { - val commitFee = commitTxFee(localDustLimit, spec) + def makeCommitTx(isSimplifiedCommitment: Boolean, commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { + val commitFee = commitTxFee(localDustLimit, spec, isSimplifiedCommitment) + val pushMeValueTotal = if(isSimplifiedCommitment) pushMeValue * 2 else Satoshi(0) // funder pays the total amount of pushme outputs val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { - (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee - pushMeValueTotal, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) } else { - (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee) + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee - pushMeValueTotal) } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway val toLocalDelayedOutput_opt = if (toLocalAmount >= localDustLimit) Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) else None @@ -202,13 +206,16 @@ object Transactions { val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) + val toLocalPushMe_opt = if(isSimplifiedCommitment && toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, Scripts.pushMeSimplified(localDelayedPaymentPubkey))) else None + val toRemotePushMe_opt = if(isSimplifiedCommitment && toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, Scripts.pushMeSimplified(remotePaymentPubkey))) else None + val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) val (sequence, locktime) = encodeTxNumber(txnumber) val tx = Transaction( version = 2, txIn = TxIn(commitTxInput.outPoint, Array.emptyByteArray, sequence = sequence) :: Nil, - txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs, + txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs ++ toLocalPushMe_opt.toSeq ++ toRemotePushMe_opt.toSeq, lockTime = locktime) CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 2989ca04be..9373a562f2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -182,6 +182,7 @@ class TestVectorsSpec extends FunSuite with Logging { val commitTx = { val tx = Transactions.makeCommitTx( + isSimplifiedCommitment = false, commitmentInput, Local.commitTxNumber, Local.payment_basepoint, Remote.payment_basepoint, true, Local.dustLimit, @@ -196,7 +197,7 @@ class TestVectorsSpec extends FunSuite with Logging { Transactions.addSigs(tx, Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig) } - val baseFee = Transactions.commitTxFee(Local.dustLimit, spec) + val baseFee = Transactions.commitTxFee(Local.dustLimit, spec, simplifiedCommitment = false) logger.info(s"# base commitment transaction fee = ${baseFee.toLong}") val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}") @@ -212,6 +213,7 @@ class TestVectorsSpec extends FunSuite with Logging { { val tx = Transactions.makeCommitTx( + isSimplifiedCommitment = false, commitmentInput, Local.commitTxNumber, Local.payment_basepoint, Remote.payment_basepoint, true, Local.dustLimit, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 7c4f05470c..ad8ad8c3c2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -68,8 +68,10 @@ class TransactionsSpec extends FunSuite with Logging { DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(800000).amount, Hash.Zeroes, 551, BinaryData.empty)) ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0) - val fee = Transactions.commitTxFee(Satoshi(546), spec) + val fee = Transactions.commitTxFee(Satoshi(546), spec, simplifiedCommitment = false) assert(fee == Satoshi(5340)) + + //TODO add case for simplified commitment } test("check pre-computed transaction weights") { @@ -203,7 +205,7 @@ class TransactionsSpec extends FunSuite with Logging { val commitTxNumber = 0x404142434445L val commitTx = { - val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) + val txinfo = makeCommitTx(isSimplifiedCommitment = false, commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) val localSig = Transactions.sign(txinfo, localPaymentPriv) val remoteSig = Transactions.sign(txinfo, remotePaymentPriv) Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) @@ -313,11 +315,6 @@ class TransactionsSpec extends FunSuite with Logging { } - def checkSuccessOrFailTest[T](input: Try[T]) = input match { - case Success(_) => () - case Failure(t) => fail(t) - } - def htlc(direction: Direction, amount: Satoshi): DirectedHtlc = DirectedHtlc(direction, UpdateAddHtlc("00" * 32, 0, amount.amount * 1000, "00" * 32, 144, "")) @@ -360,7 +357,7 @@ class TransactionsSpec extends FunSuite with Logging { tests.foreach(test => { logger.info(s"running BOLT 2 test: '${test.name}'") - val fee = commitTxFee(test.dustLimit, test.spec) + val fee = commitTxFee(test.dustLimit, test.spec, simplifiedCommitment = false) assert(fee === test.expectedFee) }) } From 0f00a68a2000e4e9db925c60c436f1a60732be6e Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Feb 2019 17:01:58 +0100 Subject: [PATCH 08/66] Remove empty lines --- .../src/main/scala/fr/acinq/eclair/channel/Channel.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 50bd47faaa..5d22cb982d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -2018,8 +2018,4 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu initialize() -} - - - - +} \ No newline at end of file From 78cd70edac06f5e7b8ba2017fc10f3b8c05566e2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Feb 2019 17:11:12 +0100 Subject: [PATCH 09/66] Use p2wsh for pushMe outputs --- .../scala/fr/acinq/eclair/transactions/Transactions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 4ea1bea7a7..ed476f372b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -206,8 +206,8 @@ object Transactions { val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) - val toLocalPushMe_opt = if(isSimplifiedCommitment && toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, Scripts.pushMeSimplified(localDelayedPaymentPubkey))) else None - val toRemotePushMe_opt = if(isSimplifiedCommitment && toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, Scripts.pushMeSimplified(remotePaymentPubkey))) else None + val toLocalPushMe_opt = if(isSimplifiedCommitment && toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None + val toRemotePushMe_opt = if(isSimplifiedCommitment && toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remotePaymentPubkey)))) else None val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) val (sequence, locktime) = encodeTxNumber(txnumber) From 2f5adcea9860e004d38a226337bc21a798e25aac Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Feb 2019 18:04:20 +0100 Subject: [PATCH 10/66] If option_simplified_commitment don't send my_current_per_commitment_point in channel_reestablish --- .../src/main/scala/fr/acinq/eclair/channel/Channel.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 5d22cb982d..a6c461873a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -1303,14 +1303,18 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) - val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index) + val myCurrentPerCommitmentPoint = if(Helpers.isSimplifiedCommitment(d.commitments.localParams)) { + None + } else { + Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) + } val channelReestablish = ChannelReestablish( channelId = d.channelId, nextLocalCommitmentNumber = d.commitments.localCommit.index + 1, nextRemoteRevocationNumber = d.commitments.remoteCommit.index, yourLastPerCommitmentSecret = Some(Scalar(yourLastPerCommitmentSecret)), - myCurrentPerCommitmentPoint = Some(myCurrentPerCommitmentPoint) + myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint ) // we update local/remote connection-local global/local features, we don't persist it right now From 63e6e482d69da73d33e4529197620499660c26b2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 12 Feb 2019 13:28:25 +0100 Subject: [PATCH 11/66] WIP abstracting Commitments --- .../fr/acinq/eclair/channel/Channel.scala | 37 +++-- .../fr/acinq/eclair/channel/Commitments.scala | 145 ++++++++++++------ .../fr/acinq/eclair/channel/Helpers.scala | 10 +- 3 files changed, 132 insertions(+), 60 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index a6c461873a..811d86fad0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -357,13 +357,17 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu channelId = channelId, signature = localSigOfRemoteTx ) - val commitments = Commitments(localParams, remoteParams, channelFlags, + val commitments = CommitmentsV1(localParams, remoteParams, channelFlags, LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 0L, remoteNextHtlcId = 0L, originChannels = Map.empty, remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, commitInput, ShaChain.init, channelId = channelId) + + + + context.parent ! ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId) // we notify the peer asap so it knows how to route messages context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) @@ -394,7 +398,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu handleLocalError(InvalidCommitmentSignature(channelId, signedLocalCommitTx.tx), d, Some(msg)) case Success(_) => val commitInput = localCommitTx.input - val commitments = Commitments(localParams, remoteParams, channelFlags, + val commitments = CommitmentsV1(localParams, remoteParams, channelFlags, LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 0L, remoteNextHtlcId = 0L, @@ -484,7 +488,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu }) when(WAIT_FOR_FUNDING_LOCKED)(handleExceptions { - case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments, shortChannelId, _)) => + case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments: CommitmentsV1, shortChannelId, _)) => // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED) context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId)) @@ -603,7 +607,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d: DATA_NORMAL) => + case Event(c@CMD_SIGN, d@DATA_NORMAL(commisments: CommitmentsV1, _, _, _, _, _, _)) => d.commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -641,7 +645,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - val commitments1 = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + val commitments1 = commisments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) stay using d.copy(commitments = commitments1) } @@ -700,7 +704,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu handleCommandSuccess(sender, store(d.copy(localShutdown = Some(shutdown)))) sending shutdown } - case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d: DATA_NORMAL) => + case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(commitments: CommitmentsV1, _, _, _, _, _, _)) => // they have pending unsigned htlcs => they violated the spec, close the channel // they don't have pending unsigned htlcs // we have pending unsigned htlcs @@ -726,7 +730,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu d.commitments.remoteNextCommitInfo match { case Left(waitForRevocation) => // yes, let's just schedule a new signature ASAP, which will include all pending unsigned htlcs - val commitments1 = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + val commitments1 = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) // in the meantime we won't send new htlcs stay using d.copy(commitments = commitments1, remoteShutdown = Some(remoteShutdown)) case Right(_) => @@ -936,7 +940,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d: DATA_SHUTDOWN) => + case Event(c@CMD_SIGN, d@DATA_SHUTDOWN(commitments: CommitmentsV1, _, _)) => d.commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -958,7 +962,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - stay using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) + stay using d.copy(commitments = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) } case Event(commit: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) => @@ -1889,11 +1893,16 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // first we clean up unacknowledged updates log.debug(s"discarding proposed OUT: ${d.commitments.localChanges.proposed.map(Commitments.msg2String(_)).mkString(",")}") log.debug(s"discarding proposed IN: ${d.commitments.remoteChanges.proposed.map(Commitments.msg2String(_)).mkString(",")}") - val commitments1 = d.commitments.copy( - localChanges = d.commitments.localChanges.copy(proposed = Nil), - remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), - localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, - remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) + val commitments1 = d.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = d.commitments.localChanges.copy(proposed = Nil), + remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), + localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, + remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) + case _: SimplifiedCommitment => ??? + } + + log.debug(s"localNextHtlcId=${d.commitments.localNextHtlcId}->${commitments1.localNextHtlcId}") log.debug(s"remoteNextHtlcId=${d.commitments.remoteNextHtlcId}->${commitments1.remoteNextHtlcId}") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 4ec8c26d48..5a2732468c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -25,7 +25,6 @@ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ import fr.acinq.eclair.{Features, Globals, UInt64} - import scala.util.{Failure, Success} // @formatter:off @@ -41,35 +40,35 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, rem case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long, reSignAsap: Boolean = false) // @formatter:on -/** - * about remoteNextCommitInfo: - * we either: - * - have built and signed their next commit tx with their next revocation hash which can now be discarded - * - have their next per-commitment point - * So, when we've signed and sent a commit message and are waiting for their revocation message, - * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point - */ -case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, - channelFlags: Byte, - localCommit: LocalCommit, remoteCommit: RemoteCommit, - localChanges: LocalChanges, remoteChanges: RemoteChanges, - localNextHtlcId: Long, remoteNextHtlcId: Long, - originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel - remoteNextCommitInfo: Either[WaitingForRevocation, Point], - commitInput: InputInfo, - remotePerCommitmentSecrets: ShaChain, channelId: BinaryData) { +sealed trait Commitments { + + val localParams: LocalParams + val remoteParams: RemoteParams + val channelFlags: Byte + val localCommit: LocalCommit + val remoteCommit: RemoteCommit + val localChanges: LocalChanges + val remoteChanges: RemoteChanges + val localNextHtlcId: Long + val remoteNextHtlcId: Long + val originChannels: Map[Long, Origin] // for outgoing htlcs relayed through us, the id of the previous channel + val remoteNextCommitInfo: Either[WaitingForRevocation, Point] + val commitInput: InputInfo + val remotePerCommitmentSecrets: ShaChain + val channelId: BinaryData + def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight + def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal) + + def addRemoteProposal(proposal: UpdateMessage): Commitments = Commitments.addRemoteProposal(this, proposal) + def timedoutOutgoingHtlcs(blockheight: Long): Set[UpdateAddHtlc] = (localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry) ++ remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry) ++ remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry)).getOrElse(Set.empty[DirectedHtlc])).map(_.add) - def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal) - - def addRemoteProposal(proposal: UpdateMessage): Commitments = Commitments.addRemoteProposal(this, proposal) - def announceChannel: Boolean = (channelFlags & 0x01) != 0 // TODO subtract the pushMe value from the balance? @@ -78,8 +77,40 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(localParams)).amount else 0 reduced.toRemoteMsat / 1000 - remoteParams.channelReserveSatoshis - fees } + } +/** + * about remoteNextCommitInfo: + * we either: + * - have built and signed their next commit tx with their next revocation hash which can now be discarded + * - have their next per-commitment point + * So, when we've signed and sent a commit message and are waiting for their revocation message, + * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point + */ +case class CommitmentsV1(localParams: LocalParams, remoteParams: RemoteParams, + channelFlags: Byte, + localCommit: LocalCommit, remoteCommit: RemoteCommit, + localChanges: LocalChanges, remoteChanges: RemoteChanges, + localNextHtlcId: Long, remoteNextHtlcId: Long, + originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel + remoteNextCommitInfo: Either[WaitingForRevocation, Point], + commitInput: InputInfo, + remotePerCommitmentSecrets: ShaChain, channelId: BinaryData) extends Commitments + + +case class SimplifiedCommitment(localParams: LocalParams, remoteParams: RemoteParams, + channelFlags: Byte, + localCommit: LocalCommit, remoteCommit: RemoteCommit, + localChanges: LocalChanges, remoteChanges: RemoteChanges, + localNextHtlcId: Long, + remoteNextHtlcId: Long, + originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel + remoteNextCommitInfo: Either[WaitingForRevocation, Point], + commitInput: InputInfo, + remotePerCommitmentSecrets: ShaChain, + channelId: BinaryData) extends Commitments + object Commitments { /** * add a change to our proposed change list @@ -88,11 +119,15 @@ object Commitments { * @param proposal * @return an updated commitment instance */ - private def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = - commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) + def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments match { + case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) + case _: SimplifiedCommitment => ??? + } - private def addRemoteProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = - commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) + def addRemoteProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) + case _: SimplifiedCommitment => ??? + } /** * @@ -125,7 +160,10 @@ object Commitments { // let's compute the current commitment *as seen by them* with this change taken into account val add = UpdateAddHtlc(commitments.channelId, commitments.localNextHtlcId, cmd.amountMsat, cmd.paymentHash, cmd.cltvExpiry, cmd.onion) // we increment the local htlc index and add an entry to the origins map - val commitments1 = addLocalProposal(commitments, add).copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) + val commitments1 = addLocalProposal(commitments, add) match { + case c: CommitmentsV1 => c.copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) + case _: SimplifiedCommitment => ??? + } // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation val remoteCommit1 = commitments1.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments1.remoteCommit) val reduced = CommitmentSpec.reduce(remoteCommit1.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed) @@ -167,7 +205,10 @@ object Commitments { } // let's compute the current commitment *as seen by us* including this change - val commitments1 = addRemoteProposal(commitments, add).copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) + val commitments1 = addRemoteProposal(commitments, add) match { + case c: CommitmentsV1 => c.copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) + case _: SimplifiedCommitment => ??? + } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) val incomingHtlcs = reduced.htlcs.filter(_.direction == IN) @@ -298,7 +339,10 @@ object Commitments { // let's compute the current commitment *as seen by them* with this change taken into account val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw) // update_fee replace each other, so we can remove previous ones - val commitments1 = commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) + case _: SimplifiedCommitment => ??? + } val reduced = CommitmentSpec.reduce(commitments1.remoteCommit.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee @@ -333,7 +377,10 @@ object Commitments { // let's compute the current commitment *as seen by us* including this change // update_fee replace each other, so we can remove previous ones - val commitments1 = commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) + case _: SimplifiedCommitment => ??? + } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee @@ -382,10 +429,15 @@ object Commitments { htlcSignatures = htlcSigs.toList ) - val commitments1 = commitments.copy( - remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), - localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), - remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy( + remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), + localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), + remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) + case _: SimplifiedCommitment => ??? + } + + (commitments1, commitSig) case Left(_) => throw CannotSignBeforeRevocation(commitments.channelId) @@ -462,7 +514,10 @@ object Commitments { publishableTxs = PublishableTxs(signedCommitTx, htlcTxsAndSigs)) val ourChanges1 = localChanges.copy(acked = Nil) val theirChanges1 = remoteChanges.copy(proposed = Nil, acked = remoteChanges.acked ++ remoteChanges.proposed) - val commitments1 = commitments.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) + case _: SimplifiedCommitment => ??? + } (commitments1, revocation) } @@ -474,7 +529,7 @@ object Commitments { case Left(_) if revocation.perCommitmentSecret.toPoint != remoteCommit.remotePerCommitmentPoint => throw InvalidRevocation(commitments.channelId) case Left(WaitingForRevocation(theirNextCommit, _, _, _)) => - val forwards = commitments.remoteChanges.signed collect { + val forwards = commitments.remoteChanges.signed collect { // we forward adds downstream only when they have been committed by both sides // it always happen when we receive a revocation, because they send the add, then they sign it, then we sign it case add: UpdateAddHtlc => ForwardAdd(add) @@ -495,13 +550,17 @@ object Commitments { val completedOutgoingHtlcs = commitments.remoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id) -- theirNextCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id) // we remove the newly completed htlcs from the origin map val originChannels1 = commitments.originChannels -- completedOutgoingHtlcs - val commitments1 = commitments.copy( - localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed), - remoteChanges = remoteChanges.copy(signed = Nil), - remoteCommit = theirNextCommit, - remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), - remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), - originChannels = originChannels1) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed), + remoteChanges = remoteChanges.copy(signed = Nil), + remoteCommit = theirNextCommit, + remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), + remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), + originChannels = originChannels1) + case _: SimplifiedCommitment => ??? + } + (commitments1, forwards) case Right(_) => throw UnexpectedRevocation(commitments.channelId) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 462b808416..73761faf79 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -60,9 +60,13 @@ object Helpers { * @return */ def updateFeatures(data: HasCommitments, localInit: Init, remoteInit: Init): HasCommitments = { - val commitments1 = data.commitments.copy( - localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures), - remoteParams = data.commitments.remoteParams.copy(globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures)) + val commitments1 = data.commitments match { + case c: CommitmentsV1 => c.copy( + localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures), + remoteParams = data.commitments.remoteParams.copy(globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures)) + case _: SimplifiedCommitment => ??? + } + data match { case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => d.copy(commitments = commitments1) case d: DATA_WAIT_FOR_FUNDING_LOCKED => d.copy(commitments = commitments1) From 581f5e10aa9d16d5f660f880578e2aea86fb8483 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 13 Feb 2019 11:20:57 +0100 Subject: [PATCH 12/66] WIP Typed commitment --- .../fr/acinq/eclair/channel/Commitments.scala | 2 +- .../eclair/db/sqlite/SqliteChannelsDb.scala | 6 +- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 139 +++++++++++++----- .../channel/states/e/NormalStateSpec.scala | 77 ++++++---- .../channel/states/f/ShutdownStateSpec.scala | 40 +++-- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 32 +++- .../fr/acinq/eclair/io/HtlcReaperSpec.scala | 4 +- .../fr/acinq/eclair/payment/RelayerSpec.scala | 2 +- 8 files changed, 214 insertions(+), 88 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 5a2732468c..da999100d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -40,7 +40,7 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, rem case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long, reSignAsap: Boolean = false) // @formatter:on -sealed trait Commitments { +trait Commitments { val localParams: LocalParams val remoteParams: RemoteParams diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala index b3821f3f7f..0929b472b9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala @@ -21,7 +21,7 @@ import java.sql.Connection import fr.acinq.bitcoin.BinaryData import fr.acinq.eclair.channel.HasCommitments import fr.acinq.eclair.db.ChannelsDb -import fr.acinq.eclair.wire.ChannelCodecs.stateDataCodec +import fr.acinq.eclair.wire.ChannelCodecs.genericStateDataCodec import scala.collection.immutable.Queue @@ -41,7 +41,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb { } override def addOrUpdateChannel(state: HasCommitments): Unit = { - val data = stateDataCodec.encode(state).require.toByteArray + val data = genericStateDataCodec.encode(state).require.toByteArray using (sqlite.prepareStatement("UPDATE local_channels SET data=? WHERE channel_id=?")) { update => update.setBytes(1, data) update.setBytes(2, state.channelId) @@ -75,7 +75,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb { override def listChannels(): Seq[HasCommitments] = { using(sqlite.createStatement) { statement => val rs = statement.executeQuery("SELECT data FROM local_channels") - codecSequence(rs, stateDataCodec) + codecSequence(rs, genericStateDataCodec) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 9f9ddc9822..af45306b37 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -27,7 +27,8 @@ import fr.acinq.eclair.wire.LightningMessageCodecs._ import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec} +import scodec.{Attempt, Codec, Decoder, Encoder, Err, GenCodec, Transformer} +import shapeless.{Generic, HNil} /** * Created by PM on 02/06/2017. @@ -178,22 +179,6 @@ object ChannelCodecs extends Logging { (wire: BitVector) => spentListCodec.decode(wire).map(_.map(_.toMap)) ) - val commitmentsCodec: Codec[Commitments] = ( - ("localParams" | localParamsCodec) :: - ("remoteParams" | remoteParamsCodec) :: - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64) :: - ("remoteNextHtlcId" | uint64) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | binarydata(32))).as[Commitments] - val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | txCodec) :: ("localClosingSigned" | closingSignedCodec)).as[ClosingTxProposed] @@ -221,18 +206,90 @@ object ChannelCodecs extends Logging { ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) :: ("spent" | spentMapCodec)).as[RevokedCommitPublished] - val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodec) :: + /* + SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT AAA TTTTTTTTTTTTTTTTTTTTTTTEEEEEEEEEEEEEEEEEEEEEE DDDDDDDDDDDDD AAA TTTTTTTTTTTTTTTTTTTTTTT AAA + SS:::::::::::::::ST:::::::::::::::::::::T A:::A T:::::::::::::::::::::TE::::::::::::::::::::E D::::::::::::DDD A:::A T:::::::::::::::::::::T A:::A + S:::::SSSSSS::::::ST:::::::::::::::::::::T A:::::A T:::::::::::::::::::::TE::::::::::::::::::::E D:::::::::::::::DD A:::::A T:::::::::::::::::::::T A:::::A + S:::::S SSSSSSST:::::TT:::::::TT:::::T A:::::::A T:::::TT:::::::TT:::::TEE::::::EEEEEEEEE::::E DDD:::::DDDDD:::::D A:::::::A T:::::TT:::::::TT:::::T A:::::::A + S:::::S TTTTTT T:::::T TTTTTT A:::::::::A TTTTTT T:::::T TTTTTT E:::::E EEEEEE D:::::D D:::::D A:::::::::A TTTTTT T:::::T TTTTTT A:::::::::A + S:::::S T:::::T A:::::A:::::A T:::::T E:::::E D:::::D D:::::D A:::::A:::::A T:::::T A:::::A:::::A + S::::SSSS T:::::T A:::::A A:::::A T:::::T E::::::EEEEEEEEEE D:::::D D:::::D A:::::A A:::::A T:::::T A:::::A A:::::A + SS::::::SSSSS T:::::T A:::::A A:::::A T:::::T E:::::::::::::::E D:::::D D:::::D A:::::A A:::::A T:::::T A:::::A A:::::A + SSS::::::::SS T:::::T A:::::A A:::::A T:::::T E:::::::::::::::E D:::::D D:::::D A:::::A A:::::A T:::::T A:::::A A:::::A + SSSSSS::::S T:::::T A:::::AAAAAAAAA:::::A T:::::T E::::::EEEEEEEEEE D:::::D D:::::D A:::::AAAAAAAAA:::::A T:::::T A:::::AAAAAAAAA:::::A + S:::::S T:::::T A:::::::::::::::::::::A T:::::T E:::::E D:::::D D:::::DA:::::::::::::::::::::A T:::::T A:::::::::::::::::::::A + S:::::S T:::::T A:::::AAAAAAAAAAAAA:::::A T:::::T E:::::E EEEEEE D:::::D D:::::DA:::::AAAAAAAAAAAAA:::::A T:::::T A:::::AAAAAAAAAAAAA:::::A + SSSSSSS S:::::S TT:::::::TT A:::::A A:::::A TT:::::::TT EE::::::EEEEEEEE:::::E DDD:::::DDDDD:::::DA:::::A A:::::A TT:::::::TT A:::::A A:::::A + S::::::SSSSSS:::::S T:::::::::T A:::::A A:::::A T:::::::::T E::::::::::::::::::::E D:::::::::::::::DDA:::::A A:::::A T:::::::::T A:::::A A:::::A + S:::::::::::::::SS T:::::::::T A:::::A A:::::A T:::::::::T E::::::::::::::::::::E D::::::::::::DDD A:::::A A:::::A T:::::::::T A:::::A A:::::A + SSSSSSSSSSSSSSS TTTTTTTTTTTAAAAAAA AAAAAAATTTTTTTTTTT EEEEEEEEEEEEEEEEEEEEEE DDDDDDDDDDDDD AAAAAAA AAAAAAATTTTTTTTTTTAAAAAAA AAAAAAA + */ + + def encodeT[T <: Commitments]: T => Attempt[Commitments] = { t => + Attempt.successful(t.asInstanceOf[Commitments]) + } + +// def decodeT[T <: Commitments]: Commitments => Attempt[T] = { +// case c: CommitmentsV1 => Attempt.successful(c) +// case s: SimplifiedCommitment => Attempt.successful(s) +// case _ => Attempt.failure(Err("Wrong type")) +// } + + private val decodeCommitV1ToGeneric: Commitments => Attempt[CommitmentsV1] = { + case c: CommitmentsV1 => Attempt.successful(c) + case _ => Attempt.failure(Err("Wrong type")) + } + + private val decodeSimplifiedToGeneric: Commitments => Attempt[SimplifiedCommitment] = { + case s: SimplifiedCommitment => Attempt.successful(s) + case _ => Attempt.failure(Err("Wrong type")) + } + + val commitmentsV1Codec: Codec[Commitments] = ( + ("localParams" | localParamsCodec) :: + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64) :: + ("remoteNextHtlcId" | uint64) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("channelId" | binarydata(32))).as[CommitmentsV1].exmap(encodeT[CommitmentsV1], decodeCommitV1ToGeneric) + + val simplifiedCommitmentCodec: Codec[Commitments] = ( + ("localParams" | localParamsCodec) :: + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64) :: + ("remoteNextHtlcId" | uint64) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("channelId" | binarydata(32))).as[SimplifiedCommitment].exmap(encodeT[SimplifiedCommitment], decodeSimplifiedToGeneric) + + + def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitCodec: Codec[Commitments]): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitCodec) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitCodec: Codec[Commitments]): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + ("commitments" | commitCodec) :: ("shortChannelId" | shortchannelid) :: ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | commitmentsCodec) :: + def DATA_NORMAL_Codec(commitCodec: Codec[Commitments]): Codec[DATA_NORMAL] = ( + ("commitments" | commitCodec) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool) :: ("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) :: @@ -240,20 +297,20 @@ object ChannelCodecs extends Logging { ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] - val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( - ("commitments" | commitmentsCodec) :: + def DATA_SHUTDOWN_Codec(commitCodec: Codec[Commitments]): Codec[DATA_SHUTDOWN] = ( + ("commitments" | commitCodec) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] - val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = ( - ("commitments" | commitmentsCodec) :: + def DATA_NEGOTIATING_Codec(commitCodec: Codec[Commitments]): Codec[DATA_NEGOTIATING] = ( + ("commitments" | commitCodec) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec) :: ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( - ("commitments" | commitmentsCodec) :: + def DATA_CLOSING_Codec(commitCodec: Codec[Commitments]): Codec[DATA_CLOSING] = ( + ("commitments" | commitCodec) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: @@ -262,17 +319,21 @@ object ChannelCodecs extends Logging { ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] - val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitCodec: Codec[Commitments]): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | commitCodec) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] - val stateDataCodec: Codec[HasCommitments] = ("version" | constant(0x00)) ~> discriminated[HasCommitments].by(uint16) - .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) - .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec) - .typecase(0x03, DATA_NORMAL_Codec) - .typecase(0x04, DATA_SHUTDOWN_Codec) - .typecase(0x05, DATA_NEGOTIATING_Codec) - .typecase(0x06, DATA_CLOSING_Codec) - .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) + private def stateDataCodec(commitCodec: Codec[Commitments]): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) + .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitCodec)) + .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitCodec)) + .typecase(0x03, DATA_NORMAL_Codec(commitCodec)) + .typecase(0x04, DATA_SHUTDOWN_Codec(commitCodec)) + .typecase(0x05, DATA_NEGOTIATING_Codec(commitCodec)) + .typecase(0x06, DATA_CLOSING_Codec(commitCodec)) + .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitCodec)) + + val genericStateDataCodec: Codec[HasCommitments] = discriminated[HasCommitments].by(uint8) + .typecase(0x00, stateDataCodec(commitmentsV1Codec)) + .typecase(0x01, stateDataCodec(simplifiedCommitmentCodec)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 59d09fdd82..e529866e2d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -70,11 +70,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val htlc = alice2bob.expectMsgType[UpdateAddHtlc] assert(htlc.id == 0 && htlc.paymentHash == h) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localNextHtlcId = 1, - localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), - originChannels = Map(0L -> Local(Some(sender.ref))) - ))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localNextHtlcId = 1, + localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), + originChannels = Map(0L -> Local(Some(sender.ref))) + )} + )) } test("recv CMD_ADD_HTLC (incrementing ids)") { f => @@ -101,11 +103,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val htlc = alice2bob.expectMsgType[UpdateAddHtlc] assert(htlc.id == 0 && htlc.paymentHash == h) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localNextHtlcId = 1, - localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), - originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) - ))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localNextHtlcId = 1, + localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), + originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) + )} + )) } test("recv CMD_ADD_HTLC (invalid payment hash)") { f => @@ -296,7 +300,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) bob ! htlc - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1) + })) // bob won't forward the add before it is cross-signed relayerB.expectNoMsg() } @@ -1038,8 +1044,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) + })) } test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => @@ -1079,7 +1086,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)) + })) // alice immediately propagates the fulfill upstream val forward = relayerA.expectMsgType[ForwardFulfill] assert(forward.fulfill === fulfill) @@ -1153,8 +1162,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) + })) } test("recv CMD_FAIL_HTLC (unknown htlc id)") { f => @@ -1180,8 +1191,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) + })) } test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { f => @@ -1215,7 +1228,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) + })) // alice won't forward the fail before it is cross-signed relayerA.expectNoMsg() } @@ -1235,7 +1250,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) + })) // alice won't forward the fail before it is cross-signed relayerA.expectNoMsg() @@ -1311,8 +1328,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)) + })) } test("recv CMD_UPDATE_FEE (two in a row)") { f => @@ -1326,8 +1345,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fee2 = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee2)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee2)) + })) } test("recv CMD_UPDATE_FEE (when fundee)") { f => @@ -1346,7 +1367,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob ! fee1 val fee2 = UpdateFee("00" * 32, 14000) bob ! fee2 - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee2), remoteNextHtlcId = 0))) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee2), remoteNextHtlcId = 0) + })) } test("recv UpdateFee (two in a row)") { f => @@ -1354,7 +1377,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val fee = UpdateFee("00" * 32, 12000) bob ! fee - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0))) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0) + })) } test("recv UpdateFee (when sender is not funder)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 693aa411e6..8353a11774 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -114,8 +114,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) + })) } test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => @@ -142,7 +144,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val fulfill = UpdateFulfillHtlc("00" * 32, 0, "11" * 32) sender.send(alice, fulfill) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)) + })) } test("recv UpdateFulfillHtlc (unknown htlc id)") { f => @@ -186,8 +190,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) + })) } test("recv CMD_FAIL_HTLC (unknown htlc id)") { f => @@ -207,8 +213,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) + })) } test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { f => @@ -235,7 +243,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val fail = UpdateFailHtlc("00" * 32, 1, "00" * 152) sender.send(alice, fail) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) + })) } test("recv UpdateFailHtlc (unknown htlc id)") { f => @@ -260,7 +270,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION) sender.send(alice, fail) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) + })) } test("recv UpdateFailMalformedHtlc (invalid failure_code)") { f => @@ -503,8 +515,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) + commitments = initialState.commitments match { + case c: CommitmentsV1 => c.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)) + })) } test("recv CMD_UPDATE_FEE (when fundee)") { f => @@ -521,7 +535,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_SHUTDOWN] val fee = UpdateFee("00" * 32, 12000) bob ! fee - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)))) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)) + })) } test("recv UpdateFee (when sender is not funder)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 8fcbbbee59..51ca56f878 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -37,13 +37,26 @@ class ChannelStateSpec extends FunSuite { import ChannelStateSpec._ - test("basic serialization test (NORMAL)") { + test("basic serialization test (NORMAL - CommitmentV1)") { val data = normal - val bin = ChannelCodecs.DATA_NORMAL_Codec.encode(data).require - val check = ChannelCodecs.DATA_NORMAL_Codec.decodeValue(bin).require + val bin = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.commitmentsV1Codec).encode(data).require + val check = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.commitmentsV1Codec).decodeValue(bin).require + assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) + assert(data.commitments.isInstanceOf[CommitmentsV1]) } + + test("basic serialization test (NORMAL - SimplifiedCommitment)") { + val data = normalSimplified + val bin = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.simplifiedCommitmentCodec).encode(data).require + val check = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.simplifiedCommitmentCodec).decodeValue(bin).require + + assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) + assert(data === check) + assert(data.commitments.isInstanceOf[SimplifiedCommitment]) + } + } object ChannelStateSpec { @@ -100,7 +113,14 @@ object ChannelStateSpec { val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000, 70000000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), BinaryData("0303030303030303030303030303030303030303030303030303030303030303"), Scalar(BinaryData("04" * 32)).toPoint) - val commitments = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + val commitmentsV1 = CommitmentsV1(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 32L, + remoteNextHtlcId = 4L, + originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), + remoteNextCommitInfo = Right(randomKey.publicKey), + commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32) + + val simplifiedCommitment = SimplifiedCommitment(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), @@ -109,5 +129,7 @@ object ChannelStateSpec { val channelUpdate = Announcements.makeChannelUpdate("11" * 32, randomKey, randomKey.publicKey, ShortChannelId(142553), 42, 15, 575, 53, Channel.MAX_FUNDING_SATOSHIS * 1000L) - val normal = DATA_NORMAL(commitments, ShortChannelId(42), true, None, channelUpdate, None, None) + val normal = DATA_NORMAL(commitmentsV1, ShortChannelId(42), true, None, channelUpdate, None, None) + + val normalSimplified = DATA_NORMAL(simplifiedCommitment, ShortChannelId(42), true, None, channelUpdate, None, None) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala index 6c950c2115..1638ab2382 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala @@ -63,7 +63,9 @@ class HtlcReaperSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { channel.expectNoMsg(100 millis) // let's now assume that the channel get's reconnected, and it had the time to fail the htlcs - val data1 = data.copy(commitments = data.commitments.copy(localCommit = data.commitments.localCommit.copy(spec = data.commitments.localCommit.spec.copy(htlcs = Set.empty)))) + val data1 = data.copy(commitments = data.commitments match { + case c: CommitmentsV1 => c.copy(localCommit = data.commitments.localCommit.copy(spec = data.commitments.localCommit.spec.copy(htlcs = Set.empty))) + }) sender.send(brokenHtlcKiller, ChannelStateChanged(channel.ref, system.deadLetters, data.commitments.remoteParams.nodeId, OFFLINE, NORMAL, data1)) channel.expectNoMsg(100 millis) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala index 93f8efa685..9eb517e844 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala @@ -55,7 +55,7 @@ class RelayerSpec extends TestkitBaseClass { val channelId_ab: BinaryData = randomBytes(32) val channelId_bc: BinaryData = randomBytes(32) - def makeCommitments(channelId: BinaryData) = new Commitments(null, null, 0.toByte, null, + def makeCommitments(channelId: BinaryData) = new CommitmentsV1(null, null, 0.toByte, null, RemoteCommit(42, CommitmentSpec(Set.empty, 20000, 5000000, 100000000), "00" * 32, randomKey.toPoint), null, null, 0, 0, Map.empty, null, null, null, channelId) { override def availableBalanceForSendMsat: Long = remoteCommit.spec.toRemoteMsat // approximation From 65bbab9bf8d9c0d5e2ac0e20f4ba07f3f8297327 Mon Sep 17 00:00:00 2001 From: sstone Date: Tue, 12 Feb 2019 17:50:09 +0100 Subject: [PATCH 13/66] Bitcoin RPC: use `signrawtransactionwithwallet` We don't use `signrawtransaction` anymore, which was deprecated in Bitcoin Core 0.17 and will be removed in 0.18. This means that we don't support 0.16.3 and older. --- .../eclair/blockchain/bitcoind/BitcoinCoreWallet.scala | 7 ++++--- .../src/test/resources/integration/bitcoin.conf | 4 ++-- .../blockchain/bitcoind/BitcoinCoreWalletSpec.scala | 10 +++------- .../bitcoind/ExtendedBitcoinClientSpec.scala | 4 ++-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala index 7843343f60..d3a27696bb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala @@ -48,13 +48,14 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC def fundTransaction(tx: Transaction, lockUnspents: Boolean, feeRatePerKw: Long): Future[FundTransactionResponse] = fundTransaction(Transaction.write(tx).toString(), lockUnspents, feeRatePerKw) def signTransaction(hex: String): Future[SignTransactionResponse] = - rpcClient.invoke("signrawtransaction", hex).map(json => { + rpcClient.invoke("signrawtransactionwithwallet", hex).map(json => { val JString(hex) = json \ "hex" val JBool(complete) = json \ "complete" if (!complete) { val message = json \ "errors" match { - case value: JValue => Serialization.write(value)(DefaultFormats) - case _ => "signrawtransaction failed" + case value: JValue => + Serialization.write(value)(DefaultFormats) + case _ => "signrawtransactionwithwallet failed" } throw new JsonRPCError(Error(-1, message)) } diff --git a/eclair-core/src/test/resources/integration/bitcoin.conf b/eclair-core/src/test/resources/integration/bitcoin.conf index 6ef9572c9d..29775744a1 100644 --- a/eclair-core/src/test/resources/integration/bitcoin.conf +++ b/eclair-core/src/test/resources/integration/bitcoin.conf @@ -2,7 +2,6 @@ regtest=1 noprinttoconsole=1 server=1 port=28333 -regtest.rpcport=28332 rpcuser=foo rpcpassword=bar txindex=1 @@ -10,4 +9,5 @@ zmqpubrawblock=tcp://127.0.0.1:28334 zmqpubrawtx=tcp://127.0.0.1:28335 rpcworkqueue=64 addresstype=bech32 -deprecatedrpc=signrawtransaction +[regtest] +rpcport=28332 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index 8701639b8a..b73a187095 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -141,10 +141,9 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe sender.send(bitcoincli, BitcoinReq("getrawtransaction", fundingTxes(2).txid.toString())) assert(sender.expectMsgType[JString](10 seconds).s === fundingTxes(2).toString()) - // NB: bitcoin core before 0.17.0 doesn't clear the locks when a tx is published + // NB: from 0.17.0 on bitcoin core will clear locks when a tx is published sender.send(bitcoincli, BitcoinReq("listlockunspent")) - val expectedLocks = if (bitcoinVersion >= 170000) 0 else 2 - assert(sender.expectMsgType[JValue](10 seconds).children.size === expectedLocks) + assert(sender.expectMsgType[JValue](10 seconds).children.size === 0) } test("encrypt wallet") { @@ -178,10 +177,7 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey.publicKey, randomKey.publicKey))) wallet.makeFundingTx(pubkeyScript, MilliBtc(50), 10000).pipeTo(sender.ref) val error = sender.expectMsgType[Failure].cause.asInstanceOf[JsonRPCError].error - // behaviour has changed, bitcoin core will now return a more generic error - if (bitcoinVersion < 170000) { - assert(error.message.contains("Please enter the wallet passphrase with walletpassphrase first.")) - } + assert(error.message.contains("Please enter the wallet passphrase with walletpassphrase first")) sender.send(bitcoincli, BitcoinReq("listlockunspent")) assert(sender.expectMsgType[JValue](10 seconds).children.size === 0) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala index d730b2bf13..0b6c7ac335 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala @@ -67,7 +67,7 @@ class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with Bitcoi val json = sender.expectMsgType[JValue] val JString(unsignedtx) = json \ "hex" val JInt(changePos) = json \ "changepos" - bitcoinClient.invoke("signrawtransaction", unsignedtx).pipeTo(sender.ref) + bitcoinClient.invoke("signrawtransactionwithwallet", unsignedtx).pipeTo(sender.ref) val JString(signedTx) = sender.expectMsgType[JValue] \ "hex" val tx = Transaction.read(signedTx) val txid = tx.txid.toString() @@ -92,7 +92,7 @@ class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with Bitcoi val pos = if (changePos == 0) 1 else 0 bitcoinClient.invoke("createrawtransaction", Array(Map("txid" -> txid, "vout" -> pos)), Map(address -> 5.99999)).pipeTo(sender.ref) val JString(unsignedtx) = sender.expectMsgType[JValue] - bitcoinClient.invoke("signrawtransaction", unsignedtx).pipeTo(sender.ref) + bitcoinClient.invoke("signrawtransactionwithwallet", unsignedtx).pipeTo(sender.ref) val JString(signedTx) = sender.expectMsgType[JValue] \ "hex" signedTx } From 3994d97b54dd1643611918112c8636dadb86be9d Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 13 Feb 2019 17:40:47 +0100 Subject: [PATCH 14/66] Typed commitment - scodec support --- .../fr/acinq/eclair/channel/Channel.scala | 9 +- .../fr/acinq/eclair/channel/Commitments.scala | 103 +++++++++++------- .../fr/acinq/eclair/channel/Helpers.scala | 18 ++- .../eclair/transactions/Transactions.scala | 13 ++- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 34 ++++-- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 10 +- 6 files changed, 112 insertions(+), 75 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 811d86fad0..ced5729778 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -366,8 +366,6 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu commitInput, ShaChain.init, channelId = channelId) - - context.parent ! ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId) // we notify the peer asap so it knows how to route messages context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) @@ -1307,10 +1305,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) - val myCurrentPerCommitmentPoint = if(Helpers.isSimplifiedCommitment(d.commitments.localParams)) { - None - } else { - Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) + val myCurrentPerCommitmentPoint = d.commitments match { + case c: CommitmentsV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) + case s: SimplifiedCommitment => None } val channelReestablish = ChannelReestablish( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index da999100d1..9170644d67 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -25,6 +25,8 @@ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ import fr.acinq.eclair.{Features, Globals, UInt64} +import sun.reflect.generics.reflectiveObjects.NotImplementedException + import scala.util.{Failure, Success} // @formatter:off @@ -40,6 +42,7 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, rem case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long, reSignAsap: Boolean = false) // @formatter:on +//TODO seal this! trait Commitments { val localParams: LocalParams @@ -74,7 +77,7 @@ trait Commitments { // TODO subtract the pushMe value from the balance? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(localParams)).amount else 0 + val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, Helpers.hasOptionSimplifiedCommitment(localParams)).amount else 0 reduced.toRemoteMsat / 1000 - remoteParams.channelReserveSatoshis - fees } @@ -136,6 +139,10 @@ object Commitments { * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc) */ def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC, origin: Origin): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { + val isSimplifiedCommitment = commitments match { + case c: CommitmentsV1 => false + case s: SimplifiedCommitment => true + } if (cmd.paymentHash.size != 32) { return Left(InvalidPaymentHash(commitments.channelId)) @@ -182,7 +189,7 @@ object Commitments { // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(commitments.localParams)).amount else 0 + val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, isSimplifiedCommitment).amount else 0 val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { return Left(InsufficientFunds(commitments.channelId, amountMsat = cmd.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.remoteParams.channelReserveSatoshis, feesSatoshis = fees)) @@ -192,6 +199,11 @@ object Commitments { } def receiveAdd(commitments: Commitments, add: UpdateAddHtlc): Commitments = { + val isSimplifiedCommitment = commitments match { + case c: CommitmentsV1 => false + case s: SimplifiedCommitment => true + } + if (add.id != commitments.remoteNextHtlcId) { throw UnexpectedHtlcId(commitments.channelId, expected = commitments.remoteNextHtlcId, actual = add.id) } @@ -222,7 +234,7 @@ object Commitments { } // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced, Helpers.isSimplifiedCommitment(commitments.localParams)).amount + val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced, isSimplifiedCommitment).amount val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw InsufficientFunds(commitments.channelId, amountMsat = add.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -379,7 +391,7 @@ object Commitments { // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) - case _: SimplifiedCommitment => ??? + case _: SimplifiedCommitment => throw new IllegalStateException("Should not update the fee on simplified_commitment") } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) @@ -407,40 +419,42 @@ object Commitments { def sendCommit(commitments: Commitments, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, CommitSig) = { import commitments._ - commitments.remoteNextCommitInfo match { - case Right(_) if !localHasChanges(commitments) => - throw CannotSignWithoutChanges(commitments.channelId) - case Right(remoteNextPerCommitmentPoint) => - // remote commitment will includes all local changes + remote acked changes - val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) - - val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) - - // NB: IN/OUT htlcs are inverted because this is the remote commit - log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) - - // don't sign if they don't get paid - val commitSig = CommitSig( - channelId = commitments.channelId, - signature = sig, - htlcSignatures = htlcSigs.toList - ) - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy( - remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), - localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), - remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) - case _: SimplifiedCommitment => ??? + commitments match { + case _: SimplifiedCommitment => throw new NotImplementedException + case c: CommitmentsV1 => + c.remoteNextCommitInfo match { + case Right(_) if !localHasChanges(c) => + throw CannotSignWithoutChanges(c.channelId) + case Right(remoteNextPerCommitmentPoint) => + // remote commitment will includes all local changes + remote acked changes + val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) + val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(false, keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + + val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) + + // NB: IN/OUT htlcs are inverted because this is the remote commit + log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) + + // don't sign if they don't get paid + val commitSig = CommitSig( + channelId = c.channelId, + signature = sig, + htlcSignatures = htlcSigs.toList + ) + + val commitments1 = c.copy( + remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), + localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), + remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) + + (commitments1, commitSig) + case Left(_) => + throw CannotSignBeforeRevocation(commitments.channelId) } - - (commitments1, commitSig) - case Left(_) => - throw CannotSignBeforeRevocation(commitments.channelId) } } @@ -458,13 +472,18 @@ object Commitments { if (!remoteHasChanges(commitments)) throw CannotSignWithoutChanges(commitments.channelId) + val isSimplifiedCommitment = commitments match { + case _: SimplifiedCommitment => true + case _: CommitmentsV1 => false + } + // check that their signature is valid // signatures are now optional in the commit message, and will be sent only if the other party is actually // receiving money i.e its commit tx has one output for them val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) - val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) + val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(isSimplifiedCommitment, keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) @@ -567,24 +586,24 @@ object Commitments { } } - def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeLocalTxs(isSimplifiedCommitment: Boolean, keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) - val remotePaymentPubkey = Helpers.isSimplifiedCommitment(localParams) match { + val remotePaymentPubkey = isSimplifiedCommitment match { case true => PublicKey(remoteParams.paymentBasepoint) case false => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) } val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(Helpers.isSimplifiedCommitment(localParams), commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(isSimplifiedCommitment, commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = Helpers.isSimplifiedCommitment(localParams) match { + def makeRemoteTxs(isSimplifiedCommitment: Boolean, keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + val localPaymentPubkey = isSimplifiedCommitment match { case true => keyManager.paymentPoint(localParams.channelKeyPath).publicKey case false => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) } @@ -592,7 +611,7 @@ object Commitments { val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(Helpers.isSimplifiedCommitment(localParams), commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(isSimplifiedCommitment, commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 73761faf79..772e8f774e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -234,6 +234,8 @@ object Helpers { * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) */ def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { + val isSimplifiedCommitment = Helpers.hasOptionSimplifiedCommitment(localParams) + val toLocalMsat = if (localParams.isFunder) fundingSatoshis * 1000 - pushMsat else pushMsat val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingSatoshis * 1000 - pushMsat @@ -243,7 +245,7 @@ object Helpers { if (!localParams.isFunder) { // they are funder, therefore they pay the fee: we need to make sure they can afford it! val toRemoteMsat = remoteSpec.toLocalMsat - val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, Helpers.isSimplifiedCommitment(localParams)).amount + val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, Helpers.hasOptionSimplifiedCommitment(localParams)).amount val missing = toRemoteMsat / 1000 - localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(temporaryChannelId, missingSatoshis = -1 * missing, reserveSatoshis = localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -252,8 +254,8 @@ object Helpers { val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) - val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) + val (localCommitTx, _, _) = Commitments.makeLocalTxs(isSimplifiedCommitment, keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(isSimplifiedCommitment, keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) (localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -317,7 +319,7 @@ object Helpers { } } - def isSimplifiedCommitment[T <: ParamsWithFeatures](params: T) = Features.hasFeature(params.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + def hasOptionSimplifiedCommitment[T <: ParamsWithFeatures](params: T) = Features.hasFeature(params.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) object Closing { @@ -477,7 +479,13 @@ object Helpers { def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) + + val isSimplifiedCommitment = commitments match { + case _: SimplifiedCommitment => true + case _: CommitmentsV1 => false + } + + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(isSimplifiedCommitment, keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == tx.txid, "txid mismatch, cannot recompute the current remote commit tx") val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index ed476f372b..28fda3d75c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -138,8 +138,8 @@ object Transactions { def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec, simplifiedCommitment: Boolean): Satoshi = { val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec) val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec) - val weight = (if(simplifiedCommitment) simplifiedCommitWeight else commitWeight) + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size) - weight2fee(if(simplifiedCommitment) simplifiedFeerateKw else spec.feeratePerKw, weight) + val weight = (if (simplifiedCommitment) simplifiedCommitWeight else commitWeight) + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size) + weight2fee(if (simplifiedCommitment) simplifiedFeerateKw else spec.feeratePerKw, weight) } /** @@ -190,7 +190,7 @@ object Transactions { def makeCommitTx(isSimplifiedCommitment: Boolean, commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { val commitFee = commitTxFee(localDustLimit, spec, isSimplifiedCommitment) - val pushMeValueTotal = if(isSimplifiedCommitment) pushMeValue * 2 else Satoshi(0) // funder pays the total amount of pushme outputs + val pushMeValueTotal = if (isSimplifiedCommitment) pushMeValue * 2 else Satoshi(0) // funder pays the total amount of pushme outputs val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee - pushMeValueTotal, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) @@ -206,8 +206,8 @@ object Transactions { val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) - val toLocalPushMe_opt = if(isSimplifiedCommitment && toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None - val toRemotePushMe_opt = if(isSimplifiedCommitment && toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remotePaymentPubkey)))) else None + val toLocalPushMe_opt = if (isSimplifiedCommitment && toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None + val toRemotePushMe_opt = if (isSimplifiedCommitment && toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remotePaymentPubkey)))) else None val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) val (sequence, locktime) = encodeTxNumber(txnumber) @@ -254,6 +254,7 @@ object Transactions { lockTime = 0), htlc.paymentHash) } + //TODO adjust for option_simplified_commitment def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => @@ -472,7 +473,7 @@ object Transactions { def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: BinaryData, outputsAlreadyUsed: Set[Int], amount_opt: Option[Satoshi]): Int = { val outputIndex = tx.txOut .zipWithIndex - .indexWhere { case (txOut, index) => amount_opt.map(_ == txOut.amount).getOrElse(true) && txOut.publicKeyScript == pubkeyScript && !outputsAlreadyUsed.contains(index)} // it's not enough to only resolve on pubkeyScript because we may have duplicates + .indexWhere { case (txOut, index) => amount_opt.map(_ == txOut.amount).getOrElse(true) && txOut.publicKeyScript == pubkeyScript && !outputsAlreadyUsed.contains(index) } // it's not enough to only resolve on pubkeyScript because we may have duplicates if (outputIndex >= 0) { outputIndex } else { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index af45306b37..2d169c1607 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.wire.LightningMessageCodecs._ import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec, Decoder, Encoder, Err, GenCodec, Transformer} +import scodec.{Attempt, Codec, Decoder, Encoder, Err, GenCodec, SizeBound, Transformer} import shapeless.{Generic, HNil} /** @@ -225,24 +225,21 @@ object ChannelCodecs extends Logging { SSSSSSSSSSSSSSS TTTTTTTTTTTAAAAAAA AAAAAAATTTTTTTTTTT EEEEEEEEEEEEEEEEEEEEEE DDDDDDDDDDDDD AAAAAAA AAAAAAATTTTTTTTTTTAAAAAAA AAAAAAA */ + val COMMITMENTv1_VERSION_BYTE = 0x00 + val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01 + def encodeT[T <: Commitments]: T => Attempt[Commitments] = { t => Attempt.successful(t.asInstanceOf[Commitments]) } -// def decodeT[T <: Commitments]: Commitments => Attempt[T] = { -// case c: CommitmentsV1 => Attempt.successful(c) -// case s: SimplifiedCommitment => Attempt.successful(s) -// case _ => Attempt.failure(Err("Wrong type")) -// } - private val decodeCommitV1ToGeneric: Commitments => Attempt[CommitmentsV1] = { case c: CommitmentsV1 => Attempt.successful(c) - case _ => Attempt.failure(Err("Wrong type")) + case _ => Attempt.failure(Err("Wrong type!!")) } private val decodeSimplifiedToGeneric: Commitments => Attempt[SimplifiedCommitment] = { case s: SimplifiedCommitment => Attempt.successful(s) - case _ => Attempt.failure(Err("Wrong type")) + case _ => Attempt.failure(Err("Wrong type??")) } val commitmentsV1Codec: Codec[Commitments] = ( @@ -332,8 +329,21 @@ object ChannelCodecs extends Logging { .typecase(0x06, DATA_CLOSING_Codec(commitCodec)) .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitCodec)) - val genericStateDataCodec: Codec[HasCommitments] = discriminated[HasCommitments].by(uint8) - .typecase(0x00, stateDataCodec(commitmentsV1Codec)) - .typecase(0x01, stateDataCodec(simplifiedCommitmentCodec)) + private val genericStateDataDecoder = discriminated[HasCommitments].by(uint8) + .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(commitmentsV1Codec)) + .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(simplifiedCommitmentCodec)).asDecoder + + private val genericStateDataEncoder = new Encoder[HasCommitments] { + override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments match { + case _: CommitmentsV1 => stateDataCodec(commitmentsV1Codec).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) + case _: SimplifiedCommitment => stateDataCodec(simplifiedCommitmentCodec).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) + case _ => Attempt.failure(Err("Unknown type")) + } + + override def sizeBound: SizeBound = SizeBound(0, None) + } + + val genericStateDataCodec = GenCodec(genericStateDataEncoder, genericStateDataDecoder).fuse + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 51ca56f878..48202cd106 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -39,9 +39,10 @@ class ChannelStateSpec extends FunSuite { test("basic serialization test (NORMAL - CommitmentV1)") { val data = normal - val bin = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.commitmentsV1Codec).encode(data).require - val check = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.commitmentsV1Codec).decodeValue(bin).require + val bin = ChannelCodecs.genericStateDataCodec.encode(data).require + val check = ChannelCodecs.genericStateDataCodec.decodeValue(bin).require + assert(bin.take(8).toByte(signed = false) == ChannelCodecs.COMMITMENTv1_VERSION_BYTE) assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) assert(data.commitments.isInstanceOf[CommitmentsV1]) @@ -49,9 +50,10 @@ class ChannelStateSpec extends FunSuite { test("basic serialization test (NORMAL - SimplifiedCommitment)") { val data = normalSimplified - val bin = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.simplifiedCommitmentCodec).encode(data).require - val check = ChannelCodecs.DATA_NORMAL_Codec(ChannelCodecs.simplifiedCommitmentCodec).decodeValue(bin).require + val bin = ChannelCodecs.genericStateDataCodec.encode(data).require + val check = ChannelCodecs.genericStateDataCodec.decodeValue(bin).require + assert(bin.take(8).toByte(signed = false) == ChannelCodecs.COMMITMENT_SIMPLIFIED_VERSION_BYTE) assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) assert(data.commitments.isInstanceOf[SimplifiedCommitment]) From 018efa3c5476e8d6560f0ce80f02697027265d6c Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 13 Feb 2019 18:08:58 +0100 Subject: [PATCH 15/66] Comments, formatting, renaming --- .../scala/fr/acinq/eclair/channel/Channel.scala | 16 ++++++++-------- .../fr/acinq/eclair/channel/Commitments.scala | 1 + .../scala/fr/acinq/eclair/channel/Helpers.scala | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index ced5729778..460b9785b7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -605,13 +605,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d@DATA_NORMAL(commisments: CommitmentsV1, _, _, _, _, _, _)) => - d.commitments.remoteNextCommitInfo match { - case _ if !Commitments.localHasChanges(d.commitments) => + case Event(c@CMD_SIGN, d@DATA_NORMAL(commitments: CommitmentsV1, _, _, _, _, _, _)) => + commitments.remoteNextCommitInfo match { + case _ if !Commitments.localHasChanges(commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") stay case Right(_) => - Try(Commitments.sendCommit(d.commitments, keyManager)) match { + Try(Commitments.sendCommit(commitments, keyManager)) match { case Success((commitments1, commit)) => log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}") commitments1.localChanges.signed.collect { @@ -629,7 +629,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.info(s"adding paymentHash=${u.paymentHash} cltvExpiry=${u.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") nodeParams.channelsDb.addOrUpdateHtlcInfo(d.channelId, nextCommitNumber, u.paymentHash, u.cltvExpiry) } - if (!Helpers.aboveReserve(d.commitments) && Helpers.aboveReserve(commitments1)) { + if (!Helpers.aboveReserve(commitments) && Helpers.aboveReserve(commitments1)) { // we just went above reserve (can't go below), let's refresh our channel_update to enable/disable it accordingly log.info(s"updating channel_update aboveReserve=${Helpers.aboveReserve(commitments1)}") self ! TickRefreshChannelUpdate @@ -643,7 +643,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - val commitments1 = commisments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + val commitments1 = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) stay using d.copy(commitments = commitments1) } @@ -1306,8 +1306,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) val myCurrentPerCommitmentPoint = d.commitments match { - case c: CommitmentsV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) - case s: SimplifiedCommitment => None + case _: CommitmentsV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) + case _: SimplifiedCommitment => None } val channelReestablish = ChannelReestablish( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 9170644d67..96ecf446d9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -75,6 +75,7 @@ trait Commitments { def announceChannel: Boolean = (channelFlags & 0x01) != 0 // TODO subtract the pushMe value from the balance? + // TODO figure out the type of commitment from the type of this? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, Helpers.hasOptionSimplifiedCommitment(localParams)).amount else 0 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 772e8f774e..e77d87f52e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -245,7 +245,7 @@ object Helpers { if (!localParams.isFunder) { // they are funder, therefore they pay the fee: we need to make sure they can afford it! val toRemoteMsat = remoteSpec.toLocalMsat - val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, Helpers.hasOptionSimplifiedCommitment(localParams)).amount + val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, isSimplifiedCommitment).amount val missing = toRemoteMsat / 1000 - localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(temporaryChannelId, missingSatoshis = -1 * missing, reserveSatoshis = localParams.channelReserveSatoshis, feesSatoshis = fees) From 8cbe347bc3d47c94b3b10552313df6b56f2ed4cd Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 14 Feb 2019 14:32:59 +0100 Subject: [PATCH 16/66] Add scripts spending the push_me outputs --- .../fr/acinq/eclair/transactions/Scripts.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index 5cb561a4ee..980284d89c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -255,7 +255,7 @@ object Scripts { */ def pushMeSimplified(pubkey: PublicKey): List[ScriptElt] = { // @formatter:off - OP_DEPTH :: + OP_DEPTH :: // Puts the number of stack items onto the stack. OP_IF :: OP_PUSHDATA(pubkey) :: OP_CHECKSIG :: OP_ELSE :: @@ -264,6 +264,19 @@ object Scripts { // @formatter:on } + /** + * The script spending 'pushMeSimplified' outputs, signature based version + * @param sig + * @return + */ + def claimPushMeOutputWithKey(sig: BinaryData) = ScriptWitness( sig :: Nil ) + + /** + * The script spending 'pushMeSimplified' outputs, 10 block delay - no signature - version. + * @return + */ + def claimPushMeOutputDelayed() = ScriptWitness(Seq.empty) + /** * This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcReceived script from commit tx) */ From f9d40293a4774de34b10e12405417df11ef9d80e Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 14 Feb 2019 15:34:51 +0100 Subject: [PATCH 17/66] Add to_remote delayed output script, use it in 'Transactions.makeCommitTx' --- .../acinq/eclair/transactions/Scripts.scala | 22 ++++++++++++++++++- .../eclair/transactions/Transactions.scala | 8 +++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index 980284d89c..a7692311e6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -184,6 +184,24 @@ object Scripts { // @formatter:on } + /** + * Output script for the to_remote output of the commitment (must use with option_simplified_commitment) + * https://github.com/lightningnetwork/lightning-rfc/pull/513/files#diff-bfcf64bee684b75d7a670873c3ece6f2R117 + * + * @param toSelfDelay the delay that will encumber the output + * @param remotePubkey the recipient's pubkey + * @return + */ + def toRemoteDelayed(remotePubkey: PublicKey, toSelfDelay: Int) = { + // @formatter:off + encodeNumber(toSelfDelay) :: + OP_CHECKSEQUENCEVERIFY :: + OP_DROP :: + OP_PUSHDATA(remotePubkey) :: + OP_CHECKSIG :: Nil + // @formatter:on + } + /** * This witness script spends a [[toLocalDelayed]] output using a local sig after a delay */ @@ -266,13 +284,15 @@ object Scripts { /** * The script spending 'pushMeSimplified' outputs, signature based version + * * @param sig * @return */ - def claimPushMeOutputWithKey(sig: BinaryData) = ScriptWitness( sig :: Nil ) + def claimPushMeOutputWithKey(sig: BinaryData) = ScriptWitness(sig :: Nil) /** * The script spending 'pushMeSimplified' outputs, 10 block delay - no signature - version. + * * @return */ def claimPushMeOutputDelayed() = ScriptWitness(Seq.empty) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 28fda3d75c..cb3a0e457b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -198,8 +198,12 @@ object Transactions { (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee - pushMeValueTotal) } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway - val toLocalDelayedOutput_opt = if (toLocalAmount >= localDustLimit) Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) else None - val toRemoteOutput_opt = if (toRemoteAmount >= localDustLimit) Some(TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey))) else None + val toLocalDelayedOutput_opt = if (toLocalAmount < localDustLimit) None else Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) + val toRemoteOutput_opt = if (toRemoteAmount < localDustLimit) None else if(isSimplifiedCommitment) { + Some(TxOut(toRemoteAmount, pay2wsh(toRemoteDelayed(remotePaymentPubkey, toLocalDelay)))) + } else { + Some(TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey))) + } val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash))))) From 5e2a86eba2c09e0e3c147cb9cdc767a1d11c17fe Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 15 Feb 2019 14:48:16 +0100 Subject: [PATCH 18/66] WIP introduced CommitmentContext, port 'makeClaimP2WPKHOutputTx' to simplified_commitment --- .../fr/acinq/eclair/channel/Commitments.scala | 18 +++- .../fr/acinq/eclair/channel/Helpers.scala | 24 +++-- .../eclair/transactions/Transactions.scala | 95 ++++++++++++++----- .../transactions/TransactionsSpec.scala | 5 +- 4 files changed, 103 insertions(+), 39 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 96ecf446d9..1487da27d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -82,8 +82,15 @@ trait Commitments { reduced.toRemoteMsat / 1000 - remoteParams.channelReserveSatoshis - fees } + def getContext: CommitmentContext + } +sealed trait CommitmentContext +object ContextCommitmentV1 extends CommitmentContext +object ContextSimplifiedCommitment extends CommitmentContext + + /** * about remoteNextCommitInfo: * we either: @@ -100,7 +107,10 @@ case class CommitmentsV1(localParams: LocalParams, remoteParams: RemoteParams, originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel remoteNextCommitInfo: Either[WaitingForRevocation, Point], commitInput: InputInfo, - remotePerCommitmentSecrets: ShaChain, channelId: BinaryData) extends Commitments + remotePerCommitmentSecrets: ShaChain, channelId: BinaryData) extends Commitments { + + override def getContext: CommitmentContext = ContextCommitmentV1 +} case class SimplifiedCommitment(localParams: LocalParams, remoteParams: RemoteParams, @@ -113,7 +123,11 @@ case class SimplifiedCommitment(localParams: LocalParams, remoteParams: RemotePa remoteNextCommitInfo: Either[WaitingForRevocation, Point], commitInput: InputInfo, remotePerCommitmentSecrets: ShaChain, - channelId: BinaryData) extends Commitments + channelId: BinaryData) extends Commitments { + + + override def getContext: CommitmentContext = ContextSimplifiedCommitment +} object Commitments { /** diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index e77d87f52e..2f353abdb3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -473,12 +473,12 @@ object Helpers { * * @param commitments our commitment data, which include payment preimages * @param remoteCommit the remote commitment data to use to claim outputs (it can be their current or next commitment) - * @param tx the remote commitment transaction that has just been published + * @param commitTx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} - require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") + require(remoteCommit.txid == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx") val isSimplifiedCommitment = commitments match { case _: SimplifiedCommitment => true @@ -486,7 +486,7 @@ object Helpers { } val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(isSimplifiedCommitment, keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) - require(remoteCommitTx.tx.txid == tx.txid, "txid mismatch, cannot recompute the current remote commit tx") + require(remoteCommitTx.tx.txid == commitTx.txid, "txid mismatch, cannot recompute the current remote commit tx") val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) @@ -522,7 +522,7 @@ object Helpers { }) }.toSeq.flatten - claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx).copy( + claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, commitTx).copy( claimHtlcSuccessTxs = txes.toList.collect { case c: ClaimHtlcSuccessTx => c.tx }, claimHtlcTimeoutTxs = txes.toList.collect { case c: ClaimHtlcTimeoutTx => c.tx } ) @@ -530,30 +530,32 @@ object Helpers { /** * - * Claim our Main output only + * Claim our Main output, if the commitment was of type `option_simplified_commitment` we also claim the to_local_pushme output * * @param commitments either our current commitment data in case of usual remote uncooperative closing * or our outdated commitment data in case of data loss protection procedure; in any case it is used only * to get some constant parameters, not commitment data * @param remotePerCommitmentPoint the remote perCommitmentPoint corresponding to this commitment - * @param tx the remote commitment transaction that has just been published + * @param commitTx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { + implicit val commitmentContext = commitments.getContext + val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 val mainTx = generateTx("claim-p2wpkh-output")(Try { - val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis), + val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint) Transactions.addSigs(claimMain, localPubkey, sig) }) RemoteCommitPublished( - commitTx = tx, + commitTx = commitTx, claimMainOutputTx = mainTx.map(_.tx), claimHtlcSuccessTxs = Nil, claimHtlcTimeoutTxs = Nil, @@ -571,6 +573,8 @@ object Helpers { * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { + implicit val commitmentContext = commitments.getContext + import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index cb3a0e457b..9ad39dee24 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -22,9 +22,11 @@ import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, ripemd160} import fr.acinq.bitcoin.Script._ import fr.acinq.bitcoin.SigVersion._ import fr.acinq.bitcoin.{BinaryData, Crypto, LexicographicalOrdering, MilliSatoshi, OutPoint, Protocol, SIGHASH_ALL, Satoshi, Script, ScriptElt, ScriptFlags, ScriptWitness, Transaction, TxIn, TxOut, millisatoshi2satoshi} +import fr.acinq.eclair.channel._ import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.wire.UpdateAddHtlc +import scala.reflect.ClassTag import scala.util.Try /** @@ -68,25 +70,25 @@ object Transactions { /** * When *local* *current* [[CommitTx]] is published: - * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay + * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay -> TODO adjust for option_simplified_commitment * - [[HtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage * - [[ClaimDelayedOutputTx]] spends [[HtlcSuccessTx]] after a delay * - [[HtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout * - [[ClaimDelayedOutputTx]] spends [[HtlcTimeoutTx]] after a delay * * When *remote* *current* [[CommitTx]] is published: - * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] - * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage - * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout + * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] TODO adjust for option_simplified_commitment + * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage TODO adjust for option_simplified_commitment + * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout TODO adjust for option_simplified_commitment * * When *remote* *revoked* [[CommitTx]] is published: - * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] - * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret + * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] TODO adjust for option_simplified_commitment + * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret TODO adjust for option_simplified_commitment * - [[HtlcSuccessTx]] spends htlc-sent outputs of [[CommitTx]] for which they have the preimage (published by remote) * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcSuccessTx]] using the revocation secret (published by local) * - [[HtlcTimeoutTx]] spends htlc-received outputs of [[CommitTx]] after a timeout (published by remote) * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcTimeoutTx]] using the revocation secret (published by local) - * - [[HtlcPenaltyTx]] spends competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] for the same outputs (published by local) + * - [[HtlcPenaltyTx]] spends competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] for the same outputs (published by local) TODO adjust for option_simplified_commitment */ /** @@ -199,7 +201,7 @@ object Transactions { } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway val toLocalDelayedOutput_opt = if (toLocalAmount < localDustLimit) None else Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) - val toRemoteOutput_opt = if (toRemoteAmount < localDustLimit) None else if(isSimplifiedCommitment) { + val toRemoteOutput_opt = if (toRemoteAmount < localDustLimit) None else if (isSimplifiedCommitment) { Some(TxOut(toRemoteAmount, pay2wsh(toRemoteDelayed(remotePaymentPubkey, toLocalDelay)))) } else { Some(TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey))) @@ -322,30 +324,59 @@ object Transactions { ClaimHtlcTimeoutTx(input, tx1) } - def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long): ClaimP2WPKHOutputTx = { - val redeemScript = Script.pay2pkh(localPaymentPubkey) - val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) - val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) - val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) + def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): ClaimP2WPKHOutputTx = { + + val claimTx = commitmentContext match { + case ContextCommitmentV1 => + val redeemScript = Script.pay2pkh(localPaymentPubkey) + val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) + val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) + val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) + + // unsigned tx + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, + txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, + lockTime = 0) + + // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey + Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)) + + case ContextSimplifiedCommitment => + val redeemScript = Script.pay2pkh(localPaymentPubkey) + val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) + val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) + val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) + + val pushMeOutputIndex = findPushMeOutputIndex(localPaymentPubkey, delayedOutputTx).getOrElse { + throw new IllegalArgumentException("Could not find the push me output") + } + val pushMeOutputRedeemScript = Script.pay2wsh(Scripts.pushMeSimplified(localPaymentPubkey)) + val pushMeInput = InputInfo(OutPoint(delayedOutputTx, pushMeOutputIndex), delayedOutputTx.txOut(pushMeOutputIndex), pushMeOutputRedeemScript) + + // unsigned tx + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: TxIn(pushMeInput.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, + txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, + lockTime = 0) + + // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey + val signed1 = Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)).tx + Transactions.addSigs(ClaimP2WPKHOutputTx(pushMeInput, signed1), BinaryData("00" * 33), BinaryData("00" * 73)) + } - // unsigned tx - val tx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) - // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey - val weight = Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)).tx.weight() - val fee = weight2fee(feeratePerKw, weight) + val fee = weight2fee(feeratePerKw, claimTx.tx.weight()) - val amount = input.txOut.amount - fee + val amount = claimTx.input.txOut.amount - fee if (amount < localDustLimit) { throw AmountBelowDustLimit } - val tx1 = tx.copy(txOut = tx.txOut(0).copy(amount = amount) :: Nil) - ClaimP2WPKHOutputTx(input, tx1) + val tx1 = claimTx.tx.copy(txOut = claimTx.tx.txOut(0).copy(amount = amount) :: Nil) // even in case of simplified commitment the main output is at index 0 + ClaimP2WPKHOutputTx(claimTx.input, tx1) } def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long): ClaimDelayedOutputTx = { @@ -400,6 +431,7 @@ object Transactions { ClaimDelayedOutputPenaltyTx(input, tx1) } + // TODO adjust for option_simplified_commitment -> sweep pushme outputs def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long): MainPenaltyTx = { val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) @@ -429,6 +461,7 @@ object Transactions { /** * We already have the redeemScript, no need to build it */ + // TODO adjust for option_simplified_commitment ? def makeHtlcPenaltyTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], redeemScript: BinaryData, localDustLimit: Satoshi, localFinalScriptPubKey: BinaryData, feeratePerKw: Long): HtlcPenaltyTx = { val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed, amount_opt = None) @@ -485,6 +518,18 @@ object Transactions { } } + /** + * Finds the output index of our push me output (see option_simplified_commitment) + * + * @param pubkey + * @param simplifiedCommitTx + * @return + */ + def findPushMeOutputIndex(pubkey: PublicKey, simplifiedCommitTx: Transaction): Option[Int] = { + simplifiedCommitTx.txOut.zipWithIndex.find { case (txOut, outputIndex) => + txOut.publicKeyScript == Script.write(pushMeSimplified(pubkey)) + }.map(_._2) + } def sign(tx: Transaction, inputIndex: Int, redeemScript: BinaryData, amount: Satoshi, key: PrivateKey): BinaryData = { Transaction.signInput(tx, inputIndex, redeemScript, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0, key) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index ad8ad8c3c2..35aeeaf190 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -21,6 +21,7 @@ import java.nio.ByteOrder import fr.acinq.bitcoin.Crypto.{PrivateKey, ripemd160, sha256} import fr.acinq.bitcoin.Script.{pay2wpkh, pay2wsh, write} import fr.acinq.bitcoin._ +import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.transactions.Scripts.{htlcOffered, htlcReceived, toLocalDelayed} import fr.acinq.eclair.transactions.Transactions.{addSigs, _} @@ -91,7 +92,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimP2WPKHOutputTx val pubKeyScript = write(pay2wpkh(localPaymentPriv.publicKey)) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimP2WPKHOutputTx, localPaymentPriv.publicKey, "bb" * 73).tx) assert(claimP2WPKHOutputWeight == weight) @@ -281,7 +282,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends main output - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx.tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx.tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv) val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) From 7e3ae467301b182948cc4e90a5cead9cc1d9fde4 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 15 Feb 2019 15:54:36 +0100 Subject: [PATCH 19/66] WIP 'makeCommitTx' to simplified_commitment and Context --- .../fr/acinq/eclair/channel/Commitments.scala | 74 ++++++++------- .../fr/acinq/eclair/channel/Helpers.scala | 18 ++-- .../eclair/transactions/Transactions.scala | 90 ++++++++++++------- .../eclair/transactions/TestVectorsSpec.scala | 9 +- .../transactions/TransactionsSpec.scala | 6 +- 5 files changed, 115 insertions(+), 82 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 1487da27d1..8de8c3c478 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -78,18 +78,20 @@ trait Commitments { // TODO figure out the type of commitment from the type of this? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, Helpers.hasOptionSimplifiedCommitment(localParams)).amount else 0 + val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced)(commitmentContext = getContext).amount else 0 reduced.toRemoteMsat / 1000 - remoteParams.channelReserveSatoshis - fees } + // get the context for this commitment def getContext: CommitmentContext } +// @formatter: off sealed trait CommitmentContext object ContextCommitmentV1 extends CommitmentContext object ContextSimplifiedCommitment extends CommitmentContext - +// @formatter: on /** * about remoteNextCommitInfo: @@ -154,10 +156,7 @@ object Commitments { * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc) */ def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC, origin: Origin): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { - val isSimplifiedCommitment = commitments match { - case c: CommitmentsV1 => false - case s: SimplifiedCommitment => true - } + implicit val commitmentContex = commitments.getContext if (cmd.paymentHash.size != 32) { return Left(InvalidPaymentHash(commitments.channelId)) @@ -184,7 +183,7 @@ object Commitments { // we increment the local htlc index and add an entry to the origins map val commitments1 = addLocalProposal(commitments, add) match { case c: CommitmentsV1 => c.copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) - case _: SimplifiedCommitment => ??? + case _: SimplifiedCommitment => throw new NotImplementedError } // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation val remoteCommit1 = commitments1.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments1.remoteCommit) @@ -204,7 +203,7 @@ object Commitments { // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, isSimplifiedCommitment).amount else 0 + val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount else 0 val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { return Left(InsufficientFunds(commitments.channelId, amountMsat = cmd.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.remoteParams.channelReserveSatoshis, feesSatoshis = fees)) @@ -214,10 +213,7 @@ object Commitments { } def receiveAdd(commitments: Commitments, add: UpdateAddHtlc): Commitments = { - val isSimplifiedCommitment = commitments match { - case c: CommitmentsV1 => false - case s: SimplifiedCommitment => true - } + implicit val commitmentContext = commitments.getContext if (add.id != commitments.remoteNextHtlcId) { throw UnexpectedHtlcId(commitments.channelId, expected = commitments.remoteNextHtlcId, actual = add.id) @@ -249,7 +245,7 @@ object Commitments { } // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced, isSimplifiedCommitment).amount + val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced).amount val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw InsufficientFunds(commitments.channelId, amountMsat = add.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -363,18 +359,24 @@ object Commitments { if (!commitments.localParams.isFunder) { throw FundeeCannotSendUpdateFee(commitments.channelId) } + + if(commitments.getContext == ContextSimplifiedCommitment){ + throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") + } + + implicit val commitmentContext = commitments.getContext + // let's compute the current commitment *as seen by them* with this change taken into account val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw) // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) - case _: SimplifiedCommitment => ??? } val reduced = CommitmentSpec.reduce(commitments1.remoteCommit.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, simplifiedCommitment = false).amount // we update the fee only in NON simplified commitment + val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount // we update the fee only in NON simplified commitment val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(commitments.channelId, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -397,6 +399,12 @@ object Commitments { throw FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw) } + if(commitments.getContext == ContextSimplifiedCommitment){ + throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") + } + + implicit val commitmentContext = commitments.getContext + // NB: we check that the funder can afford this new fee even if spec allows to do it at next signature // It is easier to do it here because under certain (race) conditions spec allows a lower-than-normal fee to be paid, // and it would be tricky to check if the conditions are met at signing @@ -406,12 +414,11 @@ object Commitments { // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) - case _: SimplifiedCommitment => throw new IllegalStateException("Should not update the fee on simplified_commitment") } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, simplifiedCommitment = false).amount // we update the fee only in NON simplified + val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount // we update the fee only in NON simplified val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(commitments.channelId, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -433,6 +440,8 @@ object Commitments { def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index)) def sendCommit(commitments: Commitments, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, CommitSig) = { + implicit val commitmentContext = commitments.getContext + import commitments._ commitments match { @@ -444,7 +453,7 @@ object Commitments { case Right(remoteNextPerCommitmentPoint) => // remote commitment will includes all local changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(false, keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) + val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) @@ -474,6 +483,8 @@ object Commitments { } def receiveCommit(commitments: Commitments, commit: CommitSig, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = { + implicit val commitmentContext = commitments.getContext + import commitments._ // they sent us a signature for *their* view of *our* next commit tx // so in terms of rev.hashes and indexes we have: @@ -487,18 +498,13 @@ object Commitments { if (!remoteHasChanges(commitments)) throw CannotSignWithoutChanges(commitments.channelId) - val isSimplifiedCommitment = commitments match { - case _: SimplifiedCommitment => true - case _: CommitmentsV1 => false - } - // check that their signature is valid // signatures are now optional in the commit message, and will be sent only if the other party is actually // receiving money i.e its commit tx has one output for them val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) - val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(isSimplifiedCommitment, keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) + val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) @@ -601,32 +607,32 @@ object Commitments { } } - def makeLocalTxs(isSimplifiedCommitment: Boolean, keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) - val remotePaymentPubkey = isSimplifiedCommitment match { - case true => PublicKey(remoteParams.paymentBasepoint) - case false => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) + val remotePaymentPubkey = commitmentContext match { + case ContextSimplifiedCommitment => PublicKey(remoteParams.paymentBasepoint) + case ContextCommitmentV1 => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) } val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(isSimplifiedCommitment, commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(isSimplifiedCommitment: Boolean, keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = isSimplifiedCommitment match { - case true => keyManager.paymentPoint(localParams.channelKeyPath).publicKey - case false => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + val localPaymentPubkey = commitmentContext match { + case ContextSimplifiedCommitment => keyManager.paymentPoint(localParams.channelKeyPath).publicKey + case ContextCommitmentV1 => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) } val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(isSimplifiedCommitment, commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 2f353abdb3..9acdbd3a80 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -234,7 +234,10 @@ object Helpers { * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) */ def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { - val isSimplifiedCommitment = Helpers.hasOptionSimplifiedCommitment(localParams) + implicit val commitmentContext = Helpers.hasOptionSimplifiedCommitment(localParams) match { + case true => ContextSimplifiedCommitment + case false => ContextCommitmentV1 + } val toLocalMsat = if (localParams.isFunder) fundingSatoshis * 1000 - pushMsat else pushMsat val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingSatoshis * 1000 - pushMsat @@ -245,7 +248,7 @@ object Helpers { if (!localParams.isFunder) { // they are funder, therefore they pay the fee: we need to make sure they can afford it! val toRemoteMsat = remoteSpec.toLocalMsat - val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, isSimplifiedCommitment).amount + val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec).amount val missing = toRemoteMsat / 1000 - localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(temporaryChannelId, missingSatoshis = -1 * missing, reserveSatoshis = localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -254,8 +257,8 @@ object Helpers { val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) - val (localCommitTx, _, _) = Commitments.makeLocalTxs(isSimplifiedCommitment, keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(isSimplifiedCommitment, keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) + val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) (localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -480,12 +483,9 @@ object Helpers { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx") - val isSimplifiedCommitment = commitments match { - case _: SimplifiedCommitment => true - case _: CommitmentsV1 => false - } + implicit val commitmentContext = commitments.getContext - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(isSimplifiedCommitment, keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == commitTx.txid, "txid mismatch, cannot recompute the current remote commit tx") val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 9ad39dee24..8b8854be51 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -137,11 +137,13 @@ object Transactions { .toSeq } - def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec, simplifiedCommitment: Boolean): Satoshi = { + def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): Satoshi = { val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec) val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec) - val weight = (if (simplifiedCommitment) simplifiedCommitWeight else commitWeight) + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size) - weight2fee(if (simplifiedCommitment) simplifiedFeerateKw else spec.feeratePerKw, weight) + commitmentContext match { + case ContextCommitmentV1 => weight2fee(spec.feeratePerKw , commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) + case ContextSimplifiedCommitment => weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) // simplified commitment has an hardcoded feerate + } } /** @@ -190,40 +192,66 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(isSimplifiedCommitment: Boolean, commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { - val commitFee = commitTxFee(localDustLimit, spec, isSimplifiedCommitment) - val pushMeValueTotal = if (isSimplifiedCommitment) pushMeValue * 2 else Satoshi(0) // funder pays the total amount of pushme outputs + def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): CommitTx = commitmentContext match { - val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { - (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee - pushMeValueTotal, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) - } else { - (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee - pushMeValueTotal) - } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway + case ContextCommitmentV1 => + val commitFee = commitTxFee(localDustLimit, spec) - val toLocalDelayedOutput_opt = if (toLocalAmount < localDustLimit) None else Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) - val toRemoteOutput_opt = if (toRemoteAmount < localDustLimit) None else if (isSimplifiedCommitment) { - Some(TxOut(toRemoteAmount, pay2wsh(toRemoteDelayed(remotePaymentPubkey, toLocalDelay)))) - } else { - Some(TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey))) - } + val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) + } else { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee) + } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway - val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec) - .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash))))) - val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) - .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) + val toLocalDelayedOutput_opt = if (toLocalAmount >= localDustLimit) Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) else None + val toRemoteOutput_opt = if (toRemoteAmount >= localDustLimit) Some(TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey))) else None - val toLocalPushMe_opt = if (isSimplifiedCommitment && toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None - val toRemotePushMe_opt = if (isSimplifiedCommitment && toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remotePaymentPubkey)))) else None + val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec) + .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash))))) + val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) + .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) - val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) - val (sequence, locktime) = encodeTxNumber(txnumber) + val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) + val (sequence, locktime) = encodeTxNumber(txnumber) + + val tx = Transaction( + version = 2, + txIn = TxIn(commitTxInput.outPoint, Array.emptyByteArray, sequence = sequence) :: Nil, + txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs, + lockTime = locktime) + CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) + + case ContextSimplifiedCommitment => + val commitFee = commitTxFee(localDustLimit, spec) + val pushMeValueTotal = pushMeValue * 2 // funder pays the total amount of pushme outputs + + val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee - pushMeValueTotal, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) + } else { + (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)), millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat)) - commitFee - pushMeValueTotal) + } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway + + val toLocalDelayedOutput_opt = if (toLocalAmount < localDustLimit) None else Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) + val toRemoteOutput_opt = if (toRemoteAmount < localDustLimit) None else Some(TxOut(toRemoteAmount, pay2wsh(toRemoteDelayed(remotePaymentPubkey, toLocalDelay)))) + + val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec) + .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash))))) + val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) + .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) + + val toLocalPushMe_opt = if (toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None + val toRemotePushMe_opt = if (toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remotePaymentPubkey)))) else None + + val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) + val (sequence, locktime) = encodeTxNumber(txnumber) + + val tx = Transaction( + version = 2, + txIn = TxIn(commitTxInput.outPoint, Array.emptyByteArray, sequence = sequence) :: Nil, + txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs ++ toLocalPushMe_opt.toSeq ++ toRemotePushMe_opt.toSeq, + lockTime = locktime) + CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) - val tx = Transaction( - version = 2, - txIn = TxIn(commitTxInput.outPoint, Array.emptyByteArray, sequence = sequence) :: Nil, - txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs ++ toLocalPushMe_opt.toSeq ++ toRemotePushMe_opt.toSeq, - lockTime = locktime) - CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) } def makeHtlcTimeoutTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcTimeoutTx = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 9373a562f2..4faba5dd6d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar} import fr.acinq.bitcoin._ +import fr.acinq.eclair.channel.ContextCommitmentV1 import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.Generators import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo} @@ -182,14 +183,13 @@ class TestVectorsSpec extends FunSuite with Logging { val commitTx = { val tx = Transactions.makeCommitTx( - isSimplifiedCommitment = false, commitmentInput, Local.commitTxNumber, Local.payment_basepoint, Remote.payment_basepoint, true, Local.dustLimit, Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key - spec) + spec)(ContextCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey) val remote_sig = Transactions.sign(tx, Remote.funding_privkey) @@ -197,7 +197,7 @@ class TestVectorsSpec extends FunSuite with Logging { Transactions.addSigs(tx, Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig) } - val baseFee = Transactions.commitTxFee(Local.dustLimit, spec, simplifiedCommitment = false) + val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)(ContextCommitmentV1) logger.info(s"# base commitment transaction fee = ${baseFee.toLong}") val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}") @@ -213,14 +213,13 @@ class TestVectorsSpec extends FunSuite with Logging { { val tx = Transactions.makeCommitTx( - isSimplifiedCommitment = false, commitmentInput, Local.commitTxNumber, Local.payment_basepoint, Remote.payment_basepoint, true, Local.dustLimit, Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key - spec) + spec)(ContextCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey) logger.info(s"# local_signature = ${toHexString(local_sig.dropRight(1))}") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 35aeeaf190..d2b4fdb6cf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -69,7 +69,7 @@ class TransactionsSpec extends FunSuite with Logging { DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(800000).amount, Hash.Zeroes, 551, BinaryData.empty)) ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0) - val fee = Transactions.commitTxFee(Satoshi(546), spec, simplifiedCommitment = false) + val fee = Transactions.commitTxFee(Satoshi(546), spec)(ContextCommitmentV1) assert(fee == Satoshi(5340)) //TODO add case for simplified commitment @@ -206,7 +206,7 @@ class TransactionsSpec extends FunSuite with Logging { val commitTxNumber = 0x404142434445L val commitTx = { - val txinfo = makeCommitTx(isSimplifiedCommitment = false, commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) + val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(ContextCommitmentV1) val localSig = Transactions.sign(txinfo, localPaymentPriv) val remoteSig = Transactions.sign(txinfo, remotePaymentPriv) Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) @@ -358,7 +358,7 @@ class TransactionsSpec extends FunSuite with Logging { tests.foreach(test => { logger.info(s"running BOLT 2 test: '${test.name}'") - val fee = commitTxFee(test.dustLimit, test.spec, simplifiedCommitment = false) + val fee = commitTxFee(test.dustLimit, test.spec)(ContextCommitmentV1) assert(fee === test.expectedFee) }) } From a5f660e9957e07836db92c437ef4fe707c9cb174 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 15 Feb 2019 16:07:25 +0100 Subject: [PATCH 20/66] WIP port 'makeClaimDelayedOutputTx' to simplified/context --- .../fr/acinq/eclair/channel/Helpers.scala | 1 + .../eclair/transactions/Transactions.scala | 41 +++++++++++-------- .../transactions/TransactionsSpec.scala | 10 ++--- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 9acdbd3a80..8dcc20e9d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -410,6 +410,7 @@ object Helpers { def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") + implicit val commitmentContext = commitments.getContext val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 8b8854be51..2dfd31b038 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -82,7 +82,7 @@ object Transactions { * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout TODO adjust for option_simplified_commitment * * When *remote* *revoked* [[CommitTx]] is published: - * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] TODO adjust for option_simplified_commitment + * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret TODO adjust for option_simplified_commitment * - [[HtlcSuccessTx]] spends htlc-sent outputs of [[CommitTx]] for which they have the preimage (published by remote) * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcSuccessTx]] using the revocation secret (published by local) @@ -407,30 +407,39 @@ object Transactions { ClaimP2WPKHOutputTx(claimTx.input, tx1) } - def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long): ClaimDelayedOutputTx = { - val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) - val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) - val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) + def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): ClaimDelayedOutputTx = { + + val claimTx = commitmentContext match { + case ContextCommitmentV1 => + val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) + val pubkeyScript = write(pay2wsh(redeemScript)) + val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) + val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) + + // unsigned transaction + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, Array.emptyByteArray, toLocalDelay) :: Nil, + txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, + lockTime = 0) + + ClaimDelayedOutputTx(input, tx) + + case ContextSimplifiedCommitment => throw new NotImplementedError("makeClaimDelayedOutputTx with option_simplified_commitment") + } - // unsigned transaction - val tx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, toLocalDelay) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = Transactions.addSigs(ClaimDelayedOutputTx(input, tx), BinaryData("00" * 73)).tx.weight() + val weight = Transactions.addSigs(claimTx, BinaryData("00" * 73)).tx.weight() val fee = weight2fee(feeratePerKw, weight) - val amount = input.txOut.amount - fee + val amount = claimTx.input.txOut.amount - fee if (amount < localDustLimit) { throw AmountBelowDustLimit } - val tx1 = tx.copy(txOut = tx.txOut(0).copy(amount = amount) :: Nil) - ClaimDelayedOutputTx(input, tx1) + val tx1 = claimTx.tx.copy(txOut = claimTx.tx.txOut(0).copy(amount = amount) :: Nil) + ClaimDelayedOutputTx(claimTx.input, tx1) } def makeClaimDelayedOutputPenaltyTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long): ClaimDelayedOutputPenaltyTx = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index d2b4fdb6cf..4b06139144 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -104,7 +104,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake htlcSuccessOrTimeoutTx tx, containing only the output that will be spent by the ClaimDelayedOutputTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val htlcSuccessOrTimeoutTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimHtlcDelayedTx = makeClaimDelayedOutputTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayedTx = makeClaimDelayedOutputTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimHtlcDelayedTx, "bb" * 73).tx) assert(claimHtlcDelayedWeight == weight) @@ -236,13 +236,13 @@ class TransactionsSpec extends FunSuite with Logging { { // local spends delayed output of htlc1 timeout tx - val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv) val signedTx = addSigs(claimHtlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit intercept[RuntimeException] { - makeClaimDelayedOutputTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + makeClaimDelayedOutputTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) } } @@ -270,13 +270,13 @@ class TransactionsSpec extends FunSuite with Logging { { // local spends delayed output of htlc2 success tx - val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv) val signedTx = addSigs(claimHtlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc4 timeout tx because it is below the dust limit intercept[RuntimeException] { - makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) } } From f503ad063b10f570e0a6df39cc2d0f451a599e0a Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 15 Feb 2019 16:35:38 +0100 Subject: [PATCH 21/66] WIP port 'makeMainPenaltyTx' to simplified/context --- .../eclair/transactions/Transactions.scala | 46 ++++++++++--------- .../transactions/TransactionsSpec.scala | 2 +- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 2dfd31b038..49734c0934 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -70,14 +70,14 @@ object Transactions { /** * When *local* *current* [[CommitTx]] is published: - * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay -> TODO adjust for option_simplified_commitment + * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay -> * - [[HtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage * - [[ClaimDelayedOutputTx]] spends [[HtlcSuccessTx]] after a delay * - [[HtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout * - [[ClaimDelayedOutputTx]] spends [[HtlcTimeoutTx]] after a delay * * When *remote* *current* [[CommitTx]] is published: - * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] TODO adjust for option_simplified_commitment + * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage TODO adjust for option_simplified_commitment * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout TODO adjust for option_simplified_commitment * @@ -469,30 +469,32 @@ object Transactions { } // TODO adjust for option_simplified_commitment -> sweep pushme outputs - def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long): MainPenaltyTx = { - val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) - val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) + def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): MainPenaltyTx = commitmentContext match { + case ContextSimplifiedCommitment => throw new NotImplementedError("makeMainPenaltyTx with option_simplified_commitment") + case ContextCommitmentV1 => + val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) + val pubkeyScript = write(pay2wsh(redeemScript)) + val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned transaction - val tx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + // unsigned transaction + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil, + txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, + lockTime = 0) - // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = Transactions.addSigs(MainPenaltyTx(input, tx), BinaryData("00" * 73)).tx.weight() - val fee = weight2fee(feeratePerKw, weight) + // compute weight with a dummy 73 bytes signature (the largest you can get) + val weight = Transactions.addSigs(MainPenaltyTx(input, tx), BinaryData("00" * 73)).tx.weight() + val fee = weight2fee(feeratePerKw, weight) - val amount = input.txOut.amount - fee - if (amount < localDustLimit) { - throw AmountBelowDustLimit - } + val amount = input.txOut.amount - fee + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } - val tx1 = tx.copy(txOut = tx.txOut(0).copy(amount = amount) :: Nil) - MainPenaltyTx(input, tx1) + val tx1 = tx.copy(txOut = tx.txOut(0).copy(amount = amount) :: Nil) + MainPenaltyTx(input, tx1) } /** diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 4b06139144..5fc37b0e33 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -116,7 +116,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the MainPenaltyTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val mainPenaltyTx = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw) + val mainPenaltyTx = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw)(ContextCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(mainPenaltyTx, "bb" * 73).tx) assert(mainPenaltyWeight == weight) From ba277676c825f2121078c43be96fd7e2b9b7f54a Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 15 Feb 2019 16:44:46 +0100 Subject: [PATCH 22/66] Comment cleanup --- .../fr/acinq/eclair/transactions/Transactions.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 49734c0934..4f21a7da5e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -70,7 +70,7 @@ object Transactions { /** * When *local* *current* [[CommitTx]] is published: - * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay -> + * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay * - [[HtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage * - [[ClaimDelayedOutputTx]] spends [[HtlcSuccessTx]] after a delay * - [[HtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout @@ -78,17 +78,17 @@ object Transactions { * * When *remote* *current* [[CommitTx]] is published: * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] - * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage TODO adjust for option_simplified_commitment - * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout TODO adjust for option_simplified_commitment + * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage + * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout * * When *remote* *revoked* [[CommitTx]] is published: * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] - * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret TODO adjust for option_simplified_commitment + * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret * - [[HtlcSuccessTx]] spends htlc-sent outputs of [[CommitTx]] for which they have the preimage (published by remote) * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcSuccessTx]] using the revocation secret (published by local) * - [[HtlcTimeoutTx]] spends htlc-received outputs of [[CommitTx]] after a timeout (published by remote) * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcTimeoutTx]] using the revocation secret (published by local) - * - [[HtlcPenaltyTx]] spends competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] for the same outputs (published by local) TODO adjust for option_simplified_commitment + * - [[HtlcPenaltyTx]] spends competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] for the same outputs (published by local) */ /** From a76e07c55975da30d74cf6073e120f0b803c62bc Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 15 Feb 2019 17:58:10 +0100 Subject: [PATCH 23/66] Add feature test for option_simplified_commitment --- .../src/test/scala/fr/acinq/eclair/FeaturesSpec.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index c629470ffd..226c6871d6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -42,6 +42,12 @@ class FeaturesSpec extends FunSuite { assert(areSupported(features) && hasFeature(features, OPTION_DATA_LOSS_PROTECT_OPTIONAL) && hasFeature(features, INITIAL_ROUTING_SYNC_BIT_OPTIONAL)) } + test("'option_simplified_commitment' feature") { + val features = "0200" + assert(areSupported(features) && hasFeature(features, OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL)) + } + + test("features compatibility") { assert(areSupported(Protocol.writeUInt64(1l << INITIAL_ROUTING_SYNC_BIT_OPTIONAL, ByteOrder.BIG_ENDIAN))) assert(areSupported(Protocol.writeUInt64(1L << OPTION_DATA_LOSS_PROTECT_MANDATORY, ByteOrder.BIG_ENDIAN))) From 4009ff573d400dbd66a54146bf9f51cd804f9882 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 10:50:17 +0100 Subject: [PATCH 24/66] Use p2wsh when looking for the pushMeOutput index --- .../main/scala/fr/acinq/eclair/transactions/Transactions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 4f21a7da5e..6df65590a7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -566,7 +566,7 @@ object Transactions { */ def findPushMeOutputIndex(pubkey: PublicKey, simplifiedCommitTx: Transaction): Option[Int] = { simplifiedCommitTx.txOut.zipWithIndex.find { case (txOut, outputIndex) => - txOut.publicKeyScript == Script.write(pushMeSimplified(pubkey)) + txOut.publicKeyScript == Script.write(pay2wsh(pushMeSimplified(pubkey))) }.map(_._2) } From 88c780cd9a281e693909ce1e9e9278966caac59a Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 11:28:43 +0100 Subject: [PATCH 25/66] Add more thorough test for detecting option_simplified_commitment --- .../fr/acinq/eclair/channel/Helpers.scala | 12 ++++++-- .../scala/fr/acinq/eclair/FeaturesSpec.scala | 30 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 8dcc20e9d1..55bc393d38 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -234,7 +234,7 @@ object Helpers { * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) */ def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { - implicit val commitmentContext = Helpers.hasOptionSimplifiedCommitment(localParams) match { + implicit val commitmentContext = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { case true => ContextSimplifiedCommitment case false => ContextCommitmentV1 } @@ -322,8 +322,16 @@ object Helpers { } } - def hasOptionSimplifiedCommitment[T <: ParamsWithFeatures](params: T) = Features.hasFeature(params.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + def canUseSimplifiedCommitment[T <: ParamsWithFeatures](local: T, remote: T) = { + val localHasOptionalSupport = Features.hasFeature(local.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + val localHasMandatorySupport = Features.hasFeature(local.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) + val remoteHasOptionalSupport = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + val remoteHasMandatorySupport = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) + (remoteHasMandatorySupport && (localHasMandatorySupport || localHasOptionalSupport)) || + (localHasMandatorySupport && (remoteHasMandatorySupport || remoteHasOptionalSupport)) || + (localHasOptionalSupport && remoteHasOptionalSupport) + } object Closing { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index 226c6871d6..b026a3f165 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -18,8 +18,9 @@ package fr.acinq.eclair import java.nio.ByteOrder -import fr.acinq.bitcoin.Protocol +import fr.acinq.bitcoin.{BinaryData, Protocol} import fr.acinq.eclair.Features._ +import fr.acinq.eclair.channel.{Helpers, LocalParams, ParamsWithFeatures} import org.scalatest.FunSuite /** @@ -47,6 +48,33 @@ class FeaturesSpec extends FunSuite { assert(areSupported(features) && hasFeature(features, OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL)) } + test("Helpers should correctly detect if the peers negotiated 'option_simplified_commitment'") { + + val optionalSupport = BinaryData("0200") + val mandatorySupport = BinaryData("0100") + + val channelParamNoSupport = new {} with ParamsWithFeatures { + override val globalFeatures: BinaryData = BinaryData.empty + override val localFeatures: BinaryData = BinaryData.empty + } + + val channelParamOptSupport = new {} with ParamsWithFeatures { + override val globalFeatures: BinaryData = BinaryData.empty + override val localFeatures: BinaryData = optionalSupport + } + + val channelParamMandatorySupport = new {} with ParamsWithFeatures { + override val globalFeatures: BinaryData = BinaryData.empty + override val localFeatures: BinaryData = mandatorySupport + } + + assert(Helpers.canUseSimplifiedCommitment(local = channelParamOptSupport, remote = channelParamOptSupport) == true) + assert(Helpers.canUseSimplifiedCommitment(local = channelParamOptSupport, remote = channelParamNoSupport) == false) + assert(Helpers.canUseSimplifiedCommitment(local = channelParamOptSupport, remote = channelParamMandatorySupport) == true) + assert(Helpers.canUseSimplifiedCommitment(local = channelParamMandatorySupport, remote = channelParamMandatorySupport) == true) + assert(Helpers.canUseSimplifiedCommitment(local = channelParamNoSupport, remote = channelParamMandatorySupport) == false) + } + test("features compatibility") { assert(areSupported(Protocol.writeUInt64(1l << INITIAL_ROUTING_SYNC_BIT_OPTIONAL, ByteOrder.BIG_ENDIAN))) From 5d2d3a2177d28beb49393e3d874473f7a832503a Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 13:46:58 +0100 Subject: [PATCH 26/66] Seal the commitments trait --- .../src/main/scala/fr/acinq/eclair/channel/Commitments.scala | 5 +++-- .../src/main/scala/fr/acinq/eclair/channel/Helpers.scala | 1 + .../scala/fr/acinq/eclair/transactions/Transactions.scala | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 8de8c3c478..09fcd0419e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -42,8 +42,7 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, rem case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long, reSignAsap: Boolean = false) // @formatter:on -//TODO seal this! -trait Commitments { +sealed trait Commitments { val localParams: LocalParams val remoteParams: RemoteParams @@ -371,6 +370,7 @@ object Commitments { // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) + case _: SimplifiedCommitment => throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") } val reduced = CommitmentSpec.reduce(commitments1.remoteCommit.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed) @@ -414,6 +414,7 @@ object Commitments { // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) + case _: SimplifiedCommitment => throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 55bc393d38..1970807c42 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -239,6 +239,7 @@ object Helpers { case false => ContextCommitmentV1 } + // TODO adjust for option_simplified_commitment val toLocalMsat = if (localParams.isFunder) fundingSatoshis * 1000 - pushMsat else pushMsat val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingSatoshis * 1000 - pushMsat diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 6df65590a7..09b652045b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -526,6 +526,7 @@ object Transactions { HtlcPenaltyTx(input, tx1) } + // TODO adjust for option_simplified_commitment def makeClosingTx(commitTxInput: InputInfo, localScriptPubKey: BinaryData, remoteScriptPubKey: BinaryData, localIsFunder: Boolean, dustLimit: Satoshi, closingFee: Satoshi, spec: CommitmentSpec): ClosingTx = { require(spec.htlcs.isEmpty, "there shouldn't be any pending htlcs") From 9b320a78475d6c34097c4032425b2bc744929d30 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 15:24:10 +0100 Subject: [PATCH 27/66] Create correct commitment type on receiving 'funding_created', add test --- .../fr/acinq/eclair/channel/Channel.scala | 23 +++++++++---- .../b/WaitForFundingCreatedStateSpec.scala | 34 +++++++++++++++---- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 460b9785b7..7e71a4c33d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -357,14 +357,23 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu channelId = channelId, signature = localSigOfRemoteTx ) - val commitments = CommitmentsV1(localParams, remoteParams, channelFlags, - LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), - LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), - localNextHtlcId = 0L, remoteNextHtlcId = 0L, - originChannels = Map.empty, - remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, - commitInput, ShaChain.init, channelId = channelId) + val commitments = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { + case true => SimplifiedCommitment(localParams, remoteParams, channelFlags, + LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), + LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 0L, remoteNextHtlcId = 0L, + originChannels = Map.empty, + remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, + commitInput, ShaChain.init, channelId = channelId) + case false => CommitmentsV1(localParams, remoteParams, channelFlags, + LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), + LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 0L, remoteNextHtlcId = 0L, + originChannels = Map.empty, + remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, + commitInput, ShaChain.init, channelId = channelId) + } context.parent ! ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId) // we notify the peer asap so it knows how to route messages context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 8d6832ce34..004902d6e0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.channel.states.b import akka.testkit.{TestFSMRef, TestProbe} +import fr.acinq.bitcoin.BinaryData import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel._ @@ -34,7 +35,7 @@ import scala.concurrent.duration._ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHelperMethods { - case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe) + case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { val setup = init() @@ -44,17 +45,22 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel } else { (TestConstants.fundingSatoshis, TestConstants.pushMsat) } - val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) - val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) + + val aliceLocalFeatures = if(test.tags.contains("simplified_commitment")) BinaryData("0200") else Alice.channelParams.localFeatures + val bobLocalFeatures = if(test.tags.contains("simplified_commitment")) BinaryData("0200") else Bob.channelParams.localFeatures + + val aliceInit = Init(Alice.channelParams.globalFeatures, aliceLocalFeatures) + val bobInit = Init(Bob.channelParams.globalFeatures, bobLocalFeatures) + within(30 seconds) { - alice ! INPUT_INIT_FUNDER("00" * 32, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) - bob ! INPUT_INIT_FUNDEE("00" * 32, Bob.channelParams, bob2alice.ref, aliceInit) + alice ! INPUT_INIT_FUNDER("00" * 32, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams.copy(localFeatures = aliceLocalFeatures), alice2bob.ref, bobInit, ChannelFlags.Empty) + bob ! INPUT_INIT_FUNDEE("00" * 32, Bob.channelParams.copy(localFeatures = bobLocalFeatures), bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain))) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, bob2blockchain))) } } @@ -92,4 +98,20 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel awaitCond(bob.stateName == CLOSED) } + test("recv FundingCreated (option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + + alice2bob.expectMsgType[FundingCreated] + alice2bob.forward(bob) + + awaitCond({ + bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == ContextSimplifiedCommitment + }) + + bob2alice.expectMsgType[FundingSigned] + bob2blockchain.expectMsgType[WatchSpent] + bob2blockchain.expectMsgType[WatchConfirmed] + } + } From cff7d3ab94a27bd065eca4e01aa29f51ca1bb008 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 15:27:22 +0100 Subject: [PATCH 28/66] Reorg test --- .../b/WaitForFundingCreatedStateSpec.scala | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 004902d6e0..3b44325120 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -46,8 +46,8 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel (TestConstants.fundingSatoshis, TestConstants.pushMsat) } - val aliceLocalFeatures = if(test.tags.contains("simplified_commitment")) BinaryData("0200") else Alice.channelParams.localFeatures - val bobLocalFeatures = if(test.tags.contains("simplified_commitment")) BinaryData("0200") else Bob.channelParams.localFeatures + val aliceLocalFeatures = if (test.tags.contains("simplified_commitment")) BinaryData("0200") else Alice.channelParams.localFeatures + val bobLocalFeatures = if (test.tags.contains("simplified_commitment")) BinaryData("0200") else Bob.channelParams.localFeatures val aliceInit = Init(Alice.channelParams.globalFeatures, aliceLocalFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, bobLocalFeatures) @@ -68,12 +68,29 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel import f._ alice2bob.expectMsgType[FundingCreated] alice2bob.forward(bob) - awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED) + awaitCond({ + bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == ContextCommitmentV1 + }) bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[WatchSpent] bob2blockchain.expectMsgType[WatchConfirmed] } + test("recv FundingCreated (option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + alice2bob.expectMsgType[FundingCreated] + alice2bob.forward(bob) + awaitCond({ + bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == ContextSimplifiedCommitment + }) + bob2alice.expectMsgType[FundingSigned] + bob2blockchain.expectMsgType[WatchSpent] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv FundingCreated (funder can't pay fees)", Tag("funder_below_reserve")) { f => import f._ val fees = Transactions.commitWeight * TestConstants.feeratePerKw / 1000 @@ -97,21 +114,4 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel bob ! CMD_CLOSE(None) awaitCond(bob.stateName == CLOSED) } - - test("recv FundingCreated (option_simplified_commitment)", Tag("simplified_commitment")) { f => - import f._ - - alice2bob.expectMsgType[FundingCreated] - alice2bob.forward(bob) - - awaitCond({ - bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && - bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == ContextSimplifiedCommitment - }) - - bob2alice.expectMsgType[FundingSigned] - bob2blockchain.expectMsgType[WatchSpent] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } From 1837a589812f70110f8a500020653341752456b5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 15:28:07 +0100 Subject: [PATCH 29/66] Remove unnecessary Alice from test fixtures --- .../channel/states/b/WaitForFundingCreatedStateSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 3b44325120..08438c38db 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -35,7 +35,7 @@ import scala.concurrent.duration._ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHelperMethods { - case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe) + case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { val setup = init() @@ -60,7 +60,7 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, bob2blockchain))) + withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain))) } } From 44c623412859280ec4b5d50abe6cfa16d5aa8d37 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 16:47:25 +0100 Subject: [PATCH 30/66] Introduce 'CannotUpdateFeeWithCommitmentType' add test for receiving a feeupdate and issuing a feeupdate with option_simplified_commitment --- .../fr/acinq/eclair/channel/Channel.scala | 33 +++++++++++----- .../eclair/channel/ChannelExceptions.scala | 1 + .../fr/acinq/eclair/channel/Commitments.scala | 4 +- .../states/StateTestsHelperMethods.scala | 6 ++- .../channel/states/e/NormalStateSpec.scala | 38 ++++++++++++++++++- 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 7e71a4c33d..803b8b0350 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -405,13 +405,23 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu handleLocalError(InvalidCommitmentSignature(channelId, signedLocalCommitTx.tx), d, Some(msg)) case Success(_) => val commitInput = localCommitTx.input - val commitments = CommitmentsV1(localParams, remoteParams, channelFlags, - LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, - LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), - localNextHtlcId = 0L, remoteNextHtlcId = 0L, - originChannels = Map.empty, - remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array - commitInput, ShaChain.init, channelId = channelId) + val commitments = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { + case true => SimplifiedCommitment(localParams, remoteParams, channelFlags, + LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, + LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 0L, remoteNextHtlcId = 0L, + originChannels = Map.empty, + remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array + commitInput, ShaChain.init, channelId = channelId) + case false => CommitmentsV1(localParams, remoteParams, channelFlags, + LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, + LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 0L, remoteNextHtlcId = 0L, + originChannels = Map.empty, + remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array + commitInput, ShaChain.init, channelId = channelId) + } + context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) log.info(s"publishing funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") // we do this to make sure that the channel state has been written to disk when we publish the funding tx @@ -495,13 +505,18 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu }) when(WAIT_FOR_FUNDING_LOCKED)(handleExceptions { - case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments: CommitmentsV1, shortChannelId, _)) => + case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments: Commitments, shortChannelId, _)) => // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED) context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId)) // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments)) - goto(NORMAL) using store(DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None)) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)) + case s: SimplifiedCommitment => s.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)) + } + + goto(NORMAL) using store(DATA_NORMAL(commitments1, shortChannelId, buried = false, None, initialChannelUpdate, None, None)) case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_LOCKED) if d.commitments.announceChannel => log.debug(s"received remote announcement signatures, delaying") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala index e5fa2668d9..28318f530f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala @@ -70,6 +70,7 @@ case class InvalidHtlcPreimage (override val channelId: BinaryDa case class UnknownHtlcId (override val channelId: BinaryData, id: Long) extends ChannelException(channelId, s"unknown htlc id=$id") case class CannotExtractSharedSecret (override val channelId: BinaryData, htlc: UpdateAddHtlc) extends ChannelException(channelId, s"can't extract shared secret: paymentHash=${htlc.paymentHash} onion=${htlc.onionRoutingPacket}") case class FundeeCannotSendUpdateFee (override val channelId: BinaryData) extends ChannelException(channelId, s"only the funder should send update_fee messages") +case class CannotUpdateFeeWithCommitmentType (override val channelId: BinaryData) extends ChannelException(channelId, s"can't update fees when option_simplified_commitment is in use") case class CannotAffordFees (override val channelId: BinaryData, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(channelId, s"can't pay the fee: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis feesSatoshis=$feesSatoshis") case class CannotSignWithoutChanges (override val channelId: BinaryData) extends ChannelException(channelId, "cannot sign when there are no changes") case class CannotSignBeforeRevocation (override val channelId: BinaryData) extends ChannelException(channelId, "cannot sign until next revocation hash is received") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 09fcd0419e..3cdf9fe987 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -360,7 +360,7 @@ object Commitments { } if(commitments.getContext == ContextSimplifiedCommitment){ - throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") + throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } implicit val commitmentContext = commitments.getContext @@ -400,7 +400,7 @@ object Commitments { } if(commitments.getContext == ContextSimplifiedCommitment){ - throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") + throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } implicit val commitmentContext = commitments.getContext diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 88712297f4..d7add43083 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -72,7 +72,11 @@ trait StateTestsHelperMethods extends TestKitBase { import setup._ val channelFlags = if (tags.contains("channels_public")) ChannelFlags.AnnounceChannel else ChannelFlags.Empty val pushMsat = if (tags.contains("no_push_msat")) 0 else TestConstants.pushMsat - val (aliceParams, bobParams) = (Alice.channelParams, Bob.channelParams) + val (aliceParams, bobParams) = if(tags.contains("simplified_commitment")) + (Alice.channelParams.copy(localFeatures = BinaryData("0200")), Bob.channelParams.copy(localFeatures = BinaryData("0200"))) + else + (Alice.channelParams, Bob.channelParams) + val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures) val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures) // reset global feerates (they may have been changed by previous tests) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index e529866e2d..7915c70115 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -71,11 +71,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(htlc.id == 0 && htlc.paymentHash == h) awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy( localNextHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), originChannels = Map(0L -> Local(Some(sender.ref))) - )} + ) + } )) } @@ -104,6 +106,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(htlc.id == 0 && htlc.paymentHash == h) awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy( localNextHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), @@ -301,6 +304,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) bob ! htlc awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1) })) // bob won't forward the add before it is cross-signed @@ -814,7 +818,6 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv RevokeAndAck (one htlc sent)") { f => import f._ val sender = TestProbe() @@ -1045,6 +1048,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) })) } @@ -1087,6 +1091,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)) })) // alice immediately propagates the fulfill upstream @@ -1163,6 +1168,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) })) @@ -1192,6 +1198,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) })) @@ -1229,6 +1236,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) })) // alice won't forward the fail before it is cross-signed @@ -1251,6 +1259,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) })) // alice won't forward the fail before it is cross-signed @@ -1329,6 +1338,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)) })) @@ -1346,6 +1356,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fee2 = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee2)) })) @@ -1360,6 +1371,15 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(initialState == bob.stateData) } + test("recv CMD_UPDATE_FEE (when option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CMD_UPDATE_FEE(20000)) + sender.expectMsg(Failure(CannotUpdateFeeWithCommitmentType(channelId(bob)))) + assert(initialState == alice.stateData) + } + test("recv UpdateFee") { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] @@ -1368,16 +1388,30 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fee2 = UpdateFee("00" * 32, 14000) bob ! fee2 awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee2), remoteNextHtlcId = 0) })) } + test("recv UpdateFee (option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] + assert(initialData.commitments.getContext == ContextSimplifiedCommitment) + + // Alice sends update_fee to Bob but this shouldn't happen with option_simplified_commitment so Bob will reply Error + val fee1 = UpdateFee("00" * 32, 12000) + bob ! fee1 + + bob2alice.expectMsgType[Error] // should bob close the channel? Yes + } + test("recv UpdateFee (two in a row)") { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val fee = UpdateFee("00" * 32, 12000) bob ! fee awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0) })) } From 66ea646cc3e61c9737fc55f677d000c117ca385b Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 17:02:22 +0100 Subject: [PATCH 31/66] Rename variables in 'canUseSimplifiedCommitment' --- .../scala/fr/acinq/eclair/channel/Helpers.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 1970807c42..52700eae59 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -324,14 +324,14 @@ object Helpers { } def canUseSimplifiedCommitment[T <: ParamsWithFeatures](local: T, remote: T) = { - val localHasOptionalSupport = Features.hasFeature(local.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) - val localHasMandatorySupport = Features.hasFeature(local.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) - val remoteHasOptionalSupport = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) - val remoteHasMandatorySupport = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) - - (remoteHasMandatorySupport && (localHasMandatorySupport || localHasOptionalSupport)) || - (localHasMandatorySupport && (remoteHasMandatorySupport || remoteHasOptionalSupport)) || - (localHasOptionalSupport && remoteHasOptionalSupport) + val localHasOptional = Features.hasFeature(local.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + val localHasMandatory = Features.hasFeature(local.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) + val remoteHasOptional = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) + val remoteHasMandatory = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) + + (remoteHasMandatory && (localHasMandatory || localHasOptional)) || + (localHasMandatory && (remoteHasMandatory || remoteHasOptional)) || + (localHasOptional && remoteHasOptional) } object Closing { From ac078bea232988c66b5415b8d7c7f587c7ab0fd6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 18 Feb 2019 17:57:20 +0100 Subject: [PATCH 32/66] Add test for receiving `update_add_htlc` in simplified_commitment mode --- .../scala/fr/acinq/eclair/channel/Commitments.scala | 8 ++++---- .../scala/fr/acinq/eclair/channel/Helpers.scala | 2 +- .../eclair/channel/states/e/NormalStateSpec.scala | 13 +++++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 3cdf9fe987..fea8cefd26 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -140,12 +140,12 @@ object Commitments { */ def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments match { case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) - case _: SimplifiedCommitment => ??? + case s: SimplifiedCommitment => s.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) } def addRemoteProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) - case _: SimplifiedCommitment => ??? + case s: SimplifiedCommitment => s.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) } /** @@ -182,7 +182,7 @@ object Commitments { // we increment the local htlc index and add an entry to the origins map val commitments1 = addLocalProposal(commitments, add) match { case c: CommitmentsV1 => c.copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) - case _: SimplifiedCommitment => throw new NotImplementedError + case s: SimplifiedCommitment => s.copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) } // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation val remoteCommit1 = commitments1.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments1.remoteCommit) @@ -229,7 +229,7 @@ object Commitments { // let's compute the current commitment *as seen by us* including this change val commitments1 = addRemoteProposal(commitments, add) match { case c: CommitmentsV1 => c.copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) - case _: SimplifiedCommitment => ??? + case s: SimplifiedCommitment => s.copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) val incomingHtlcs = reduced.htlcs.filter(_.direction == IN) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 52700eae59..464a043750 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -64,7 +64,7 @@ object Helpers { case c: CommitmentsV1 => c.copy( localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures), remoteParams = data.commitments.remoteParams.copy(globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures)) - case _: SimplifiedCommitment => ??? + case _: SimplifiedCommitment => throw new NotImplementedError("Missing impl for simplified_commitment") } data match { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 7915c70115..d053ab0277 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -311,6 +311,19 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { relayerB.expectNoMsg() } + test("recv UpdateAddHtlc (option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] + val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) + bob ! htlc + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case s: SimplifiedCommitment => s.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1) + case _: CommitmentsV1 => throw new IllegalArgumentException("Shouldn't be here") + })) + // bob won't forward the add before it is cross-signed + relayerB.expectNoMsg() + } + test("recv UpdateAddHtlc (unexpected id)") { f => import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx From 054dd9d0b7b2d9ce237dba54544fb4ad0f76dff7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 19 Feb 2019 17:18:25 +0100 Subject: [PATCH 33/66] Use remote_delayedpubkey to the to_remote_pushme --- .../fr/acinq/eclair/channel/Commitments.scala | 23 ++++++++---- .../fr/acinq/eclair/channel/Helpers.scala | 8 ++-- .../eclair/transactions/Transactions.scala | 4 +- .../b/WaitForFundingSignedStateSpec.scala | 37 ++++++++++++++++--- .../channel/states/f/ShutdownStateSpec.scala | 11 +++++- .../fr/acinq/eclair/io/HtlcReaperSpec.scala | 1 + .../eclair/transactions/TestVectorsSpec.scala | 3 ++ .../transactions/TransactionsSpec.scala | 34 ++++++++++++----- 8 files changed, 89 insertions(+), 32 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index fea8cefd26..64b69f28f1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -446,7 +446,7 @@ object Commitments { import commitments._ commitments match { - case _: SimplifiedCommitment => throw new NotImplementedException + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.remoteNextCommitInfo match { case Right(_) if !localHasChanges(c) => @@ -454,7 +454,8 @@ object Commitments { case Right(remoteNextPerCommitmentPoint) => // remote commitment will includes all local changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) + val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, localPerCommitmentPoint, spec) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) @@ -505,7 +506,12 @@ object Commitments { val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) - val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) + val remotePerCommitmentPoint = remoteNextCommitInfo match { + case Left(_) => throw new IllegalArgumentException("Received commit while waiting for revocation") + case Right(point) => point + } + + val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, remotePerCommitmentPoint, spec) val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) @@ -608,7 +614,7 @@ object Commitments { } } - def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) @@ -616,24 +622,25 @@ object Commitments { case ContextSimplifiedCommitment => PublicKey(remoteParams.paymentBasepoint) case ContextCommitmentV1 => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) } - + val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, remoteDelayedPaymentPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localPaymentPubkey = commitmentContext match { case ContextSimplifiedCommitment => keyManager.paymentPoint(localParams.channelKeyPath).publicKey case ContextCommitmentV1 => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) } + val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, localDelayedPaymentPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 464a043750..e17e626ae6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -258,8 +258,8 @@ object Helpers { val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) - val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) + val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, remoteFirstPerCommitmentPoint, localSpec) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, localPerCommitmentPoint, remoteSpec) (localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -495,12 +495,12 @@ object Helpers { implicit val commitmentContext = commitments.getContext - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, localPerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == commitTx.txid, "txid mismatch, cannot recompute the current remote commit tx") val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) // we need to use a rather high fee for htlc-claim because we compete with the counterparty diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 09b652045b..f206b7860f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -192,7 +192,7 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): CommitTx = commitmentContext match { + def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): CommitTx = commitmentContext match { case ContextCommitmentV1 => val commitFee = commitTxFee(localDustLimit, spec) @@ -240,7 +240,7 @@ object Transactions { .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) val toLocalPushMe_opt = if (toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None - val toRemotePushMe_opt = if (toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remotePaymentPubkey)))) else None + val toRemotePushMe_opt = if (toRemoteOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(remoteDelayedPaymentPubkey)))) else None val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) val (sequence, locktime) = encodeTxNumber(txnumber) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index aa9f46a7bf..0890fa8b59 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -17,14 +17,15 @@ package fr.acinq.eclair.channel.states.b import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.BinaryData +import fr.acinq.bitcoin.{BinaryData, Satoshi} 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.transactions.{Scripts, Transactions} import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel} import fr.acinq.eclair.{TestConstants, TestkitBaseClass} -import org.scalatest.Outcome +import org.scalatest.{Outcome, Tag} import scala.concurrent.duration._ @@ -39,11 +40,18 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp override def withFixture(test: OneArgTest): Outcome = { val setup = init() import setup._ - val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) - val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) + + val (aliceParams, bobParams) = if(test.tags.contains("simplified_commitment")) + (Alice.channelParams.copy(localFeatures = BinaryData("0200")), Bob.channelParams.copy(localFeatures = BinaryData("0200"))) + else + (Alice.channelParams, Bob.channelParams) + + val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures) + val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures) + within(30 seconds) { - alice ! INPUT_INIT_FUNDER("00" * 32, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) - bob ! INPUT_INIT_FUNDEE("00" * 32, Bob.channelParams, bob2alice.ref, aliceInit) + alice ! INPUT_INIT_FUNDER("00" * 32, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + bob ! INPUT_INIT_FUNDEE("00" * 32, bobParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] @@ -64,6 +72,23 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp alice2blockchain.expectMsgType[WatchConfirmed] } + test("recv FundingSigned (option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + + bob2alice.expectMsgType[FundingSigned] + bob2alice.forward(alice) + + val aliceStateData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] + assert(aliceStateData.commitments.getContext == ContextSimplifiedCommitment) + + // there should be 2 push-me outputs with amount 1000 sat + val aliceLocalCommitTx = aliceStateData.commitments.localCommit.publishableTxs.commitTx.tx + assert(aliceLocalCommitTx.txOut.count(_.amount == Transactions.pushMeValue) == 2) + + alice2blockchain.expectMsgType[WatchSpent] + alice2blockchain.expectMsgType[WatchConfirmed] + } + test("recv FundingSigned with invalid signature") { f => import f._ // sending an invalid sig diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 8353a11774..acae5330cc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -115,8 +115,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments match { - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) + case c: CommitmentsV1 => c.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) + case _: SimplifiedCommitment => ??? })) } @@ -146,6 +146,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(alice, fulfill) awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)) + case _: SimplifiedCommitment => ??? })) } @@ -191,6 +192,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) })) @@ -214,6 +216,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) })) @@ -244,6 +247,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fail = UpdateFailHtlc("00" * 32, 1, "00" * 152) sender.send(alice, fail) awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) })) } @@ -271,6 +275,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION) sender.send(alice, fail) awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) })) } @@ -516,6 +521,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( commitments = initialState.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy( localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)) })) @@ -536,6 +542,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val fee = UpdateFee("00" * 32, 12000) bob ! fee awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)) })) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala index 1638ab2382..a3ecaf2c23 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala @@ -64,6 +64,7 @@ class HtlcReaperSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { // let's now assume that the channel get's reconnected, and it had the time to fail the htlcs val data1 = data.copy(commitments = data.commitments match { + case _: SimplifiedCommitment => ??? case c: CommitmentsV1 => c.copy(localCommit = data.commitments.localCommit.copy(spec = data.commitments.localCommit.spec.copy(htlcs = Set.empty))) }) sender.send(brokenHtlcKiller, ChannelStateChanged(channel.ref, system.deadLetters, data.commitments.remoteParams.nodeId, OFFLINE, NORMAL, data1)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 4faba5dd6d..a362c65c66 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -116,6 +116,7 @@ class TestVectorsSpec extends FunSuite with Logging { val funding_pubkey = funding_privkey.publicKey val payment_privkey = Generators.derivePrivKey(payment_basepoint_secret, Local.per_commitment_point) val per_commitment_point = Point(BinaryData("022c76692fd70814a8d1ed9dedc833318afaaed8188db4d14727e2e99bc619d325")) + val delayed_payment_pubkey = Generators.derivePubKey(payment_basepoint, per_commitment_point) } val coinbaseTx = Transaction.read("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0100f2052a010000001976a9143ca33c2e4446f4a305f23c80df8ad1afdcf652f988ac00000000") @@ -189,6 +190,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key + Remote.delayed_payment_pubkey, spec)(ContextCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey) @@ -219,6 +221,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key + Remote.delayed_payment_pubkey, spec)(ContextCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 5fc37b0e33..72013a00c4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -174,6 +174,7 @@ class TransactionsSpec extends FunSuite with Logging { val localPaymentPriv = PrivateKey(BinaryData("a4" * 32) :+ 1.toByte) val localDelayedPaymentPriv = PrivateKey(BinaryData("a5" * 32) :+ 1.toByte) val remotePaymentPriv = PrivateKey(BinaryData("a6" * 32) :+ 1.toByte) + val remoteDelayedPaymentPriv = PrivateKey(BinaryData("a9" * 32) :+ 1.toByte) val localHtlcPriv = PrivateKey(BinaryData("a7" * 32) :+ 1.toByte) val remoteHtlcPriv = PrivateKey(BinaryData("a8" * 32) :+ 1.toByte) val finalPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(BinaryData("a9" * 32), true).publicKey)) @@ -205,21 +206,21 @@ class TransactionsSpec extends FunSuite with Logging { toRemoteMsat = millibtc2satoshi(MilliBtc(300)).amount * 1000) val commitTxNumber = 0x404142434445L - val commitTx = { - val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(ContextCommitmentV1) + def commitTx(commitContext: CommitmentContext = ContextCommitmentV1) = { + val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, remoteDelayedPaymentPriv.publicKey, spec)(commitContext) val localSig = Transactions.sign(txinfo, localPaymentPriv) val remoteSig = Transactions.sign(txinfo, remotePaymentPriv) Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) } { - assert(getCommitTxNumber(commitTx.tx, true, localPaymentPriv.publicKey, remotePaymentPriv.publicKey) == commitTxNumber) + assert(getCommitTxNumber(commitTx().tx, true, localPaymentPriv.publicKey, remotePaymentPriv.publicKey) == commitTxNumber) val hash: Array[Byte] = Crypto.sha256(localPaymentPriv.publicKey.toBin ++ remotePaymentPriv.publicKey.toBin) val num = Protocol.uint64(hash.takeRight(8), ByteOrder.BIG_ENDIAN) & 0xffffffffffffL - val check = ((commitTx.tx.txIn(0).sequence & 0xffffff) << 24) | (commitTx.tx.lockTime) + val check = ((commitTx().tx.txIn(0).sequence & 0xffffff) << 24) | (commitTx().tx.lockTime) assert((check ^ num) == commitTxNumber) } - val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) + val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx().tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) assert(htlcTimeoutTxs.size == 2) // htlc1 and htlc3 assert(htlcSuccessTxs.size == 2) // htlc2 and htlc4 @@ -234,6 +235,19 @@ class TransactionsSpec extends FunSuite with Logging { } } + { + // Simplified commitment + val commitTransaction = commitTx(commitContext = ContextSimplifiedCommitment) + // there must be 2 push-me outputs + val toLocalPushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(localDelayedPaymentPriv.publicKey))) // to_local_pushme uses local_delayedpubkey + val toRemotePushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(remoteDelayedPaymentPriv.publicKey))) // to_remote_pushme uses remote_delayedpubkey + assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toLocalPushMe)) + assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toRemotePushMe)) + // to_remote is delayed with to_self_delay + val toRemoteDelayed = Script.write(pay2wsh(Scripts.toRemoteDelayed(remotePaymentPriv.publicKey, toLocalDelay))) + assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toRemoteDelayed)) + } + { // local spends delayed output of htlc1 timeout tx val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) @@ -249,7 +263,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends local->remote htlc1/htlc3 output directly in case of success for ((htlc, paymentPreimage) <- (htlc1, paymentPreimage1) :: (htlc3, paymentPreimage3) :: Nil) { - val claimHtlcSuccessTx = makeClaimHtlcSuccessTx(commitTx.tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw) + val claimHtlcSuccessTx = makeClaimHtlcSuccessTx(commitTx().tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw) val localSig = sign(claimHtlcSuccessTx, remoteHtlcPriv) val signed = addSigs(claimHtlcSuccessTx, localSig, paymentPreimage) assert(checkSpendable(signed).isSuccess) @@ -282,7 +296,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends main output - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx.tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx().tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv) val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) @@ -290,7 +304,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends remote->local htlc output directly in case of timeout - val claimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx.tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc2, feeratePerKw) + val claimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx().tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc2, feeratePerKw) val localSig = sign(claimHtlcTimeoutTx, remoteHtlcPriv) val signed = addSigs(claimHtlcTimeoutTx, localSig) assert(checkSpendable(signed).isSuccess) @@ -299,7 +313,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends offered HTLC output with revocation key val script = Script.write(Scripts.htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc1.paymentHash))) - val htlcPenaltyTx = makeHtlcPenaltyTx(commitTx.tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw) + val htlcPenaltyTx = makeHtlcPenaltyTx(commitTx().tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw) val sig = sign(htlcPenaltyTx, localRevocationPriv) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) @@ -308,7 +322,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends received HTLC output with revocation key val script = Script.write(Scripts.htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc2.paymentHash), htlc2.cltvExpiry)) - val htlcPenaltyTx = makeHtlcPenaltyTx(commitTx.tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw) + val htlcPenaltyTx = makeHtlcPenaltyTx(commitTx().tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw) val sig = sign(htlcPenaltyTx, localRevocationPriv) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) From 1fadb53297e977f1ac742e11590cb9913c340ee7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 20 Feb 2019 10:33:36 +0100 Subject: [PATCH 34/66] Add test to check remote_pubkey is not rotated across commitments. --- .../fr/acinq/eclair/channel/Channel.scala | 7 +- .../fr/acinq/eclair/channel/Commitments.scala | 82 ++++++++++--------- .../eclair/transactions/CommitmentSpec.scala | 1 + .../channel/states/e/NormalStateSpec.scala | 32 +++++++- 4 files changed, 81 insertions(+), 41 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 803b8b0350..008940690a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -629,7 +629,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d@DATA_NORMAL(commitments: CommitmentsV1, _, _, _, _, _, _)) => + case Event(c@CMD_SIGN, d@DATA_NORMAL(commitments: Commitments, _, _, _, _, _, _)) => commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -667,7 +667,10 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - val commitments1 = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + val commitments1 = commitments match { + case c: CommitmentsV1 => c.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + case s: SimplifiedCommitment => s.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + } stay using d.copy(commitments = commitments1) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 64b69f28f1..15ed28bffa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -445,44 +445,44 @@ object Commitments { import commitments._ - commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => - c.remoteNextCommitInfo match { - case Right(_) if !localHasChanges(c) => - throw CannotSignWithoutChanges(c.channelId) - case Right(remoteNextPerCommitmentPoint) => - // remote commitment will includes all local changes + remote acked changes - val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) - val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, localPerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) - - val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) - - // NB: IN/OUT htlcs are inverted because this is the remote commit - log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) - - // don't sign if they don't get paid - val commitSig = CommitSig( - channelId = c.channelId, - signature = sig, - htlcSignatures = htlcSigs.toList - ) - - val commitments1 = c.copy( - remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), - localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), - remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) - - (commitments1, commitSig) - case Left(_) => - throw CannotSignBeforeRevocation(commitments.channelId) + commitments.remoteNextCommitInfo match { + case Right(_) if !localHasChanges(commitments) => + throw CannotSignWithoutChanges(commitments.channelId) + case Right(remoteNextPerCommitmentPoint) => + // remote commitment will includes all local changes + remote acked changes + val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) + val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, localPerCommitmentPoint, spec) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + + val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) + + // NB: IN/OUT htlcs are inverted because this is the remote commit + log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) + + // don't sign if they don't get paid + val commitSig = CommitSig( + channelId = commitments.channelId, + signature = sig, + htlcSignatures = htlcSigs.toList + ) + + val commitments1 = commitments match { + case c:CommitmentsV1 => c.copy( + remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), + localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), + remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) + case s: SimplifiedCommitment => s.copy( + remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), + localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), + remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) } - } - } + (commitments1, commitSig) + case Left(_) => + throw CannotSignBeforeRevocation(commitments.channelId) + } } def receiveCommit(commitments: Commitments, commit: CommitSig, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = { implicit val commitmentContext = commitments.getContext @@ -563,7 +563,7 @@ object Commitments { val theirChanges1 = remoteChanges.copy(proposed = Nil, acked = remoteChanges.acked ++ remoteChanges.proposed) val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) - case _: SimplifiedCommitment => ??? + case s: SimplifiedCommitment => s.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) } (commitments1, revocation) @@ -605,7 +605,13 @@ object Commitments { remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), originChannels = originChannels1) - case _: SimplifiedCommitment => ??? + case s: SimplifiedCommitment => s.copy( + localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed), + remoteChanges = remoteChanges.copy(signed = Nil), + remoteCommit = theirNextCommit, + remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), + remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), + originChannels = originChannels1) } (commitments1, forwards) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala index 1e23d8205d..19a91dbbe9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala @@ -30,6 +30,7 @@ case object OUT extends Direction { def opposite = IN } case class DirectedHtlc(direction: Direction, add: UpdateAddHtlc) +// TODO consider option_simplified_commitment final case class CommitmentSpec(htlcs: Set[DirectedHtlc], feeratePerKw: Long, toLocalMsat: Long, toRemoteMsat: Long) { val totalFunds = toLocalMsat + toRemoteMsat + htlcs.toSeq.map(_.add.amountMsat).sum } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index d053ab0277..4105490873 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel.states.e import akka.actor.Status import akka.actor.Status.Failure import akka.testkit.TestProbe -import fr.acinq.bitcoin.Crypto.Scalar +import fr.acinq.bitcoin.Crypto.{PublicKey, Scalar} import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, ScriptFlags, Transaction} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.UInt64.Conversions._ @@ -849,6 +849,36 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localChanges.acked.size == 1) } + test("recv RevokeAndAck (one htlc sent, simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val sender = TestProbe() + + val aliceStatePre = alice.stateData.asInstanceOf[DATA_NORMAL] + + // get the remote pubkey (used in to_remote output) + val startingPaymentPubkey = PublicKey(aliceStatePre.commitments.remoteParams.paymentBasepoint) + + // now alice offers to bob an htlc + addHtlc(50000000, alice, bob, alice2bob, bob2alice) + + // alice signs it's state and sends a commit to bob + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + + // bob acknowledges and sends back revoke_and_ack + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + + // TEST in alice's view bob remotePaymentPubkey (remote_pubkey) stays the same across commitments + awaitCond({ + alice.stateName == NORMAL && + PublicKey(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteParams.paymentBasepoint) == startingPaymentPubkey + }) + + } + test("recv RevokeAndAck (one htlc received)") { f => import f._ val sender = TestProbe() From e25efda07672d9ca4dd2f9ecccc1beb7928c3fcc Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 20 Feb 2019 10:37:35 +0100 Subject: [PATCH 35/66] Rename test, reorg code --- .../acinq/eclair/channel/states/e/NormalStateSpec.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 4105490873..7745015a37 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -849,14 +849,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localChanges.acked.size == 1) } - test("recv RevokeAndAck (one htlc sent, simplified_commitment)", Tag("simplified_commitment")) { f => + test("recv RevokeAndAck (simplified_commitment - non rotating remote_pubkey)", Tag("simplified_commitment")) { f => import f._ val sender = TestProbe() - val aliceStatePre = alice.stateData.asInstanceOf[DATA_NORMAL] - - // get the remote pubkey (used in to_remote output) - val startingPaymentPubkey = PublicKey(aliceStatePre.commitments.remoteParams.paymentBasepoint) + // get the remote pubkey (used in to_remote output) before the payment has been sent + val startingPaymentPubkey = PublicKey(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteParams.paymentBasepoint) // now alice offers to bob an htlc addHtlc(50000000, alice, bob, alice2bob, bob2alice) From 1ee7b1f184600b8f064d2f992a24a49cdc2584a5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 20 Feb 2019 11:44:31 +0100 Subject: [PATCH 36/66] Use previous perCommitmentPoint if we received a commit_sig while waiting for revoke_and_ack --- .../scala/fr/acinq/eclair/channel/Channel.scala | 14 +++++++++++--- .../fr/acinq/eclair/channel/Commitments.scala | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 008940690a..c94f12ddb5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -729,7 +729,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu handleCommandSuccess(sender, store(d.copy(localShutdown = Some(shutdown)))) sending shutdown } - case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(commitments: CommitmentsV1, _, _, _, _, _, _)) => + case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d: DATA_NORMAL) => // they have pending unsigned htlcs => they violated the spec, close the channel // they don't have pending unsigned htlcs // we have pending unsigned htlcs @@ -755,7 +755,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu d.commitments.remoteNextCommitInfo match { case Left(waitForRevocation) => // yes, let's just schedule a new signature ASAP, which will include all pending unsigned htlcs - val commitments1 = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + val commitments1 = d.commitments match { + case c: CommitmentsV1 => c.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + case s: SimplifiedCommitment => s.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + + } // in the meantime we won't send new htlcs stay using d.copy(commitments = commitments1, remoteShutdown = Some(remoteShutdown)) case Right(_) => @@ -1923,7 +1927,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) - case _: SimplifiedCommitment => ??? + case s: SimplifiedCommitment => s.copy( + localChanges = d.commitments.localChanges.copy(proposed = Nil), + remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), + localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, + remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 15ed28bffa..5dfa479327 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -414,7 +414,7 @@ object Commitments { // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) - case _: SimplifiedCommitment => throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") +` case _: SimplifiedCommitment => throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) @@ -507,7 +507,7 @@ object Commitments { val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) val remotePerCommitmentPoint = remoteNextCommitInfo match { - case Left(_) => throw new IllegalArgumentException("Received commit while waiting for revocation") + case Left(_) => commitments.remoteCommit.remotePerCommitmentPoint case Right(point) => point } From 5c5dda7f19e50b4aa1fa261e0b9623c42059486f Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 20 Feb 2019 11:50:00 +0100 Subject: [PATCH 37/66] Remove leftover char --- .../src/main/scala/fr/acinq/eclair/channel/Commitments.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 5dfa479327..0dc2a79bc0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -414,7 +414,7 @@ object Commitments { // update_fee replace each other, so we can remove previous ones val commitments1 = commitments match { case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) -` case _: SimplifiedCommitment => throw CannotUpdateFeeWithCommitmentType(commitments.channelId) + case _: SimplifiedCommitment => throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) From b17693106be668e952695087d8834b304d91062e Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 21 Feb 2019 16:48:40 +0100 Subject: [PATCH 38/66] Add commit structure test for option_simplified_commitment --- .../transactions/TransactionsSpec.scala | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 72013a00c4..84711cb24a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -61,18 +61,21 @@ class TransactionsSpec extends FunSuite with Logging { } test("compute fees") { + + val expectedSizeSimplifiedCommitment = weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * 2) + val expectedSizeCommitmentV1 = Satoshi(5340) + // see BOLT #3 specs val htlcs = Set( - DirectedHtlc(OUT, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(5000000).amount, Hash.Zeroes, 552, BinaryData.empty)), + DirectedHtlc(OUT, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(5000000).amount, Hash.Zeroes, 552, BinaryData.empty)), // trimmed DirectedHtlc(OUT, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(1000000).amount, Hash.Zeroes, 553, BinaryData.empty)), - DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(7000000).amount, Hash.Zeroes, 550, BinaryData.empty)), + DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(7000000).amount, Hash.Zeroes, 550, BinaryData.empty)), // trimmed DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(800000).amount, Hash.Zeroes, 551, BinaryData.empty)) ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0) - val fee = Transactions.commitTxFee(Satoshi(546), spec)(ContextCommitmentV1) - assert(fee == Satoshi(5340)) - //TODO add case for simplified commitment + assert(commitTxFee(Satoshi(546), spec)(ContextSimplifiedCommitment) == expectedSizeSimplifiedCommitment) + assert(commitTxFee(Satoshi(546), spec)(ContextCommitmentV1) == expectedSizeCommitmentV1) } test("check pre-computed transaction weights") { @@ -236,6 +239,10 @@ class TransactionsSpec extends FunSuite with Logging { } { + // local is funder, pays for fees + push_me outputs + val expectedToLocalAmount = Satoshi(spec.toLocalMsat / 1000) - Transactions.pushMeValue * 2 - commitTxFee(localDustLimit, spec)(ContextSimplifiedCommitment) + val expectedToRemoteAmount = Satoshi(spec.toRemoteMsat / 1000) + // Simplified commitment val commitTransaction = commitTx(commitContext = ContextSimplifiedCommitment) // there must be 2 push-me outputs @@ -243,9 +250,16 @@ class TransactionsSpec extends FunSuite with Logging { val toRemotePushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(remoteDelayedPaymentPriv.publicKey))) // to_remote_pushme uses remote_delayedpubkey assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toLocalPushMe)) assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toRemotePushMe)) + // to_remote is delayed with to_self_delay - val toRemoteDelayed = Script.write(pay2wsh(Scripts.toRemoteDelayed(remotePaymentPriv.publicKey, toLocalDelay))) - assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toRemoteDelayed)) + val toRemoteDelayedScript = Script.write(pay2wsh(Scripts.toRemoteDelayed(remotePaymentPriv.publicKey, toLocalDelay))) + val Some(toRemoteMainOut) = commitTransaction.tx.txOut.find(_.publicKeyScript == toRemoteDelayedScript) + + val toLocalMainScript = Script.write(pay2wsh(Scripts.toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey))) + val Some(toLocalMainOut) = commitTransaction.tx.txOut.find(_.publicKeyScript == toLocalMainScript) + + assert(toLocalMainOut.amount == expectedToLocalAmount) + assert(toRemoteMainOut.amount == expectedToRemoteAmount) } { From 1695912aadaf4470772587ba9e864c614d1d2a00 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 21 Feb 2019 16:54:02 +0100 Subject: [PATCH 39/66] Rework comments --- .../fr/acinq/eclair/transactions/TransactionsSpec.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 84711cb24a..be74ad5e48 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -239,15 +239,16 @@ class TransactionsSpec extends FunSuite with Logging { } { + // Simplified commitment + val commitTransaction = commitTx(commitContext = ContextSimplifiedCommitment) + // local is funder, pays for fees + push_me outputs val expectedToLocalAmount = Satoshi(spec.toLocalMsat / 1000) - Transactions.pushMeValue * 2 - commitTxFee(localDustLimit, spec)(ContextSimplifiedCommitment) val expectedToRemoteAmount = Satoshi(spec.toRemoteMsat / 1000) - // Simplified commitment - val commitTransaction = commitTx(commitContext = ContextSimplifiedCommitment) // there must be 2 push-me outputs - val toLocalPushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(localDelayedPaymentPriv.publicKey))) // to_local_pushme uses local_delayedpubkey - val toRemotePushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(remoteDelayedPaymentPriv.publicKey))) // to_remote_pushme uses remote_delayedpubkey + val toLocalPushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(localDelayedPaymentPriv.publicKey))) + val toRemotePushMe = Script.write(pay2wsh(Scripts.pushMeSimplified(remoteDelayedPaymentPriv.publicKey))) assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toLocalPushMe)) assert(commitTransaction.tx.txOut.exists(_.publicKeyScript == toRemotePushMe)) From 3f8f328f70a8aa9d6df41b8a8ccb226544f874e5 Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 25 Feb 2019 09:38:10 +0100 Subject: [PATCH 40/66] Remove unused travis files We download bitcoin core and check with maven now. --- travis/builddeps.sh | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100755 travis/builddeps.sh diff --git a/travis/builddeps.sh b/travis/builddeps.sh deleted file mode 100755 index bfff183d5f..0000000000 --- a/travis/builddeps.sh +++ /dev/null @@ -1,25 +0,0 @@ -pushd . -# lightning deps -sudo add-apt-repository -y ppa:chris-lea/libsodium -sudo apt-get update -sudo apt-get install -y libsodium-dev libgmp-dev libsqlite3-dev -cd -git clone https://github.com/luke-jr/libbase58.git -cd libbase58 -./autogen.sh && ./configure && make && sudo make install -# lightning -cd -git clone https://github.com/ElementsProject/lightning.git -cd lightning -git checkout fce9ee29e3c37b4291ebb050e6a687cfaa7df95a -git submodule init -git submodule update -make -# bitcoind -cd -wget https://bitcoin.org/bin/bitcoin-core-0.13.0/bitcoin-0.13.0-x86_64-linux-gnu.tar.gz -echo "bcc1e42d61f88621301bbb00512376287f9df4568255f8b98bc10547dced96c8 bitcoin-0.13.0-x86_64-linux-gnu.tar.gz" > sha256sum.asc -sha256sum -c sha256sum.asc -tar xzvf bitcoin-0.13.0-x86_64-linux-gnu.tar.gz -popd - From 9504c51fdf6c5c0868efe9bf60a5c5a546c9edee Mon Sep 17 00:00:00 2001 From: araspitzu Date: Fri, 1 Mar 2019 11:14:10 +0100 Subject: [PATCH 41/66] Pass sigHash to sign functions, sign with SIGHASH_SINGLE | SIGHASH_ANYONECANPAY if simplified_commitment --- .../fr/acinq/eclair/channel/Channel.scala | 8 +++--- .../fr/acinq/eclair/channel/Commitments.scala | 27 ++++++++++++------ .../fr/acinq/eclair/channel/Helpers.scala | 20 ++++++------- .../fr/acinq/eclair/crypto/KeyManager.scala | 6 ++-- .../acinq/eclair/crypto/LocalKeyManager.scala | 12 ++++---- .../eclair/transactions/Transactions.scala | 12 ++++---- .../eclair/transactions/TestVectorsSpec.scala | 20 ++++++------- .../transactions/TransactionsSpec.scala | 28 +++++++++---------- 8 files changed, 72 insertions(+), 61 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index c94f12ddb5..3eff9fbb68 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -306,7 +306,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // let's create the first commitment tx that spends the yet uncommitted funding tx val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -344,12 +344,12 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) // check remote signature validity - val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None) case Success(_) => - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) val channelId = toLongId(fundingTxHash, fundingTxOutputIndex) // watch the funding tx transaction val commitInput = localCommitTx.input @@ -395,7 +395,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, channelFlags, fundingCreated)) => // we make sure that their sig checks out and that our first commit tx is spendable - val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 0dc2a79bc0..9452162941 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -25,8 +25,7 @@ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ import fr.acinq.eclair.{Features, Globals, UInt64} -import sun.reflect.generics.reflectiveObjects.NotImplementedException - +import fr.acinq.bitcoin._ import scala.util.{Failure, Success} // @formatter:off @@ -453,10 +452,13 @@ object Commitments { val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, localPerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) + val htlcSigs = commitmentContext match { + case ContextCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_ALL)) + case ContextSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) + } // NB: IN/OUT htlcs are inverted because this is the remote commit log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) @@ -512,7 +514,7 @@ object Commitments { } val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, remotePerCommitmentPoint, spec) - val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) @@ -528,7 +530,10 @@ object Commitments { if (commit.htlcSignatures.size != sortedHtlcTxs.size) { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint)) + val htlcSigs = commitmentContext match { + case ContextCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)) + case ContextSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) + } val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) // combine the sigs to make signed txes val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect { @@ -537,9 +542,15 @@ object Commitments { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == ContextCommitmentV1 => + // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig + if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_ALL) == false) { + throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) + } + HtlcTxAndSigs(htlcTx, localSig, remoteSig) + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == ContextSimplifiedCommitment => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig - if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey) == false) { + if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index e17e626ae6..70dbb19a76 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -375,7 +375,7 @@ object Helpers { // TODO: check that val dustLimitSatoshis = Satoshi(Math.max(localParams.dustLimitSatoshis, remoteParams.dustLimitSatoshis)) val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec) - val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath)) + val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath), SIGHASH_ALL) val closingSigned = ClosingSigned(channelId, closingFee.amount, localClosingSig) log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}") log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}") @@ -431,7 +431,7 @@ object Helpers { // first we will claim our main output as soon as the delay is over val mainDelayedTx = generateTx("main-delayed-output")(Try { val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) + val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimDelayed, sig) }) @@ -466,7 +466,7 @@ object Helpers { remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) + val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimDelayed, sig) }) } @@ -517,7 +517,7 @@ object Helpers { val preimage = preimages.find(r => sha256(r) == add.paymentHash).get val txinfo = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputsAlreadyUsed, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt - val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) + val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(txinfo, sig, preimage) }) @@ -527,7 +527,7 @@ object Helpers { case DirectedHtlc(IN, add: UpdateAddHtlc) => generateTx("claim-htlc-timeout")(Try { val txinfo = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputsAlreadyUsed, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt - val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) + val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(txinfo, sig) }) }.toSeq.flatten @@ -560,7 +560,7 @@ object Helpers { val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) @@ -611,14 +611,14 @@ object Helpers { // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) // then we punish them by stealing their main output val mainPenaltyTx = generateTx("main-penalty")(Try { val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty) - val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret, SIGHASH_ALL) Transactions.addSigs(txinfo, sig) }) @@ -639,7 +639,7 @@ object Helpers { generateTx("htlc-penalty")(Try { val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, Satoshi(localParams.dustLimitSatoshis), localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt - val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret, SIGHASH_ALL) Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) }) }.toList.flatten @@ -694,7 +694,7 @@ object Helpers { generateTx("claim-htlc-delayed-penalty")(Try { val htlcDelayedPenalty = Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) - val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret, SIGHASH_ALL) val signedTx = Transactions.addSigs(htlcDelayedPenalty, sig) // we need to make sure that the tx is indeed valid Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala index ec0b150194..532ae5caa7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala @@ -48,7 +48,7 @@ trait KeyManager { * @return a signature generated with the private key that matches the input * extended public key */ - def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): BinaryData + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, sigHash: Int): BinaryData /** * This method is used to spend funds send to htlc keys/delayed keys @@ -59,7 +59,7 @@ trait KeyManager { * @return a signature generated with a private key generated from the input keys's matching * private key and the remote point. */ - def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point): BinaryData + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point, sigHash: Int): BinaryData /** * Ths method is used to spend revoked transactions, with the corresponding revocation key @@ -70,7 +70,7 @@ trait KeyManager { * @return a signature generated with a private key generated from the input keys's matching * private key and the remote secret. */ - def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): BinaryData + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar, sigHash: Int): BinaryData def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: BinaryData, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: BinaryData): (BinaryData, BinaryData) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index 30980a4770..5987ba9ff1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -100,9 +100,9 @@ class LocalKeyManager(seed: BinaryData, chainHash: BinaryData) extends KeyManage * @return a signature generated with the private key that matches the input * extended public key */ - def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): BinaryData = { + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, sigHash: Int): BinaryData = { val privateKey = privateKeys.get(publicKey.path) - Transactions.sign(tx, privateKey.privateKey) + Transactions.sign(tx, privateKey.privateKey, sigHash) } /** @@ -114,10 +114,10 @@ class LocalKeyManager(seed: BinaryData, chainHash: BinaryData) extends KeyManage * @return a signature generated with a private key generated from the input keys's matching * private key and the remote point. */ - def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point): BinaryData = { + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point, sigHash: Int): BinaryData = { val privateKey = privateKeys.get(publicKey.path) val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint) - Transactions.sign(tx, currentKey) + Transactions.sign(tx, currentKey, sigHash) } /** @@ -129,10 +129,10 @@ class LocalKeyManager(seed: BinaryData, chainHash: BinaryData) extends KeyManage * @return a signature generated with a private key generated from the input keys's matching * private key and the remote secret. */ - def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): BinaryData = { + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar, sigHash: Int): BinaryData = { val privateKey = privateKeys.get(publicKey.path) val currentKey = Generators.revocationPrivKey(privateKey.privateKey, remoteSecret) - Transactions.sign(tx, currentKey) + Transactions.sign(tx, currentKey, sigHash) } override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: BinaryData, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: BinaryData): (BinaryData, BinaryData) = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index f206b7860f..0aa06b12a8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -571,13 +571,13 @@ object Transactions { }.map(_._2) } - def sign(tx: Transaction, inputIndex: Int, redeemScript: BinaryData, amount: Satoshi, key: PrivateKey): BinaryData = { - Transaction.signInput(tx, inputIndex, redeemScript, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0, key) + def sign(tx: Transaction, inputIndex: Int, redeemScript: BinaryData, amount: Satoshi, key: PrivateKey, sigHash: Int): BinaryData = { + Transaction.signInput(tx, inputIndex, redeemScript, sigHash, amount, SIGVERSION_WITNESS_V0, key) } - def sign(txinfo: TransactionWithInputInfo, key: PrivateKey): BinaryData = { + def sign(txinfo: TransactionWithInputInfo, key: PrivateKey, sigHash: Int): BinaryData = { require(txinfo.tx.txIn.lengthCompare(1) == 0, "only one input allowed") - sign(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, txinfo.input.txOut.amount, key) + sign(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, txinfo.input.txOut.amount, key, sigHash) } def addSigs(commitTx: CommitTx, localFundingPubkey: PublicKey, remoteFundingPubkey: PublicKey, localSig: BinaryData, remoteSig: BinaryData): CommitTx = { @@ -638,8 +638,8 @@ object Transactions { def checkSpendable(txinfo: TransactionWithInputInfo): Try[Unit] = Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn.head.outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) - def checkSig(txinfo: TransactionWithInputInfo, sig: BinaryData, pubKey: PublicKey): Boolean = { - val data = Transaction.hashForSigning(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, SIGHASH_ALL, txinfo.input.txOut.amount, SIGVERSION_WITNESS_V0) + def checkSig(txinfo: TransactionWithInputInfo, sig: BinaryData, pubKey: PublicKey, sigHash: Int): Boolean = { + val data = Transaction.hashForSigning(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, sigHash, txinfo.input.txOut.amount, SIGVERSION_WITNESS_V0) Crypto.verifySignature(data, sig, pubKey) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index a362c65c66..336c248bbc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -193,8 +193,8 @@ class TestVectorsSpec extends FunSuite with Logging { Remote.delayed_payment_pubkey, spec)(ContextCommitmentV1) - val local_sig = Transactions.sign(tx, Local.funding_privkey) - val remote_sig = Transactions.sign(tx, Remote.funding_privkey) + val local_sig = Transactions.sign(tx, Local.funding_privkey, SIGHASH_ALL) + val remote_sig = Transactions.sign(tx, Remote.funding_privkey, SIGHASH_ALL) Transactions.addSigs(tx, Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig) } @@ -224,9 +224,9 @@ class TestVectorsSpec extends FunSuite with Logging { Remote.delayed_payment_pubkey, spec)(ContextCommitmentV1) - val local_sig = Transactions.sign(tx, Local.funding_privkey) + val local_sig = Transactions.sign(tx, Local.funding_privkey, SIGHASH_ALL) logger.info(s"# local_signature = ${toHexString(local_sig.dropRight(1))}") - val remote_sig = Transactions.sign(tx, Remote.funding_privkey) + val remote_sig = Transactions.sign(tx, Remote.funding_privkey, SIGHASH_ALL) logger.info(s"remote_signature: ${toHexString(remote_sig.dropRight(1))}") } @@ -248,12 +248,12 @@ class TestVectorsSpec extends FunSuite with Logging { htlcTxs.collect { case tx: HtlcSuccessTx => - val remoteSig = Transactions.sign(tx, Remote.payment_privkey) + val remoteSig = Transactions.sign(tx, Remote.payment_privkey, SIGHASH_ALL) val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript)) logger.info(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)") logger.info(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}") case tx: HtlcTimeoutTx => - val remoteSig = Transactions.sign(tx, Remote.payment_privkey) + val remoteSig = Transactions.sign(tx, Remote.payment_privkey, SIGHASH_ALL) val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript)) logger.info(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)") logger.info(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}") @@ -262,8 +262,8 @@ class TestVectorsSpec extends FunSuite with Logging { val signedTxs = htlcTxs collect { case tx: HtlcSuccessTx => //val tx = tx0.copy(tx = tx0.tx.copy(txOut = tx0.tx.txOut(0).copy(amount = Satoshi(545)) :: Nil)) - val localSig = Transactions.sign(tx, Local.payment_privkey) - val remoteSig = Transactions.sign(tx, Remote.payment_privkey) + val localSig = Transactions.sign(tx, Local.payment_privkey, SIGHASH_ALL) + val remoteSig = Transactions.sign(tx, Remote.payment_privkey, SIGHASH_ALL) val preimage = paymentPreimages.find(p => Crypto.sha256(p) == tx.paymentHash).get val tx1 = Transactions.addSigs(tx, localSig, remoteSig, preimage) Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -272,8 +272,8 @@ class TestVectorsSpec extends FunSuite with Logging { logger.info(s"output htlc_success_tx ${htlcIndex}: ${tx1.tx}") tx1 case tx: HtlcTimeoutTx => - val localSig = Transactions.sign(tx, Local.payment_privkey) - val remoteSig = Transactions.sign(tx, Remote.payment_privkey) + val localSig = Transactions.sign(tx, Local.payment_privkey, SIGHASH_ALL) + val remoteSig = Transactions.sign(tx, Remote.payment_privkey, SIGHASH_ALL) val tx1 = Transactions.addSigs(tx, localSig, remoteSig) Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) logger.info(s"# local_signature = ${toHexString(localSig.dropRight(1))}") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 84711cb24a..2fff519ca4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -211,8 +211,8 @@ class TransactionsSpec extends FunSuite with Logging { val commitTxNumber = 0x404142434445L def commitTx(commitContext: CommitmentContext = ContextCommitmentV1) = { val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, remoteDelayedPaymentPriv.publicKey, spec)(commitContext) - val localSig = Transactions.sign(txinfo, localPaymentPriv) - val remoteSig = Transactions.sign(txinfo, remotePaymentPriv) + val localSig = Transactions.sign(txinfo, localPaymentPriv, SIGHASH_ALL) + val remoteSig = Transactions.sign(txinfo, remotePaymentPriv, SIGHASH_ALL) Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) } @@ -231,8 +231,8 @@ class TransactionsSpec extends FunSuite with Logging { { // either party spends local->remote htlc output with htlc timeout tx for (htlcTimeoutTx <- htlcTimeoutTxs) { - val localSig = sign(htlcTimeoutTx, localHtlcPriv) - val remoteSig = sign(htlcTimeoutTx, remoteHtlcPriv) + val localSig = sign(htlcTimeoutTx, localHtlcPriv, SIGHASH_ALL) + val remoteSig = sign(htlcTimeoutTx, remoteHtlcPriv, SIGHASH_ALL) val signed = addSigs(htlcTimeoutTx, localSig, remoteSig) assert(checkSpendable(signed).isSuccess) } @@ -265,7 +265,7 @@ class TransactionsSpec extends FunSuite with Logging { { // local spends delayed output of htlc1 timeout tx val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) - val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv) + val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimHtlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit @@ -278,7 +278,7 @@ class TransactionsSpec extends FunSuite with Logging { // remote spends local->remote htlc1/htlc3 output directly in case of success for ((htlc, paymentPreimage) <- (htlc1, paymentPreimage1) :: (htlc3, paymentPreimage3) :: Nil) { val claimHtlcSuccessTx = makeClaimHtlcSuccessTx(commitTx().tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw) - val localSig = sign(claimHtlcSuccessTx, remoteHtlcPriv) + val localSig = sign(claimHtlcSuccessTx, remoteHtlcPriv, SIGHASH_ALL) val signed = addSigs(claimHtlcSuccessTx, localSig, paymentPreimage) assert(checkSpendable(signed).isSuccess) } @@ -287,19 +287,19 @@ class TransactionsSpec extends FunSuite with Logging { { // local spends remote->local htlc2/htlc4 output with htlc success tx using payment preimage for ((htlcSuccessTx, paymentPreimage) <- (htlcSuccessTxs(0), paymentPreimage2) :: (htlcSuccessTxs(1), paymentPreimage4) :: Nil) { - val localSig = sign(htlcSuccessTx, localHtlcPriv) - val remoteSig = sign(htlcSuccessTx, remoteHtlcPriv) + val localSig = sign(htlcSuccessTx, localHtlcPriv, SIGHASH_ALL) + val remoteSig = sign(htlcSuccessTx, remoteHtlcPriv, SIGHASH_ALL) val signedTx = addSigs(htlcSuccessTx, localSig, remoteSig, paymentPreimage) assert(checkSpendable(signedTx).isSuccess) // check remote sig - assert(checkSig(htlcSuccessTx, remoteSig, remoteHtlcPriv.publicKey)) + assert(checkSig(htlcSuccessTx, remoteSig, remoteHtlcPriv.publicKey, SIGHASH_ALL)) } } { // local spends delayed output of htlc2 success tx val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) - val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv) + val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimHtlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc4 timeout tx because it is below the dust limit @@ -311,7 +311,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends main output val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx().tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) - val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv) + val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) } @@ -319,7 +319,7 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends remote->local htlc output directly in case of timeout val claimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx().tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc2, feeratePerKw) - val localSig = sign(claimHtlcTimeoutTx, remoteHtlcPriv) + val localSig = sign(claimHtlcTimeoutTx, remoteHtlcPriv, SIGHASH_ALL) val signed = addSigs(claimHtlcTimeoutTx, localSig) assert(checkSpendable(signed).isSuccess) } @@ -328,7 +328,7 @@ class TransactionsSpec extends FunSuite with Logging { // remote spends offered HTLC output with revocation key val script = Script.write(Scripts.htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc1.paymentHash))) val htlcPenaltyTx = makeHtlcPenaltyTx(commitTx().tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw) - val sig = sign(htlcPenaltyTx, localRevocationPriv) + val sig = sign(htlcPenaltyTx, localRevocationPriv, SIGHASH_ALL) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) } @@ -337,7 +337,7 @@ class TransactionsSpec extends FunSuite with Logging { // remote spends received HTLC output with revocation key val script = Script.write(Scripts.htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc2.paymentHash), htlc2.cltvExpiry)) val htlcPenaltyTx = makeHtlcPenaltyTx(commitTx().tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw) - val sig = sign(htlcPenaltyTx, localRevocationPriv) + val sig = sign(htlcPenaltyTx, localRevocationPriv, SIGHASH_ALL) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) } From 9fd1e95720861ed16079755af32239d4333042b4 Mon Sep 17 00:00:00 2001 From: araspitzu Date: Fri, 1 Mar 2019 12:31:47 +0100 Subject: [PATCH 42/66] Add comments --- .../main/scala/fr/acinq/eclair/crypto/KeyManager.scala | 3 +++ .../scala/fr/acinq/eclair/crypto/LocalKeyManager.scala | 3 +++ .../scala/fr/acinq/eclair/transactions/Transactions.scala | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala index 532ae5caa7..ed745c0421 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala @@ -45,6 +45,7 @@ trait KeyManager { * * @param tx input transaction * @param publicKey extended public key + * @param sigHash the sigHash type used to sign * @return a signature generated with the private key that matches the input * extended public key */ @@ -56,6 +57,7 @@ trait KeyManager { * @param tx input transaction * @param publicKey extended public key * @param remotePoint remote point + * @param sigHash the sigHash type used to sign * @return a signature generated with a private key generated from the input keys's matching * private key and the remote point. */ @@ -67,6 +69,7 @@ trait KeyManager { * @param tx input transaction * @param publicKey extended public key * @param remoteSecret remote secret + * @param sigHash the sigHash type used to sign * @return a signature generated with a private key generated from the input keys's matching * private key and the remote secret. */ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index 5987ba9ff1..e9916fafc8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -97,6 +97,7 @@ class LocalKeyManager(seed: BinaryData, chainHash: BinaryData) extends KeyManage * * @param tx input transaction * @param publicKey extended public key + * @param sigHash the sigHash type used to sign * @return a signature generated with the private key that matches the input * extended public key */ @@ -111,6 +112,7 @@ class LocalKeyManager(seed: BinaryData, chainHash: BinaryData) extends KeyManage * @param tx input transaction * @param publicKey extended public key * @param remotePoint remote point + * @param sigHash the sigHash type used to sign * @return a signature generated with a private key generated from the input keys's matching * private key and the remote point. */ @@ -126,6 +128,7 @@ class LocalKeyManager(seed: BinaryData, chainHash: BinaryData) extends KeyManage * @param tx input transaction * @param publicKey extended public key * @param remoteSecret remote secret + * @param sigHash the sigHash type used to sign * @return a signature generated with a private key generated from the input keys's matching * private key and the remote secret. */ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 0aa06b12a8..355cfb805e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -638,6 +638,14 @@ object Transactions { def checkSpendable(txinfo: TransactionWithInputInfo): Try[Unit] = Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn.head.outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + /** + * + * @param txinfo the transaction containing the signature to check + * @param sig the signature that is going to be checked against + * @param pubKey pubkey of the signature + * @param sigHash the type used to produce the hash for signing + * @return + */ def checkSig(txinfo: TransactionWithInputInfo, sig: BinaryData, pubKey: PublicKey, sigHash: Int): Boolean = { val data = Transaction.hashForSigning(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, sigHash, txinfo.input.txOut.amount, SIGVERSION_WITNESS_V0) Crypto.verifySignature(data, sig, pubKey) From 679ece1d154a27bcff7a4d2344028d7990a6271a Mon Sep 17 00:00:00 2001 From: araspitzu Date: Fri, 1 Mar 2019 14:07:14 +0100 Subject: [PATCH 43/66] Finish merging master --- .../scala/fr/acinq/eclair/channel/Commitments.scala | 10 ++-------- .../fr/acinq/eclair/transactions/Transactions.scala | 2 +- .../scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 8ba9c96738..44e542e712 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -73,11 +73,10 @@ sealed trait Commitments { def announceChannel: Boolean = (channelFlags & 0x01) != 0 // TODO subtract the pushMe value from the balance? - // TODO figure out the type of commitment from the type of this? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val fees = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced)(commitmentContext = getContext).amount else 0 - reduced.toRemoteMsat / 1000 - remoteParams.channelReserveSatoshis - fees + val feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced)(commitmentContext = getContext).amount * 1000 else 0 + reduced.toRemoteMsat - remoteParams.channelReserveSatoshis * 1000 - feesMsat } // get the context for this commitment @@ -126,11 +125,6 @@ case class SimplifiedCommitment(localParams: LocalParams, remoteParams: RemotePa channelId: BinaryData) extends Commitments { - def availableBalanceForSendMsat: Long = { - val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced).amount * 1000 else 0 - reduced.toRemoteMsat - remoteParams.channelReserveSatoshis * 1000 - feesMsat - } override def getContext: CommitmentContext = ContextSimplifiedCommitment } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 355cfb805e..9cbe8f5879 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -99,7 +99,7 @@ object Transactions { val htlcTimeoutWeight = 663 val htlcSuccessWeight = 703 val simplifiedFeerateKw = 253 - val pushMeValue = Satoshi(1000) + val pushMeValue = Satoshi(1000) // used in option_simplified_commitment /** * these values specific to us and used to estimate fees diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala index fcfb88560b..17536a3611 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala @@ -48,7 +48,7 @@ class SqliteAuditDbSpec extends FunSuite { val e4 = NetworkFeePaid(null, randomKey.publicKey, randomBytes(32), Transaction(0, Seq.empty, Seq.empty, 0), Satoshi(42), "mutual") val e5 = PaymentSent(MilliSatoshi(42000), MilliSatoshi(1000), randomBytes(32), randomBytes(32), randomBytes(32), timestamp = 0) val e6 = PaymentSent(MilliSatoshi(42000), MilliSatoshi(1000), randomBytes(32), randomBytes(32), randomBytes(32), timestamp = Platform.currentTime * 2) - val e7 = AvailableBalanceChanged(null, randomBytes(32), ShortChannelId(500000, 42, 1), 456123000, ChannelStateSpec.commitments) + val e7 = AvailableBalanceChanged(null, randomBytes(32), ShortChannelId(500000, 42, 1), 456123000, ChannelStateSpec.commitmentsV1) val e8 = ChannelLifecycleEvent(randomBytes(32), randomKey.publicKey, 456123000, true, false, "mutual") db.add(e1) From 3b5da6fce47598cd03fb1dc600fbbe51ec5fef38 Mon Sep 17 00:00:00 2001 From: araspitzu Date: Fri, 1 Mar 2019 14:40:59 +0100 Subject: [PATCH 44/66] Add test for send/receive hltc (commitSig + revokeAndAck) with option_simplified_commitment --- .../eclair/transactions/Transactions.scala | 2 +- .../channel/states/e/NormalStateSpec.scala | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 9cbe8f5879..ee97c1e119 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -288,7 +288,7 @@ object Transactions { lockTime = 0), htlc.paymentHash) } - //TODO adjust for option_simplified_commitment + //TODO adjust for option_simplified_commitment? def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 7745015a37..707b0a6b28 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -659,6 +659,34 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.signed.size == 1) } + test("recv CommitSig (one htlc received, option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val sender = TestProbe() + + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + + // actual test begins + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + + bob2alice.expectMsgType[RevokeAndAck] + // bob replies immediately with a signature + bob2alice.expectMsgType[CommitSig] + + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN)) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked.size == 0) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.signed.size == 1) + } + + test("recv CommitSig (one htlc sent)") { f => import f._ val sender = TestProbe() @@ -682,6 +710,31 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) } + test("recv CommitSig (one htlc sent, option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val sender = TestProbe() + + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + + // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == OUT)) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) + } + test("recv CommitSig (multiple htlcs in both directions)") { f => import f._ val sender = TestProbe() From 6de1495a8448a59477232695df8ef4e0b1865587 Mon Sep 17 00:00:00 2001 From: araspitzu Date: Fri, 1 Mar 2019 16:00:05 +0100 Subject: [PATCH 45/66] Negotiate closing tx *upwards* from fundee->funder --- .../main/scala/fr/acinq/eclair/channel/Channel.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index b31f7134c0..f7acd5d2f1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -784,8 +784,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // are there pending signed htlcs on either side? we need to have received their last revocation! if (d.commitments.hasNoPendingHtlcs) { // there are no pending signed htlcs, let's go directly to NEGOTIATING - if (d.commitments.localParams.isFunder) { - // we are funder, need to initiate the negotiation by sending the first closing_signed + if (d.commitments.localParams.isFunder || d.commitments.getContext == ContextSimplifiedCommitment) { + // we are funder or we're using option_simplified_commitment, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned } else { @@ -1040,8 +1040,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } if (commitments1.hasNoPendingHtlcs) { log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") - if (d.commitments.localParams.isFunder) { - // we are funder, need to initiate the negotiation by sending the first closing_signed + if (d.commitments.localParams.isFunder || commitments1.getContext == ContextSimplifiedCommitment) { + // we are funder or we're using option_simplified_commitmemt, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned } else { @@ -1475,7 +1475,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // BOLT 2: A node if it has sent a previous shutdown MUST retransmit shutdown. // negotiation restarts from the beginning, and is initialized by the funder // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them - if (d.commitments.localParams.isFunder) { + if (d.commitments.localParams.isFunder || d.commitments.getContext == ContextSimplifiedCommitment) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) From e9fb058345cb921db9a511d754fa9b389ab5560d Mon Sep 17 00:00:00 2001 From: sstone Date: Sat, 2 Mar 2019 15:19:42 +0100 Subject: [PATCH 46/66] Bitcoin Wallet: cleaner handling of signtransaction errors --- .../bitcoind/BitcoinCoreWallet.scala | 6 +--- .../bitcoind/BitcoinCoreWalletSpec.scala | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala index d3a27696bb..5723a177ba 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala @@ -52,11 +52,7 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC val JString(hex) = json \ "hex" val JBool(complete) = json \ "complete" if (!complete) { - val message = json \ "errors" match { - case value: JValue => - Serialization.write(value)(DefaultFormats) - case _ => "signrawtransactionwithwallet failed" - } + val message = (json \ "errors" \\ classOf[JString]).mkString(",") throw new JsonRPCError(Error(-1, message)) } SignTransactionResponse(Transaction.read(hex), complete) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index b73a187095..d077646ae7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -21,7 +21,7 @@ import akka.actor.Status.Failure import akka.pattern.pipe import akka.testkit.{TestKit, TestProbe} import com.typesafe.config.ConfigFactory -import fr.acinq.bitcoin.{Block, MilliBtc, Satoshi, Script} +import fr.acinq.bitcoin.{Block, MilliBtc, Satoshi, Script, Transaction, TxOut} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.FundTransactionResponse import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, JsonRPCError} @@ -94,6 +94,32 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe } } + test("handle error when signing transactions") { + val bitcoinClient = new BasicBitcoinJsonRPCClient( + user = config.getString("bitcoind.rpcuser"), + password = config.getString("bitcoind.rpcpassword"), + host = config.getString("bitcoind.host"), + port = config.getInt("bitcoind.rpcport")) + val wallet = new BitcoinCoreWallet(bitcoinClient) + + val sender = TestProbe() + + // create and fund a transaction + wallet.getFinalAddress.pipeTo(sender.ref) + val address = sender.expectMsgType[String] + val unsignedTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(Satoshi(1000000), addressToPublicKeyScript(address, Block.RegtestGenesisBlock.hash)) :: Nil, lockTime = 0) + wallet.fundTransaction(unsignedTx, false, 1500).pipeTo(sender.ref) + val FundTransactionResponse(fundedTx, _, _) = sender.expectMsgType[FundTransactionResponse] + + // change the index of the UTXO that it spends + val fundedTx1 = fundedTx.copy(txIn = fundedTx.txIn.updated(0, fundedTx.txIn(0).copy(outPoint = fundedTx.txIn(0).outPoint.copy(index = fundedTx.txIn(0).outPoint.index + 1)))) + + // signing it should fail, and the error message should contain the txid of the UTXO that could not be used + wallet.signTransaction(fundedTx1).pipeTo(sender.ref) + val Failure(JsonRPCError(error)) = sender.expectMsgType[Failure] + assert(error.message.contains(fundedTx1.txIn(0).outPoint.txid.toString())) + } + test("create/commit/rollback funding txes") { val bitcoinClient = new BasicBitcoinJsonRPCClient( user = config.getString("bitcoind.rpcuser"), From de83752feb8e57c42803dddc291a13ac146670d8 Mon Sep 17 00:00:00 2001 From: sstone Date: Sun, 3 Mar 2019 20:35:11 +0100 Subject: [PATCH 47/66] Check that bitcoind version is 0.17.0 or higher Plus minor code reformatting for some tests. --- eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala | 4 +--- .../blockchain/bitcoind/BitcoinCoreWalletSpec.scala | 9 ++++++++- .../blockchain/bitcoind/ExtendedBitcoinClientSpec.scala | 9 ++++++++- .../blockchain/fee/BitcoinCoreFeeProviderSpec.scala | 9 ++++++++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index a9ea3d376c..39eab054a5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -126,15 +126,13 @@ class Setup(datadir: File, } yield (progress, chainHash, bitcoinVersion, unspentAddresses, blocks, headers) // blocking sanity checks val (progress, chainHash, bitcoinVersion, unspentAddresses, blocks, headers) = await(future, 30 seconds, "bicoind did not respond after 30 seconds") - assert(bitcoinVersion >= 160300, "Eclair requires Bitcoin Core 0.16.3 or higher") + assert(bitcoinVersion >= 170000, "Eclair requires Bitcoin Core 0.17.0 or higher") assert(chainHash == nodeParams.chainHash, s"chainHash mismatch (conf=${nodeParams.chainHash} != bitcoind=$chainHash)") if (chainHash != Block.RegtestGenesisBlock.hash) { assert(unspentAddresses.forall(address => !isPay2PubkeyHash(address)), "Make sure that all your UTXOS are segwit UTXOS and not p2pkh (check out our README for more details)") } assert(progress > 0.999, s"bitcoind should be synchronized (progress=$progress") assert(headers - blocks <= 1, s"bitcoind should be synchronized (headers=$headers blocks=$blocks") - // TODO: add a check on bitcoin version? - Bitcoind(bitcoinClient) case ELECTRUM => val addresses = config.hasPath("electrum") match { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index d077646ae7..f1a5842a52 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -41,7 +41,14 @@ import scala.util.{Random, Try} class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging { - val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false)) + val commonConfig = ConfigFactory.parseMap(Map( + "eclair.chain" -> "regtest", + "eclair.spv" -> false, + "eclair.server.public-ips.1" -> "localhost", + "eclair.bitcoind.port" -> 28333, + "eclair.bitcoind.rpcport" -> 28332, + "eclair.router-broadcast-interval" -> "2 second", + "eclair.auto-reconnect" -> false)) val config = ConfigFactory.load(commonConfig).getConfig("eclair") val walletPassword = Random.alphanumeric.take(8).mkString diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala index 0b6c7ac335..18a08923de 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala @@ -34,7 +34,14 @@ import scala.concurrent.ExecutionContext.Implicits.global class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging { - val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false)) + val commonConfig = ConfigFactory.parseMap(Map( + "eclair.chain" -> "regtest", + "eclair.spv" -> false, + "eclair.server.public-ips.1" -> "localhost", + "eclair.bitcoind.port" -> 28333, + "eclair.bitcoind.rpcport" -> 28332, + "eclair.router-broadcast-interval" -> "2 second", + "eclair.auto-reconnect" -> false)) val config = ConfigFactory.load(commonConfig).getConfig("eclair") implicit val formats = DefaultFormats diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala index 82c292166c..7b9126b2ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala @@ -38,7 +38,14 @@ import scala.util.Random class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging { - val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false)) + val commonConfig = ConfigFactory.parseMap(Map( + "eclair.chain" -> "regtest", + "eclair.spv" -> false, + "eclair.server.public-ips.1" -> "localhost", + "eclair.bitcoind.port" -> 28333, + "eclair.bitcoind.rpcport" -> 28332, + "eclair.router-broadcast-interval" -> "2 second", + "eclair.auto-reconnect" -> false)) val config = ConfigFactory.load(commonConfig).getConfig("eclair") val walletPassword = Random.alphanumeric.take(8).mkString From 794c0d71828cbe7e4131f87b11ba868d73b258cc Mon Sep 17 00:00:00 2001 From: araspitzu Date: Mon, 4 Mar 2019 08:58:16 +0100 Subject: [PATCH 48/66] WIP closing --- .../fr/acinq/eclair/channel/Channel.scala | 2 +- .../fr/acinq/eclair/channel/Helpers.scala | 21 ++++++---- .../states/g/NegotiatingStateSpec.scala | 42 +++++++++++++++---- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index f7acd5d2f1..7b0ce6a30f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -784,7 +784,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // are there pending signed htlcs on either side? we need to have received their last revocation! if (d.commitments.hasNoPendingHtlcs) { // there are no pending signed htlcs, let's go directly to NEGOTIATING - if (d.commitments.localParams.isFunder || d.commitments.getContext == ContextSimplifiedCommitment) { + if (d.commitments.localParams.isFunder) { // we are funder or we're using option_simplified_commitment, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 70dbb19a76..d02f028970 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -349,15 +349,18 @@ object Helpers { } } - def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = { - import commitments._ - // this is just to estimate the weight, it depends on size of the pubkey scripts - val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) - val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, remoteParams.fundingPubKey, "aa" * 71, "bb" * 71).tx) - // no need to use a very high fee here, so we target 6 blocks; also, we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" - val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw) - log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") - Transactions.weight2fee(feeratePerKw, closingWeight) + def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = commitments match { + case c: CommitmentsV1 => + import commitments._ + // this is just to estimate the weight, it depends on size of the pubkey scripts + val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) + val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, remoteParams.fundingPubKey, "aa" * 71, "bb" * 71).tx) + // no need to use a very high fee here, so we target 6 blocks; also, we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" + val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw) + log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") + Transactions.weight2fee(feeratePerKw, closingWeight) + case s: SimplifiedCommitment => + Satoshi(282) } def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 1ebc75c8b6..040033da80 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -45,8 +45,11 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods override def withFixture(test: OneArgTest): Outcome = { val setup = init() import setup._ + + val isSimplifiedCommitment = test.tags.contains("simplified_commitment") + within(30 seconds) { - reachNormal(setup) + reachNormal(setup, test.tags) val sender = TestProbe() // alice initiates a closing if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4319)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(10000)) @@ -54,13 +57,16 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods bob2alice.expectMsgType[Shutdown] bob2alice.forward(alice) alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == NEGOTIATING) - // NB: at this point, alice has already computed and sent the first ClosingSigned message - // In order to force a fee negotiation, we will change the current fee before forwarding - // the Shutdown message to alice, so that alice computes a different initial closing fee. - if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000)) - alice2bob.forward(bob) - awaitCond(bob.stateName == NEGOTIATING) + if(!isSimplifiedCommitment){ + awaitCond(alice.stateName == NEGOTIATING) + // NB: at this point, alice has already computed and sent the first ClosingSigned message + // In order to force a fee negotiation, we will change the current fee before forwarding + // the Shutdown message to alice, so that alice computes a different initial closing fee. + if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000)) + alice2bob.forward(bob) + awaitCond(bob.stateName == NEGOTIATING) + } + withFixture(test.toNoArgTest(setup)) } } @@ -93,6 +99,25 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods awaitCond(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.map(_.localClosingSigned) == initialState.closingTxProposed.last.map(_.localClosingSigned) :+ aliceCloseSig2) } + test("recv ClosingSigned (option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + + awaitCond(alice.stateName == NEGOTIATING) + + + val closingSig = alice2bob.expectMsgType[ClosingSigned] + + assert(closingSig.feeSatoshis == 282) + + bob2alice.forward(alice) + + awaitCond(bob.stateName == NORMAL) + + val bobCloseSig1 = bob2alice.expectMsgType[ClosingSigned] //err + bob2alice.forward(alice) + assert(1 == 1) + } + private def testFeeConverge(f: FixtureParam) = { import f._ var aliceCloseFee, bobCloseFee = 0L @@ -186,7 +211,6 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods assert(alice.stateName == CLOSING) } - test("recv CMD_CLOSE") { f => import f._ val sender = TestProbe() From ae7fafda2a0f807d2255267baea2c27717eec475 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Mar 2019 11:37:16 +0100 Subject: [PATCH 49/66] Negotiate closing tx *upwards* from fundee->funder --- .../fr/acinq/eclair/channel/Channel.scala | 16 ++++++-- .../acinq/eclair/channel/ChannelTypes.scala | 2 +- .../states/g/NegotiatingStateSpec.scala | 39 +++++++++---------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 7b0ce6a30f..df45cdaa63 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -784,13 +784,18 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // are there pending signed htlcs on either side? we need to have received their last revocation! if (d.commitments.hasNoPendingHtlcs) { // there are no pending signed htlcs, let's go directly to NEGOTIATING - if (d.commitments.localParams.isFunder) { - // we are funder or we're using option_simplified_commitment, need to initiate the negotiation by sending the first closing_signed + if (d.commitments.localParams.isFunder && d.commitments.getContext == ContextCommitmentV1) { + // we are funder and we're using commitmentV1, need to initiate the negotiation by sending the first closing_signed + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned + } else if(!d.commitments.localParams.isFunder && d.commitments.getContext == ContextSimplifiedCommitment) { + // we are fundee BUT we're using option_simplified_commitment, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned } else { // we are fundee, will wait for their closing_signed goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, closingTxProposed = List(List()), bestUnpublishedClosingTx_opt = None)) sending sendList + } } else { @@ -972,7 +977,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d@DATA_SHUTDOWN(commitments: CommitmentsV1, _, _)) => + case Event(c@CMD_SIGN, d@DATA_SHUTDOWN(commitments: Commitments, _, _)) => d.commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -994,7 +999,10 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - stay using d.copy(commitments = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) + commitments match { + case c: CommitmentsV1 => stay using d.copy(commitments = c.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) + case s: SimplifiedCommitment => stay using d.copy(commitments = s.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) + } } case Event(commit: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index f0b45896aa..a7c3a78014 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -166,7 +166,7 @@ final case class DATA_NEGOTIATING(commitments: Commitments, closingTxProposed: List[List[ClosingTxProposed]], // one list for every negotiation (there can be several in case of disconnection) bestUnpublishedClosingTx_opt: Option[Transaction]) extends Data with HasCommitments { require(!closingTxProposed.isEmpty, "there must always be a list for the current negotiation") - require(!commitments.localParams.isFunder || closingTxProposed.forall(!_.isEmpty), "funder must have at least one closing signature for every negotation attempt because it initiates the closing") + require(commitments.getContext == ContextSimplifiedCommitment || !commitments.localParams.isFunder || closingTxProposed.forall(!_.isEmpty), "funder must have at least one closing signature for every negotation attempt because it initiates the closing") } final case class DATA_CLOSING(commitments: Commitments, mutualCloseProposed: List[Transaction], // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 040033da80..88d85e08bf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -46,8 +46,6 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods val setup = init() import setup._ - val isSimplifiedCommitment = test.tags.contains("simplified_commitment") - within(30 seconds) { reachNormal(setup, test.tags) val sender = TestProbe() @@ -57,16 +55,13 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods bob2alice.expectMsgType[Shutdown] bob2alice.forward(alice) alice2bob.expectMsgType[Shutdown] - if(!isSimplifiedCommitment){ - awaitCond(alice.stateName == NEGOTIATING) - // NB: at this point, alice has already computed and sent the first ClosingSigned message - // In order to force a fee negotiation, we will change the current fee before forwarding - // the Shutdown message to alice, so that alice computes a different initial closing fee. - if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000)) - alice2bob.forward(bob) - awaitCond(bob.stateName == NEGOTIATING) - } - + awaitCond(alice.stateName == NEGOTIATING) + // NB: at this point, alice has already computed and sent the first ClosingSigned message + // In order to force a fee negotiation, we will change the current fee before forwarding + // the Shutdown message to alice, so that alice computes a different initial closing fee. + if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000)) + alice2bob.forward(bob) + awaitCond(bob.stateName == NEGOTIATING) withFixture(test.toNoArgTest(setup)) } } @@ -103,19 +98,21 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods import f._ awaitCond(alice.stateName == NEGOTIATING) + alice2bob.forward(bob) + awaitCond(bob.stateName == NEGOTIATING) - - val closingSig = alice2bob.expectMsgType[ClosingSigned] - + // bob should send the first closing signatures with a hardcoded base fee of 282 sats + val closingSig = bob2alice.expectMsgType[ClosingSigned] assert(closingSig.feeSatoshis == 282) - bob2alice.forward(alice) - awaitCond(bob.stateName == NORMAL) - val bobCloseSig1 = bob2alice.expectMsgType[ClosingSigned] //err - bob2alice.forward(alice) - assert(1 == 1) + val closingSig2 = alice2bob.expectMsgType[ClosingSigned] + assert(closingSig2.feeSatoshis == 282) // alice sends back the same fees + alice2bob.forward(bob) + + // bob will now publish the closing tx + bob2blockchain.expectMsgType[PublishAsap] } private def testFeeConverge(f: FixtureParam) = { @@ -145,7 +142,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods val sender = TestProbe() val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx sender.send(bob, aliceCloseSig.copy(feeSatoshis = 99000)) // sig doesn't matter, it is checked later - val error = bob2alice.expectMsgType[Error] + val error = bob2alice.expectMsgType[Error] assert(new String(error.data).startsWith("invalid close fee: fee_satoshis=99000")) bob2blockchain.expectMsg(PublishAsap(tx)) bob2blockchain.expectMsgType[PublishAsap] From a7099917e2e919e367cea7088cbbcef45951226e Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Mar 2019 13:13:25 +0100 Subject: [PATCH 50/66] Use feerate_kw=0 when computing HTLC-TX fees --- .../scala/fr/acinq/eclair/channel/Channel.scala | 9 +++++---- .../scala/fr/acinq/eclair/channel/Helpers.scala | 4 ++-- .../eclair/transactions/Transactions.scala | 17 +++++++++++------ .../eclair/transactions/TestVectorsSpec.scala | 2 +- .../eclair/transactions/TransactionsSpec.scala | 14 ++++++++++---- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index df45cdaa63..78e9500d19 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -647,7 +647,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val nextCommitNumber = nextRemoteCommit.index // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec) + val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.getContext) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.getContext) trimmedHtlcs collect { case DirectedHtlc(_, u) => log.info(s"adding paymentHash=${u.paymentHash} cltvExpiry=${u.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") @@ -1229,6 +1229,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(WatchEventConfirmed(BITCOIN_TX_CONFIRMED(tx), blockHeight, _), d: DATA_CLOSING) => log.info(s"txid=${tx.txid} has reached mindepth, updating closing state") + implicit val commitmentContext = d.commitments.getContext // first we check if this tx belongs to one of the current local/remote commits and update it val localCommitPublished1 = d.localCommitPublished.map(Closing.updateLocalCommitPublished(_, tx)) val remoteCommitPublished1 = d.remoteCommitPublished.map(Closing.updateRemoteCommitPublished(_, tx)) @@ -1268,13 +1269,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } // then let's see if any of the possible close scenarii can be considered done val mutualCloseDone = d.mutualClosePublished.exists(_.txid == tx.txid) // this case is trivial, in a mutual close scenario we only need to make sure that one of the closing txes is confirmed - val localCommitDone = localCommitPublished1.map(Closing.isLocalCommitDone(_)).getOrElse(false) + val localCommitDone = localCommitPublished1.map(Closing.isLocalCommitDone(_)).getOrElse(false) val remoteCommitDone = remoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false) val nextRemoteCommitDone = nextRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false) val futureRemoteCommitDone = futureRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false) val revokedCommitDone = revokedCommitPublished1.map(Closing.isRevokedCommitDone(_)).exists(_ == true) // we only need one revoked commit done - // finally, if one of the unilateral closes is done, we move to CLOSED state, otherwise we stay (note that we don't store the state) - val d1 = d.copy(localCommitPublished = localCommitPublished1, remoteCommitPublished = remoteCommitPublished1, nextRemoteCommitPublished = nextRemoteCommitPublished1, futureRemoteCommitPublished = futureRemoteCommitPublished1, revokedCommitPublished = revokedCommitPublished1) + // finally, if one of the unilateral closes is done, we move to CLOSED state, otherwise we stay (note that we don't store the state) + val d1 = d.copy(localCommitPublished = localCommitPublished1, remoteCommitPublished = remoteCommitPublished1, nextRemoteCommitPublished = nextRemoteCommitPublished1, futureRemoteCommitPublished = futureRemoteCommitPublished1, revokedCommitPublished = revokedCommitPublished1) // we also send events related to fee Closing.networkFeePaid(tx, d1) map { case (fee, desc) => feePaid(fee, tx, desc, d.channelId) } val closeType_opt = if (mutualCloseDone) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index d02f028970..e01b4bd6b6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -761,7 +761,7 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentContext, log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == localCommit.publishableTxs.commitTx.tx.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) (localCommit.spec.htlcs.filter(_.direction == OUT) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec)).map(_.add) @@ -785,7 +785,7 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentContext, log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == remoteCommit.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) (remoteCommit.spec.htlcs.filter(_.direction == IN) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec)).map(_.add) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index ee97c1e119..ca559d4374 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -121,16 +121,22 @@ object Transactions { */ def fee2rate(fee: Satoshi, weight: Int) = (fee.amount * 1000L) / weight - def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec): Seq[DirectedHtlc] = { - val htlcTimeoutFee = weight2fee(spec.feeratePerKw, htlcTimeoutWeight) + def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): Seq[DirectedHtlc] = { + val htlcTimeoutFee = commitmentContext match { + case ContextCommitmentV1 => weight2fee(spec.feeratePerKw, htlcTimeoutWeight) + case ContextSimplifiedCommitment => Satoshi(0) + } spec.htlcs .filter(_.direction == OUT) .filter(htlc => MilliSatoshi(htlc.add.amountMsat) >= (dustLimit + htlcTimeoutFee)) .toSeq } - def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec): Seq[DirectedHtlc] = { - val htlcSuccessFee = weight2fee(spec.feeratePerKw, htlcSuccessWeight) + def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): Seq[DirectedHtlc] = { + val htlcSuccessFee = commitmentContext match { + case ContextCommitmentV1 => weight2fee(spec.feeratePerKw, htlcSuccessWeight) + case ContextSimplifiedCommitment => Satoshi(0) + } spec.htlcs .filter(_.direction == IN) .filter(htlc => MilliSatoshi(htlc.add.amountMsat) >= (dustLimit + htlcSuccessFee)) @@ -288,8 +294,7 @@ object Transactions { lockTime = 0), htlc.paymentHash) } - //TODO adjust for option_simplified_commitment? - def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => val htlcTx = makeHtlcTimeoutTx(commitTx, outputsAlreadyUsed, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec.feeratePerKw, htlc.add) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 336c248bbc..de4146f415 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -240,7 +240,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key - spec) + spec)(commitmentContext = ContextCommitmentV1) logger.info(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}") val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 259b8d1ea9..3e4930276e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -62,14 +62,14 @@ class TransactionsSpec extends FunSuite with Logging { test("compute fees") { - val expectedSizeSimplifiedCommitment = weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * 2) + val expectedSizeSimplifiedCommitment = weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * 4) val expectedSizeCommitmentV1 = Satoshi(5340) // see BOLT #3 specs val htlcs = Set( - DirectedHtlc(OUT, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(5000000).amount, Hash.Zeroes, 552, BinaryData.empty)), // trimmed + DirectedHtlc(OUT, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(5000000).amount, Hash.Zeroes, 552, BinaryData.empty)), DirectedHtlc(OUT, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(1000000).amount, Hash.Zeroes, 553, BinaryData.empty)), - DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(7000000).amount, Hash.Zeroes, 550, BinaryData.empty)), // trimmed + DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(7000000).amount, Hash.Zeroes, 550, BinaryData.empty)), DirectedHtlc(IN, UpdateAddHtlc("00" * 32, 0, MilliSatoshi(800000).amount, Hash.Zeroes, 551, BinaryData.empty)) ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0) @@ -216,6 +216,10 @@ class TransactionsSpec extends FunSuite with Logging { Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) } + def createHtlcTxs(commitmentContext: CommitmentContext = ContextCommitmentV1):(Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + makeHtlcTxs(commitTx(commitmentContext).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentContext) + } + { assert(getCommitTxNumber(commitTx().tx, true, localPaymentPriv.publicKey, remotePaymentPriv.publicKey) == commitTxNumber) val hash: Array[Byte] = Crypto.sha256(localPaymentPriv.publicKey.toBin ++ remotePaymentPriv.publicKey.toBin) @@ -223,7 +227,9 @@ class TransactionsSpec extends FunSuite with Logging { val check = ((commitTx().tx.txIn(0).sequence & 0xffffff) << 24) | (commitTx().tx.lockTime) assert((check ^ num) == commitTxNumber) } - val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx().tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) + + //val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx().tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentContext = ContextCommitmentV1) + val (htlcTimeoutTxs, htlcSuccessTxs) = createHtlcTxs(ContextCommitmentV1) assert(htlcTimeoutTxs.size == 2) // htlc1 and htlc3 assert(htlcSuccessTxs.size == 2) // htlc2 and htlc4 From bdab013b07903e8c5e0540972f927dc7ee98dc8b Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Mar 2019 13:44:55 +0100 Subject: [PATCH 51/66] Print commitment type in toString helpers --- .../src/main/scala/fr/acinq/eclair/channel/Commitments.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 44e542e712..cec3a5cbdf 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -675,7 +675,7 @@ object Commitments { def changes2String(commitments: Commitments): String = { import commitments._ - s"""commitments: + s"""(${commitments.getContext}) commitments: | localChanges: | proposed: ${localChanges.proposed.map(msg2String(_)).mkString(" ")} | signed: ${localChanges.signed.map(msg2String(_)).mkString(" ")} @@ -690,7 +690,7 @@ object Commitments { } def specs2String(commitments: Commitments): String = { - s"""specs: + s"""(${commitments.getContext}) specs: |localcommit: | toLocal: ${commitments.localCommit.spec.toLocalMsat} | toRemote: ${commitments.localCommit.spec.toRemoteMsat} From 25e046e85b372995efae969874a4116b8d46e19d Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 4 Mar 2019 16:17:00 +0100 Subject: [PATCH 52/66] WIP claim main delayed output with option_simplified_commitment --- .../fr/acinq/eclair/channel/Helpers.scala | 32 +++++++++++++------ .../eclair/transactions/Transactions.scala | 16 +++------- .../transactions/TransactionsSpec.scala | 32 +++++++++++-------- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index e01b4bd6b6..baca9fef33 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -543,7 +543,8 @@ object Helpers { /** * - * Claim our Main output, if the commitment was of type `option_simplified_commitment` we also claim the to_local_pushme output + * Claim our Main output + * * * @param commitments either our current commitment data in case of usual remote uncooperative closing * or our outdated commitment data in case of data loss protection procedure; in any case it is used only @@ -555,17 +556,28 @@ object Helpers { def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { implicit val commitmentContext = commitments.getContext - val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - // no need to use a high fee rate for our main output (we are the only one who can spend it) val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 - val mainTx = generateTx("claim-p2wpkh-output")(Try { - val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), - localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) - Transactions.addSigs(claimMain, localPubkey, sig) - }) + val mainTx = commitments.getContext match { + case ContextCommitmentV1 => + val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + generateTx("claim-p2wpkh-output")(Try { + val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), + localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain, None) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) + Transactions.addSigs(claimMain, localPubkey, sig) + }) + + case ContextSimplifiedCommitment => + val localPubkey = keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey + generateTx("claim-p2wpkh-output")(Try { + val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), + localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain, Some(commitments.localParams.toSelfDelay)) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) + Transactions.addSigs(claimMain, localPubkey, sig) + }) + } RemoteCommitPublished( commitTx = commitTx, @@ -613,7 +625,7 @@ object Helpers { // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { - val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) + val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain, ???) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index ca559d4374..6527982b0c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -357,7 +357,7 @@ object Transactions { ClaimHtlcTimeoutTx(input, tx1) } - def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): ClaimP2WPKHOutputTx = { + def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int])(implicit commitmentContext: CommitmentContext): ClaimP2WPKHOutputTx = { val claimTx = commitmentContext match { case ContextCommitmentV1 => @@ -376,28 +376,22 @@ object Transactions { // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)) + // here localPaymentPubkey == localDelayedPaymentPubkey case ContextSimplifiedCommitment => val redeemScript = Script.pay2pkh(localPaymentPubkey) - val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) + val pubkeyScript = write(toRemoteDelayed(localPaymentPubkey, toRemoteDelay.getOrElse(throw new IllegalArgumentException("Error claiming the main output from remote commit, no 'toRemoteDelay' specified. (option_simplified_commitment)")))) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) - val pushMeOutputIndex = findPushMeOutputIndex(localPaymentPubkey, delayedOutputTx).getOrElse { - throw new IllegalArgumentException("Could not find the push me output") - } - val pushMeOutputRedeemScript = Script.pay2wsh(Scripts.pushMeSimplified(localPaymentPubkey)) - val pushMeInput = InputInfo(OutPoint(delayedOutputTx, pushMeOutputIndex), delayedOutputTx.txOut(pushMeOutputIndex), pushMeOutputRedeemScript) - // unsigned tx val tx = Transaction( version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: TxIn(pushMeInput.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, + txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, lockTime = 0) // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey - val signed1 = Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)).tx - Transactions.addSigs(ClaimP2WPKHOutputTx(pushMeInput, signed1), BinaryData("00" * 33), BinaryData("00" * 73)) + Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 3e4930276e..3b64daa5e4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -95,7 +95,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimP2WPKHOutputTx val pubKeyScript = write(pay2wpkh(localPaymentPriv.publicKey)) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(ContextCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimP2WPKHOutputTx, localPaymentPriv.publicKey, "bb" * 73).tx) assert(claimP2WPKHOutputWeight == weight) @@ -228,7 +228,6 @@ class TransactionsSpec extends FunSuite with Logging { assert((check ^ num) == commitTxNumber) } - //val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx().tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentContext = ContextCommitmentV1) val (htlcTimeoutTxs, htlcSuccessTxs) = createHtlcTxs(ContextCommitmentV1) assert(htlcTimeoutTxs.size == 2) // htlc1 and htlc3 @@ -269,17 +268,17 @@ class TransactionsSpec extends FunSuite with Logging { assert(toRemoteMainOut.amount == expectedToRemoteAmount) } - { - // local spends delayed output of htlc1 timeout tx - val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) - val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) - val signedTx = addSigs(claimHtlcDelayed, localSig) - assert(checkSpendable(signedTx).isSuccess) - // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit - intercept[RuntimeException] { - makeClaimDelayedOutputTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) - } - } +// { +// // local spends delayed output of htlc1 timeout tx +// val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) +// val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) +// val signedTx = addSigs(claimHtlcDelayed, localSig) +// assert(checkSpendable(signedTx).isSuccess) +// // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit +// intercept[RuntimeException] { +// makeClaimDelayedOutputTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) +// } +// } { // remote spends local->remote htlc1/htlc3 output directly in case of success @@ -317,10 +316,15 @@ class TransactionsSpec extends FunSuite with Logging { { // remote spends main output - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx().tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx(ContextCommitmentV1).tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(ContextCommitmentV1) val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) + +// val claimP2WPKHOutputTx1 = makeClaimP2WPKHOutputTx(commitTx(ContextSimplifiedCommitment).tx, localDustLimit, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, Some(toLocalDelay))(ContextSimplifiedCommitment) +// val localSig1 = sign(claimP2WPKHOutputTx1, localDelayedPaymentPriv, SIGHASH_ALL) +// val signedTx1 = addSigs(claimP2WPKHOutputTx1, localDelayedPaymentPriv.publicKey, localSig1) +// assert(checkSpendable(signedTx1).isSuccess) } { From 90daf551b0005a6f2cb7a86001b59e820ea48ae0 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 5 Mar 2019 15:58:27 +0100 Subject: [PATCH 53/66] Add pushMe trasaction to LocalCommitPublished, WIP CPFP.. --- .../fr/acinq/eclair/channel/Channel.scala | 5 +- .../acinq/eclair/channel/ChannelTypes.scala | 4 +- .../fr/acinq/eclair/channel/Helpers.scala | 24 +++++-- .../eclair/transactions/Transactions.scala | 72 +++++++++++-------- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 2 + .../channel/states/e/NormalStateSpec.scala | 1 + .../states/g/NegotiatingStateSpec.scala | 1 - .../channel/states/h/ClosingStateSpec.scala | 32 ++++++++- .../transactions/TransactionsSpec.scala | 1 + 9 files changed, 100 insertions(+), 42 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 78e9500d19..997a074bbe 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -1729,6 +1729,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu blockchain ! WatchConfirmed(self, closingTx, nodeParams.minDepthBlocks, BITCOIN_TX_CONFIRMED(closingTx)) } + // TODO: adjust for option_simplified_commitmenmt def spendLocalCurrent(d: HasCommitments) = { val outdatedCommitment = d match { @@ -1799,13 +1800,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu def doPublish(localCommitPublished: LocalCommitPublished) = { import localCommitPublished._ - val publishQueue = List(commitTx) ++ claimMainDelayedOutputTx ++ htlcSuccessTxs ++ htlcTimeoutTxs ++ claimHtlcDelayedTxs + val publishQueue = List(commitTx) ++ pushMeTx ++ claimMainDelayedOutputTx ++ htlcSuccessTxs ++ htlcTimeoutTxs ++ claimHtlcDelayedTxs publishIfNeeded(publishQueue, irrevocablySpent) // we watch: // - the commitment tx itself, so that we can handle the case where we don't have any outputs // - 'final txes' that send funds to our wallet and that spend outputs that only us control - val watchConfirmedQueue = List(commitTx) ++ claimMainDelayedOutputTx ++ claimHtlcDelayedTxs + val watchConfirmedQueue = List(commitTx) ++ pushMeTx ++ claimMainDelayedOutputTx ++ claimHtlcDelayedTxs watchConfirmedIfNeeded(watchConfirmedQueue, irrevocablySpent) // we watch outputs of the commitment tx that both parties may spend diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index a7c3a78014..f16e7d09fa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -141,8 +141,8 @@ sealed trait HasCommitments extends Data { case class ClosingTxProposed(unsignedTx: Transaction, localClosingSigned: ClosingSigned) -case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], irrevocablySpent: Map[OutPoint, BinaryData]) -case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: List[Transaction], claimHtlcTimeoutTxs: List[Transaction], irrevocablySpent: Map[OutPoint, BinaryData]) +case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], pushMeTx: Option[Transaction], irrevocablySpent: Map[OutPoint, BinaryData]) +case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: List[Transaction], claimHtlcTimeoutTxs: List[Transaction], pushMeTx: Option[Transaction], irrevocablySpent: Map[OutPoint, BinaryData]) case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], mainPenaltyTx: Option[Transaction], htlcPenaltyTxs: List[Transaction], claimHtlcDelayedPenaltyTxs: List[Transaction], irrevocablySpent: Map[OutPoint, BinaryData]) final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index baca9fef33..6aa3c446f7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -329,9 +329,9 @@ object Helpers { val remoteHasOptional = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL) val remoteHasMandatory = Features.hasFeature(remote.localFeatures, Features.OPTION_SIMPLIFIED_COMMITMENT_MANDATORY) - (remoteHasMandatory && (localHasMandatory || localHasOptional)) || - (localHasMandatory && (remoteHasMandatory || remoteHasOptional)) || - (localHasOptional && remoteHasOptional) + (remoteHasMandatory && (localHasMandatory || localHasOptional)) || + (localHasMandatory && (remoteHasMandatory || remoteHasOptional)) || + (localHasOptional && remoteHasOptional) } object Closing { @@ -438,6 +438,17 @@ object Helpers { Transactions.addSigs(claimDelayed, sig) }) + // the push-me trasaction attaches the fees to the commitmentTx + val pushMeTransaction = commitmentContext match { + case ContextCommitmentV1 => None + case ContextSimplifiedCommitment => + generateTx("push-me-cpfp")(Try { + val pushMeTx = Transactions.makePushMeCPFP(tx, localDelayedPubkey, feeratePerKwDelayed, Satoshi(localParams.dustLimitSatoshis)) + val sig = keyManager.sign(pushMeTx, keyManager.delayedPaymentPoint(localParams.channelKeyPath), SIGHASH_ALL) // TODO use SIGHASH_SINGLE + Transactions.addSigs(pushMeTx, sig) + }) + } + // those are the preimages to existing received htlcs val preimages = commitments.localChanges.all.collect { case u: UpdateFulfillHtlc => u.paymentPreimage } @@ -480,6 +491,7 @@ object Helpers { htlcSuccessTxs = htlcTxes.collect { case c: HtlcSuccessTx => c.tx }, htlcTimeoutTxs = htlcTxes.collect { case c: HtlcTimeoutTx => c.tx }, claimHtlcDelayedTxs = htlcDelayedTxes.map(_.tx), + pushMeTx = pushMeTransaction.map(_.tx), irrevocablySpent = Map.empty) } @@ -489,7 +501,7 @@ object Helpers { * * @param commitments our commitment data, which include payment preimages * @param remoteCommit the remote commitment data to use to claim outputs (it can be their current or next commitment) - * @param commitTx the remote commitment transaction that has just been published + * @param commitTx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { @@ -545,7 +557,6 @@ object Helpers { * * Claim our Main output * - * * @param commitments either our current commitment data in case of usual remote uncooperative closing * or our outdated commitment data in case of data loss protection procedure; in any case it is used only * to get some constant parameters, not commitment data @@ -584,6 +595,7 @@ object Helpers { claimMainOutputTx = mainTx.map(_.tx), claimHtlcSuccessTxs = Nil, claimHtlcTimeoutTxs = Nil, + pushMeTx = None, irrevocablySpent = Map.empty ) } @@ -625,7 +637,7 @@ object Helpers { // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { - val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain, ???) + val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain, Some(localParams.toSelfDelay)) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 6527982b0c..d72c9656a9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -61,6 +61,7 @@ object Transactions { case class MainPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo case class HtlcPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo case class ClosingTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo + case class PushMeTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo sealed trait TxGenerationSkipped extends RuntimeException case object OutputNotFound extends RuntimeException(s"output not found (probably trimmed)") with TxGenerationSkipped @@ -408,24 +409,19 @@ object Transactions { def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): ClaimDelayedOutputTx = { - val claimTx = commitmentContext match { - case ContextCommitmentV1 => - val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) - val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) - val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) - - // unsigned transaction - val tx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, toLocalDelay) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) + val pubkeyScript = write(pay2wsh(redeemScript)) + val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) + val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript)) - ClaimDelayedOutputTx(input, tx) + // unsigned transaction + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, Array.emptyByteArray, toLocalDelay) :: Nil, + txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, + lockTime = 0) - case ContextSimplifiedCommitment => throw new NotImplementedError("makeClaimDelayedOutputTx with option_simplified_commitment") - } + val claimTx = ClaimDelayedOutputTx(input, tx) // compute weight with a dummy 73 bytes signature (the largest you can get) @@ -546,6 +542,32 @@ object Transactions { ClosingTx(commitTxInput, LexicographicalOrdering.sort(tx)) } + def makePushMeCPFP(commitTx: Transaction, toLocalDelayed: PublicKey, feeratePerKw: Long, localDustLimit: Satoshi): PushMeTx = { + val redeemScript = Scripts.pushMeSimplified(toLocalDelayed) + val pubKeyScript = write(pay2wsh(redeemScript)) + + val outputIndex = findPubKeyScriptIndex(commitTx, pubKeyScript, Set.empty, Some(pushMeValue)) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), redeemScript) + + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil, + txOut = TxOut(pushMeValue, List.empty) :: Nil, // TODO to what do we spend the pushMe? FIXME + lockTime = 0 + ) + + val weight = Transactions.addSigs(PushMeTx(input, tx), BinaryData("00" * 73)).tx.weight() + val fee = weight2fee(feeratePerKw, weight) + + val amount = input.txOut.amount - fee + if (amount < localDustLimit) { + throw AmountBelowDustLimit + } + + val tx1 = tx.copy(txOut = tx.txOut(0).copy(amount = amount) :: Nil) + PushMeTx(input, tx1) + } + def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: BinaryData, outputsAlreadyUsed: Set[Int], amount_opt: Option[Satoshi]): Int = { val outputIndex = tx.txOut .zipWithIndex @@ -557,19 +579,6 @@ object Transactions { } } - /** - * Finds the output index of our push me output (see option_simplified_commitment) - * - * @param pubkey - * @param simplifiedCommitTx - * @return - */ - def findPushMeOutputIndex(pubkey: PublicKey, simplifiedCommitTx: Transaction): Option[Int] = { - simplifiedCommitTx.txOut.zipWithIndex.find { case (txOut, outputIndex) => - txOut.publicKeyScript == Script.write(pay2wsh(pushMeSimplified(pubkey))) - }.map(_._2) - } - def sign(tx: Transaction, inputIndex: Int, redeemScript: BinaryData, amount: Satoshi, key: PrivateKey, sigHash: Int): BinaryData = { Transaction.signInput(tx, inputIndex, redeemScript, sigHash, amount, SIGVERSION_WITNESS_V0, key) } @@ -634,6 +643,11 @@ object Transactions { closingTx.copy(tx = closingTx.tx.updateWitness(0, witness)) } + def addSigs(pushMeTx: PushMeTx, localSig: BinaryData): PushMeTx = { + val witness = Scripts.claimPushMeOutputWithKey(localSig) + pushMeTx.copy(tx = pushMeTx.tx.updateWitness(0, witness)) + } + def checkSpendable(txinfo: TransactionWithInputInfo): Try[Unit] = Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn.head.outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 2d169c1607..856dde4759 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -189,6 +189,7 @@ object ChannelCodecs extends Logging { ("htlcSuccessTxs" | listOfN(uint16, txCodec)) :: ("htlcTimeoutTxs" | listOfN(uint16, txCodec)) :: ("claimHtlcDelayedTx" | listOfN(uint16, txCodec)) :: + ("pushMeTx" | optional(bool, txCodec)) :: ("spent" | spentMapCodec)).as[LocalCommitPublished] val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( @@ -196,6 +197,7 @@ object ChannelCodecs extends Logging { ("claimMainOutputTx" | optional(bool, txCodec)) :: ("claimHtlcSuccessTxs" | listOfN(uint16, txCodec)) :: ("claimHtlcTimeoutTxs" | listOfN(uint16, txCodec)) :: + ("pushMeTx" | optional(bool, txCodec)) :: ("spent" | spentMapCodec)).as[RemoteCommitPublished] val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 707b0a6b28..0cece5784c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1465,6 +1465,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(initialState == bob.stateData) } + // TODO review, remove? test("recv CMD_UPDATE_FEE (when option_simplified_commitment)", Tag("simplified_commitment")) { f => import f._ val sender = TestProbe() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 88d85e08bf..2df9099540 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -106,7 +106,6 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods assert(closingSig.feeSatoshis == 282) bob2alice.forward(alice) - val closingSig2 = alice2bob.expectMsgType[ClosingSigned] assert(closingSig2.feeSatoshis == 282) // alice sends back the same fees alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index e6acdf2593..d5f3e7a695 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -26,9 +26,10 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{Data, State, _} import fr.acinq.eclair.payment.{CommandBuffer, ForwardAdd, ForwardFulfill, Local} import fr.acinq.eclair.transactions.Scripts +import fr.acinq.eclair.transactions.Transactions.HtlcTimeoutTx import fr.acinq.eclair.wire._ import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass} -import org.scalatest.Outcome +import org.scalatest.{Outcome, Tag} import scala.concurrent.duration._ @@ -45,7 +46,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import setup._ within(30 seconds) { - reachNormal(setup) + reachNormal(setup, test.tags) val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield { val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) @@ -256,6 +257,33 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateName == CLOSED) } + // TODO improve watcher adding inputs => PublishAsapAndAddFees + ignore("recv BITCOIN_TX_CONFIRMED (local commit, option_simplified_commitment)", Tag("simplified_commitment")) { f => + import f._ + val listener = TestProbe() + system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed]) + // alice sends an htlc to bob + val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + // an error occurs and alice publishes her commit tx + val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val htlcTimeoutTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.head.txinfo.tx + alice ! Error("00" * 32, "oops".getBytes) + alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx + val pushMeTx = alice2blockchain.expectMsgType[PublishAsap].tx // pushMe tx + assert(alice2blockchain.expectMsgType[PublishAsap].tx.txIn.head.outPoint.txid == aliceCommitTx.txid) // claim-main-delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc-timeout + alice2blockchain.expectMsgType[PublishAsap] // claim-htlc-delayed + + + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(pushMeTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(htlcTimeoutTx))// htlc-timeout + alice2blockchain.expectMsgType[WatchConfirmed] // claim-main-delayed + assert(alice2blockchain.expectMsgType[WatchSpent].txId === aliceCommitTx.txid) + alice2blockchain.expectMsgType[WatchConfirmed] // claim-htlc-delayed + } + test("recv BITCOIN_TX_CONFIRMED (local commit with htlcs only signed by local)") { f => import f._ val sender = TestProbe() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 3b64daa5e4..8b8fa3c015 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -321,6 +321,7 @@ class TransactionsSpec extends FunSuite with Logging { val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) + //FIXME // val claimP2WPKHOutputTx1 = makeClaimP2WPKHOutputTx(commitTx(ContextSimplifiedCommitment).tx, localDustLimit, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, Some(toLocalDelay))(ContextSimplifiedCommitment) // val localSig1 = sign(claimP2WPKHOutputTx1, localDelayedPaymentPriv, SIGHASH_ALL) // val signedTx1 = addSigs(claimP2WPKHOutputTx1, localDelayedPaymentPriv.publicKey, localSig1) From 31513605953f3bd58ed0958697ed0bc951a89911 Mon Sep 17 00:00:00 2001 From: sstone Date: Wed, 6 Mar 2019 10:17:41 +0100 Subject: [PATCH 54/66] BitcoinCoreWallet: add signing tests with multiple bad inputs Check that we handle errors properly when signrawtransactionwithwallet returns multiple errors. --- .../bitcoind/BitcoinCoreWalletSpec.scala | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index f1a5842a52..c726e4b226 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -21,7 +21,7 @@ import akka.actor.Status.Failure import akka.pattern.pipe import akka.testkit.{TestKit, TestProbe} import com.typesafe.config.ConfigFactory -import fr.acinq.bitcoin.{Block, MilliBtc, Satoshi, Script, Transaction, TxOut} +import fr.acinq.bitcoin.{BinaryData, Block, MilliBtc, OutPoint, Satoshi, Script, Transaction, TxIn, TxOut} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.FundTransactionResponse import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, JsonRPCError} @@ -101,7 +101,7 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe } } - test("handle error when signing transactions") { + test("handle errors when signing transactions") { val bitcoinClient = new BasicBitcoinJsonRPCClient( user = config.getString("bitcoind.rpcuser"), password = config.getString("bitcoind.rpcpassword"), @@ -111,20 +111,27 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe val sender = TestProbe() - // create and fund a transaction + // create a transaction that spends UTXOs that don't exist wallet.getFinalAddress.pipeTo(sender.ref) val address = sender.expectMsgType[String] - val unsignedTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(Satoshi(1000000), addressToPublicKeyScript(address, Block.RegtestGenesisBlock.hash)) :: Nil, lockTime = 0) - wallet.fundTransaction(unsignedTx, false, 1500).pipeTo(sender.ref) - val FundTransactionResponse(fundedTx, _, _) = sender.expectMsgType[FundTransactionResponse] - - // change the index of the UTXO that it spends - val fundedTx1 = fundedTx.copy(txIn = fundedTx.txIn.updated(0, fundedTx.txIn(0).copy(outPoint = fundedTx.txIn(0).outPoint.copy(index = fundedTx.txIn(0).outPoint.index + 1)))) - - // signing it should fail, and the error message should contain the txid of the UTXO that could not be used - wallet.signTransaction(fundedTx1).pipeTo(sender.ref) + val unknownTxids = Seq( + BinaryData("01" *32), + BinaryData("02" *32), + BinaryData("03" *32) + ) + val unsignedTx = Transaction(version = 2, + txIn = Seq( + TxIn(OutPoint(unknownTxids(0), 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL), + TxIn(OutPoint(unknownTxids(1), 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL), + TxIn(OutPoint(unknownTxids(2), 0), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) + ), + txOut = TxOut(Satoshi(1000000), addressToPublicKeyScript(address, Block.RegtestGenesisBlock.hash)) :: Nil, + lockTime = 0) + + // signing it should fail, and the error message should contain the txids of the UTXOs that could not be used + wallet.signTransaction(unsignedTx).pipeTo(sender.ref) val Failure(JsonRPCError(error)) = sender.expectMsgType[Failure] - assert(error.message.contains(fundedTx1.txIn(0).outPoint.txid.toString())) + unknownTxids.foreach(id => assert(error.message.contains(id.toString()))) } test("create/commit/rollback funding txes") { From a752d65c195a9d805c5b1520050f79d187d9d51d Mon Sep 17 00:00:00 2001 From: sstone Date: Wed, 6 Mar 2019 20:17:49 +0100 Subject: [PATCH 55/66] BitcoinCoreWalletSpec: fix formatting issue --- .../blockchain/bitcoind/BitcoinCoreWalletSpec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index f391a17eae..01869a6a7b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -115,9 +115,9 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe wallet.getFinalAddress.pipeTo(sender.ref) val address = sender.expectMsgType[String] val unknownTxids = Seq( - BinaryData("01" *32), - BinaryData("02" *32), - BinaryData("03" *32) + BinaryData("01" * 32), + BinaryData("02" * 32), + BinaryData("03" * 32) ) val unsignedTx = Transaction(version = 2, txIn = Seq( @@ -276,4 +276,4 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe sender.expectMsg(true) } -} \ No newline at end of file +} From 9a1aa6e4f16a308bb4125c58e4eac00bad4748be Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 7 Mar 2019 16:23:15 +0100 Subject: [PATCH 56/66] Reintroduce non-typed design, commit version/context inside Commitments --- .../fr/acinq/eclair/channel/Channel.scala | 111 +++++------- .../fr/acinq/eclair/channel/Commitments.scala | 160 +++++------------ .../fr/acinq/eclair/channel/Helpers.scala | 15 +- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 169 ++++++++++-------- .../channel/states/e/NormalStateSpec.scala | 97 +++------- .../channel/states/f/ShutdownStateSpec.scala | 47 ++--- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 10 +- .../fr/acinq/eclair/io/HtlcReaperSpec.scala | 5 +- .../fr/acinq/eclair/payment/RelayerSpec.scala | 2 +- 9 files changed, 233 insertions(+), 383 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 997a074bbe..f0634839dd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -358,23 +358,19 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu signature = localSigOfRemoteTx ) - val commitments = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { - case true => SimplifiedCommitment(localParams, remoteParams, channelFlags, - LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), - LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), - localNextHtlcId = 0L, remoteNextHtlcId = 0L, - originChannels = Map.empty, - remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, - commitInput, ShaChain.init, channelId = channelId) - case false => CommitmentsV1(localParams, remoteParams, channelFlags, - LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), - LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), - localNextHtlcId = 0L, remoteNextHtlcId = 0L, - originChannels = Map.empty, - remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, - commitInput, ShaChain.init, channelId = channelId) + val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { + case true => ContextSimplifiedCommitment + case false => ContextCommitmentV1 } + val commitments = Commitments(localParams, remoteParams, channelFlags, + LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), + LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 0L, remoteNextHtlcId = 0L, + originChannels = Map.empty, + remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array, + commitInput, ShaChain.init, channelId = channelId, version = commitmentVersion) + context.parent ! ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId) // we notify the peer asap so it knows how to route messages context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) @@ -405,23 +401,19 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu handleLocalError(InvalidCommitmentSignature(channelId, signedLocalCommitTx.tx), d, Some(msg)) case Success(_) => val commitInput = localCommitTx.input - val commitments = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { - case true => SimplifiedCommitment(localParams, remoteParams, channelFlags, - LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, - LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), - localNextHtlcId = 0L, remoteNextHtlcId = 0L, - originChannels = Map.empty, - remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array - commitInput, ShaChain.init, channelId = channelId) - case false => CommitmentsV1(localParams, remoteParams, channelFlags, - LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, - LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), - localNextHtlcId = 0L, remoteNextHtlcId = 0L, - originChannels = Map.empty, - remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array - commitInput, ShaChain.init, channelId = channelId) + val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { + case true => ContextSimplifiedCommitment + case false => ContextCommitmentV1 } + val commitments = Commitments(localParams, remoteParams, channelFlags, + LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil)), remoteCommit, + LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + localNextHtlcId = 0L, remoteNextHtlcId = 0L, + originChannels = Map.empty, + remoteNextCommitInfo = Right(randomKey.publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array + commitInput, ShaChain.init, channelId = channelId, version = commitmentVersion) + context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) log.info(s"publishing funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") // we do this to make sure that the channel state has been written to disk when we publish the funding tx @@ -505,18 +497,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu }) when(WAIT_FOR_FUNDING_LOCKED)(handleExceptions { - case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments: Commitments, shortChannelId, _)) => + case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments, shortChannelId, _)) => // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED) context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId)) // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments)) - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)) - case s: SimplifiedCommitment => s.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)) - } - - goto(NORMAL) using store(DATA_NORMAL(commitments1, shortChannelId, buried = false, None, initialChannelUpdate, None, None)) + goto(NORMAL) using store(DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None)) case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_LOCKED) if d.commitments.announceChannel => log.debug(s"received remote announcement signatures, delaying") @@ -670,10 +657,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) - case s: SimplifiedCommitment => s.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) - } + val commitments1 = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) stay using d.copy(commitments = commitments1) } @@ -758,11 +742,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu d.commitments.remoteNextCommitInfo match { case Left(waitForRevocation) => // yes, let's just schedule a new signature ASAP, which will include all pending unsigned htlcs - val commitments1 = d.commitments match { - case c: CommitmentsV1 => c.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) - case s: SimplifiedCommitment => s.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) - - } + val commitments1 = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) // in the meantime we won't send new htlcs stay using d.copy(commitments = commitments1, remoteShutdown = Some(remoteShutdown)) case Right(_) => @@ -795,7 +775,6 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } else { // we are fundee, will wait for their closing_signed goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, closingTxProposed = List(List()), bestUnpublishedClosingTx_opt = None)) sending sendList - } } else { @@ -977,7 +956,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d@DATA_SHUTDOWN(commitments: Commitments, _, _)) => + case Event(c@CMD_SIGN, d: DATA_SHUTDOWN) => d.commitments.remoteNextCommitInfo match { case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") @@ -999,10 +978,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - commitments match { - case c: CommitmentsV1 => stay using d.copy(commitments = c.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) - case s: SimplifiedCommitment => stay using d.copy(commitments = s.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) - } + stay using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true)))) } case Event(commit: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) => @@ -1349,9 +1325,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) - val myCurrentPerCommitmentPoint = d.commitments match { - case _: CommitmentsV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) - case _: SimplifiedCommitment => None + val myCurrentPerCommitmentPoint = d.commitments.getContext match { + case ContextCommitmentV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) + case ContextSimplifiedCommitment => None } val channelReestablish = ChannelReestablish( @@ -1935,20 +1911,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // first we clean up unacknowledged updates log.debug(s"discarding proposed OUT: ${d.commitments.localChanges.proposed.map(Commitments.msg2String(_)).mkString(",")}") log.debug(s"discarding proposed IN: ${d.commitments.remoteChanges.proposed.map(Commitments.msg2String(_)).mkString(",")}") - val commitments1 = d.commitments match { - case c: CommitmentsV1 => c.copy( - localChanges = d.commitments.localChanges.copy(proposed = Nil), - remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), - localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, - remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) - case s: SimplifiedCommitment => s.copy( - localChanges = d.commitments.localChanges.copy(proposed = Nil), - remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), - localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, - remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) - } - - + val commitments1 = d.commitments.copy( + localChanges = d.commitments.localChanges.copy(proposed = Nil), + remoteChanges = d.commitments.remoteChanges.copy(proposed = Nil), + localNextHtlcId = d.commitments.localNextHtlcId - d.commitments.localChanges.proposed.collect { case u: UpdateAddHtlc => u }.size, + remoteNextHtlcId = d.commitments.remoteNextHtlcId - d.commitments.remoteChanges.proposed.collect { case u: UpdateAddHtlc => u }.size) log.debug(s"localNextHtlcId=${d.commitments.localNextHtlcId}->${commitments1.localNextHtlcId}") log.debug(s"remoteNextHtlcId=${d.commitments.remoteNextHtlcId}->${commitments1.remoteNextHtlcId}") @@ -2077,4 +2044,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu initialize() -} \ No newline at end of file +} + + + + diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index cec3a5cbdf..c635303f82 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -41,35 +41,37 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, rem case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long, reSignAsap: Boolean = false) // @formatter:on -sealed trait Commitments { - - val localParams: LocalParams - val remoteParams: RemoteParams - val channelFlags: Byte - val localCommit: LocalCommit - val remoteCommit: RemoteCommit - val localChanges: LocalChanges - val remoteChanges: RemoteChanges - val localNextHtlcId: Long - val remoteNextHtlcId: Long - val originChannels: Map[Long, Origin] // for outgoing htlcs relayed through us, the id of the previous channel - val remoteNextCommitInfo: Either[WaitingForRevocation, Point] - val commitInput: InputInfo - val remotePerCommitmentSecrets: ShaChain - val channelId: BinaryData - +/** + * about remoteNextCommitInfo: + * we either: + * - have built and signed their next commit tx with their next revocation hash which can now be discarded + * - have their next per-commitment point + * So, when we've signed and sent a commit message and are waiting for their revocation message, + * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point + */ +case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, + channelFlags: Byte, + localCommit: LocalCommit, remoteCommit: RemoteCommit, + localChanges: LocalChanges, remoteChanges: RemoteChanges, + localNextHtlcId: Long, remoteNextHtlcId: Long, + originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel + remoteNextCommitInfo: Either[WaitingForRevocation, Point], + commitInput: InputInfo, + remotePerCommitmentSecrets: ShaChain, channelId: BinaryData, + version: CommitmentContext = ContextCommitmentV1 + ) { def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight - def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal) - - def addRemoteProposal(proposal: UpdateMessage): Commitments = Commitments.addRemoteProposal(this, proposal) - def timedoutOutgoingHtlcs(blockheight: Long): Set[UpdateAddHtlc] = (localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry) ++ remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry) ++ remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry)).getOrElse(Set.empty[DirectedHtlc])).map(_.add) + def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal) + + def addRemoteProposal(proposal: UpdateMessage): Commitments = Commitments.addRemoteProposal(this, proposal) + def announceChannel: Boolean = (channelFlags & 0x01) != 0 // TODO subtract the pushMe value from the balance? @@ -79,9 +81,7 @@ sealed trait Commitments { reduced.toRemoteMsat - remoteParams.channelReserveSatoshis * 1000 - feesMsat } - // get the context for this commitment - def getContext: CommitmentContext - + def getContext = version } // @formatter: off @@ -90,44 +90,6 @@ object ContextCommitmentV1 extends CommitmentContext object ContextSimplifiedCommitment extends CommitmentContext // @formatter: on -/** - * about remoteNextCommitInfo: - * we either: - * - have built and signed their next commit tx with their next revocation hash which can now be discarded - * - have their next per-commitment point - * So, when we've signed and sent a commit message and are waiting for their revocation message, - * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point - */ -case class CommitmentsV1(localParams: LocalParams, remoteParams: RemoteParams, - channelFlags: Byte, - localCommit: LocalCommit, remoteCommit: RemoteCommit, - localChanges: LocalChanges, remoteChanges: RemoteChanges, - localNextHtlcId: Long, remoteNextHtlcId: Long, - originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel - remoteNextCommitInfo: Either[WaitingForRevocation, Point], - commitInput: InputInfo, - remotePerCommitmentSecrets: ShaChain, channelId: BinaryData) extends Commitments { - - override def getContext: CommitmentContext = ContextCommitmentV1 -} - - -case class SimplifiedCommitment(localParams: LocalParams, remoteParams: RemoteParams, - channelFlags: Byte, - localCommit: LocalCommit, remoteCommit: RemoteCommit, - localChanges: LocalChanges, remoteChanges: RemoteChanges, - localNextHtlcId: Long, - remoteNextHtlcId: Long, - originChannels: Map[Long, Origin], // for outgoing htlcs relayed through us, the id of the previous channel - remoteNextCommitInfo: Either[WaitingForRevocation, Point], - commitInput: InputInfo, - remotePerCommitmentSecrets: ShaChain, - channelId: BinaryData) extends Commitments { - - - override def getContext: CommitmentContext = ContextSimplifiedCommitment -} - object Commitments { /** * add a change to our proposed change list @@ -136,15 +98,11 @@ object Commitments { * @param proposal * @return an updated commitment instance */ - def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments match { - case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) - case s: SimplifiedCommitment => s.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) - } + private def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = + commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) - def addRemoteProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments match { - case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) - case s: SimplifiedCommitment => s.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) - } + private def addRemoteProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = + commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) /** * @@ -178,10 +136,7 @@ object Commitments { // let's compute the current commitment *as seen by them* with this change taken into account val add = UpdateAddHtlc(commitments.channelId, commitments.localNextHtlcId, cmd.amountMsat, cmd.paymentHash, cmd.cltvExpiry, cmd.onion) // we increment the local htlc index and add an entry to the origins map - val commitments1 = addLocalProposal(commitments, add) match { - case c: CommitmentsV1 => c.copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) - case s: SimplifiedCommitment => s.copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) - } + val commitments1 = addLocalProposal(commitments, add).copy(localNextHtlcId = commitments.localNextHtlcId + 1, originChannels = commitments.originChannels + (add.id -> origin)) // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation val remoteCommit1 = commitments1.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit).getOrElse(commitments1.remoteCommit) val reduced = CommitmentSpec.reduce(remoteCommit1.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed) @@ -225,10 +180,7 @@ object Commitments { } // let's compute the current commitment *as seen by us* including this change - val commitments1 = addRemoteProposal(commitments, add) match { - case c: CommitmentsV1 => c.copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) - case s: SimplifiedCommitment => s.copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) - } + val commitments1 = addRemoteProposal(commitments, add).copy(remoteNextHtlcId = commitments.remoteNextHtlcId + 1) val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) val incomingHtlcs = reduced.htlcs.filter(_.direction == IN) @@ -366,10 +318,7 @@ object Commitments { // let's compute the current commitment *as seen by them* with this change taken into account val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw) // update_fee replace each other, so we can remove previous ones - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) - case _: SimplifiedCommitment => throw new IllegalArgumentException(s"Should not send fee update when using simplified_commitment=$commitments") - } + val commitments1 = commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) val reduced = CommitmentSpec.reduce(commitments1.remoteCommit.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee @@ -410,10 +359,7 @@ object Commitments { // let's compute the current commitment *as seen by us* including this change // update_fee replace each other, so we can remove previous ones - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) - case _: SimplifiedCommitment => throw CannotUpdateFeeWithCommitmentType(commitments.channelId) - } + val commitments1 = commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee @@ -469,16 +415,10 @@ object Commitments { htlcSignatures = htlcSigs.toList ) - val commitments1 = commitments match { - case c:CommitmentsV1 => c.copy( - remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), - localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), - remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) - case s: SimplifiedCommitment => s.copy( - remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), - localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), - remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) - } + val commitments1 = commitments.copy( + remoteNextCommitInfo = Left(WaitingForRevocation(RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint), commitSig, commitments.localCommit.index)), + localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed), + remoteChanges = remoteChanges.copy(acked = Nil, signed = remoteChanges.acked)) (commitments1, commitSig) case Left(_) => @@ -571,10 +511,7 @@ object Commitments { publishableTxs = PublishableTxs(signedCommitTx, htlcTxsAndSigs)) val ourChanges1 = localChanges.copy(acked = Nil) val theirChanges1 = remoteChanges.copy(proposed = Nil, acked = remoteChanges.acked ++ remoteChanges.proposed) - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) - case s: SimplifiedCommitment => s.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) - } + val commitments1 = commitments.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1) (commitments1, revocation) } @@ -607,22 +544,13 @@ object Commitments { val completedOutgoingHtlcs = commitments.remoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id) -- theirNextCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id) // we remove the newly completed htlcs from the origin map val originChannels1 = commitments.originChannels -- completedOutgoingHtlcs - val commitments1 = commitments match { - case c: CommitmentsV1 => c.copy( - localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed), - remoteChanges = remoteChanges.copy(signed = Nil), - remoteCommit = theirNextCommit, - remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), - remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), - originChannels = originChannels1) - case s: SimplifiedCommitment => s.copy( - localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed), - remoteChanges = remoteChanges.copy(signed = Nil), - remoteCommit = theirNextCommit, - remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), - remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), - originChannels = originChannels1) - } + val commitments1 = commitments.copy( + localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed), + remoteChanges = remoteChanges.copy(signed = Nil), + remoteCommit = theirNextCommit, + remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), + remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), + originChannels = originChannels1) (commitments1, forwards) case Right(_) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 6aa3c446f7..c41c0d7ffc 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -60,12 +60,9 @@ object Helpers { * @return */ def updateFeatures(data: HasCommitments, localInit: Init, remoteInit: Init): HasCommitments = { - val commitments1 = data.commitments match { - case c: CommitmentsV1 => c.copy( - localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures), - remoteParams = data.commitments.remoteParams.copy(globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures)) - case _: SimplifiedCommitment => throw new NotImplementedError("Missing impl for simplified_commitment") - } + val commitments1 = data.commitments.copy( + localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures), + remoteParams = data.commitments.remoteParams.copy(globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures)) data match { case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => d.copy(commitments = commitments1) @@ -349,8 +346,8 @@ object Helpers { } } - def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = commitments match { - case c: CommitmentsV1 => + def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = commitments.getContext match { + case ContextCommitmentV1 => import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) @@ -359,7 +356,7 @@ object Helpers { val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw) log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") Transactions.weight2fee(feeratePerKw, closingWeight) - case s: SimplifiedCommitment => + case ContextSimplifiedCommitment => Satoshi(282) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 856dde4759..f0450e3a21 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -16,6 +16,7 @@ package fr.acinq.eclair.wire +import fr.acinq.bitcoin.Crypto.Point import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{BinaryData, OutPoint, Transaction, TxOut} import fr.acinq.eclair.channel._ @@ -28,7 +29,7 @@ import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec, Decoder, Encoder, Err, GenCodec, SizeBound, Transformer} -import shapeless.{Generic, HNil} +import shapeless.{Generic, HList, HNil} /** * Created by PM on 02/06/2017. @@ -230,65 +231,87 @@ object ChannelCodecs extends Logging { val COMMITMENTv1_VERSION_BYTE = 0x00 val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01 - def encodeT[T <: Commitments]: T => Attempt[Commitments] = { t => - Attempt.successful(t.asInstanceOf[Commitments]) + def commitmentCodec(commitmentContext: CommitmentContext): Codec[Commitments] = { + import shapeless.{::} + (("localParams" | localParamsCodec) :: + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64) :: + ("remoteNextHtlcId" | uint64) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("channelId" | binarydata(32))).xmap( + f = { + case + localParams :: + remoteParams :: + flags :: + localCommit :: + remoteCommit :: + localChanges :: + remoteChanges :: + localNextHtlcId :: + remoteNextHtlcId :: + originChannels :: + remoteNextCommitInfo :: + commitInput :: + remotePerCommitmentSecrets :: + channelId :: + HNil => Commitments( + localParams, + remoteParams, + flags, + localCommit, + remoteCommit, + localChanges, + remoteChanges, + localNextHtlcId, + remoteNextHtlcId, + originChannels, + remoteNextCommitInfo, + commitInput, + remotePerCommitmentSecrets, + channelId, + version = commitmentContext + ) + }, + g = { c: Commitments => + c.localParams :: + c.remoteParams :: + c.channelFlags :: + c.localCommit :: + c.remoteCommit :: + c.localChanges :: + c.remoteChanges :: + c.localNextHtlcId :: + c.remoteNextHtlcId :: + c.originChannels :: + c.remoteNextCommitInfo :: + c.commitInput :: + c.remotePerCommitmentSecrets :: + c.channelId :: HNil + } + ) } - private val decodeCommitV1ToGeneric: Commitments => Attempt[CommitmentsV1] = { - case c: CommitmentsV1 => Attempt.successful(c) - case _ => Attempt.failure(Err("Wrong type!!")) - } - - private val decodeSimplifiedToGeneric: Commitments => Attempt[SimplifiedCommitment] = { - case s: SimplifiedCommitment => Attempt.successful(s) - case _ => Attempt.failure(Err("Wrong type??")) - } - - val commitmentsV1Codec: Codec[Commitments] = ( - ("localParams" | localParamsCodec) :: - ("remoteParams" | remoteParamsCodec) :: - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64) :: - ("remoteNextHtlcId" | uint64) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | binarydata(32))).as[CommitmentsV1].exmap(encodeT[CommitmentsV1], decodeCommitV1ToGeneric) - - val simplifiedCommitmentCodec: Codec[Commitments] = ( - ("localParams" | localParamsCodec) :: - ("remoteParams" | remoteParamsCodec) :: - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64) :: - ("remoteNextHtlcId" | uint64) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | binarydata(32))).as[SimplifiedCommitment].exmap(encodeT[SimplifiedCommitment], decodeSimplifiedToGeneric) - - - def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitCodec: Codec[Commitments]): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitCodec) :: + def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext: CommitmentContext): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitCodec: Codec[Commitments]): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( - ("commitments" | commitCodec) :: + def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext: CommitmentContext): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("shortChannelId" | shortchannelid) :: ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - def DATA_NORMAL_Codec(commitCodec: Codec[Commitments]): Codec[DATA_NORMAL] = ( - ("commitments" | commitCodec) :: + def DATA_NORMAL_Codec(commitmentContext: CommitmentContext): Codec[DATA_NORMAL] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool) :: ("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) :: @@ -296,20 +319,20 @@ object ChannelCodecs extends Logging { ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] - def DATA_SHUTDOWN_Codec(commitCodec: Codec[Commitments]): Codec[DATA_SHUTDOWN] = ( - ("commitments" | commitCodec) :: + def DATA_SHUTDOWN_Codec(commitmentContext: CommitmentContext): Codec[DATA_SHUTDOWN] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] - def DATA_NEGOTIATING_Codec(commitCodec: Codec[Commitments]): Codec[DATA_NEGOTIATING] = ( - ("commitments" | commitCodec) :: + def DATA_NEGOTIATING_Codec(commitmentContext: CommitmentContext): Codec[DATA_NEGOTIATING] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec) :: ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - def DATA_CLOSING_Codec(commitCodec: Codec[Commitments]): Codec[DATA_CLOSING] = ( - ("commitments" | commitCodec) :: + def DATA_CLOSING_Codec(commitmentContext: CommitmentContext): Codec[DATA_CLOSING] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: @@ -318,27 +341,27 @@ object ChannelCodecs extends Logging { ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] - def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitCodec: Codec[Commitments]): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( - ("commitments" | commitCodec) :: + def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext: CommitmentContext): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | commitmentCodec(commitmentContext)) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] - private def stateDataCodec(commitCodec: Codec[Commitments]): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) - .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitCodec)) - .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitCodec)) - .typecase(0x03, DATA_NORMAL_Codec(commitCodec)) - .typecase(0x04, DATA_SHUTDOWN_Codec(commitCodec)) - .typecase(0x05, DATA_NEGOTIATING_Codec(commitCodec)) - .typecase(0x06, DATA_CLOSING_Codec(commitCodec)) - .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitCodec)) + def stateDataCodec(commitmentContext: CommitmentContext): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) + .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext)) + .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext)) + .typecase(0x03, DATA_NORMAL_Codec(commitmentContext)) + .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentContext)) + .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentContext)) + .typecase(0x06, DATA_CLOSING_Codec(commitmentContext)) + .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext)) private val genericStateDataDecoder = discriminated[HasCommitments].by(uint8) - .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(commitmentsV1Codec)) - .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(simplifiedCommitmentCodec)).asDecoder + .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(ContextCommitmentV1)) + .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(ContextSimplifiedCommitment)).asDecoder private val genericStateDataEncoder = new Encoder[HasCommitments] { - override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments match { - case _: CommitmentsV1 => stateDataCodec(commitmentsV1Codec).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) - case _: SimplifiedCommitment => stateDataCodec(simplifiedCommitmentCodec).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) + override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments.getContext match { + case ContextCommitmentV1 => stateDataCodec(ContextCommitmentV1).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) + case ContextSimplifiedCommitment => stateDataCodec(ContextSimplifiedCommitment).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) case _ => Attempt.failure(Err("Unknown type")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 0cece5784c..f760c5333f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -70,15 +70,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val htlc = alice2bob.expectMsgType[UpdateAddHtlc] assert(htlc.id == 0 && htlc.paymentHash == h) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy( - localNextHtlcId = 1, - localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), - originChannels = Map(0L -> Local(Some(sender.ref))) - ) - } - )) + commitments = initialState.commitments.copy( + localNextHtlcId = 1, + localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), + originChannels = Map(0L -> Local(Some(sender.ref))) + ))) } test("recv CMD_ADD_HTLC (incrementing ids)") { f => @@ -105,14 +101,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val htlc = alice2bob.expectMsgType[UpdateAddHtlc] assert(htlc.id == 0 && htlc.paymentHash == h) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy( - localNextHtlcId = 1, - localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), - originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) - )} - )) + commitments = initialState.commitments.copy( + localNextHtlcId = 1, + localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), + originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) + ))) } test("recv CMD_ADD_HTLC (invalid payment hash)") { f => @@ -303,10 +296,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) bob ! htlc - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1) - })) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) // bob won't forward the add before it is cross-signed relayerB.expectNoMsg() } @@ -316,10 +306,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) bob ! htlc - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { - case s: SimplifiedCommitment => s.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1) - case _: CommitmentsV1 => throw new IllegalArgumentException("Shouldn't be here") - })) + awaitCond(initialData.commitments.getContext == ContextSimplifiedCommitment) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) // bob won't forward the add before it is cross-signed relayerB.expectNoMsg() } @@ -1141,10 +1129,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) } test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => @@ -1184,10 +1170,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)) - })) + commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)))) // alice immediately propagates the fulfill upstream val forward = relayerA.expectMsgType[ForwardFulfill] assert(forward.fulfill === fulfill) @@ -1261,11 +1244,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) } test("recv CMD_FAIL_HTLC (unknown htlc id)") { f => @@ -1291,11 +1271,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) } test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { f => @@ -1329,10 +1306,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) - })) + commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) // alice won't forward the fail before it is cross-signed relayerA.expectNoMsg() } @@ -1352,10 +1326,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) - })) + commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) // alice won't forward the fail before it is cross-signed relayerA.expectNoMsg() @@ -1431,11 +1402,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) } test("recv CMD_UPDATE_FEE (two in a row)") { f => @@ -1449,11 +1417,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fee2 = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee2)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee2)))) } test("recv CMD_UPDATE_FEE (when fundee)") { f => @@ -1482,10 +1447,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob ! fee1 val fee2 = UpdateFee("00" * 32, 14000) bob ! fee2 - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee2), remoteNextHtlcId = 0) - })) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee2), remoteNextHtlcId = 0))) } test("recv UpdateFee (option_simplified_commitment)", Tag("simplified_commitment")) { f => @@ -1505,10 +1467,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val fee = UpdateFee("00" * 32, 12000) bob ! fee - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { - case _: SimplifiedCommitment => throw new IllegalArgumentException("Shouldn't be here") - case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0) - })) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0))) } test("recv UpdateFee (when sender is not funder)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index acae5330cc..693aa411e6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -114,10 +114,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments match { - case c: CommitmentsV1 => c.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)) - case _: SimplifiedCommitment => ??? - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) } test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => @@ -144,10 +142,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val fulfill = UpdateFulfillHtlc("00" * 32, 0, "11" * 32) sender.send(alice, fulfill) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { - case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)) - case _: SimplifiedCommitment => ??? - })) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))) } test("recv UpdateFulfillHtlc (unknown htlc id)") { f => @@ -191,11 +186,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) } test("recv CMD_FAIL_HTLC (unknown htlc id)") { f => @@ -215,11 +207,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) } test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { f => @@ -246,10 +235,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val fail = UpdateFailHtlc("00" * 32, 1, "00" * 152) sender.send(alice, fail) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) - })) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) } test("recv UpdateFailHtlc (unknown htlc id)") { f => @@ -274,10 +260,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION) sender.send(alice, fail) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == (initialState.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)) - })) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) } test("recv UpdateFailMalformedHtlc (invalid failure_code)") { f => @@ -520,11 +503,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)) - })) + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) } test("recv CMD_UPDATE_FEE (when fundee)") { f => @@ -541,10 +521,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_SHUTDOWN] val fee = UpdateFee("00" * 32, 12000) bob ! fee - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)) - })) + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)))) } test("recv UpdateFee (when sender is not funder)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 48202cd106..4f650af2ed 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -45,7 +45,6 @@ class ChannelStateSpec extends FunSuite { assert(bin.take(8).toByte(signed = false) == ChannelCodecs.COMMITMENTv1_VERSION_BYTE) assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) - assert(data.commitments.isInstanceOf[CommitmentsV1]) } test("basic serialization test (NORMAL - SimplifiedCommitment)") { @@ -56,7 +55,6 @@ class ChannelStateSpec extends FunSuite { assert(bin.take(8).toByte(signed = false) == ChannelCodecs.COMMITMENT_SIMPLIFIED_VERSION_BYTE) assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) - assert(data.commitments.isInstanceOf[SimplifiedCommitment]) } } @@ -115,19 +113,19 @@ object ChannelStateSpec { val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000, 70000000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), BinaryData("0303030303030303030303030303030303030303030303030303030303030303"), Scalar(BinaryData("04" * 32)).toPoint) - val commitmentsV1 = CommitmentsV1(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + val commitmentsV1 = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), remoteNextCommitInfo = Right(randomKey.publicKey), - commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32) + commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = ContextCommitmentV1) - val simplifiedCommitment = SimplifiedCommitment(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), + val simplifiedCommitment = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), remoteNextCommitInfo = Right(randomKey.publicKey), - commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32) + commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = ContextSimplifiedCommitment) val channelUpdate = Announcements.makeChannelUpdate("11" * 32, randomKey, randomKey.publicKey, ShortChannelId(142553), 42, 15, 575, 53, Channel.MAX_FUNDING_SATOSHIS * 1000L) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala index a3ecaf2c23..6c950c2115 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala @@ -63,10 +63,7 @@ class HtlcReaperSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { channel.expectNoMsg(100 millis) // let's now assume that the channel get's reconnected, and it had the time to fail the htlcs - val data1 = data.copy(commitments = data.commitments match { - case _: SimplifiedCommitment => ??? - case c: CommitmentsV1 => c.copy(localCommit = data.commitments.localCommit.copy(spec = data.commitments.localCommit.spec.copy(htlcs = Set.empty))) - }) + val data1 = data.copy(commitments = data.commitments.copy(localCommit = data.commitments.localCommit.copy(spec = data.commitments.localCommit.spec.copy(htlcs = Set.empty)))) sender.send(brokenHtlcKiller, ChannelStateChanged(channel.ref, system.deadLetters, data.commitments.remoteParams.nodeId, OFFLINE, NORMAL, data1)) channel.expectNoMsg(100 millis) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala index 9eb517e844..93f8efa685 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala @@ -55,7 +55,7 @@ class RelayerSpec extends TestkitBaseClass { val channelId_ab: BinaryData = randomBytes(32) val channelId_bc: BinaryData = randomBytes(32) - def makeCommitments(channelId: BinaryData) = new CommitmentsV1(null, null, 0.toByte, null, + def makeCommitments(channelId: BinaryData) = new Commitments(null, null, 0.toByte, null, RemoteCommit(42, CommitmentSpec(Set.empty, 20000, 5000000, 100000000), "00" * 32, randomKey.toPoint), null, null, 0, 0, Map.empty, null, null, null, channelId) { override def availableBalanceForSendMsat: Long = remoteCommit.spec.toRemoteMsat // approximation From fcd0ea37dbdd3a5b3b43e2feaf1eb253bf47e094 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 7 Mar 2019 16:26:53 +0100 Subject: [PATCH 57/66] Rename CommitmentContext => CommitmentVersion --- .../fr/acinq/eclair/channel/Channel.scala | 20 +++++----- .../acinq/eclair/channel/ChannelTypes.scala | 2 +- .../fr/acinq/eclair/channel/Commitments.scala | 36 ++++++++--------- .../fr/acinq/eclair/channel/Helpers.scala | 20 +++++----- .../eclair/transactions/Transactions.scala | 40 +++++++++---------- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 26 ++++++------ .../b/WaitForFundingCreatedStateSpec.scala | 4 +- .../b/WaitForFundingSignedStateSpec.scala | 2 +- .../channel/states/e/NormalStateSpec.scala | 12 +++--- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 4 +- .../eclair/transactions/TestVectorsSpec.scala | 10 ++--- .../transactions/TransactionsSpec.scala | 28 ++++++------- 12 files changed, 102 insertions(+), 102 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index f0634839dd..fdf07449e9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -359,8 +359,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu ) val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { - case true => ContextSimplifiedCommitment - case false => ContextCommitmentV1 + case true => VersionSimplifiedCommitment + case false => VersionCommitmentV1 } val commitments = Commitments(localParams, remoteParams, channelFlags, @@ -402,8 +402,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success(_) => val commitInput = localCommitTx.input val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { - case true => ContextSimplifiedCommitment - case false => ContextCommitmentV1 + case true => VersionSimplifiedCommitment + case false => VersionCommitmentV1 } val commitments = Commitments(localParams, remoteParams, channelFlags, @@ -764,11 +764,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // are there pending signed htlcs on either side? we need to have received their last revocation! if (d.commitments.hasNoPendingHtlcs) { // there are no pending signed htlcs, let's go directly to NEGOTIATING - if (d.commitments.localParams.isFunder && d.commitments.getContext == ContextCommitmentV1) { + if (d.commitments.localParams.isFunder && d.commitments.getContext == VersionCommitmentV1) { // we are funder and we're using commitmentV1, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned - } else if(!d.commitments.localParams.isFunder && d.commitments.getContext == ContextSimplifiedCommitment) { + } else if(!d.commitments.localParams.isFunder && d.commitments.getContext == VersionSimplifiedCommitment) { // we are fundee BUT we're using option_simplified_commitment, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned @@ -1024,7 +1024,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } if (commitments1.hasNoPendingHtlcs) { log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") - if (d.commitments.localParams.isFunder || commitments1.getContext == ContextSimplifiedCommitment) { + if (d.commitments.localParams.isFunder || commitments1.getContext == VersionSimplifiedCommitment) { // we are funder or we're using option_simplified_commitmemt, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned @@ -1326,8 +1326,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) val myCurrentPerCommitmentPoint = d.commitments.getContext match { - case ContextCommitmentV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) - case ContextSimplifiedCommitment => None + case VersionCommitmentV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) + case VersionSimplifiedCommitment => None } val channelReestablish = ChannelReestablish( @@ -1460,7 +1460,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // BOLT 2: A node if it has sent a previous shutdown MUST retransmit shutdown. // negotiation restarts from the beginning, and is initialized by the funder // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them - if (d.commitments.localParams.isFunder || d.commitments.getContext == ContextSimplifiedCommitment) { + if (d.commitments.localParams.isFunder || d.commitments.getContext == VersionSimplifiedCommitment) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index f16e7d09fa..ece7333507 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -166,7 +166,7 @@ final case class DATA_NEGOTIATING(commitments: Commitments, closingTxProposed: List[List[ClosingTxProposed]], // one list for every negotiation (there can be several in case of disconnection) bestUnpublishedClosingTx_opt: Option[Transaction]) extends Data with HasCommitments { require(!closingTxProposed.isEmpty, "there must always be a list for the current negotiation") - require(commitments.getContext == ContextSimplifiedCommitment || !commitments.localParams.isFunder || closingTxProposed.forall(!_.isEmpty), "funder must have at least one closing signature for every negotation attempt because it initiates the closing") + require(commitments.getContext == VersionSimplifiedCommitment || !commitments.localParams.isFunder || closingTxProposed.forall(!_.isEmpty), "funder must have at least one closing signature for every negotation attempt because it initiates the closing") } final case class DATA_CLOSING(commitments: Commitments, mutualCloseProposed: List[Transaction], // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index c635303f82..e63935597c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -58,7 +58,7 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, remoteNextCommitInfo: Either[WaitingForRevocation, Point], commitInput: InputInfo, remotePerCommitmentSecrets: ShaChain, channelId: BinaryData, - version: CommitmentContext = ContextCommitmentV1 + version: CommitmentVersion = VersionCommitmentV1 ) { def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight @@ -85,9 +85,9 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, } // @formatter: off -sealed trait CommitmentContext -object ContextCommitmentV1 extends CommitmentContext -object ContextSimplifiedCommitment extends CommitmentContext +sealed trait CommitmentVersion +object VersionCommitmentV1 extends CommitmentVersion +object VersionSimplifiedCommitment extends CommitmentVersion // @formatter: on object Commitments { @@ -309,7 +309,7 @@ object Commitments { throw FundeeCannotSendUpdateFee(commitments.channelId) } - if(commitments.getContext == ContextSimplifiedCommitment){ + if(commitments.getContext == VersionSimplifiedCommitment){ throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } @@ -346,7 +346,7 @@ object Commitments { throw FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw) } - if(commitments.getContext == ContextSimplifiedCommitment){ + if(commitments.getContext == VersionSimplifiedCommitment){ throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } @@ -401,8 +401,8 @@ object Commitments { val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) val htlcSigs = commitmentContext match { - case ContextCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_ALL)) - case ContextSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) + case VersionCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_ALL)) + case VersionSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) } // NB: IN/OUT htlcs are inverted because this is the remote commit @@ -470,8 +470,8 @@ object Commitments { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } val htlcSigs = commitmentContext match { - case ContextCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)) - case ContextSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) + case VersionCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)) + case VersionSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) } val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) // combine the sigs to make signed txes @@ -481,13 +481,13 @@ object Commitments { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == ContextCommitmentV1 => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == VersionCommitmentV1 => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_ALL) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == ContextSimplifiedCommitment => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == VersionSimplifiedCommitment => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) @@ -558,13 +558,13 @@ object Commitments { } } - def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val remotePaymentPubkey = commitmentContext match { - case ContextSimplifiedCommitment => PublicKey(remoteParams.paymentBasepoint) - case ContextCommitmentV1 => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) + case VersionSimplifiedCommitment => PublicKey(remoteParams.paymentBasepoint) + case VersionCommitmentV1 => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) } val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) @@ -574,10 +574,10 @@ object Commitments { (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localPaymentPubkey = commitmentContext match { - case ContextSimplifiedCommitment => keyManager.paymentPoint(localParams.channelKeyPath).publicKey - case ContextCommitmentV1 => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + case VersionSimplifiedCommitment => keyManager.paymentPoint(localParams.channelKeyPath).publicKey + case VersionCommitmentV1 => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) } val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index c41c0d7ffc..266fa5b0f7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -232,8 +232,8 @@ object Helpers { */ def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { implicit val commitmentContext = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { - case true => ContextSimplifiedCommitment - case false => ContextCommitmentV1 + case true => VersionSimplifiedCommitment + case false => VersionCommitmentV1 } // TODO adjust for option_simplified_commitment @@ -347,7 +347,7 @@ object Helpers { } def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = commitments.getContext match { - case ContextCommitmentV1 => + case VersionCommitmentV1 => import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) @@ -356,7 +356,7 @@ object Helpers { val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw) log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") Transactions.weight2fee(feeratePerKw, closingWeight) - case ContextSimplifiedCommitment => + case VersionSimplifiedCommitment => Satoshi(282) } @@ -437,8 +437,8 @@ object Helpers { // the push-me trasaction attaches the fees to the commitmentTx val pushMeTransaction = commitmentContext match { - case ContextCommitmentV1 => None - case ContextSimplifiedCommitment => + case VersionCommitmentV1 => None + case VersionSimplifiedCommitment => generateTx("push-me-cpfp")(Try { val pushMeTx = Transactions.makePushMeCPFP(tx, localDelayedPubkey, feeratePerKwDelayed, Satoshi(localParams.dustLimitSatoshis)) val sig = keyManager.sign(pushMeTx, keyManager.delayedPaymentPoint(localParams.channelKeyPath), SIGHASH_ALL) // TODO use SIGHASH_SINGLE @@ -568,7 +568,7 @@ object Helpers { val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 val mainTx = commitments.getContext match { - case ContextCommitmentV1 => + case VersionCommitmentV1 => val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), @@ -577,7 +577,7 @@ object Helpers { Transactions.addSigs(claimMain, localPubkey, sig) }) - case ContextSimplifiedCommitment => + case VersionSimplifiedCommitment => val localPubkey = keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), @@ -782,7 +782,7 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentContext, log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == localCommit.publishableTxs.commitTx.tx.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) (localCommit.spec.htlcs.filter(_.direction == OUT) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec)).map(_.add) @@ -806,7 +806,7 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentContext, log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == remoteCommit.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) (remoteCommit.spec.htlcs.filter(_.direction == IN) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec)).map(_.add) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index d72c9656a9..5ff953fbbe 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -122,10 +122,10 @@ object Transactions { */ def fee2rate(fee: Satoshi, weight: Int) = (fee.amount * 1000L) / weight - def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): Seq[DirectedHtlc] = { + def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): Seq[DirectedHtlc] = { val htlcTimeoutFee = commitmentContext match { - case ContextCommitmentV1 => weight2fee(spec.feeratePerKw, htlcTimeoutWeight) - case ContextSimplifiedCommitment => Satoshi(0) + case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcTimeoutWeight) + case VersionSimplifiedCommitment => Satoshi(0) } spec.htlcs .filter(_.direction == OUT) @@ -133,10 +133,10 @@ object Transactions { .toSeq } - def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): Seq[DirectedHtlc] = { + def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): Seq[DirectedHtlc] = { val htlcSuccessFee = commitmentContext match { - case ContextCommitmentV1 => weight2fee(spec.feeratePerKw, htlcSuccessWeight) - case ContextSimplifiedCommitment => Satoshi(0) + case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcSuccessWeight) + case VersionSimplifiedCommitment => Satoshi(0) } spec.htlcs .filter(_.direction == IN) @@ -144,12 +144,12 @@ object Transactions { .toSeq } - def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): Satoshi = { + def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): Satoshi = { val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec) val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec) commitmentContext match { - case ContextCommitmentV1 => weight2fee(spec.feeratePerKw , commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) - case ContextSimplifiedCommitment => weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) // simplified commitment has an hardcoded feerate + case VersionCommitmentV1 => weight2fee(spec.feeratePerKw , commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) + case VersionSimplifiedCommitment => weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) // simplified commitment has an hardcoded feerate } } @@ -199,9 +199,9 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): CommitTx = commitmentContext match { + def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): CommitTx = commitmentContext match { - case ContextCommitmentV1 => + case VersionCommitmentV1 => val commitFee = commitTxFee(localDustLimit, spec) val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { @@ -228,7 +228,7 @@ object Transactions { lockTime = locktime) CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) - case ContextSimplifiedCommitment => + case VersionSimplifiedCommitment => val commitFee = commitTxFee(localDustLimit, spec) val pushMeValueTotal = pushMeValue * 2 // funder pays the total amount of pushme outputs @@ -295,7 +295,7 @@ object Transactions { lockTime = 0), htlc.paymentHash) } - def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentContext): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => val htlcTx = makeHtlcTimeoutTx(commitTx, outputsAlreadyUsed, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec.feeratePerKw, htlc.add) @@ -358,10 +358,10 @@ object Transactions { ClaimHtlcTimeoutTx(input, tx1) } - def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int])(implicit commitmentContext: CommitmentContext): ClaimP2WPKHOutputTx = { + def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int])(implicit commitmentContext: CommitmentVersion): ClaimP2WPKHOutputTx = { val claimTx = commitmentContext match { - case ContextCommitmentV1 => + case VersionCommitmentV1 => val redeemScript = Script.pay2pkh(localPaymentPubkey) val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) @@ -378,7 +378,7 @@ object Transactions { Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)) // here localPaymentPubkey == localDelayedPaymentPubkey - case ContextSimplifiedCommitment => + case VersionSimplifiedCommitment => val redeemScript = Script.pay2pkh(localPaymentPubkey) val pubkeyScript = write(toRemoteDelayed(localPaymentPubkey, toRemoteDelay.getOrElse(throw new IllegalArgumentException("Error claiming the main output from remote commit, no 'toRemoteDelay' specified. (option_simplified_commitment)")))) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) @@ -407,7 +407,7 @@ object Transactions { ClaimP2WPKHOutputTx(claimTx.input, tx1) } - def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): ClaimDelayedOutputTx = { + def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentVersion): ClaimDelayedOutputTx = { val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) @@ -464,9 +464,9 @@ object Transactions { } // TODO adjust for option_simplified_commitment -> sweep pushme outputs - def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long)(implicit commitmentContext: CommitmentContext): MainPenaltyTx = commitmentContext match { - case ContextSimplifiedCommitment => throw new NotImplementedError("makeMainPenaltyTx with option_simplified_commitment") - case ContextCommitmentV1 => + def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long)(implicit commitmentContext: CommitmentVersion): MainPenaltyTx = commitmentContext match { + case VersionSimplifiedCommitment => throw new NotImplementedError("makeMainPenaltyTx with option_simplified_commitment") + case VersionCommitmentV1 => val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index f0450e3a21..b0a4fa7866 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -231,7 +231,7 @@ object ChannelCodecs extends Logging { val COMMITMENTv1_VERSION_BYTE = 0x00 val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01 - def commitmentCodec(commitmentContext: CommitmentContext): Codec[Commitments] = { + def commitmentCodec(commitmentContext: CommitmentVersion): Codec[Commitments] = { import shapeless.{::} (("localParams" | localParamsCodec) :: ("remoteParams" | remoteParamsCodec) :: @@ -300,17 +300,17 @@ object ChannelCodecs extends Logging { ) } - def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext: CommitmentContext): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext: CommitmentContext): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("shortChannelId" | shortchannelid) :: ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - def DATA_NORMAL_Codec(commitmentContext: CommitmentContext): Codec[DATA_NORMAL] = ( + def DATA_NORMAL_Codec(commitmentContext: CommitmentVersion): Codec[DATA_NORMAL] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool) :: @@ -319,19 +319,19 @@ object ChannelCodecs extends Logging { ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] - def DATA_SHUTDOWN_Codec(commitmentContext: CommitmentContext): Codec[DATA_SHUTDOWN] = ( + def DATA_SHUTDOWN_Codec(commitmentContext: CommitmentVersion): Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] - def DATA_NEGOTIATING_Codec(commitmentContext: CommitmentContext): Codec[DATA_NEGOTIATING] = ( + def DATA_NEGOTIATING_Codec(commitmentContext: CommitmentVersion): Codec[DATA_NEGOTIATING] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec) :: ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - def DATA_CLOSING_Codec(commitmentContext: CommitmentContext): Codec[DATA_CLOSING] = ( + def DATA_CLOSING_Codec(commitmentContext: CommitmentVersion): Codec[DATA_CLOSING] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: @@ -341,11 +341,11 @@ object ChannelCodecs extends Logging { ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] - def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext: CommitmentContext): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext: CommitmentVersion): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( ("commitments" | commitmentCodec(commitmentContext)) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] - def stateDataCodec(commitmentContext: CommitmentContext): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) + def stateDataCodec(commitmentContext: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext)) .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext)) .typecase(0x03, DATA_NORMAL_Codec(commitmentContext)) @@ -355,13 +355,13 @@ object ChannelCodecs extends Logging { .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext)) private val genericStateDataDecoder = discriminated[HasCommitments].by(uint8) - .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(ContextCommitmentV1)) - .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(ContextSimplifiedCommitment)).asDecoder + .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(VersionCommitmentV1)) + .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(VersionSimplifiedCommitment)).asDecoder private val genericStateDataEncoder = new Encoder[HasCommitments] { override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments.getContext match { - case ContextCommitmentV1 => stateDataCodec(ContextCommitmentV1).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) - case ContextSimplifiedCommitment => stateDataCodec(ContextSimplifiedCommitment).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) + case VersionCommitmentV1 => stateDataCodec(VersionCommitmentV1).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) + case VersionSimplifiedCommitment => stateDataCodec(VersionSimplifiedCommitment).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) case _ => Attempt.failure(Err("Unknown type")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 08438c38db..6ccd00c82c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -70,7 +70,7 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel alice2bob.forward(bob) awaitCond({ bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && - bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == ContextCommitmentV1 + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == VersionCommitmentV1 }) bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[WatchSpent] @@ -83,7 +83,7 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel alice2bob.forward(bob) awaitCond({ bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && - bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == ContextSimplifiedCommitment + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == VersionSimplifiedCommitment }) bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[WatchSpent] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 0890fa8b59..124f264d60 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -79,7 +79,7 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp bob2alice.forward(alice) val aliceStateData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] - assert(aliceStateData.commitments.getContext == ContextSimplifiedCommitment) + assert(aliceStateData.commitments.getContext == VersionSimplifiedCommitment) // there should be 2 push-me outputs with amount 1000 sat val aliceLocalCommitTx = aliceStateData.commitments.localCommit.publishableTxs.commitTx.tx diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index f760c5333f..d719e36931 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -306,7 +306,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) bob ! htlc - awaitCond(initialData.commitments.getContext == ContextSimplifiedCommitment) + awaitCond(initialData.commitments.getContext == VersionSimplifiedCommitment) awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) // bob won't forward the add before it is cross-signed relayerB.expectNoMsg() @@ -666,8 +666,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.expectMsgType[CommitSig] awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN)) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked.size == 0) @@ -717,8 +717,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == OUT)) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == ContextSimplifiedCommitment) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) } @@ -1453,7 +1453,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (option_simplified_commitment)", Tag("simplified_commitment")) { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] - assert(initialData.commitments.getContext == ContextSimplifiedCommitment) + assert(initialData.commitments.getContext == VersionSimplifiedCommitment) // Alice sends update_fee to Bob but this shouldn't happen with option_simplified_commitment so Bob will reply Error val fee1 = UpdateFee("00" * 32, 12000) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 4f650af2ed..1a418ecd11 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -118,14 +118,14 @@ object ChannelStateSpec { remoteNextHtlcId = 4L, originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), remoteNextCommitInfo = Right(randomKey.publicKey), - commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = ContextCommitmentV1) + commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = VersionCommitmentV1) val simplifiedCommitment = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), remoteNextCommitInfo = Right(randomKey.publicKey), - commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = ContextSimplifiedCommitment) + commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = VersionSimplifiedCommitment) val channelUpdate = Announcements.makeChannelUpdate("11" * 32, randomKey, randomKey.publicKey, ShortChannelId(142553), 42, 15, 575, 53, Channel.MAX_FUNDING_SATOSHIS * 1000L) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index de4146f415..f3344e4e9e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar} import fr.acinq.bitcoin._ -import fr.acinq.eclair.channel.ContextCommitmentV1 +import fr.acinq.eclair.channel.VersionCommitmentV1 import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.Generators import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo} @@ -191,7 +191,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key Remote.delayed_payment_pubkey, - spec)(ContextCommitmentV1) + spec)(VersionCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey, SIGHASH_ALL) val remote_sig = Transactions.sign(tx, Remote.funding_privkey, SIGHASH_ALL) @@ -199,7 +199,7 @@ class TestVectorsSpec extends FunSuite with Logging { Transactions.addSigs(tx, Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig) } - val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)(ContextCommitmentV1) + val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)(VersionCommitmentV1) logger.info(s"# base commitment transaction fee = ${baseFee.toLong}") val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}") @@ -222,7 +222,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key Remote.delayed_payment_pubkey, - spec)(ContextCommitmentV1) + spec)(VersionCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey, SIGHASH_ALL) logger.info(s"# local_signature = ${toHexString(local_sig.dropRight(1))}") @@ -240,7 +240,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key - spec)(commitmentContext = ContextCommitmentV1) + spec)(commitmentContext = VersionCommitmentV1) logger.info(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}") val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 8b8fa3c015..21004d0f82 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -74,8 +74,8 @@ class TransactionsSpec extends FunSuite with Logging { ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0) - assert(commitTxFee(Satoshi(546), spec)(ContextSimplifiedCommitment) == expectedSizeSimplifiedCommitment) - assert(commitTxFee(Satoshi(546), spec)(ContextCommitmentV1) == expectedSizeCommitmentV1) + assert(commitTxFee(Satoshi(546), spec)(VersionSimplifiedCommitment) == expectedSizeSimplifiedCommitment) + assert(commitTxFee(Satoshi(546), spec)(VersionCommitmentV1) == expectedSizeCommitmentV1) } test("check pre-computed transaction weights") { @@ -95,7 +95,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimP2WPKHOutputTx val pubKeyScript = write(pay2wpkh(localPaymentPriv.publicKey)) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(ContextCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(VersionCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimP2WPKHOutputTx, localPaymentPriv.publicKey, "bb" * 73).tx) assert(claimP2WPKHOutputWeight == weight) @@ -107,7 +107,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake htlcSuccessOrTimeoutTx tx, containing only the output that will be spent by the ClaimDelayedOutputTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val htlcSuccessOrTimeoutTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimHtlcDelayedTx = makeClaimDelayedOutputTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) + val claimHtlcDelayedTx = makeClaimDelayedOutputTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(VersionCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimHtlcDelayedTx, "bb" * 73).tx) assert(claimHtlcDelayedWeight == weight) @@ -119,7 +119,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the MainPenaltyTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val mainPenaltyTx = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw)(ContextCommitmentV1) + val mainPenaltyTx = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw)(VersionCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(mainPenaltyTx, "bb" * 73).tx) assert(mainPenaltyWeight == weight) @@ -209,14 +209,14 @@ class TransactionsSpec extends FunSuite with Logging { toRemoteMsat = millibtc2satoshi(MilliBtc(300)).amount * 1000) val commitTxNumber = 0x404142434445L - def commitTx(commitContext: CommitmentContext = ContextCommitmentV1) = { + def commitTx(commitContext: CommitmentVersion = VersionCommitmentV1) = { val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, remoteDelayedPaymentPriv.publicKey, spec)(commitContext) val localSig = Transactions.sign(txinfo, localPaymentPriv, SIGHASH_ALL) val remoteSig = Transactions.sign(txinfo, remotePaymentPriv, SIGHASH_ALL) Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) } - def createHtlcTxs(commitmentContext: CommitmentContext = ContextCommitmentV1):(Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def createHtlcTxs(commitmentContext: CommitmentVersion = VersionCommitmentV1):(Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { makeHtlcTxs(commitTx(commitmentContext).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentContext) } @@ -228,7 +228,7 @@ class TransactionsSpec extends FunSuite with Logging { assert((check ^ num) == commitTxNumber) } - val (htlcTimeoutTxs, htlcSuccessTxs) = createHtlcTxs(ContextCommitmentV1) + val (htlcTimeoutTxs, htlcSuccessTxs) = createHtlcTxs(VersionCommitmentV1) assert(htlcTimeoutTxs.size == 2) // htlc1 and htlc3 assert(htlcSuccessTxs.size == 2) // htlc2 and htlc4 @@ -245,10 +245,10 @@ class TransactionsSpec extends FunSuite with Logging { { // Simplified commitment - val commitTransaction = commitTx(commitContext = ContextSimplifiedCommitment) + val commitTransaction = commitTx(commitContext = VersionSimplifiedCommitment) // local is funder, pays for fees + push_me outputs - val expectedToLocalAmount = Satoshi(spec.toLocalMsat / 1000) - Transactions.pushMeValue * 2 - commitTxFee(localDustLimit, spec)(ContextSimplifiedCommitment) + val expectedToLocalAmount = Satoshi(spec.toLocalMsat / 1000) - Transactions.pushMeValue * 2 - commitTxFee(localDustLimit, spec)(VersionSimplifiedCommitment) val expectedToRemoteAmount = Satoshi(spec.toRemoteMsat / 1000) // there must be 2 push-me outputs @@ -304,19 +304,19 @@ class TransactionsSpec extends FunSuite with Logging { { // local spends delayed output of htlc2 success tx - val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) + val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(VersionCommitmentV1) val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimHtlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc4 timeout tx because it is below the dust limit intercept[RuntimeException] { - makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) + makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(VersionCommitmentV1) } } { // remote spends main output - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx(ContextCommitmentV1).tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(ContextCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx(VersionCommitmentV1).tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(VersionCommitmentV1) val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) @@ -398,7 +398,7 @@ class TransactionsSpec extends FunSuite with Logging { tests.foreach(test => { logger.info(s"running BOLT 2 test: '${test.name}'") - val fee = commitTxFee(test.dustLimit, test.spec)(ContextCommitmentV1) + val fee = commitTxFee(test.dustLimit, test.spec)(VersionCommitmentV1) assert(fee === test.expectedFee) }) } From 610cea812072d4a88e82b2cfbee34c23a8847d29 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 7 Mar 2019 16:35:19 +0100 Subject: [PATCH 58/66] Rename context => version in function params --- .../fr/acinq/eclair/channel/Channel.scala | 14 +++--- .../acinq/eclair/channel/ChannelTypes.scala | 2 +- .../fr/acinq/eclair/channel/Commitments.scala | 39 +++++++-------- .../fr/acinq/eclair/channel/Helpers.scala | 22 ++++---- .../eclair/transactions/Transactions.scala | 24 ++++----- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 50 +++++++++---------- .../b/WaitForFundingCreatedStateSpec.scala | 4 +- .../b/WaitForFundingSignedStateSpec.scala | 2 +- .../channel/states/e/NormalStateSpec.scala | 12 ++--- .../eclair/transactions/TestVectorsSpec.scala | 2 +- .../transactions/TransactionsSpec.scala | 4 +- 11 files changed, 87 insertions(+), 88 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index fdf07449e9..0023bff824 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -634,7 +634,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val nextCommitNumber = nextRemoteCommit.index // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.getContext) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.getContext) + val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.version) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.version) trimmedHtlcs collect { case DirectedHtlc(_, u) => log.info(s"adding paymentHash=${u.paymentHash} cltvExpiry=${u.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") @@ -764,11 +764,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // are there pending signed htlcs on either side? we need to have received their last revocation! if (d.commitments.hasNoPendingHtlcs) { // there are no pending signed htlcs, let's go directly to NEGOTIATING - if (d.commitments.localParams.isFunder && d.commitments.getContext == VersionCommitmentV1) { + if (d.commitments.localParams.isFunder && d.commitments.version == VersionCommitmentV1) { // we are funder and we're using commitmentV1, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned - } else if(!d.commitments.localParams.isFunder && d.commitments.getContext == VersionSimplifiedCommitment) { + } else if(!d.commitments.localParams.isFunder && d.commitments.version == VersionSimplifiedCommitment) { // we are fundee BUT we're using option_simplified_commitment, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned @@ -1024,7 +1024,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } if (commitments1.hasNoPendingHtlcs) { log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") - if (d.commitments.localParams.isFunder || commitments1.getContext == VersionSimplifiedCommitment) { + if (d.commitments.localParams.isFunder || commitments1.version == VersionSimplifiedCommitment) { // we are funder or we're using option_simplified_commitmemt, need to initiate the negotiation by sending the first closing_signed val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned @@ -1205,7 +1205,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(WatchEventConfirmed(BITCOIN_TX_CONFIRMED(tx), blockHeight, _), d: DATA_CLOSING) => log.info(s"txid=${tx.txid} has reached mindepth, updating closing state") - implicit val commitmentContext = d.commitments.getContext + implicit val commitmentVersion = d.commitments.version // first we check if this tx belongs to one of the current local/remote commits and update it val localCommitPublished1 = d.localCommitPublished.map(Closing.updateLocalCommitPublished(_, tx)) val remoteCommitPublished1 = d.remoteCommitPublished.map(Closing.updateRemoteCommitPublished(_, tx)) @@ -1325,7 +1325,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) - val myCurrentPerCommitmentPoint = d.commitments.getContext match { + val myCurrentPerCommitmentPoint = d.commitments.version match { case VersionCommitmentV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)) case VersionSimplifiedCommitment => None } @@ -1460,7 +1460,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // BOLT 2: A node if it has sent a previous shutdown MUST retransmit shutdown. // negotiation restarts from the beginning, and is initialized by the funder // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them - if (d.commitments.localParams.isFunder || d.commitments.getContext == VersionSimplifiedCommitment) { + if (d.commitments.localParams.isFunder || d.commitments.version == VersionSimplifiedCommitment) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index ece7333507..26e25ec7ff 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -166,7 +166,7 @@ final case class DATA_NEGOTIATING(commitments: Commitments, closingTxProposed: List[List[ClosingTxProposed]], // one list for every negotiation (there can be several in case of disconnection) bestUnpublishedClosingTx_opt: Option[Transaction]) extends Data with HasCommitments { require(!closingTxProposed.isEmpty, "there must always be a list for the current negotiation") - require(commitments.getContext == VersionSimplifiedCommitment || !commitments.localParams.isFunder || closingTxProposed.forall(!_.isEmpty), "funder must have at least one closing signature for every negotation attempt because it initiates the closing") + require(commitments.version == VersionSimplifiedCommitment || !commitments.localParams.isFunder || closingTxProposed.forall(!_.isEmpty), "funder must have at least one closing signature for every negotation attempt because it initiates the closing") } final case class DATA_CLOSING(commitments: Commitments, mutualCloseProposed: List[Transaction], // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index e63935597c..a4ba87c359 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -77,11 +77,10 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, // TODO subtract the pushMe value from the balance? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced)(commitmentContext = getContext).amount * 1000 else 0 + val feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced)(commitmentVersion = version).amount * 1000 else 0 reduced.toRemoteMsat - remoteParams.channelReserveSatoshis * 1000 - feesMsat } - def getContext = version } // @formatter: off @@ -111,7 +110,7 @@ object Commitments { * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc) */ def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC, origin: Origin): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { - implicit val commitmentContex = commitments.getContext + implicit val commitmentVersion = commitments.version if (cmd.paymentHash.size != 32) { return Left(InvalidPaymentHash(commitments.channelId)) @@ -165,7 +164,7 @@ object Commitments { } def receiveAdd(commitments: Commitments, add: UpdateAddHtlc): Commitments = { - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version if (add.id != commitments.remoteNextHtlcId) { throw UnexpectedHtlcId(commitments.channelId, expected = commitments.remoteNextHtlcId, actual = add.id) @@ -309,11 +308,11 @@ object Commitments { throw FundeeCannotSendUpdateFee(commitments.channelId) } - if(commitments.getContext == VersionSimplifiedCommitment){ + if(commitments.version == VersionSimplifiedCommitment){ throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version // let's compute the current commitment *as seen by them* with this change taken into account val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw) @@ -346,11 +345,11 @@ object Commitments { throw FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw) } - if(commitments.getContext == VersionSimplifiedCommitment){ + if(commitments.version == VersionSimplifiedCommitment){ throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version // NB: we check that the funder can afford this new fee even if spec allows to do it at next signature // It is easier to do it here because under certain (race) conditions spec allows a lower-than-normal fee to be paid, @@ -385,7 +384,7 @@ object Commitments { def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index)) def sendCommit(commitments: Commitments, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, CommitSig) = { - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version import commitments._ @@ -400,7 +399,7 @@ object Commitments { val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = commitmentContext match { + val htlcSigs = commitmentVersion match { case VersionCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_ALL)) case VersionSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) } @@ -426,7 +425,7 @@ object Commitments { } } def receiveCommit(commitments: Commitments, commit: CommitSig, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = { - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version import commitments._ // they sent us a signature for *their* view of *our* next commit tx @@ -469,7 +468,7 @@ object Commitments { if (commit.htlcSignatures.size != sortedHtlcTxs.size) { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } - val htlcSigs = commitmentContext match { + val htlcSigs = commitmentVersion match { case VersionCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)) case VersionSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) } @@ -481,13 +480,13 @@ object Commitments { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == VersionCommitmentV1 => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentVersion == VersionCommitmentV1 => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_ALL) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentContext == VersionSimplifiedCommitment => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentVersion == VersionSimplifiedCommitment => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) @@ -558,11 +557,11 @@ object Commitments { } } - def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) - val remotePaymentPubkey = commitmentContext match { + val remotePaymentPubkey = commitmentVersion match { case VersionSimplifiedCommitment => PublicKey(remoteParams.paymentBasepoint) case VersionCommitmentV1 => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) } @@ -574,8 +573,8 @@ object Commitments { (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = commitmentContext match { + def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + val localPaymentPubkey = commitmentVersion match { case VersionSimplifiedCommitment => keyManager.paymentPoint(localParams.channelKeyPath).publicKey case VersionCommitmentV1 => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) } @@ -603,7 +602,7 @@ object Commitments { def changes2String(commitments: Commitments): String = { import commitments._ - s"""(${commitments.getContext}) commitments: + s"""(${commitments.version}) commitments: | localChanges: | proposed: ${localChanges.proposed.map(msg2String(_)).mkString(" ")} | signed: ${localChanges.signed.map(msg2String(_)).mkString(" ")} @@ -618,7 +617,7 @@ object Commitments { } def specs2String(commitments: Commitments): String = { - s"""(${commitments.getContext}) specs: + s"""(${commitments.version}) specs: |localcommit: | toLocal: ${commitments.localCommit.spec.toLocalMsat} | toRemote: ${commitments.localCommit.spec.toRemoteMsat} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 266fa5b0f7..855fac7359 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -231,9 +231,9 @@ object Helpers { * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) */ def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { - implicit val commitmentContext = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { - case true => VersionSimplifiedCommitment + implicit val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match { case false => VersionCommitmentV1 + case true => VersionSimplifiedCommitment } // TODO adjust for option_simplified_commitment @@ -346,7 +346,7 @@ object Helpers { } } - def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = commitments.getContext match { + def firstClosingFee(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): Satoshi = commitments.version match { case VersionCommitmentV1 => import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts @@ -419,7 +419,7 @@ object Helpers { def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) @@ -436,7 +436,7 @@ object Helpers { }) // the push-me trasaction attaches the fees to the commitmentTx - val pushMeTransaction = commitmentContext match { + val pushMeTransaction = commitmentVersion match { case VersionCommitmentV1 => None case VersionSimplifiedCommitment => generateTx("push-me-cpfp")(Try { @@ -505,7 +505,7 @@ object Helpers { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx") - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, localPerCommitmentPoint, remoteCommit.spec) @@ -562,12 +562,12 @@ object Helpers { * @return a list of transactions (one per HTLC that we can claim) */ def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version // no need to use a high fee rate for our main output (we are the only one who can spend it) val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 - val mainTx = commitments.getContext match { + val mainTx = commitments.version match { case VersionCommitmentV1 => val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) generateTx("claim-p2wpkh-output")(Try { @@ -607,7 +607,7 @@ object Helpers { * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { - implicit val commitmentContext = commitments.getContext + implicit val commitmentVersion = commitments.version import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") @@ -782,7 +782,7 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit commitmentVersion: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == localCommit.publishableTxs.commitTx.tx.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) (localCommit.spec.htlcs.filter(_.direction == OUT) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec)).map(_.add) @@ -806,7 +806,7 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit commitmentContext: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit commitmentVersion: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == remoteCommit.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) (remoteCommit.spec.htlcs.filter(_.direction == IN) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec)).map(_.add) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 5ff953fbbe..b6027ccb88 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -122,8 +122,8 @@ object Transactions { */ def fee2rate(fee: Satoshi, weight: Int) = (fee.amount * 1000L) / weight - def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): Seq[DirectedHtlc] = { - val htlcTimeoutFee = commitmentContext match { + def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = { + val htlcTimeoutFee = commitmentVersion match { case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcTimeoutWeight) case VersionSimplifiedCommitment => Satoshi(0) } @@ -133,8 +133,8 @@ object Transactions { .toSeq } - def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): Seq[DirectedHtlc] = { - val htlcSuccessFee = commitmentContext match { + def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = { + val htlcSuccessFee = commitmentVersion match { case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcSuccessWeight) case VersionSimplifiedCommitment => Satoshi(0) } @@ -144,10 +144,10 @@ object Transactions { .toSeq } - def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): Satoshi = { + def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): Satoshi = { val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec) val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec) - commitmentContext match { + commitmentVersion match { case VersionCommitmentV1 => weight2fee(spec.feeratePerKw , commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) case VersionSimplifiedCommitment => weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) // simplified commitment has an hardcoded feerate } @@ -199,7 +199,7 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): CommitTx = commitmentContext match { + def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): CommitTx = commitmentVersion match { case VersionCommitmentV1 => val commitFee = commitTxFee(localDustLimit, spec) @@ -295,7 +295,7 @@ object Transactions { lockTime = 0), htlc.paymentHash) } - def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentContext: CommitmentVersion): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => val htlcTx = makeHtlcTimeoutTx(commitTx, outputsAlreadyUsed, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec.feeratePerKw, htlc.add) @@ -358,9 +358,9 @@ object Transactions { ClaimHtlcTimeoutTx(input, tx1) } - def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int])(implicit commitmentContext: CommitmentVersion): ClaimP2WPKHOutputTx = { + def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int])(implicit commitmentVersion: CommitmentVersion): ClaimP2WPKHOutputTx = { - val claimTx = commitmentContext match { + val claimTx = commitmentVersion match { case VersionCommitmentV1 => val redeemScript = Script.pay2pkh(localPaymentPubkey) val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) @@ -407,7 +407,7 @@ object Transactions { ClaimP2WPKHOutputTx(claimTx.input, tx1) } - def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentContext: CommitmentVersion): ClaimDelayedOutputTx = { + def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentVersion: CommitmentVersion): ClaimDelayedOutputTx = { val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) @@ -464,7 +464,7 @@ object Transactions { } // TODO adjust for option_simplified_commitment -> sweep pushme outputs - def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long)(implicit commitmentContext: CommitmentVersion): MainPenaltyTx = commitmentContext match { + def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long)(implicit commitmentVersion: CommitmentVersion): MainPenaltyTx = commitmentVersion match { case VersionSimplifiedCommitment => throw new NotImplementedError("makeMainPenaltyTx with option_simplified_commitment") case VersionCommitmentV1 => val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index b0a4fa7866..dea4de012c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -231,7 +231,7 @@ object ChannelCodecs extends Logging { val COMMITMENTv1_VERSION_BYTE = 0x00 val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01 - def commitmentCodec(commitmentContext: CommitmentVersion): Codec[Commitments] = { + def commitmentCodec(commitmentVersion: CommitmentVersion): Codec[Commitments] = { import shapeless.{::} (("localParams" | localParamsCodec) :: ("remoteParams" | remoteParamsCodec) :: @@ -278,7 +278,7 @@ object ChannelCodecs extends Logging { commitInput, remotePerCommitmentSecrets, channelId, - version = commitmentContext + version = commitmentVersion ) }, g = { c: Commitments => @@ -300,18 +300,18 @@ object ChannelCodecs extends Logging { ) } - def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("shortChannelId" | shortchannelid) :: ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - def DATA_NORMAL_Codec(commitmentContext: CommitmentVersion): Codec[DATA_NORMAL] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_NORMAL_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NORMAL] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool) :: ("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) :: @@ -319,20 +319,20 @@ object ChannelCodecs extends Logging { ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] - def DATA_SHUTDOWN_Codec(commitmentContext: CommitmentVersion): Codec[DATA_SHUTDOWN] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_SHUTDOWN_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_SHUTDOWN] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] - def DATA_NEGOTIATING_Codec(commitmentContext: CommitmentVersion): Codec[DATA_NEGOTIATING] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_NEGOTIATING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NEGOTIATING] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec) :: ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - def DATA_CLOSING_Codec(commitmentContext: CommitmentVersion): Codec[DATA_CLOSING] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_CLOSING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_CLOSING] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: @@ -341,25 +341,25 @@ object ChannelCodecs extends Logging { ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] - def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext: CommitmentVersion): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( - ("commitments" | commitmentCodec(commitmentContext)) :: + def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] - def stateDataCodec(commitmentContext: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) - .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentContext)) - .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentContext)) - .typecase(0x03, DATA_NORMAL_Codec(commitmentContext)) - .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentContext)) - .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentContext)) - .typecase(0x06, DATA_CLOSING_Codec(commitmentContext)) - .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentContext)) + def stateDataCodec(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) + .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) + .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion)) + .typecase(0x03, DATA_NORMAL_Codec(commitmentVersion)) + .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentVersion)) + .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentVersion)) + .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) + .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) private val genericStateDataDecoder = discriminated[HasCommitments].by(uint8) .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(VersionCommitmentV1)) .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(VersionSimplifiedCommitment)).asDecoder private val genericStateDataEncoder = new Encoder[HasCommitments] { - override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments.getContext match { + override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments.version match { case VersionCommitmentV1 => stateDataCodec(VersionCommitmentV1).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) case VersionSimplifiedCommitment => stateDataCodec(VersionSimplifiedCommitment).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) case _ => Attempt.failure(Err("Unknown type")) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 6ccd00c82c..d4e4b220e4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -70,7 +70,7 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel alice2bob.forward(bob) awaitCond({ bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && - bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == VersionCommitmentV1 + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.version == VersionCommitmentV1 }) bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[WatchSpent] @@ -83,7 +83,7 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel alice2bob.forward(bob) awaitCond({ bob.stateName == WAIT_FOR_FUNDING_CONFIRMED && - bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.getContext == VersionSimplifiedCommitment + bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.version == VersionSimplifiedCommitment }) bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[WatchSpent] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 124f264d60..358d1e987a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -79,7 +79,7 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp bob2alice.forward(alice) val aliceStateData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] - assert(aliceStateData.commitments.getContext == VersionSimplifiedCommitment) + assert(aliceStateData.commitments.version == VersionSimplifiedCommitment) // there should be 2 push-me outputs with amount 1000 sat val aliceLocalCommitTx = aliceStateData.commitments.localCommit.publishableTxs.commitTx.tx diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index d719e36931..2453000f6f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -306,7 +306,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) bob ! htlc - awaitCond(initialData.commitments.getContext == VersionSimplifiedCommitment) + awaitCond(initialData.commitments.version == VersionSimplifiedCommitment) awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) // bob won't forward the add before it is cross-signed relayerB.expectNoMsg() @@ -666,8 +666,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.expectMsgType[CommitSig] awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN)) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.version == VersionSimplifiedCommitment) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.version == VersionSimplifiedCommitment) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked.size == 0) @@ -717,8 +717,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == OUT)) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.getContext == VersionSimplifiedCommitment) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.version == VersionSimplifiedCommitment) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.version == VersionSimplifiedCommitment) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) } @@ -1453,7 +1453,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (option_simplified_commitment)", Tag("simplified_commitment")) { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] - assert(initialData.commitments.getContext == VersionSimplifiedCommitment) + assert(initialData.commitments.version == VersionSimplifiedCommitment) // Alice sends update_fee to Bob but this shouldn't happen with option_simplified_commitment so Bob will reply Error val fee1 = UpdateFee("00" * 32, 12000) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index f3344e4e9e..bf680252cf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -240,7 +240,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key - spec)(commitmentContext = VersionCommitmentV1) + spec)(commitmentVersion = VersionCommitmentV1) logger.info(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}") val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 21004d0f82..289ae2877c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -216,8 +216,8 @@ class TransactionsSpec extends FunSuite with Logging { Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) } - def createHtlcTxs(commitmentContext: CommitmentVersion = VersionCommitmentV1):(Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - makeHtlcTxs(commitTx(commitmentContext).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentContext) + def createHtlcTxs(commitmentVersion: CommitmentVersion = VersionCommitmentV1):(Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + makeHtlcTxs(commitTx(commitmentVersion).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentVersion) } { From 178d6cf9bdd1de782c7269053ce9a12bcdfc4c36 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 7 Mar 2019 17:32:54 +0100 Subject: [PATCH 59/66] Add test for backward compatibility (broken) --- .../fr/acinq/eclair/channel/Channel.scala | 14 +++++++------- .../fr/acinq/eclair/channel/Commitments.scala | 3 +-- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 13 +++++++------ .../fr/acinq/eclair/db/ChannelStateSpec.scala | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 0023bff824..7bf3a3ae7e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -616,13 +616,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Failure(cause) => handleLocalError(cause, d, Some(fee)) } - case Event(c@CMD_SIGN, d@DATA_NORMAL(commitments: Commitments, _, _, _, _, _, _)) => - commitments.remoteNextCommitInfo match { - case _ if !Commitments.localHasChanges(commitments) => + case Event(c@CMD_SIGN, d: DATA_NORMAL) => + d.commitments.remoteNextCommitInfo match { + case _ if !Commitments.localHasChanges(d.commitments) => log.debug("ignoring CMD_SIGN (nothing to sign)") stay case Right(_) => - Try(Commitments.sendCommit(commitments, keyManager)) match { + Try(Commitments.sendCommit(d.commitments, keyManager)) match { case Success((commitments1, commit)) => log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}") commitments1.localChanges.signed.collect { @@ -634,13 +634,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val nextCommitNumber = nextRemoteCommit.index // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.version) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(commitments.version) + val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(d.commitments.version) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(d.commitments.version) trimmedHtlcs collect { case DirectedHtlc(_, u) => log.info(s"adding paymentHash=${u.paymentHash} cltvExpiry=${u.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") nodeParams.channelsDb.addOrUpdateHtlcInfo(d.channelId, nextCommitNumber, u.paymentHash, u.cltvExpiry) } - if (!Helpers.aboveReserve(commitments) && Helpers.aboveReserve(commitments1)) { + if (!Helpers.aboveReserve(d.commitments) && Helpers.aboveReserve(commitments1)) { // we just went above reserve (can't go below), let's refresh our channel_update to enable/disable it accordingly log.info(s"updating channel_update aboveReserve=${Helpers.aboveReserve(commitments1)}") self ! TickRefreshChannelUpdate @@ -657,7 +657,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Left(waitForRevocation) => log.debug(s"already in the process of signing, will sign again as soon as possible") - val commitments1 = commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) + val commitments1 = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))) stay using d.copy(commitments = commitments1) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index a4ba87c359..640926141a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -522,7 +522,7 @@ object Commitments { case Left(_) if revocation.perCommitmentSecret.toPoint != remoteCommit.remotePerCommitmentPoint => throw InvalidRevocation(commitments.channelId) case Left(WaitingForRevocation(theirNextCommit, _, _, _)) => - val forwards = commitments.remoteChanges.signed collect { + val forwards = commitments.remoteChanges.signed collect { // we forward adds downstream only when they have been committed by both sides // it always happen when we receive a revocation, because they send the add, then they sign it, then we sign it case add: UpdateAddHtlc => ForwardAdd(add) @@ -550,7 +550,6 @@ object Commitments { remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint), remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index), originChannels = originChannels1) - (commitments1, forwards) case Right(_) => throw UnexpectedRevocation(commitments.channelId) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index dea4de012c..3dfa3e1e22 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -228,8 +228,8 @@ object ChannelCodecs extends Logging { SSSSSSSSSSSSSSS TTTTTTTTTTTAAAAAAA AAAAAAATTTTTTTTTTT EEEEEEEEEEEEEEEEEEEEEE DDDDDDDDDDDDD AAAAAAA AAAAAAATTTTTTTTTTTAAAAAAA AAAAAAA */ - val COMMITMENTv1_VERSION_BYTE = 0x00 - val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01 + val COMMITMENTv1_VERSION_BYTE = 0x00.toByte + val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01.toByte def commitmentCodec(commitmentVersion: CommitmentVersion): Codec[Commitments] = { import shapeless.{::} @@ -359,10 +359,11 @@ object ChannelCodecs extends Logging { .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(VersionSimplifiedCommitment)).asDecoder private val genericStateDataEncoder = new Encoder[HasCommitments] { - override def encode(value: HasCommitments): Attempt[BitVector] = value.commitments.version match { - case VersionCommitmentV1 => stateDataCodec(VersionCommitmentV1).encode(value).map(bv => BitVector(COMMITMENTv1_VERSION_BYTE) ++ bv) - case VersionSimplifiedCommitment => stateDataCodec(VersionSimplifiedCommitment).encode(value).map(bv => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ bv) - case _ => Attempt.failure(Err("Unknown type")) + override def encode(value: HasCommitments): Attempt[BitVector] = stateDataCodec(value.commitments.version).encode(value).map { serializedState => + value.commitments.version match { + case VersionCommitmentV1 => BitVector(COMMITMENTv1_VERSION_BYTE) ++ serializedState + case VersionSimplifiedCommitment => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ serializedState + } } override def sizeBound: SizeBound = SizeBound(0, None) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 1a418ecd11..9c38121d81 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -28,6 +28,7 @@ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc} import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey} import org.scalatest.FunSuite +import scodec.bits.BitVector /** * Created by fabrice on 07/02/17. @@ -57,6 +58,20 @@ class ChannelStateSpec extends FunSuite { assert(data === check) } + test("backward compatible with previously stored commitments") { + val state = ChannelCodecs.genericStateDataCodec.decodeValue(BitVector(rawCommitment.data)).require + + assert(state.commitments.localParams.nodeId.raw == BinaryData("036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")) + assert(state.commitments.version === VersionCommitmentV1) + + val bin = ChannelCodecs.genericStateDataCodec.encode(state).require + val state1 = ChannelCodecs.genericStateDataCodec.decodeValue(bin).require + + assert(state === state1) + assert(bin.size === 0) // 15329 + assert(BitVector(rawCommitment.data) === 0) // 15336 + } + } object ChannelStateSpec { @@ -132,4 +147,7 @@ object ChannelStateSpec { val normal = DATA_NORMAL(commitmentsV1, ShortChannelId(42), true, None, channelUpdate, None, None) val normalSimplified = DATA_NORMAL(simplifiedCommitment, ShortChannelId(42), true, None, channelUpdate, None, None) + + // This is a serialized commitment (CommitmemtV1) taken from a mainnet DB and generated with code (commit: f9ead30) that did not account for other commitments type + val rawCommitment = BinaryData("000003036D65409C41AB7380A43448F257809E7496B52BF92057C09C4F300CBD61C50D96000429A0E801FFB3E3B20083A3F8C548677F0000000000000222000000012A05F20000000000000008FC00000000000000010090001E800BD48A44296C8BE17C66F9F5675400AE1ADFF2BF4C776F4380000000C501C3277812FEF47DAC3ECC48C36735250C344AF72254935FE1B87161B32CBD1FC78000000000000111000000009502F900000000000000047E00000000000000008048000F0180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A0152D19626E31E85DCC547D6452BFECEA4A58D6421DC3ED9C31EBB5BE25F5EC81301826DC6CF05233C470A78CD05F10719F58CC6E3F3297A86F29F41AD3EC17CD07B81D2E0F42ECE10F90F47068AD225E39205BE9F62234D8217239254D1B149BC91A881BDDB713BD0D5A69215373CB4DD6C082ACCCCA37973FC3ED56486184B4060A08200000001C0404100800000000000000A000000000A5B0000000006CAC68C00000000000FFC3400122B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE87000000000015B8410180000000001100102BC29224A48EBA98852295B2000EE07CBE322503CDAC9E423DFABF5A0D307C790023A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95700AD01000000000080AB6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE870000000000752B8CC00117840000000000000B000A2B55A3F061D2BD8FF8D65643AE2ABBE481B612B32135818000000000110010596BBCBEEA49205A1E44895DBE34810E998C5E3041AF6F154F174B877E08AED0820023982201102713B619608C4602CDCB4820CA2E2D1F18F632E14AD1AFBAA2530B195DB7E4B3811037E26CE7057B4B90C7CC88E0AAFCCA551824239C51DACAEAD6938B56E218264880A418228110804CBDAB73C3F569F5A7D6141639C3A94F923C6D15109F3E7C7019472671261B4201103E15DCB850126C070EF6E513AA9CF6D1B624692F4610DFFC6BBF519754B99DE880A3A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95745930A100000000000000000000A000000000A5B00000000000FFC340000000006CAC68C1882C81E7E9B4BAD22DECC021DED07B2A04CF401246F21260608E6D0362BB89981E6F8F85DBFDA1385E5274CBE5B0D21DD45329DE7FB534805C11187456282EE6E000000000000000000000000000000000000000500000000000000000000409D4457D14079D3398F705B263163D4841D8DFB50EF8E8FFFD7D72208E512D884800915B5F5BB68136004E09C3125C27C7AB6D6AD24E794184C27336B36E63398DF4380000000000ADC2080C00000000008800815E1491252475D4C42914AD90007703E5F191281E6D64F211EFD5FAD06983E3C8011D48840C04426479B88E587D3004FE9C3DD8C5EF1D73CE37CCFFDED122D443471EC27250840D91F44E8C2380484032792A5448197CB0C123E3C0583199654AA3D3C1DDE629754AB8000800F00003FFFFFFFFFFC008259C04D32D731B196AED4DC91753BC131B113C3BF619A9D7EB7A3222B3F376B9000F80003FFFFFFFFFFB0020703B9CA4B1B9FB5BA2809C0CF5FB8330E29FB47C27EC40C671E9798BADAAD29580007FFFFFFFFFF62B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE8704510980054B800070B821EA2784D9FC32BFEA579D3C6136DF87E6A232947BCC9DF84BC5337794370FF9740C589C29EB6931134B44414343132B149099E5DCA288BFD526B3C4BC888F22EA7DBA63472300DFBF40588AAC14A954A91FA3A7B1E760B9408841FC33B9C2137CC172868DBF6A5B3EA430FF121D3E09DEE03446599B79B33A980BB57721744E873A8774D20F9A26B466939C91BB0C9EA31367A8526F223FC505F5FAE0796C28711C7C0D4FE45184A8A5D284BA86AF7A06C7FBD30717DAAEAE237EBD1E9FD91D93A03FDAE3B23B8A2C360D692276F38C518BA33A18899D9E8C74CFA7F814AA37B0C12942FBD3BC19F24B56C5AB1E72645A06C79737C154F451FA67410A9460000DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E000006DACA81388356E701486891E4AF013CE92D6A57F240AF81389E60197AC38A1B2C070C9DE04BFBD1F6B0FB31230D9CD49430D12BDC89524D7F86E1C586CCB2F47F1E06C8FA274611C02420193C952A240CBE586091F1E02C18CCB2A551E9E0EEF314BA060221323CDC472C3E98027F4E1EEC62F78EB9E71BE67FEF68916A21A38F613929E7E8606E1BCCB0FEBDE1C2692621783F368F36FCB3B4840C7F47FF0EFE011EDCE3F34E089065806CC7E04039166B7EEB7B6B2116CE357A36B7A1DCCD7793B242DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E0000B8FD31EE020001200000000000000002000007D0000000C8000000001B6B0B0000") } From b27dc868d2c59d97056d98b8003624f959265d99 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 7 Mar 2019 18:14:36 +0100 Subject: [PATCH 60/66] Simplify scodecs for stateData (broken test) --- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 101 ++++-------------- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 5 +- 2 files changed, 21 insertions(+), 85 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 3dfa3e1e22..d82c530c1b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.wire.LightningMessageCodecs._ import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec, Decoder, Encoder, Err, GenCodec, SizeBound, Transformer} +import scodec.{Attempt, Codec, DecodeResult, Decoder, Encoder, Err, GenCodec, SizeBound, Transformer} import shapeless.{Generic, HList, HNil} /** @@ -232,72 +232,21 @@ object ChannelCodecs extends Logging { val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01.toByte def commitmentCodec(commitmentVersion: CommitmentVersion): Codec[Commitments] = { - import shapeless.{::} (("localParams" | localParamsCodec) :: - ("remoteParams" | remoteParamsCodec) :: - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64) :: - ("remoteNextHtlcId" | uint64) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | binarydata(32))).xmap( - f = { - case - localParams :: - remoteParams :: - flags :: - localCommit :: - remoteCommit :: - localChanges :: - remoteChanges :: - localNextHtlcId :: - remoteNextHtlcId :: - originChannels :: - remoteNextCommitInfo :: - commitInput :: - remotePerCommitmentSecrets :: - channelId :: - HNil => Commitments( - localParams, - remoteParams, - flags, - localCommit, - remoteCommit, - localChanges, - remoteChanges, - localNextHtlcId, - remoteNextHtlcId, - originChannels, - remoteNextCommitInfo, - commitInput, - remotePerCommitmentSecrets, - channelId, - version = commitmentVersion - ) - }, - g = { c: Commitments => - c.localParams :: - c.remoteParams :: - c.channelFlags :: - c.localCommit :: - c.remoteCommit :: - c.localChanges :: - c.remoteChanges :: - c.localNextHtlcId :: - c.remoteNextHtlcId :: - c.originChannels :: - c.remoteNextCommitInfo :: - c.commitInput :: - c.remotePerCommitmentSecrets :: - c.channelId :: HNil - } - ) + ("remoteParams" | remoteParamsCodec) :: + ("channelFlags" | byte) :: + ("localCommit" | localCommitCodec) :: + ("remoteCommit" | remoteCommitCodec) :: + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64) :: + ("remoteNextHtlcId" | uint64) :: + ("originChannels" | originsMapCodec) :: + ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: + ("commitInput" | inputInfoCodec) :: + ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: + ("channelId" | binarydata(32)) :: + ("version" | provide(commitmentVersion))).as[Commitments] } def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( @@ -354,22 +303,8 @@ object ChannelCodecs extends Logging { .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) - private val genericStateDataDecoder = discriminated[HasCommitments].by(uint8) - .typecase(COMMITMENTv1_VERSION_BYTE, stateDataCodec(VersionCommitmentV1)) - .typecase(COMMITMENT_SIMPLIFIED_VERSION_BYTE, stateDataCodec(VersionSimplifiedCommitment)).asDecoder - - private val genericStateDataEncoder = new Encoder[HasCommitments] { - override def encode(value: HasCommitments): Attempt[BitVector] = stateDataCodec(value.commitments.version).encode(value).map { serializedState => - value.commitments.version match { - case VersionCommitmentV1 => BitVector(COMMITMENTv1_VERSION_BYTE) ++ serializedState - case VersionSimplifiedCommitment => BitVector(COMMITMENT_SIMPLIFIED_VERSION_BYTE) ++ serializedState - } - } - - override def sizeBound: SizeBound = SizeBound(0, None) - } - - val genericStateDataCodec = GenCodec(genericStateDataEncoder, genericStateDataDecoder).fuse - + val genericStateDataCodec = discriminated[HasCommitments].by(uint8) + .\ (COMMITMENTv1_VERSION_BYTE) { case c if c.commitments.version == VersionCommitmentV1 => c } (stateDataCodec(VersionCommitmentV1)) + .\ (COMMITMENT_SIMPLIFIED_VERSION_BYTE) { case c if c.commitments.version == VersionSimplifiedCommitment => c } (stateDataCodec(VersionSimplifiedCommitment)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 9c38121d81..dd30a1dca1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -67,9 +67,10 @@ class ChannelStateSpec extends FunSuite { val bin = ChannelCodecs.genericStateDataCodec.encode(state).require val state1 = ChannelCodecs.genericStateDataCodec.decodeValue(bin).require + assert(BitVector(rawCommitment.data).toByteArray.length === rawCommitment.data.size) + assert(state === state1) - assert(bin.size === 0) // 15329 - assert(BitVector(rawCommitment.data) === 0) // 15336 + assert(bin === BitVector(rawCommitment.data)) } } From c3540b38786384058bab44d67254964b627dd2d4 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 7 Mar 2019 18:18:11 +0100 Subject: [PATCH 61/66] Fail decoding if there are remaining bits --- .../src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index d82c530c1b..1d6a461786 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -306,5 +306,6 @@ object ChannelCodecs extends Logging { val genericStateDataCodec = discriminated[HasCommitments].by(uint8) .\ (COMMITMENTv1_VERSION_BYTE) { case c if c.commitments.version == VersionCommitmentV1 => c } (stateDataCodec(VersionCommitmentV1)) .\ (COMMITMENT_SIMPLIFIED_VERSION_BYTE) { case c if c.commitments.version == VersionSimplifiedCommitment => c } (stateDataCodec(VersionSimplifiedCommitment)) + .complete // Converts this codec to a new codec that fails decoding if there are remaining bits. } From da11388c4354fcc68c5504dece4b4d7c6f0ef231 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 8 Mar 2019 10:13:06 +0100 Subject: [PATCH 62/66] Fix test for backward compatible reads of stored commitments --- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 26 +++---------------- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 5 +--- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 1d6a461786..444dd0322a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -209,28 +209,6 @@ object ChannelCodecs extends Logging { ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) :: ("spent" | spentMapCodec)).as[RevokedCommitPublished] - /* - SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT AAA TTTTTTTTTTTTTTTTTTTTTTTEEEEEEEEEEEEEEEEEEEEEE DDDDDDDDDDDDD AAA TTTTTTTTTTTTTTTTTTTTTTT AAA - SS:::::::::::::::ST:::::::::::::::::::::T A:::A T:::::::::::::::::::::TE::::::::::::::::::::E D::::::::::::DDD A:::A T:::::::::::::::::::::T A:::A - S:::::SSSSSS::::::ST:::::::::::::::::::::T A:::::A T:::::::::::::::::::::TE::::::::::::::::::::E D:::::::::::::::DD A:::::A T:::::::::::::::::::::T A:::::A - S:::::S SSSSSSST:::::TT:::::::TT:::::T A:::::::A T:::::TT:::::::TT:::::TEE::::::EEEEEEEEE::::E DDD:::::DDDDD:::::D A:::::::A T:::::TT:::::::TT:::::T A:::::::A - S:::::S TTTTTT T:::::T TTTTTT A:::::::::A TTTTTT T:::::T TTTTTT E:::::E EEEEEE D:::::D D:::::D A:::::::::A TTTTTT T:::::T TTTTTT A:::::::::A - S:::::S T:::::T A:::::A:::::A T:::::T E:::::E D:::::D D:::::D A:::::A:::::A T:::::T A:::::A:::::A - S::::SSSS T:::::T A:::::A A:::::A T:::::T E::::::EEEEEEEEEE D:::::D D:::::D A:::::A A:::::A T:::::T A:::::A A:::::A - SS::::::SSSSS T:::::T A:::::A A:::::A T:::::T E:::::::::::::::E D:::::D D:::::D A:::::A A:::::A T:::::T A:::::A A:::::A - SSS::::::::SS T:::::T A:::::A A:::::A T:::::T E:::::::::::::::E D:::::D D:::::D A:::::A A:::::A T:::::T A:::::A A:::::A - SSSSSS::::S T:::::T A:::::AAAAAAAAA:::::A T:::::T E::::::EEEEEEEEEE D:::::D D:::::D A:::::AAAAAAAAA:::::A T:::::T A:::::AAAAAAAAA:::::A - S:::::S T:::::T A:::::::::::::::::::::A T:::::T E:::::E D:::::D D:::::DA:::::::::::::::::::::A T:::::T A:::::::::::::::::::::A - S:::::S T:::::T A:::::AAAAAAAAAAAAA:::::A T:::::T E:::::E EEEEEE D:::::D D:::::DA:::::AAAAAAAAAAAAA:::::A T:::::T A:::::AAAAAAAAAAAAA:::::A - SSSSSSS S:::::S TT:::::::TT A:::::A A:::::A TT:::::::TT EE::::::EEEEEEEE:::::E DDD:::::DDDDD:::::DA:::::A A:::::A TT:::::::TT A:::::A A:::::A - S::::::SSSSSS:::::S T:::::::::T A:::::A A:::::A T:::::::::T E::::::::::::::::::::E D:::::::::::::::DDA:::::A A:::::A T:::::::::T A:::::A A:::::A - S:::::::::::::::SS T:::::::::T A:::::A A:::::A T:::::::::T E::::::::::::::::::::E D::::::::::::DDD A:::::A A:::::A T:::::::::T A:::::A A:::::A - SSSSSSSSSSSSSSS TTTTTTTTTTTAAAAAAA AAAAAAATTTTTTTTTTT EEEEEEEEEEEEEEEEEEEEEE DDDDDDDDDDDDD AAAAAAA AAAAAAATTTTTTTTTTTAAAAAAA AAAAAAA - */ - - val COMMITMENTv1_VERSION_BYTE = 0x00.toByte - val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01.toByte - def commitmentCodec(commitmentVersion: CommitmentVersion): Codec[Commitments] = { (("localParams" | localParamsCodec) :: ("remoteParams" | remoteParamsCodec) :: @@ -303,9 +281,11 @@ object ChannelCodecs extends Logging { .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) + val COMMITMENTv1_VERSION_BYTE = 0x00.toByte + val COMMITMENT_SIMPLIFIED_VERSION_BYTE = 0x01.toByte + val genericStateDataCodec = discriminated[HasCommitments].by(uint8) .\ (COMMITMENTv1_VERSION_BYTE) { case c if c.commitments.version == VersionCommitmentV1 => c } (stateDataCodec(VersionCommitmentV1)) .\ (COMMITMENT_SIMPLIFIED_VERSION_BYTE) { case c if c.commitments.version == VersionSimplifiedCommitment => c } (stateDataCodec(VersionSimplifiedCommitment)) - .complete // Converts this codec to a new codec that fails decoding if there are remaining bits. } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index dd30a1dca1..3e1ce04c13 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -58,7 +58,7 @@ class ChannelStateSpec extends FunSuite { assert(data === check) } - test("backward compatible with previously stored commitments") { + test("backward compatible READING of previously stored commitments") { val state = ChannelCodecs.genericStateDataCodec.decodeValue(BitVector(rawCommitment.data)).require assert(state.commitments.localParams.nodeId.raw == BinaryData("036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")) @@ -67,10 +67,7 @@ class ChannelStateSpec extends FunSuite { val bin = ChannelCodecs.genericStateDataCodec.encode(state).require val state1 = ChannelCodecs.genericStateDataCodec.decodeValue(bin).require - assert(BitVector(rawCommitment.data).toByteArray.length === rawCommitment.data.size) - assert(state === state1) - assert(bin === BitVector(rawCommitment.data)) } } From e8f229ec92f0a094c5164dc5077695bcc7d251e6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 8 Mar 2019 10:26:15 +0100 Subject: [PATCH 63/66] Migrate commitmentVersion implicits to standard parameter --- .../fr/acinq/eclair/channel/Channel.scala | 8 +-- .../fr/acinq/eclair/channel/Commitments.scala | 43 ++++++-------- .../fr/acinq/eclair/channel/Helpers.scala | 32 +++++------ .../eclair/transactions/Transactions.scala | 36 ++++++------ .../eclair/transactions/TestVectorsSpec.scala | 8 +-- .../transactions/TransactionsSpec.scala | 56 +++++++++---------- 6 files changed, 82 insertions(+), 101 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 7bf3a3ae7e..6504c38295 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -634,7 +634,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val nextCommitNumber = nextRemoteCommit.index // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(d.commitments.version) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec)(d.commitments.version) + val trimmedHtlcs = Transactions.trimOfferedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec, d.commitments.version) ++ Transactions.trimReceivedHtlcs(Satoshi(d.commitments.remoteParams.dustLimitSatoshis), nextRemoteCommit.spec, d.commitments.version) trimmedHtlcs collect { case DirectedHtlc(_, u) => log.info(s"adding paymentHash=${u.paymentHash} cltvExpiry=${u.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") @@ -1218,9 +1218,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } // we may need to fail some htlcs in case a commitment tx was published and they have reached the timeout threshold val timedoutHtlcs = - Closing.timedoutHtlcs(d.commitments.localCommit, Satoshi(d.commitments.localParams.dustLimitSatoshis), tx) ++ - Closing.timedoutHtlcs(d.commitments.remoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx) ++ - d.commitments.remoteNextCommitInfo.left.toSeq.flatMap(r => Closing.timedoutHtlcs(r.nextRemoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx)) + Closing.timedoutHtlcs(d.commitments.localCommit, Satoshi(d.commitments.localParams.dustLimitSatoshis), tx, d.commitments.version) ++ + Closing.timedoutHtlcs(d.commitments.remoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx, d.commitments.version) ++ + d.commitments.remoteNextCommitInfo.left.toSeq.flatMap(r => Closing.timedoutHtlcs(r.nextRemoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx, d.commitments.version)) timedoutHtlcs.foreach { add => d.commitments.originChannels.get(add.id) match { case Some(origin) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 640926141a..3d45884383 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -77,7 +77,7 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams, // TODO subtract the pushMe value from the balance? def availableBalanceForSendMsat: Long = { val reduced = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) - val feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced)(commitmentVersion = version).amount * 1000 else 0 + val feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced, version).amount * 1000 else 0 reduced.toRemoteMsat - remoteParams.channelReserveSatoshis * 1000 - feesMsat } @@ -110,7 +110,6 @@ object Commitments { * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc) */ def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC, origin: Origin): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { - implicit val commitmentVersion = commitments.version if (cmd.paymentHash.size != 32) { return Left(InvalidPaymentHash(commitments.channelId)) @@ -154,7 +153,7 @@ object Commitments { // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount else 0 + val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, commitments.version).amount else 0 val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { return Left(InsufficientFunds(commitments.channelId, amountMsat = cmd.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.remoteParams.channelReserveSatoshis, feesSatoshis = fees)) @@ -164,7 +163,6 @@ object Commitments { } def receiveAdd(commitments: Commitments, add: UpdateAddHtlc): Commitments = { - implicit val commitmentVersion = commitments.version if (add.id != commitments.remoteNextHtlcId) { throw UnexpectedHtlcId(commitments.channelId, expected = commitments.remoteNextHtlcId, actual = add.id) @@ -193,7 +191,7 @@ object Commitments { } // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced).amount + val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced, commitments.version).amount val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw InsufficientFunds(commitments.channelId, amountMsat = add.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -312,8 +310,6 @@ object Commitments { throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } - implicit val commitmentVersion = commitments.version - // let's compute the current commitment *as seen by them* with this change taken into account val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw) // update_fee replace each other, so we can remove previous ones @@ -322,7 +318,7 @@ object Commitments { // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is funder remote doesn't pay the fees - val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount // we update the fee only in NON simplified commitment + val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, commitments.version).amount // we update the fee only in NON simplified commitment val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(commitments.channelId, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -349,8 +345,6 @@ object Commitments { throw CannotUpdateFeeWithCommitmentType(commitments.channelId) } - implicit val commitmentVersion = commitments.version - // NB: we check that the funder can afford this new fee even if spec allows to do it at next signature // It is easier to do it here because under certain (race) conditions spec allows a lower-than-normal fee to be paid, // and it would be tricky to check if the conditions are met at signing @@ -362,7 +356,7 @@ object Commitments { val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount // we update the fee only in NON simplified + val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced, commitments.version).amount // we update the fee only in NON simplified val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(commitments.channelId, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -384,8 +378,6 @@ object Commitments { def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index)) def sendCommit(commitments: Commitments, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, CommitSig) = { - implicit val commitmentVersion = commitments.version - import commitments._ commitments.remoteNextCommitInfo match { @@ -395,11 +387,11 @@ object Commitments { // remote commitment will includes all local changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) - val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, localPerCommitmentPoint, spec) + val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, localPerCommitmentPoint, spec, commitments.version) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = commitmentVersion match { + val htlcSigs = commitments.version match { case VersionCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_ALL)) case VersionSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) } @@ -425,7 +417,6 @@ object Commitments { } } def receiveCommit(commitments: Commitments, commit: CommitSig, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = { - implicit val commitmentVersion = commitments.version import commitments._ // they sent us a signature for *their* view of *our* next commit tx @@ -451,7 +442,7 @@ object Commitments { case Right(point) => point } - val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, remotePerCommitmentPoint, spec) + val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, remotePerCommitmentPoint, spec, commitments.version) val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath), SIGHASH_ALL) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) @@ -468,7 +459,7 @@ object Commitments { if (commit.htlcSignatures.size != sortedHtlcTxs.size) { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } - val htlcSigs = commitmentVersion match { + val htlcSigs = commitments.version match { case VersionCommitmentV1 => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)) case VersionSimplifiedCommitment => sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY)) } @@ -480,13 +471,13 @@ object Commitments { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentVersion == VersionCommitmentV1 => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitments.version == VersionCommitmentV1 => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_ALL) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) } HtlcTxAndSigs(htlcTx, localSig, remoteSig) - case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitmentVersion == VersionSimplifiedCommitment => + case (htlcTx: HtlcSuccessTx, localSig, remoteSig) if commitments.version == VersionSimplifiedCommitment => // we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY) == false) { throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx) @@ -556,7 +547,7 @@ object Commitments { } } - def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, remotePerCommitmentPoint: Point, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) @@ -567,12 +558,12 @@ object Commitments { val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, remoteDelayedPaymentPubkey, spec) - val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, remoteDelayedPaymentPubkey, spec, commitmentVersion) + val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec, commitmentVersion) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, localPerCommitmentPoint: Point, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { val localPaymentPubkey = commitmentVersion match { case VersionSimplifiedCommitment => keyManager.paymentPoint(localParams.channelKeyPath).publicKey case VersionCommitmentV1 => Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) @@ -582,8 +573,8 @@ object Commitments { val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, localDelayedPaymentPubkey, spec) - val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, localDelayedPaymentPubkey, spec, commitmentVersion) + val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec, commitmentVersion) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 855fac7359..535536ccbf 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -246,7 +246,7 @@ object Helpers { if (!localParams.isFunder) { // they are funder, therefore they pay the fee: we need to make sure they can afford it! val toRemoteMsat = remoteSpec.toLocalMsat - val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec).amount + val fees = Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), remoteSpec, commitmentVersion).amount val missing = toRemoteMsat / 1000 - localParams.channelReserveSatoshis - fees if (missing < 0) { throw CannotAffordFees(temporaryChannelId, missingSatoshis = -1 * missing, reserveSatoshis = localParams.channelReserveSatoshis, feesSatoshis = fees) @@ -255,8 +255,8 @@ object Helpers { val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) - val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, remoteFirstPerCommitmentPoint, localSpec) - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, localPerCommitmentPoint, remoteSpec) + val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, remoteFirstPerCommitmentPoint, localSpec, commitmentVersion) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, localPerCommitmentPoint, remoteSpec, commitmentVersion) (localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -430,7 +430,7 @@ object Helpers { // first we will claim our main output as soon as the delay is over val mainDelayedTx = generateTx("main-delayed-output")(Try { - val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) + val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed, commitmentVersion) val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimDelayed, sig) }) @@ -476,7 +476,7 @@ object Helpers { localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, - localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) + localParams.defaultFinalScriptPubKey, feeratePerKwDelayed, commitmentVersion) val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimDelayed, sig) }) @@ -505,10 +505,8 @@ object Helpers { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx") - implicit val commitmentVersion = commitments.version - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, localPerCommitmentPoint, remoteCommit.spec) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, localPerCommitmentPoint, remoteCommit.spec, commitments.version) require(remoteCommitTx.tx.txid == commitTx.txid, "txid mismatch, cannot recompute the current remote commit tx") val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) @@ -562,7 +560,6 @@ object Helpers { * @return a list of transactions (one per HTLC that we can claim) */ def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { - implicit val commitmentVersion = commitments.version // no need to use a high fee rate for our main output (we are the only one who can spend it) val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 @@ -572,7 +569,7 @@ object Helpers { val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), - localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain, None) + localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain, None, commitments.version) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) @@ -581,7 +578,7 @@ object Helpers { val localPubkey = keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(commitTx, Satoshi(commitments.localParams.dustLimitSatoshis), - localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain, Some(commitments.localParams.toSelfDelay)) + localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain, Some(commitments.localParams.toSelfDelay), commitments.version) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) @@ -607,7 +604,6 @@ object Helpers { * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { - implicit val commitmentVersion = commitments.version import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") @@ -634,14 +630,14 @@ object Helpers { // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { - val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain, Some(localParams.toSelfDelay)) + val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain, Some(localParams.toSelfDelay), commitments.version) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL) Transactions.addSigs(claimMain, localPubkey, sig) }) // then we punish them by stealing their main output val mainPenaltyTx = generateTx("main-penalty")(Try { - val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty) + val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty, commitments.version) val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret, SIGHASH_ALL) Transactions.addSigs(txinfo, sig) }) @@ -782,10 +778,10 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit commitmentVersion: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction, commitmentVersion: CommitmentVersion)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == localCommit.publishableTxs.commitTx.tx.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) - (localCommit.spec.htlcs.filter(_.direction == OUT) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec)).map(_.add) + (localCommit.spec.htlcs.filter(_.direction == OUT) -- Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec, commitmentVersion)).map(_.add) } else { // maybe this is a timeout tx, in that case we can resolve and fail the corresponding htlc tx.txIn.map(_.witness match { @@ -806,10 +802,10 @@ object Helpers { * @param tx a tx that has reached mindepth * @return a set of htlcs that need to be failed upstream */ - def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit commitmentVersion: CommitmentVersion, log: LoggingAdapter): Set[UpdateAddHtlc] = + def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction, commitmentVersion: CommitmentVersion)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == remoteCommit.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) - (remoteCommit.spec.htlcs.filter(_.direction == IN) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec)).map(_.add) + (remoteCommit.spec.htlcs.filter(_.direction == IN) -- Transactions.trimReceivedHtlcs(remoteDustLimit, remoteCommit.spec, commitmentVersion)).map(_.add) } else { // maybe this is a timeout tx, in that case we can resolve and fail the corresponding htlc tx.txIn.map(_.witness match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index b6027ccb88..d3c3ff5397 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -122,7 +122,7 @@ object Transactions { */ def fee2rate(fee: Satoshi, weight: Int) = (fee.amount * 1000L) / weight - def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = { + def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = { val htlcTimeoutFee = commitmentVersion match { case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcTimeoutWeight) case VersionSimplifiedCommitment => Satoshi(0) @@ -133,7 +133,7 @@ object Transactions { .toSeq } - def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = { + def trimReceivedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = { val htlcSuccessFee = commitmentVersion match { case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcSuccessWeight) case VersionSimplifiedCommitment => Satoshi(0) @@ -144,9 +144,9 @@ object Transactions { .toSeq } - def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): Satoshi = { - val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec) - val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec) + def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): Satoshi = { + val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec, commitmentVersion) + val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec, commitmentVersion) commitmentVersion match { case VersionCommitmentV1 => weight2fee(spec.feeratePerKw , commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) case VersionSimplifiedCommitment => weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)) // simplified commitment has an hardcoded feerate @@ -199,10 +199,10 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): CommitTx = commitmentVersion match { + def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: Point, remotePaymentBasePoint: Point, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, remoteDelayedPaymentPubkey: PublicKey, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): CommitTx = commitmentVersion match { case VersionCommitmentV1 => - val commitFee = commitTxFee(localDustLimit, spec) + val commitFee = commitTxFee(localDustLimit, spec, commitmentVersion) val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { (millisatoshi2satoshi(MilliSatoshi(spec.toLocalMsat)) - commitFee, millisatoshi2satoshi(MilliSatoshi(spec.toRemoteMsat))) @@ -213,9 +213,9 @@ object Transactions { val toLocalDelayedOutput_opt = if (toLocalAmount >= localDustLimit) Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) else None val toRemoteOutput_opt = if (toRemoteAmount >= localDustLimit) Some(TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey))) else None - val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec) + val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec, commitmentVersion) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash))))) - val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) + val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec, commitmentVersion) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint) @@ -229,7 +229,7 @@ object Transactions { CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) case VersionSimplifiedCommitment => - val commitFee = commitTxFee(localDustLimit, spec) + val commitFee = commitTxFee(localDustLimit, spec, commitmentVersion) val pushMeValueTotal = pushMeValue * 2 // funder pays the total amount of pushme outputs val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { @@ -241,9 +241,9 @@ object Transactions { val toLocalDelayedOutput_opt = if (toLocalAmount < localDustLimit) None else Some(TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)))) val toRemoteOutput_opt = if (toRemoteAmount < localDustLimit) None else Some(TxOut(toRemoteAmount, pay2wsh(toRemoteDelayed(remotePaymentPubkey, toLocalDelay)))) - val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec) + val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec, commitmentVersion) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash))))) - val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec) + val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec, commitmentVersion) .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash), htlc.add.cltvExpiry)))) val toLocalPushMe_opt = if (toLocalDelayedOutput_opt.isDefined) Some(TxOut(pushMeValue, pay2wsh(pushMeSimplified(localDelayedPaymentPubkey)))) else None @@ -295,14 +295,14 @@ object Transactions { lockTime = 0), htlc.paymentHash) } - def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec)(implicit commitmentVersion: CommitmentVersion): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec, commitmentVersion: CommitmentVersion): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs - val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => + val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec, commitmentVersion).map { htlc => val htlcTx = makeHtlcTimeoutTx(commitTx, outputsAlreadyUsed, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec.feeratePerKw, htlc.add) outputsAlreadyUsed = outputsAlreadyUsed + htlcTx.input.outPoint.index.toInt htlcTx } - val htlcSuccessTxs = trimReceivedHtlcs(localDustLimit, spec).map { htlc => + val htlcSuccessTxs = trimReceivedHtlcs(localDustLimit, spec, commitmentVersion).map { htlc => val htlcTx = makeHtlcSuccessTx(commitTx, outputsAlreadyUsed, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec.feeratePerKw, htlc.add) outputsAlreadyUsed = outputsAlreadyUsed + htlcTx.input.outPoint.index.toInt htlcTx @@ -358,7 +358,7 @@ object Transactions { ClaimHtlcTimeoutTx(input, tx1) } - def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int])(implicit commitmentVersion: CommitmentVersion): ClaimP2WPKHOutputTx = { + def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, toRemoteDelay: Option[Int], commitmentVersion: CommitmentVersion): ClaimP2WPKHOutputTx = { val claimTx = commitmentVersion match { case VersionCommitmentV1 => @@ -407,7 +407,7 @@ object Transactions { ClaimP2WPKHOutputTx(claimTx.input, tx1) } - def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long)(implicit commitmentVersion: CommitmentVersion): ClaimDelayedOutputTx = { + def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: BinaryData, feeratePerKw: Long, commitmentVersion: CommitmentVersion): ClaimDelayedOutputTx = { val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) @@ -464,7 +464,7 @@ object Transactions { } // TODO adjust for option_simplified_commitment -> sweep pushme outputs - def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long)(implicit commitmentVersion: CommitmentVersion): MainPenaltyTx = commitmentVersion match { + def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: BinaryData, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long, commitmentVersion: CommitmentVersion): MainPenaltyTx = commitmentVersion match { case VersionSimplifiedCommitment => throw new NotImplementedError("makeMainPenaltyTx with option_simplified_commitment") case VersionCommitmentV1 => val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index bf680252cf..2de9c82820 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -191,7 +191,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key Remote.delayed_payment_pubkey, - spec)(VersionCommitmentV1) + spec, VersionCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey, SIGHASH_ALL) val remote_sig = Transactions.sign(tx, Remote.funding_privkey, SIGHASH_ALL) @@ -199,7 +199,7 @@ class TestVectorsSpec extends FunSuite with Logging { Transactions.addSigs(tx, Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig) } - val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)(VersionCommitmentV1) + val baseFee = Transactions.commitTxFee(Local.dustLimit, spec, VersionCommitmentV1) logger.info(s"# base commitment transaction fee = ${baseFee.toLong}") val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}") @@ -222,7 +222,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.delayed_payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key Remote.delayed_payment_pubkey, - spec)(VersionCommitmentV1) + spec, VersionCommitmentV1) val local_sig = Transactions.sign(tx, Local.funding_privkey, SIGHASH_ALL) logger.info(s"# local_signature = ${toHexString(local_sig.dropRight(1))}") @@ -240,7 +240,7 @@ class TestVectorsSpec extends FunSuite with Logging { Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey, Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key - spec)(commitmentVersion = VersionCommitmentV1) + spec, VersionCommitmentV1) logger.info(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}") val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 289ae2877c..cde2de24e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -74,8 +74,8 @@ class TransactionsSpec extends FunSuite with Logging { ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0) - assert(commitTxFee(Satoshi(546), spec)(VersionSimplifiedCommitment) == expectedSizeSimplifiedCommitment) - assert(commitTxFee(Satoshi(546), spec)(VersionCommitmentV1) == expectedSizeCommitmentV1) + assert(commitTxFee(Satoshi(546), spec, VersionSimplifiedCommitment) == expectedSizeSimplifiedCommitment) + assert(commitTxFee(Satoshi(546), spec, VersionCommitmentV1) == expectedSizeCommitmentV1) } test("check pre-computed transaction weights") { @@ -95,7 +95,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimP2WPKHOutputTx val pubKeyScript = write(pay2wpkh(localPaymentPriv.publicKey)) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(VersionCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx, localDustLimit, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None, VersionCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimP2WPKHOutputTx, localPaymentPriv.publicKey, "bb" * 73).tx) assert(claimP2WPKHOutputWeight == weight) @@ -107,7 +107,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake htlcSuccessOrTimeoutTx tx, containing only the output that will be spent by the ClaimDelayedOutputTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val htlcSuccessOrTimeoutTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val claimHtlcDelayedTx = makeClaimDelayedOutputTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(VersionCommitmentV1) + val claimHtlcDelayedTx = makeClaimDelayedOutputTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, VersionCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimHtlcDelayedTx, "bb" * 73).tx) assert(claimHtlcDelayedWeight == weight) @@ -119,7 +119,7 @@ class TransactionsSpec extends FunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the MainPenaltyTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(20000), pubKeyScript) :: Nil, lockTime = 0) - val mainPenaltyTx = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw)(VersionCommitmentV1) + val mainPenaltyTx = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw, VersionCommitmentV1) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(mainPenaltyTx, "bb" * 73).tx) assert(mainPenaltyWeight == weight) @@ -209,15 +209,15 @@ class TransactionsSpec extends FunSuite with Logging { toRemoteMsat = millibtc2satoshi(MilliBtc(300)).amount * 1000) val commitTxNumber = 0x404142434445L - def commitTx(commitContext: CommitmentVersion = VersionCommitmentV1) = { - val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, remoteDelayedPaymentPriv.publicKey, spec)(commitContext) + def commitTx(commitmentVersion: CommitmentVersion = VersionCommitmentV1) = { + val txinfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.toPoint, remotePaymentPriv.toPoint, true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, remoteDelayedPaymentPriv.publicKey, spec, commitmentVersion) val localSig = Transactions.sign(txinfo, localPaymentPriv, SIGHASH_ALL) val remoteSig = Transactions.sign(txinfo, remotePaymentPriv, SIGHASH_ALL) Transactions.addSigs(txinfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) } def createHtlcTxs(commitmentVersion: CommitmentVersion = VersionCommitmentV1):(Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - makeHtlcTxs(commitTx(commitmentVersion).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec)(commitmentVersion) + makeHtlcTxs(commitTx(commitmentVersion).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec, commitmentVersion) } { @@ -245,10 +245,10 @@ class TransactionsSpec extends FunSuite with Logging { { // Simplified commitment - val commitTransaction = commitTx(commitContext = VersionSimplifiedCommitment) + val commitTransaction = commitTx(commitmentVersion = VersionSimplifiedCommitment) // local is funder, pays for fees + push_me outputs - val expectedToLocalAmount = Satoshi(spec.toLocalMsat / 1000) - Transactions.pushMeValue * 2 - commitTxFee(localDustLimit, spec)(VersionSimplifiedCommitment) + val expectedToLocalAmount = Satoshi(spec.toLocalMsat / 1000) - Transactions.pushMeValue * 2 - commitTxFee(localDustLimit, spec, VersionSimplifiedCommitment) val expectedToRemoteAmount = Satoshi(spec.toRemoteMsat / 1000) // there must be 2 push-me outputs @@ -268,17 +268,17 @@ class TransactionsSpec extends FunSuite with Logging { assert(toRemoteMainOut.amount == expectedToRemoteAmount) } -// { -// // local spends delayed output of htlc1 timeout tx -// val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) -// val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) -// val signedTx = addSigs(claimHtlcDelayed, localSig) -// assert(checkSpendable(signedTx).isSuccess) -// // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit -// intercept[RuntimeException] { -// makeClaimDelayedOutputTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(ContextCommitmentV1) -// } -// } + { + // local spends delayed output of htlc1 timeout tx + val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, VersionCommitmentV1) + val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) + val signedTx = addSigs(claimHtlcDelayed, localSig) + assert(checkSpendable(signedTx).isSuccess) + // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit + intercept[RuntimeException] { + makeClaimDelayedOutputTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, VersionCommitmentV1) + } + } { // remote spends local->remote htlc1/htlc3 output directly in case of success @@ -304,28 +304,22 @@ class TransactionsSpec extends FunSuite with Logging { { // local spends delayed output of htlc2 success tx - val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(VersionCommitmentV1) + val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, VersionCommitmentV1) val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimHtlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc4 timeout tx because it is below the dust limit intercept[RuntimeException] { - makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)(VersionCommitmentV1) + makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, VersionCommitmentV1) } } { // remote spends main output - val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx(VersionCommitmentV1).tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None)(VersionCommitmentV1) + val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx(VersionCommitmentV1).tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, None, VersionCommitmentV1) val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv, SIGHASH_ALL) val signedTx = addSigs(claimP2WPKHOutputTx, remotePaymentPriv.publicKey, localSig) assert(checkSpendable(signedTx).isSuccess) - - //FIXME -// val claimP2WPKHOutputTx1 = makeClaimP2WPKHOutputTx(commitTx(ContextSimplifiedCommitment).tx, localDustLimit, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, Some(toLocalDelay))(ContextSimplifiedCommitment) -// val localSig1 = sign(claimP2WPKHOutputTx1, localDelayedPaymentPriv, SIGHASH_ALL) -// val signedTx1 = addSigs(claimP2WPKHOutputTx1, localDelayedPaymentPriv.publicKey, localSig1) -// assert(checkSpendable(signedTx1).isSuccess) } { @@ -398,7 +392,7 @@ class TransactionsSpec extends FunSuite with Logging { tests.foreach(test => { logger.info(s"running BOLT 2 test: '${test.name}'") - val fee = commitTxFee(test.dustLimit, test.spec)(VersionCommitmentV1) + val fee = commitTxFee(test.dustLimit, test.spec, VersionCommitmentV1) assert(fee === test.expectedFee) }) } From 90c32953090e1cdc173ae3085c39b93f991d316f Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 14 Mar 2019 16:02:02 +0100 Subject: [PATCH 64/66] Finish merging master --- .../eclair/channel/ChannelExceptions.scala | 2 +- .../acinq/eclair/channel/ChannelTypes.scala | 4 +-- .../acinq/eclair/transactions/Scripts.scala | 2 +- .../eclair/transactions/Transactions.scala | 12 ++++---- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 29 +++++-------------- .../scala/fr/acinq/eclair/FeaturesSpec.scala | 20 ++++++------- .../states/StateTestsHelperMethods.scala | 3 +- .../b/WaitForFundingCreatedStateSpec.scala | 6 ++-- .../b/WaitForFundingSignedStateSpec.scala | 4 +-- .../channel/states/e/NormalStateSpec.scala | 4 +-- .../channel/states/h/ClosingStateSpec.scala | 2 +- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 14 ++++----- .../transactions/TransactionsSpec.scala | 2 +- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 6 ++-- 14 files changed, 48 insertions(+), 62 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala index 36207d207a..60d58088bb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala @@ -69,7 +69,7 @@ case class InvalidHtlcPreimage (override val channelId: ByteVect case class UnknownHtlcId (override val channelId: ByteVector32, id: Long) extends ChannelException(channelId, s"unknown htlc id=$id") case class CannotExtractSharedSecret (override val channelId: ByteVector32, htlc: UpdateAddHtlc) extends ChannelException(channelId, s"can't extract shared secret: paymentHash=${htlc.paymentHash} onion=${htlc.onionRoutingPacket}") case class FundeeCannotSendUpdateFee (override val channelId: ByteVector32) extends ChannelException(channelId, s"only the funder should send update_fee messages") -case class CannotUpdateFeeWithCommitmentType (override val channelId: BinaryData) extends ChannelException(channelId, s"can't update fees when option_simplified_commitment is in use") +case class CannotUpdateFeeWithCommitmentType (override val channelId: ByteVector32) extends ChannelException(channelId, s"can't update fees when option_simplified_commitment is in use") case class CannotAffordFees (override val channelId: ByteVector32, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(channelId, s"can't pay the fee: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis feesSatoshis=$feesSatoshis") case class CannotSignWithoutChanges (override val channelId: ByteVector32) extends ChannelException(channelId, "cannot sign when there are no changes") case class CannotSignBeforeRevocation (override val channelId: ByteVector32) extends ChannelException(channelId, "cannot sign until next revocation hash is received") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index a3072fca16..2ea6e86fc8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -188,8 +188,8 @@ final case class DATA_CLOSING(commitments: Commitments, final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends Data with HasCommitments trait ParamsWithFeatures { - val globalFeatures: BinaryData - val localFeatures: BinaryData + val globalFeatures: ByteVector + val localFeatures: ByteVector } final case class LocalParams(nodeId: PublicKey, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index 59f8014787..54bd27b5ce 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -289,7 +289,7 @@ object Scripts { * @param sig * @return */ - def claimPushMeOutputWithKey(sig: BinaryData) = ScriptWitness(sig :: Nil) + def claimPushMeOutputWithKey(sig: ByteVector) = ScriptWitness(sig :: Nil) /** * The script spending 'pushMeSimplified' outputs, 10 block delay - no signature - version. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 2f45533d4e..8d9b10f088 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -255,7 +255,7 @@ object Transactions { val tx = Transaction( version = 2, - txIn = TxIn(commitTxInput.outPoint, Array.emptyByteArray, sequence = sequence) :: Nil, + txIn = TxIn(commitTxInput.outPoint, ByteVector.empty, sequence = sequence) :: Nil, txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs ++ toLocalPushMe_opt.toSeq ++ toRemotePushMe_opt.toSeq, lockTime = locktime) CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) @@ -387,12 +387,12 @@ object Transactions { // unsigned tx val tx = Transaction( version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, 0x00000000L) :: Nil, + txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, lockTime = 0) // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey - Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), BinaryData("00" * 33), BinaryData("00" * 73)) + Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), ByteVector.fill(33)(0), ByteVector.fill(73)(0)) } @@ -550,12 +550,12 @@ object Transactions { val tx = Transaction( version = 2, - txIn = TxIn(input.outPoint, Array.emptyByteArray, 0xffffffffL) :: Nil, + txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil, txOut = TxOut(pushMeValue, List.empty) :: Nil, // TODO to what do we spend the pushMe? FIXME lockTime = 0 ) - val weight = Transactions.addSigs(PushMeTx(input, tx), BinaryData("00" * 73)).tx.weight() + val weight = Transactions.addSigs(PushMeTx(input, tx), ByteVector.fill(73)(0)).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee @@ -642,7 +642,7 @@ object Transactions { closingTx.copy(tx = closingTx.tx.updateWitness(0, witness)) } - def addSigs(pushMeTx: PushMeTx, localSig: BinaryData): PushMeTx = { + def addSigs(pushMeTx: PushMeTx, localSig: ByteVector): PushMeTx = { val witness = Scripts.claimPushMeOutputWithKey(localSig) pushMeTx.copy(tx = pushMeTx.tx.updateWitness(0, witness)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 2544fc8edd..a374ab4d5b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -180,22 +180,6 @@ object ChannelCodecs extends Logging { (wire: BitVector) => spentListCodec.decode(wire).map(_.map(_.toMap)) ) - val commitmentsCodec: Codec[Commitments] = ( - ("localParams" | localParamsCodec) :: - ("remoteParams" | remoteParamsCodec) :: - ("channelFlags" | byte) :: - ("localCommit" | localCommitCodec) :: - ("remoteCommit" | remoteCommitCodec) :: - ("localChanges" | localChangesCodec) :: - ("remoteChanges" | remoteChangesCodec) :: - ("localNextHtlcId" | uint64) :: - ("remoteNextHtlcId" | uint64) :: - ("originChannels" | originsMapCodec) :: - ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: - ("commitInput" | inputInfoCodec) :: - ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | bytes32)).as[Commitments] - val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | txCodec) :: ("localClosingSigned" | closingSignedCodec)).as[ClosingTxProposed] @@ -239,23 +223,24 @@ object ChannelCodecs extends Logging { ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, point)) :: ("commitInput" | inputInfoCodec) :: ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: - ("channelId" | binarydata(32)) :: + ("channelId" | bytes32) :: ("version" | provide(commitmentVersion))).as[Commitments] } // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields - val DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentCodec(commitmentVersion)) :: ("fundingTx" | provide[Option[Transaction]](None)) :: ("waitingSince" | provide(compat.Platform.currentTime / 1000)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly - def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: + ("fundingTx" | optional(bool, txCodec)) :: + ("waitingSince" | int64) :: ("deferred" | optional(bool, fundingLockedCodec)) :: - ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] + ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: @@ -299,7 +284,7 @@ object ChannelCodecs extends Logging { def stateDataCodec(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) - .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec) + .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion)) .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion)) .typecase(0x03, DATA_NORMAL_Codec(commitmentVersion)) .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentVersion)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index 2f7393a9e2..5b826b08b6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair import java.nio.ByteOrder -import fr.acinq.bitcoin.{BinaryData, Protocol} +import fr.acinq.bitcoin.{Protocol} import fr.acinq.eclair.Features._ import fr.acinq.eclair.channel.{Helpers, LocalParams, ParamsWithFeatures} import org.scalatest.FunSuite @@ -45,28 +45,28 @@ class FeaturesSpec extends FunSuite { } test("'option_simplified_commitment' feature") { - val features = "0200" + val features = hex"0200" assert(areSupported(features) && hasFeature(features, OPTION_SIMPLIFIED_COMMITMENT_OPTIONAL)) } test("Helpers should correctly detect if the peers negotiated 'option_simplified_commitment'") { - val optionalSupport = BinaryData("0200") - val mandatorySupport = BinaryData("0100") + val optionalSupport = hex"0200" + val mandatorySupport = hex"0100" val channelParamNoSupport = new {} with ParamsWithFeatures { - override val globalFeatures: BinaryData = BinaryData.empty - override val localFeatures: BinaryData = BinaryData.empty + override val globalFeatures: ByteVector = ByteVector.empty + override val localFeatures: ByteVector = ByteVector.empty } val channelParamOptSupport = new {} with ParamsWithFeatures { - override val globalFeatures: BinaryData = BinaryData.empty - override val localFeatures: BinaryData = optionalSupport + override val globalFeatures: ByteVector = ByteVector.empty + override val localFeatures: ByteVector = optionalSupport } val channelParamMandatorySupport = new {} with ParamsWithFeatures { - override val globalFeatures: BinaryData = BinaryData.empty - override val localFeatures: BinaryData = mandatorySupport + override val globalFeatures: ByteVector = ByteVector.empty + override val localFeatures: ByteVector = mandatorySupport } assert(Helpers.canUseSimplifiedCommitment(local = channelParamOptSupport, remote = channelParamOptSupport) == true) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 2551354461..6071744f9e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -28,6 +28,7 @@ import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire._ import fr.acinq.eclair.{Globals, NodeParams, TestConstants, randomBytes32} import scodec.bits.ByteVector +import scodec.bits._ /** * Created by PM on 23/08/2016. @@ -71,7 +72,7 @@ trait StateTestsHelperMethods extends TestKitBase { val channelFlags = if (tags.contains("channels_public")) ChannelFlags.AnnounceChannel else ChannelFlags.Empty val pushMsat = if (tags.contains("no_push_msat")) 0 else TestConstants.pushMsat val (aliceParams, bobParams) = if(tags.contains("simplified_commitment")) - (Alice.channelParams.copy(localFeatures = BinaryData("0200")), Bob.channelParams.copy(localFeatures = BinaryData("0200"))) + (Alice.channelParams.copy(localFeatures = hex"0200"), Bob.channelParams.copy(localFeatures = hex"0200")) else (Alice.channelParams, Bob.channelParams) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 810bcb6e76..11fdfdccb7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire._ import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} - +import scodec.bits._ import scala.concurrent.duration._ /** @@ -46,8 +46,8 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel (TestConstants.fundingSatoshis, TestConstants.pushMsat) } - val aliceLocalFeatures = if (test.tags.contains("simplified_commitment")) BinaryData("0200") else Alice.channelParams.localFeatures - val bobLocalFeatures = if (test.tags.contains("simplified_commitment")) BinaryData("0200") else Bob.channelParams.localFeatures + val aliceLocalFeatures = if (test.tags.contains("simplified_commitment")) hex"0200" else Alice.channelParams.localFeatures + val bobLocalFeatures = if (test.tags.contains("simplified_commitment")) hex"0200" else Bob.channelParams.localFeatures val aliceInit = Init(Alice.channelParams.globalFeatures, aliceLocalFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, bobLocalFeatures) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index ce9aa2ec8b..8e346ca60d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.Outcome import scodec.bits.ByteVector import org.scalatest.{Outcome, Tag} - +import scodec.bits._ import scala.concurrent.duration._ /** @@ -44,7 +44,7 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp import setup._ val (aliceParams, bobParams) = if(test.tags.contains("simplified_commitment")) - (Alice.channelParams.copy(localFeatures = BinaryData("0200")), Bob.channelParams.copy(localFeatures = BinaryData("0200"))) + (Alice.channelParams.copy(localFeatures = hex"0200"), Bob.channelParams.copy(localFeatures = hex"0200")) else (Alice.channelParams, Bob.channelParams) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 93aca2c25b..bdad4f61f0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -292,7 +292,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (option_simplified_commitment)", Tag("simplified_commitment")) { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] - val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, 150000, ByteVector32(ByteVector.fill(32)(42)), 400144, defaultOnion) bob ! htlc awaitCond(initialData.commitments.version == VersionSimplifiedCommitment) awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) @@ -1431,7 +1431,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(initialData.commitments.version == VersionSimplifiedCommitment) // Alice sends update_fee to Bob but this shouldn't happen with option_simplified_commitment so Bob will reply Error - val fee1 = UpdateFee("00" * 32, 12000) + val fee1 = UpdateFee(ByteVector32.Zeroes, 12000) bob ! fee1 bob2alice.expectMsgType[Error] // should bob close the channel? Yes diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 24e745de77..96deda1f0a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -271,7 +271,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // an error occurs and alice publishes her commit tx val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val htlcTimeoutTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.head.txinfo.tx - alice ! Error("00" * 32, "oops".getBytes) + alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx val pushMeTx = alice2blockchain.expectMsgType[PublishAsap].tx // pushMe tx assert(alice2blockchain.expectMsgType[PublishAsap].tx.txIn.head.outPoint.txid == aliceCommitTx.txid) // claim-main-delayed diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 08c3e67a09..38194c76e6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -60,9 +60,9 @@ class ChannelStateSpec extends FunSuite { } test("backward compatible READING of previously stored commitments") { - val state = ChannelCodecs.genericStateDataCodec.decodeValue(BitVector(rawCommitment.data)).require + val state = ChannelCodecs.genericStateDataCodec.decodeValue(BitVector(rawCommitment)).require - assert(state.commitments.localParams.nodeId.raw == BinaryData("036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")) + assert(state.commitments.localParams.nodeId.raw == hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96") assert(state.commitments.version === VersionCommitmentV1) val bin = ChannelCodecs.genericStateDataCodec.encode(state).require @@ -126,18 +126,18 @@ object ChannelStateSpec { val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000, 70000000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) - val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), BinaryData("0303030303030303030303030303030303030303030303030303030303030303"), Scalar(ByteVector.fill(32)(4)).toPoint) + val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), Scalar(ByteVector.fill(32)(4)).toPoint) val commitmentsV1 = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, - originChannels = Map(42L -> Local(None), 15000L -> Relayed("42" * 32, 43, 11000000L, 10000000L)), + originChannels = Map(42L -> Local(None), 15000L -> Relayed(ByteVector32(ByteVector.fill(32)(42)), 43, 11000000L, 10000000L)), remoteNextCommitInfo = Right(randomKey.publicKey), - commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32, version = VersionCommitmentV1) + commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = ByteVector32.Zeroes, version = VersionCommitmentV1) val simplifiedCommitment = Commitments(localParams, remoteParams, channelFlags = 0x01.toByte, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32L, remoteNextHtlcId = 4L, - originChannels = Map(42L -> Local(None), 15000L -> Relayed(ByteVector32(ByteVector.fill(32)(42), 43, 11000000L, 10000000L)), + originChannels = Map(42L -> Local(None), 15000L -> Relayed(ByteVector32(ByteVector.fill(32)(42)), 43, 11000000L, 10000000L)), remoteNextCommitInfo = Right(randomKey.publicKey), commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = ByteVector32.Zeroes, version = VersionSimplifiedCommitment) @@ -148,5 +148,5 @@ object ChannelStateSpec { val normalSimplified = DATA_NORMAL(simplifiedCommitment, ShortChannelId(42), true, None, channelUpdate, None, None) // This is a serialized commitment (CommitmemtV1) taken from a mainnet DB and generated with code (commit: f9ead30) that did not account for other commitments type - val rawCommitment = BinaryData("000003036D65409C41AB7380A43448F257809E7496B52BF92057C09C4F300CBD61C50D96000429A0E801FFB3E3B20083A3F8C548677F0000000000000222000000012A05F20000000000000008FC00000000000000010090001E800BD48A44296C8BE17C66F9F5675400AE1ADFF2BF4C776F4380000000C501C3277812FEF47DAC3ECC48C36735250C344AF72254935FE1B87161B32CBD1FC78000000000000111000000009502F900000000000000047E00000000000000008048000F0180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A0152D19626E31E85DCC547D6452BFECEA4A58D6421DC3ED9C31EBB5BE25F5EC81301826DC6CF05233C470A78CD05F10719F58CC6E3F3297A86F29F41AD3EC17CD07B81D2E0F42ECE10F90F47068AD225E39205BE9F62234D8217239254D1B149BC91A881BDDB713BD0D5A69215373CB4DD6C082ACCCCA37973FC3ED56486184B4060A08200000001C0404100800000000000000A000000000A5B0000000006CAC68C00000000000FFC3400122B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE87000000000015B8410180000000001100102BC29224A48EBA98852295B2000EE07CBE322503CDAC9E423DFABF5A0D307C790023A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95700AD01000000000080AB6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE870000000000752B8CC00117840000000000000B000A2B55A3F061D2BD8FF8D65643AE2ABBE481B612B32135818000000000110010596BBCBEEA49205A1E44895DBE34810E998C5E3041AF6F154F174B877E08AED0820023982201102713B619608C4602CDCB4820CA2E2D1F18F632E14AD1AFBAA2530B195DB7E4B3811037E26CE7057B4B90C7CC88E0AAFCCA551824239C51DACAEAD6938B56E218264880A418228110804CBDAB73C3F569F5A7D6141639C3A94F923C6D15109F3E7C7019472671261B4201103E15DCB850126C070EF6E513AA9CF6D1B624692F4610DFFC6BBF519754B99DE880A3A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95745930A100000000000000000000A000000000A5B00000000000FFC340000000006CAC68C1882C81E7E9B4BAD22DECC021DED07B2A04CF401246F21260608E6D0362BB89981E6F8F85DBFDA1385E5274CBE5B0D21DD45329DE7FB534805C11187456282EE6E000000000000000000000000000000000000000500000000000000000000409D4457D14079D3398F705B263163D4841D8DFB50EF8E8FFFD7D72208E512D884800915B5F5BB68136004E09C3125C27C7AB6D6AD24E794184C27336B36E63398DF4380000000000ADC2080C00000000008800815E1491252475D4C42914AD90007703E5F191281E6D64F211EFD5FAD06983E3C8011D48840C04426479B88E587D3004FE9C3DD8C5EF1D73CE37CCFFDED122D443471EC27250840D91F44E8C2380484032792A5448197CB0C123E3C0583199654AA3D3C1DDE629754AB8000800F00003FFFFFFFFFFC008259C04D32D731B196AED4DC91753BC131B113C3BF619A9D7EB7A3222B3F376B9000F80003FFFFFFFFFFB0020703B9CA4B1B9FB5BA2809C0CF5FB8330E29FB47C27EC40C671E9798BADAAD29580007FFFFFFFFFF62B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE8704510980054B800070B821EA2784D9FC32BFEA579D3C6136DF87E6A232947BCC9DF84BC5337794370FF9740C589C29EB6931134B44414343132B149099E5DCA288BFD526B3C4BC888F22EA7DBA63472300DFBF40588AAC14A954A91FA3A7B1E760B9408841FC33B9C2137CC172868DBF6A5B3EA430FF121D3E09DEE03446599B79B33A980BB57721744E873A8774D20F9A26B466939C91BB0C9EA31367A8526F223FC505F5FAE0796C28711C7C0D4FE45184A8A5D284BA86AF7A06C7FBD30717DAAEAE237EBD1E9FD91D93A03FDAE3B23B8A2C360D692276F38C518BA33A18899D9E8C74CFA7F814AA37B0C12942FBD3BC19F24B56C5AB1E72645A06C79737C154F451FA67410A9460000DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E000006DACA81388356E701486891E4AF013CE92D6A57F240AF81389E60197AC38A1B2C070C9DE04BFBD1F6B0FB31230D9CD49430D12BDC89524D7F86E1C586CCB2F47F1E06C8FA274611C02420193C952A240CBE586091F1E02C18CCB2A551E9E0EEF314BA060221323CDC472C3E98027F4E1EEC62F78EB9E71BE67FEF68916A21A38F613929E7E8606E1BCCB0FEBDE1C2692621783F368F36FCB3B4840C7F47FF0EFE011EDCE3F34E089065806CC7E04039166B7EEB7B6B2116CE357A36B7A1DCCD7793B242DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E0000B8FD31EE020001200000000000000002000007D0000000C8000000001B6B0B0000") + val rawCommitment = hex"000003036D65409C41AB7380A43448F257809E7496B52BF92057C09C4F300CBD61C50D96000429A0E801FFB3E3B20083A3F8C548677F0000000000000222000000012A05F20000000000000008FC00000000000000010090001E800BD48A44296C8BE17C66F9F5675400AE1ADFF2BF4C776F4380000000C501C3277812FEF47DAC3ECC48C36735250C344AF72254935FE1B87161B32CBD1FC78000000000000111000000009502F900000000000000047E00000000000000008048000F0180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A0152D19626E31E85DCC547D6452BFECEA4A58D6421DC3ED9C31EBB5BE25F5EC81301826DC6CF05233C470A78CD05F10719F58CC6E3F3297A86F29F41AD3EC17CD07B81D2E0F42ECE10F90F47068AD225E39205BE9F62234D8217239254D1B149BC91A881BDDB713BD0D5A69215373CB4DD6C082ACCCCA37973FC3ED56486184B4060A08200000001C0404100800000000000000A000000000A5B0000000006CAC68C00000000000FFC3400122B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE87000000000015B8410180000000001100102BC29224A48EBA98852295B2000EE07CBE322503CDAC9E423DFABF5A0D307C790023A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95700AD01000000000080AB6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE870000000000752B8CC00117840000000000000B000A2B55A3F061D2BD8FF8D65643AE2ABBE481B612B32135818000000000110010596BBCBEEA49205A1E44895DBE34810E998C5E3041AF6F154F174B877E08AED0820023982201102713B619608C4602CDCB4820CA2E2D1F18F632E14AD1AFBAA2530B195DB7E4B3811037E26CE7057B4B90C7CC88E0AAFCCA551824239C51DACAEAD6938B56E218264880A418228110804CBDAB73C3F569F5A7D6141639C3A94F923C6D15109F3E7C7019472671261B4201103E15DCB850126C070EF6E513AA9CF6D1B624692F4610DFFC6BBF519754B99DE880A3A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95745930A100000000000000000000A000000000A5B00000000000FFC340000000006CAC68C1882C81E7E9B4BAD22DECC021DED07B2A04CF401246F21260608E6D0362BB89981E6F8F85DBFDA1385E5274CBE5B0D21DD45329DE7FB534805C11187456282EE6E000000000000000000000000000000000000000500000000000000000000409D4457D14079D3398F705B263163D4841D8DFB50EF8E8FFFD7D72208E512D884800915B5F5BB68136004E09C3125C27C7AB6D6AD24E794184C27336B36E63398DF4380000000000ADC2080C00000000008800815E1491252475D4C42914AD90007703E5F191281E6D64F211EFD5FAD06983E3C8011D48840C04426479B88E587D3004FE9C3DD8C5EF1D73CE37CCFFDED122D443471EC27250840D91F44E8C2380484032792A5448197CB0C123E3C0583199654AA3D3C1DDE629754AB8000800F00003FFFFFFFFFFC008259C04D32D731B196AED4DC91753BC131B113C3BF619A9D7EB7A3222B3F376B9000F80003FFFFFFFFFFB0020703B9CA4B1B9FB5BA2809C0CF5FB8330E29FB47C27EC40C671E9798BADAAD29580007FFFFFFFFFF62B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE8704510980054B800070B821EA2784D9FC32BFEA579D3C6136DF87E6A232947BCC9DF84BC5337794370FF9740C589C29EB6931134B44414343132B149099E5DCA288BFD526B3C4BC888F22EA7DBA63472300DFBF40588AAC14A954A91FA3A7B1E760B9408841FC33B9C2137CC172868DBF6A5B3EA430FF121D3E09DEE03446599B79B33A980BB57721744E873A8774D20F9A26B466939C91BB0C9EA31367A8526F223FC505F5FAE0796C28711C7C0D4FE45184A8A5D284BA86AF7A06C7FBD30717DAAEAE237EBD1E9FD91D93A03FDAE3B23B8A2C360D692276F38C518BA33A18899D9E8C74CFA7F814AA37B0C12942FBD3BC19F24B56C5AB1E72645A06C79737C154F451FA67410A9460000DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E000006DACA81388356E701486891E4AF013CE92D6A57F240AF81389E60197AC38A1B2C070C9DE04BFBD1F6B0FB31230D9CD49430D12BDC89524D7F86E1C586CCB2F47F1E06C8FA274611C02420193C952A240CBE586091F1E02C18CCB2A551E9E0EEF314BA060221323CDC472C3E98027F4E1EEC62F78EB9E71BE67FEF68916A21A38F613929E7E8606E1BCCB0FEBDE1C2692621783F368F36FCB3B4840C7F47FF0EFE011EDCE3F34E089065806CC7E04039166B7EEB7B6B2116CE357A36B7A1DCCD7793B242DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E0000B8FD31EE020001200000000000000002000007D0000000C8000000001B6B0B0000" } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index ed3447ee54..1148add365 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -179,7 +179,7 @@ class TransactionsSpec extends FunSuite with Logging { val localPaymentPriv = PrivateKey(randomBytes32 :+ 1.toByte) val localDelayedPaymentPriv = PrivateKey(randomBytes32 :+ 1.toByte) val remotePaymentPriv = PrivateKey(randomBytes32 :+ 1.toByte) - val remoteDelayedPaymentPriv = PrivateKey(BinaryData("a9" * 32) :+ 1.toByte) + val remoteDelayedPaymentPriv = PrivateKey(randomBytes32 :+ 1.toByte) val localHtlcPriv = PrivateKey(randomBytes32 :+ 1.toByte) val remoteHtlcPriv = PrivateKey(randomBytes32 :+ 1.toByte) val finalPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, true).publicKey)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index 276e6db7a6..d1c0b1e430 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -172,15 +172,15 @@ class ChannelCodecsSpec extends FunSuite { // currently version=0 and discriminator type=1 assert(bin_old.startsWith(hex"000001")) // let's decode the old data (this will use the old codec that provides default values for new fields) - val data_new = stateDataCodec.decode(bin_old.toBitVector).require.value + val data_new = stateDataCodec(VersionCommitmentV1).decode(bin_old.toBitVector).require.value assert(data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx === None) assert(Platform.currentTime / 1000 - data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].waitingSince < 3600) // we just set this timestamp to current time // and re-encode it with the new codec - val bin_new = ByteVector(stateDataCodec.encode(data_new).require.toByteVector.toArray) + val bin_new = ByteVector(stateDataCodec(VersionCommitmentV1).encode(data_new).require.toByteVector.toArray) // data should now be encoded under the new format, with version=0 and type=8 assert(bin_new.startsWith(hex"000008")) // now let's decode it again - val data_new2 = stateDataCodec.decode(bin_new.toBitVector).require.value + val data_new2 = stateDataCodec(VersionCommitmentV1).decode(bin_new.toBitVector).require.value // data should match perfectly assert(data_new === data_new2) } From 31ce0c688ea3297664e1dd05f860d377c178f923 Mon Sep 17 00:00:00 2001 From: sstone Date: Thu, 14 Mar 2019 16:04:29 +0100 Subject: [PATCH 65/66] Fix transactions unit test To reconstruct the tx commit number we need to take the lower 24 bits of the locktime field. --- .../scala/fr/acinq/eclair/transactions/TransactionsSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index d81b2c8d1a..8e32ae7e12 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -215,7 +215,7 @@ class TransactionsSpec extends FunSuite with Logging { assert(getCommitTxNumber(commitTx.tx, true, localPaymentPriv.publicKey, remotePaymentPriv.publicKey) == commitTxNumber) val hash = Crypto.sha256(localPaymentPriv.publicKey.toBin ++ remotePaymentPriv.publicKey.toBin) val num = Protocol.uint64(hash.takeRight(8).toArray, ByteOrder.BIG_ENDIAN) & 0xffffffffffffL - val check = ((commitTx.tx.txIn.head.sequence & 0xffffff) << 24) | commitTx.tx.lockTime + val check = ((commitTx.tx.txIn.head.sequence & 0xffffff) << 24) | (commitTx.tx.lockTime & 0xffffff) assert((check ^ num) == commitTxNumber) } val (htlcTimeoutTxs, htlcSuccessTxs) = makeHtlcTxs(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, spec) From 171ddcacba5bcce22ad770fef657da5472cf1892 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 14 Mar 2019 17:59:59 +0100 Subject: [PATCH 66/66] Fix encoding with DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec --- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 22 +++++++++---------- .../eclair/wire/LightningMessageTypes.scala | 8 ++++++- .../states/StateTestsHelperMethods.scala | 5 +++-- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 6 ++--- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index a374ab4d5b..d955b3f11b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -209,7 +209,7 @@ object ChannelCodecs extends Logging { ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) :: ("spent" | spentMapCodec)).as[RevokedCommitPublished] - def commitmentCodec(commitmentVersion: CommitmentVersion): Codec[Commitments] = { + private def commitmentCodec(commitmentVersion: CommitmentVersion): Codec[Commitments] = { (("localParams" | localParamsCodec) :: ("remoteParams" | remoteParamsCodec) :: ("channelFlags" | byte) :: @@ -228,26 +228,26 @@ object ChannelCodecs extends Logging { } // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields - def DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + private def DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("fundingTx" | provide[Option[Transaction]](None)) :: ("waitingSince" | provide(compat.Platform.currentTime / 1000)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly - def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + private def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("fundingTx" | optional(bool, txCodec)) :: ("waitingSince" | int64) :: ("deferred" | optional(bool, fundingLockedCodec)) :: - ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly + ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + private def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("shortChannelId" | shortchannelid) :: ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - def DATA_NORMAL_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NORMAL] = ( + private def DATA_NORMAL_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NORMAL] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool) :: @@ -256,19 +256,19 @@ object ChannelCodecs extends Logging { ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] - def DATA_SHUTDOWN_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_SHUTDOWN] = ( + private def DATA_SHUTDOWN_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] - def DATA_NEGOTIATING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NEGOTIATING] = ( + private def DATA_NEGOTIATING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NEGOTIATING] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec) :: ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - def DATA_CLOSING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_CLOSING] = ( + private def DATA_CLOSING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_CLOSING] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: @@ -278,11 +278,11 @@ object ChannelCodecs extends Logging { ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] - def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + private def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( ("commitments" | commitmentCodec(commitmentVersion)) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] - def stateDataCodec(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) + private def stateDataCodec(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion)) .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index 880ab0d05d..a50faa12f1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -47,7 +47,13 @@ case class Init(globalFeatures: ByteVector, localFeatures: ByteVector) extends SetupMessage case class Error(channelId: ByteVector32, - data: ByteVector) extends SetupMessage with HasChannelId + data: ByteVector) extends SetupMessage with HasChannelId { + + override def toString: String = data.decodeAscii match { + case Left(err) => s"Could not decode error msg, err=$err data=$data " + case Right(str) => s"Error(channelId=$channelId, data=$str)" + } +} object Error { def apply(channelId: ByteVector32, msg: String): Error = Error(channelId, ByteVector.view(msg.getBytes(Charsets.US_ASCII))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 6071744f9e..0f11f836c3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -71,10 +71,11 @@ trait StateTestsHelperMethods extends TestKitBase { import setup._ val channelFlags = if (tags.contains("channels_public")) ChannelFlags.AnnounceChannel else ChannelFlags.Empty val pushMsat = if (tags.contains("no_push_msat")) 0 else TestConstants.pushMsat - val (aliceParams, bobParams) = if(tags.contains("simplified_commitment")) + val (aliceParams, bobParams) = if(tags.contains("simplified_commitment")) { (Alice.channelParams.copy(localFeatures = hex"0200"), Bob.channelParams.copy(localFeatures = hex"0200")) - else + } else { (Alice.channelParams, Bob.channelParams) + } val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures) val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index d1c0b1e430..c8a42a398f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -172,15 +172,15 @@ class ChannelCodecsSpec extends FunSuite { // currently version=0 and discriminator type=1 assert(bin_old.startsWith(hex"000001")) // let's decode the old data (this will use the old codec that provides default values for new fields) - val data_new = stateDataCodec(VersionCommitmentV1).decode(bin_old.toBitVector).require.value + val data_new = genericStateDataCodec.decode(bin_old.toBitVector).require.value assert(data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx === None) assert(Platform.currentTime / 1000 - data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].waitingSince < 3600) // we just set this timestamp to current time // and re-encode it with the new codec - val bin_new = ByteVector(stateDataCodec(VersionCommitmentV1).encode(data_new).require.toByteVector.toArray) + val bin_new = ByteVector(genericStateDataCodec.encode(data_new).require.toByteVector.toArray) // data should now be encoded under the new format, with version=0 and type=8 assert(bin_new.startsWith(hex"000008")) // now let's decode it again - val data_new2 = stateDataCodec(VersionCommitmentV1).decode(bin_new.toBitVector).require.value + val data_new2 = genericStateDataCodec.decode(bin_new.toBitVector).require.value // data should match perfectly assert(data_new === data_new2) }