diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml
index 679f4a5915..22d61f6c7a 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/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
index 9ff59b4b00..b14755e275 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: ByteVector, 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/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
index 9c94257838..ffe2c095d8 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/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala
index 9a7f3bc4d4..008326fa00 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 scodec.bits.ByteVector
import scala.concurrent.{ExecutionContext, Future}
@@ -47,9 +49,13 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC
def fundTransaction(tx: Transaction, lockUnspents: Boolean, feeRatePerKw: Long): Future[FundTransactionResponse] = fundTransaction(Transaction.write(tx).toHex, 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" \\ classOf[JString]).mkString(",")
+ throw new JsonRPCError(Error(-1, message))
+ }
SignTransactionResponse(Transaction.read(hex), complete)
})
@@ -74,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
@@ -94,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/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
index 671be23852..b1ea67d3ec 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
@@ -339,7 +339,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,
@@ -377,12 +377,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
@@ -390,13 +390,20 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
channelId = channelId,
signature = localSigOfRemoteTx
)
+
+ val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match {
+ case true => VersionSimplifiedCommitment
+ case false => VersionCommitmentV1
+ }
+
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)
+ 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))
@@ -419,7 +426,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) =>
@@ -429,13 +436,18 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
handleLocalError(InvalidCommitmentSignature(channelId, signedLocalCommitTx.tx), d, Some(msg))
case Success(_) =>
val commitInput = localCommitTx.input
+ val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match {
+ case true => VersionSimplifiedCommitment
+ case false => VersionCommitmentV1
+ }
+
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)
+ commitInput, ShaChain.init, channelId = channelId, version = commitmentVersion)
val now = Platform.currentTime / 1000
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments))
log.info(s"publishing funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}")
@@ -645,7 +657,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, 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")
@@ -775,8 +787,12 @@ 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.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.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
} else {
@@ -1031,8 +1047,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.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
} else {
@@ -1055,7 +1071,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
@@ -1212,6 +1228,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 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))
@@ -1224,9 +1241,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) =>
@@ -1251,13 +1268,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) {
@@ -1331,14 +1348,17 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
forwarder ! r
val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
- val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index)
+ val myCurrentPerCommitmentPoint = d.commitments.version match {
+ case VersionCommitmentV1 => Some(keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index))
+ case VersionSimplifiedCommitment => None
+ }
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
@@ -1467,7 +1487,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.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))
@@ -1738,6 +1758,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 {
@@ -1808,13 +1829,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/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala
index 3cf1d713ca..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,6 +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: 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 544ae9a1f4..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
@@ -142,8 +142,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, ByteVector32])
-case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: List[Transaction], claimHtlcTimeoutTxs: List[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32])
+case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], pushMeTx: Option[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32])
+case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: List[Transaction], claimHtlcTimeoutTxs: List[Transaction], pushMeTx: Option[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32])
case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], mainPenaltyTx: Option[Transaction], htlcPenaltyTxs: List[Transaction], claimHtlcDelayedPenaltyTxs: List[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32])
final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data
@@ -171,7 +171,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.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
@@ -187,6 +187,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: ByteVector
+ val localFeatures: ByteVector
+}
+
final case class LocalParams(nodeId: PublicKey,
channelKeyPath: DeterministicWallet.KeyPath,
dustLimitSatoshis: Long,
@@ -198,7 +203,7 @@ final case class LocalParams(nodeId: PublicKey,
isFunder: Boolean,
defaultFinalScriptPubKey: ByteVector,
globalFeatures: ByteVector,
- localFeatures: ByteVector)
+ localFeatures: ByteVector) extends ParamsWithFeatures
final case class RemoteParams(nodeId: PublicKey,
dustLimitSatoshis: Long,
@@ -213,7 +218,7 @@ final case class RemoteParams(nodeId: PublicKey,
delayedPaymentBasepoint: Point,
htlcBasepoint: Point,
globalFeatures: ByteVector,
- localFeatures: ByteVector)
+ localFeatures: ByteVector) 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 0da1e760d4..171f7464b7 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,13 +17,15 @@
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.{ByteVector32, Crypto, Satoshi}
import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx}
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.{Features, Globals, UInt64}
+import fr.acinq.bitcoin._
import fr.acinq.eclair.{Globals, UInt64}
import scodec.bits.ByteVector
@@ -51,14 +53,16 @@ case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig,
* 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: ByteVector32) {
+ 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: ByteVector32,
+ version: CommitmentVersion = VersionCommitmentV1
+ ) {
def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight
@@ -73,13 +77,21 @@ 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 feesMsat = if (localParams.isFunder) Transactions.commitTxFee(Satoshi(remoteParams.dustLimitSatoshis), reduced).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
}
+
}
+// @formatter: off
+sealed trait CommitmentVersion
+object VersionCommitmentV1 extends CommitmentVersion
+object VersionSimplifiedCommitment extends CommitmentVersion
+// @formatter: on
+
object Commitments {
/**
* add a change to our proposed change list
@@ -140,7 +152,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))
@@ -150,6 +162,7 @@ object Commitments {
}
def receiveAdd(commitments: Commitments, add: UpdateAddHtlc): Commitments = {
+
if (add.id != commitments.remoteNextHtlcId) {
throw UnexpectedHtlcId(commitments.channelId, expected = commitments.remoteNextHtlcId, actual = add.id)
}
@@ -173,7 +186,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)
@@ -287,6 +300,11 @@ object Commitments {
if (!commitments.localParams.isFunder) {
throw FundeeCannotSendUpdateFee(commitments.channelId)
}
+
+ if(commitments.version == VersionSimplifiedCommitment){
+ throw CannotUpdateFeeWithCommitmentType(commitments.channelId)
+ }
+
// 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
@@ -295,7 +313,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, 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)
@@ -318,6 +336,10 @@ object Commitments {
throw FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw)
}
+ if(commitments.version == VersionSimplifiedCommitment){
+ throw CannotUpdateFeeWithCommitmentType(commitments.channelId)
+ }
+
// 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
@@ -329,7 +351,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, 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)
@@ -352,17 +374,22 @@ 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 localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1)
+ 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 = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint))
+ 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))
+ }
// 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)
@@ -378,13 +405,14 @@ object Commitments {
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) = {
+
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:
@@ -404,8 +432,13 @@ 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 sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
+ val remotePerCommitmentPoint = remoteNextCommitInfo match {
+ case Left(_) => commitments.remoteCommit.remotePerCommitmentPoint
+ case Right(point) => point
+ }
+
+ 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)
@@ -421,7 +454,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 = 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))
+ }
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
// combine the sigs to make signed txes
val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect {
@@ -430,9 +466,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 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) == false) {
+ 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 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)
}
HtlcTxAndSigs(htlcTx, localSig, remoteSig)
@@ -500,25 +542,34 @@ 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(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)
- val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
+
+ val remotePaymentPubkey = commitmentVersion match {
+ 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)
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 (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, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
- val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
+ 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)
+ }
+ 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 (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)
}
@@ -536,7 +587,7 @@ object Commitments {
def changes2String(commitments: Commitments): String = {
import commitments._
- s"""commitments:
+ s"""(${commitments.version}) commitments:
| localChanges:
| proposed: ${localChanges.proposed.map(msg2String(_)).mkString(" ")}
| signed: ${localChanges.signed.map(msg2String(_)).mkString(" ")}
@@ -551,7 +602,7 @@ object Commitments {
}
def specs2String(commitments: Commitments): String = {
- s"""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 0013f3a6ff..6e58b9c68e 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,6 +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._
import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId, addressToPublicKeyScript}
import scodec.bits.ByteVector
@@ -66,6 +67,7 @@ object Helpers {
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)
case d: DATA_WAIT_FOR_FUNDING_LOCKED => d.copy(commitments = commitments1)
@@ -233,6 +235,12 @@ object Helpers {
* @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
*/
def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = {
+ implicit val commitmentVersion = Helpers.canUseSimplifiedCommitment(localParams, remoteParams) match {
+ case false => VersionCommitmentV1
+ case true => VersionSimplifiedCommitment
+ }
+
+ // 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
@@ -242,7 +250,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)
@@ -251,8 +259,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, commitmentVersion)
+ val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, localPerCommitmentPoint, remoteSpec, commitmentVersion)
(localSpec, localCommitTx, remoteSpec, remoteCommitTx)
}
@@ -333,6 +341,16 @@ object Helpers {
}
}
+ def canUseSimplifiedCommitment[T <: ParamsWithFeatures](local: T, remote: T) = {
+ 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 {
@@ -362,15 +380,18 @@ object Helpers {
}
}
- def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(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, ByteVector.fill(71)(0xaa), ByteVector.fill(71)(0xbb)).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: ByteVector, remoteScriptPubkey: ByteVector)(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
+ 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, ByteVector.fill(71)(0xaa), ByteVector.fill(71)(0xbb)).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 VersionSimplifiedCommitment =>
+ Satoshi(282)
}
def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2
@@ -388,7 +409,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}}")
@@ -432,6 +453,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 commitmentVersion = commitments.version
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt)
val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint)
@@ -442,11 +464,22 @@ 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 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)
})
+ // the push-me trasaction attaches the fees to the commitmentTx
+ val pushMeTransaction = commitmentVersion match {
+ 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
+ Transactions.addSigs(pushMeTx, sig)
+ })
+ }
+
// those are the preimages to existing received htlcs
val preimages = commitments.localChanges.all.collect { case u: UpdateFulfillHtlc => u.paymentPreimage }
@@ -477,8 +510,8 @@ object Helpers {
localRevocationPubkey,
remoteParams.toSelfDelay,
localDelayedPubkey,
- localParams.defaultFinalScriptPubKey, feeratePerKwDelayed)
- val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint)
+ localParams.defaultFinalScriptPubKey, feeratePerKwDelayed, commitmentVersion)
+ val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)
Transactions.addSigs(claimDelayed, sig)
})
}
@@ -489,6 +522,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)
}
@@ -498,18 +532,19 @@ 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")
- val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(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(remoteCommit.txid == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx")
+
+ 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, 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)
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
@@ -526,7 +561,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)
})
@@ -536,12 +571,12 @@ 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
- 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 }
)
@@ -549,33 +584,46 @@ object Helpers {
/**
*
- * Claim our Main output only
+ * 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
* @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 = {
- val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
+ def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, commitTx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = {
// 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),
- localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain)
- val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint)
- Transactions.addSigs(claimMain, localPubkey, sig)
- })
+ val mainTx = commitments.version match {
+ 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),
+ 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)
+ })
+
+ case VersionSimplifiedCommitment =>
+ 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), commitments.version)
+ val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL)
+ Transactions.addSigs(claimMain, localPubkey, sig)
+ })
+ }
RemoteCommitPublished(
- commitTx = tx,
+ commitTx = commitTx,
claimMainOutputTx = mainTx.map(_.tx),
claimHtlcSuccessTxs = Nil,
claimHtlcTimeoutTxs = Nil,
+ pushMeTx = None,
irrevocablySpent = Map.empty
)
}
@@ -590,6 +638,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] = {
+
import commitments._
require(tx.txIn.size == 1, "commitment tx should have 1 input")
val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime)
@@ -615,15 +664,15 @@ 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 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 sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret)
+ 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)
})
@@ -644,7 +693,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
@@ -699,7 +748,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)
@@ -763,10 +812,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 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 {
@@ -787,10 +836,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 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/crypto/KeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala
index 28a739c868..72799ceb45 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
@@ -46,10 +46,11 @@ 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
*/
- def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): ByteVector
+ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, sigHash: Int): ByteVector
/**
* This method is used to spend funds send to htlc keys/delayed keys
@@ -57,10 +58,11 @@ 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.
*/
- def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point): ByteVector
+ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point, sigHash: Int): ByteVector
/**
* Ths method is used to spend revoked transactions, with the corresponding revocation key
@@ -68,10 +70,11 @@ 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.
*/
- def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): ByteVector
+ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar, sigHash: Int): ByteVector
def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector)
}
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 3c8e8e5f44..8bc12987e6 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
@@ -98,12 +98,13 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
*
* @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
*/
- def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): ByteVector = {
+ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, sigHash: Int): ByteVector = {
val privateKey = privateKeys.get(publicKey.path)
- Transactions.sign(tx, privateKey.privateKey)
+ Transactions.sign(tx, privateKey.privateKey, sigHash)
}
/**
@@ -112,13 +113,14 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
* @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.
*/
- def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point): ByteVector = {
+ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point, sigHash: Int): ByteVector = {
val privateKey = privateKeys.get(publicKey.path)
val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint)
- Transactions.sign(tx, currentKey)
+ Transactions.sign(tx, currentKey, sigHash)
}
/**
@@ -127,13 +129,14 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
* @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.
*/
- def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): ByteVector = {
+ def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar, sigHash: Int): ByteVector = {
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: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = {
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 0ccc2fb2fa..0b7c6f3f80 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.ByteVector32
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
@@ -42,7 +42,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.toArray)
@@ -76,7 +76,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/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
index 66b9a2f4b1..b6e42b19e5 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
@@ -121,8 +121,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"peer is using globalFeatures=${remoteInit.globalFeatures.toBin} and localFeatures=${remoteInit.localFeatures.toBin}")
- 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")
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/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala
index d8ab4c77fb..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
@@ -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.{ByteVector32, 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.{ByteVector32, 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}
import scodec.bits.ByteVector
/**
@@ -185,6 +185,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
*/
@@ -251,6 +269,35 @@ object Scripts {
// @formatter:on
}
+ /**
+ * Use with `option_simplified_commitment`
+ */
+ def pushMeSimplified(pubkey: PublicKey): List[ScriptElt] = {
+ // @formatter:off
+ OP_DEPTH :: // Puts the number of stack items onto the stack.
+ OP_IF ::
+ OP_PUSHDATA(pubkey) :: OP_CHECKSIG ::
+ OP_ELSE ::
+ OP_10 :: OP_CHECKSEQUENCEVERIFY ::
+ OP_ENDIF :: Nil
+ // @formatter:on
+ }
+
+ /**
+ * The script spending 'pushMeSimplified' outputs, signature based version
+ *
+ * @param sig
+ * @return
+ */
+ def claimPushMeOutputWithKey(sig: ByteVector) = 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)
*/
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 f725fb5293..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
@@ -22,10 +22,12 @@ import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, ripemd160}
import fr.acinq.bitcoin.Script._
import fr.acinq.bitcoin.SigVersion._
import fr.acinq.bitcoin.{ByteVector32, 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 scodec.bits.ByteVector
+import scala.reflect.ClassTag
import scala.util.Try
/**
@@ -60,6 +62,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
@@ -94,8 +97,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) // used in option_simplified_commitment
/**
* these values specific to us and used to estimate fees
@@ -117,27 +123,35 @@ 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, commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = {
+ val htlcTimeoutFee = commitmentVersion match {
+ case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcTimeoutWeight)
+ case VersionSimplifiedCommitment => 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, commitmentVersion: CommitmentVersion): Seq[DirectedHtlc] = {
+ val htlcSuccessFee = commitmentVersion match {
+ case VersionCommitmentV1 => weight2fee(spec.feeratePerKw, htlcSuccessWeight)
+ case VersionSimplifiedCommitment => Satoshi(0)
+ }
spec.htlcs
.filter(_.direction == IN)
.filter(htlc => MilliSatoshi(htlc.add.amountMsat) >= (dustLimit + htlcSuccessFee))
.toSeq
}
- def commitTxFee(dustLimit: Satoshi, spec: CommitmentSpec): Satoshi = {
- val trimmedOfferedHtlcs = trimOfferedHtlcs(dustLimit, spec)
- val trimmedReceivedHtlcs = trimReceivedHtlcs(dustLimit, spec)
- val weight = commitWeight + 172 * (trimmedOfferedHtlcs.size + trimmedReceivedHtlcs.size)
- weight2fee(spec.feeratePerKw, weight)
+ 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
+ }
}
/**
@@ -186,32 +200,65 @@ 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(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 {
- 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
+ case VersionCommitmentV1 =>
+ val commitFee = commitTxFee(localDustLimit, spec, commitmentVersion)
- 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 (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.bytes)))))
- val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec)
- .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes), 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 txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint)
- val (sequence, locktime) = encodeTxNumber(txnumber)
+ val htlcOfferedOutputs = trimOfferedHtlcs(localDustLimit, spec, commitmentVersion)
+ .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes)))))
+ val htlcReceivedOutputs = trimReceivedHtlcs(localDustLimit, spec, commitmentVersion)
+ .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes), htlc.add.cltvExpiry))))
- val tx = Transaction(
- version = 2,
- txIn = TxIn(commitTxInput.outPoint, ByteVector.empty, sequence = sequence) :: Nil,
- txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs,
- lockTime = locktime)
- CommitTx(commitTxInput, LexicographicalOrdering.sort(tx))
+ val txnumber = obscuredCommitTxNumber(commitTxNumber, localIsFunder, localPaymentBasePoint, remotePaymentBasePoint)
+ val (sequence, locktime) = encodeTxNumber(txnumber)
+
+ val tx = Transaction(
+ version = 2,
+ txIn = TxIn(commitTxInput.outPoint, ByteVector.empty, sequence = sequence) :: Nil,
+ txOut = toLocalDelayedOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ htlcOfferedOutputs ++ htlcReceivedOutputs,
+ lockTime = locktime)
+ CommitTx(commitTxInput, LexicographicalOrdering.sort(tx))
+
+ case VersionSimplifiedCommitment =>
+ 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) {
+ (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, commitmentVersion)
+ .map(htlc => TxOut(MilliSatoshi(htlc.add.amountMsat), pay2wsh(htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash)))))
+ 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
+ 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)
+
+ val tx = Transaction(
+ version = 2,
+ 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))
}
def makeHtlcTimeoutTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcTimeoutTx = {
@@ -248,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): (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
@@ -311,33 +358,56 @@ object Transactions {
ClaimHtlcTimeoutTx(input, tx1)
}
- def makeClaimP2WPKHOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, 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: ByteVector, feeratePerKw: Long, toRemoteDelay: Option[Int], commitmentVersion: CommitmentVersion): ClaimP2WPKHOutputTx = {
+
+ val claimTx = commitmentVersion match {
+ case VersionCommitmentV1 =>
+ 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, 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), ByteVector.fill(33)(0), ByteVector.fill(73)(0))
+
+ // here localPaymentPubkey == localDelayedPaymentPubkey
+ 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)
+ val input = InputInfo(OutPoint(delayedOutputTx, outputIndex), delayedOutputTx.txOut(outputIndex), write(redeemScript))
+
+ // unsigned tx
+ val tx = Transaction(
+ version = 2,
+ 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), ByteVector.fill(33)(0), ByteVector.fill(73)(0))
+ }
- // unsigned tx
- val tx = Transaction(
- version = 2,
- 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
- val weight = Transactions.addSigs(ClaimP2WPKHOutputTx(input, tx), ByteVector.fill(33)(0), ByteVector.fill(73)(0)).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: ByteVector, feeratePerKw: Long): ClaimDelayedOutputTx = {
+ def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: Long, commitmentVersion: CommitmentVersion): ClaimDelayedOutputTx = {
val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)
val pubkeyScript = write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None)
@@ -350,17 +420,20 @@ object Transactions {
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
lockTime = 0)
+ val claimTx = ClaimDelayedOutputTx(input, tx)
+
+
// compute weight with a dummy 73 bytes signature (the largest you can get)
- val weight = Transactions.addSigs(ClaimDelayedOutputTx(input, tx), ByteVector.fill(73)(0)).tx.weight()
+ val weight = Transactions.addSigs(claimTx, ByteVector.fill(73)(0)).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: ByteVector, feeratePerKw: Long): ClaimDelayedOutputPenaltyTx = {
@@ -389,35 +462,39 @@ object Transactions {
ClaimDelayedOutputPenaltyTx(input, tx1)
}
- def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, 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))
+ // TODO adjust for option_simplified_commitment -> sweep pushme outputs
+ def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, 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)
+ 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, ByteVector.empty, 0xffffffffL) :: Nil,
- txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
- lockTime = 0)
+ // unsigned transaction
+ val tx = Transaction(
+ version = 2,
+ txIn = TxIn(input.outPoint, ByteVector.empty, 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), ByteVector.fill(73)(0)).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), ByteVector.fill(73)(0)).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)
}
/**
* We already have the redeemScript, no need to build it
*/
+ // TODO adjust for option_simplified_commitment ?
def makeHtlcPenaltyTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], redeemScript: ByteVector, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feeratePerKw: Long): HtlcPenaltyTx = {
val pubkeyScript = write(pay2wsh(redeemScript))
val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed, amount_opt = None)
@@ -443,6 +520,7 @@ object Transactions {
HtlcPenaltyTx(input, tx1)
}
+ // TODO adjust for option_simplified_commitment
def makeClosingTx(commitTxInput: InputInfo, localScriptPubKey: ByteVector, remoteScriptPubKey: ByteVector, localIsFunder: Boolean, dustLimit: Satoshi, closingFee: Satoshi, spec: CommitmentSpec): ClosingTx = {
require(spec.htlcs.isEmpty, "there shouldn't be any pending htlcs")
@@ -463,10 +541,36 @@ 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, 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), ByteVector.fill(73)(0)).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: ByteVector, 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 {
@@ -474,14 +578,13 @@ object Transactions {
}
}
-
- def sign(tx: Transaction, inputIndex: Int, redeemScript: ByteVector, amount: Satoshi, key: PrivateKey): ByteVector = {
- Transaction.signInput(tx, inputIndex, redeemScript, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0, key)
+ def sign(tx: Transaction, inputIndex: Int, redeemScript: ByteVector, amount: Satoshi, key: PrivateKey, sigHash: Int): ByteVector = {
+ Transaction.signInput(tx, inputIndex, redeemScript, sigHash, amount, SIGVERSION_WITNESS_V0, key)
}
- def sign(txinfo: TransactionWithInputInfo, key: PrivateKey): ByteVector = {
+ def sign(txinfo: TransactionWithInputInfo, key: PrivateKey, sigHash: Int): ByteVector = {
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: ByteVector, remoteSig: ByteVector): CommitTx = {
@@ -539,11 +642,24 @@ object Transactions {
closingTx.copy(tx = closingTx.tx.updateWitness(0, witness))
}
+ def addSigs(pushMeTx: PushMeTx, localSig: ByteVector): 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))
- def checkSig(txinfo: TransactionWithInputInfo, sig: ByteVector, pubKey: PublicKey): Boolean = {
- val data = Transaction.hashForSigning(txinfo.tx, inputIndex = 0, txinfo.input.redeemScript, SIGHASH_ALL, txinfo.input.txOut.amount, SIGVERSION_WITNESS_V0)
+ /**
+ *
+ * @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: ByteVector, 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/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala
index 14b2028087..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
@@ -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.{ByteVector32, OutPoint, Transaction, TxOut}
import fr.acinq.eclair.channel._
@@ -27,7 +28,8 @@ import fr.acinq.eclair.wire.LightningMessageCodecs._
import grizzled.slf4j.Logging
import scodec.bits.BitVector
import scodec.codecs._
-import scodec.{Attempt, Codec}
+import scodec.{Attempt, Codec, DecodeResult, Decoder, Encoder, Err, GenCodec, SizeBound, Transformer}
+import shapeless.{Generic, HList, HNil}
/**
* Created by PM on 02/06/2017.
@@ -178,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]
@@ -204,6 +190,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] = (
@@ -211,6 +198,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] = (
@@ -221,28 +209,46 @@ object ChannelCodecs extends Logging {
("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, txCodec)) ::
("spent" | spentMapCodec)).as[RevokedCommitPublished]
+ private def commitmentCodec(commitmentVersion: CommitmentVersion): 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) ::
+ ("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) ::
+ 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
- val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
- ("commitments" | commitmentsCodec) ::
+ 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]
- val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = (
- ("commitments" | commitmentsCodec) ::
+ 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]
- val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = (
- ("commitments" | commitmentsCodec) ::
+ private def DATA_NORMAL_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_NORMAL] = (
+ ("commitments" | commitmentCodec(commitmentVersion)) ::
("shortChannelId" | shortchannelid) ::
("buried" | bool) ::
("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) ::
@@ -250,20 +256,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) ::
+ private def DATA_SHUTDOWN_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_SHUTDOWN] = (
+ ("commitments" | commitmentCodec(commitmentVersion)) ::
("localShutdown" | shutdownCodec) ::
("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN]
- val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = (
- ("commitments" | commitmentsCodec) ::
+ 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]
- val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = (
- ("commitments" | commitmentsCodec) ::
+ private def DATA_CLOSING_Codec(commitmentVersion: CommitmentVersion): Codec[DATA_CLOSING] = (
+ ("commitments" | commitmentCodec(commitmentVersion)) ::
("mutualCloseProposed" | listOfN(uint16, txCodec)) ::
("mutualClosePublished" | listOfN(uint16, txCodec)) ::
("localCommitPublished" | optional(bool, localCommitPublishedCodec)) ::
@@ -272,30 +278,25 @@ 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) ::
+ 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]
-
- /**
- * Order matters!!
- *
- * We use the fact that the discriminated codec encodes using the first suitable codec it finds in the list to handle
- * database migration.
- *
- * For example, a data encoded with type 01 will be decoded using [[DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec]] and
- * encoded to a type 08 using [[DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec]].
- *
- * More info here: https://github.com/scodec/scodec/issues/122
- */
- val stateDataCodec: Codec[HasCommitments] = ("version" | constant(0x00)) ~> discriminated[HasCommitments].by(uint16)
- .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec)
- .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_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(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))
+ .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))
+
+ 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))
}
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/resources/integration/bitcoin.conf b/eclair-core/src/test/resources/integration/bitcoin.conf
index da4dd59a07..29775744a1 100644
--- a/eclair-core/src/test/resources/integration/bitcoin.conf
+++ b/eclair-core/src/test/resources/integration/bitcoin.conf
@@ -1,7 +1,7 @@
regtest=1
+noprinttoconsole=1
server=1
port=28333
-rpcport=28332
rpcuser=foo
rpcpassword=bar
txindex=1
@@ -9,3 +9,5 @@ zmqpubrawblock=tcp://127.0.0.1:28334
zmqpubrawtx=tcp://127.0.0.1:28335
rpcworkqueue=64
addresstype=bech32
+[regtest]
+rpcport=28332
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 0f2dc94013..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,8 +18,9 @@ package fr.acinq.eclair
import java.nio.ByteOrder
-import fr.acinq.bitcoin.Protocol
+import fr.acinq.bitcoin.{Protocol}
import fr.acinq.eclair.Features._
+import fr.acinq.eclair.channel.{Helpers, LocalParams, ParamsWithFeatures}
import org.scalatest.FunSuite
import scodec.bits._
@@ -43,6 +44,39 @@ 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 = 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 = hex"0200"
+ val mandatorySupport = hex"0100"
+
+ val channelParamNoSupport = new {} with ParamsWithFeatures {
+ override val globalFeatures: ByteVector = ByteVector.empty
+ override val localFeatures: ByteVector = ByteVector.empty
+ }
+
+ val channelParamOptSupport = new {} with ParamsWithFeatures {
+ override val globalFeatures: ByteVector = ByteVector.empty
+ override val localFeatures: ByteVector = optionalSupport
+ }
+
+ val channelParamMandatorySupport = new {} with ParamsWithFeatures {
+ override val globalFeatures: ByteVector = ByteVector.empty
+ override val localFeatures: ByteVector = 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)))
assert(areSupported(Protocol.writeUInt64(1L << OPTION_DATA_LOSS_PROTECT_MANDATORY, ByteOrder.BIG_ENDIAN)))
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 2f4adea65d..3d0a510292 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}
+import fr.acinq.bitcoin.{ByteVector32, 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}
@@ -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
@@ -94,6 +101,39 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe
}
}
+ test("handle errors 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 a transaction that spends UTXOs that don't exist
+ wallet.getFinalAddress.pipeTo(sender.ref)
+ val address = sender.expectMsgType[String]
+ val unknownTxids = Seq(
+ ByteVector32.fromValidHex("01" * 32),
+ ByteVector32.fromValidHex("02" * 32),
+ ByteVector32.fromValidHex("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]
+ unknownTxids.foreach(id => assert(error.message.contains(id.toString())))
+ }
+
test("create/commit/rollback funding txes") {
val bitcoinClient = new BasicBitcoinJsonRPCClient(
user = config.getString("bitcoind.rpcuser"),
@@ -141,10 +181,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 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"))
- assert(sender.expectMsgType[JValue](10 seconds).children.size === 2)
-
+ assert(sender.expectMsgType[JValue](10 seconds).children.size === 0)
}
test("encrypt wallet") {
@@ -177,7 +216,8 @@ 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
+ 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)
@@ -212,14 +252,14 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe
bitcoinClient.invoke("fundrawtransaction", noinputTx1).pipeTo(sender.ref)
val json = sender.expectMsgType[JValue]
val JString(unsignedtx1) = json \ "hex"
- bitcoinClient.invoke("signrawtransaction", unsignedtx1).pipeTo(sender.ref)
+ bitcoinClient.invoke("signrawtransactionwithwallet", unsignedtx1).pipeTo(sender.ref)
val JString(signedTx1) = sender.expectMsgType[JValue] \ "hex"
val tx1 = Transaction.read(signedTx1)
// let's then generate another tx that double spends the first one
val inputs = tx1.txIn.map(txIn => Map("txid" -> txIn.outPoint.txid.toString, "vout" -> txIn.outPoint.index)).toArray
bitcoinClient.invoke("createrawtransaction", inputs, Map(address -> tx1.txOut.map(_.amount.toLong).sum * 1.0 / 1e8)).pipeTo(sender.ref)
val JString(unsignedtx2) = sender.expectMsgType[JValue]
- bitcoinClient.invoke("signrawtransaction", unsignedtx2).pipeTo(sender.ref)
+ bitcoinClient.invoke("signrawtransactionwithwallet", unsignedtx2).pipeTo(sender.ref)
val JString(signedTx2) = sender.expectMsgType[JValue] \ "hex"
val tx2 = Transaction.read(signedTx2)
@@ -236,4 +276,4 @@ class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindSe
sender.expectMsg(true)
}
-}
\ No newline at end of file
+}
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 42f7f949c3..479072f4e9 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
@@ -44,7 +44,7 @@ trait BitcoindService extends Logging {
val INTEGRATION_TMP_DIR = new File(TestUtils.BUILD_DIRECTORY, s"integration-${UUID.randomUUID()}")
logger.info(s"using tmp dir: $INTEGRATION_TMP_DIR")
- val PATH_BITCOIND = new File(TestUtils.BUILD_DIRECTORY, "bitcoin-0.16.3/bin/bitcoind")
+ val PATH_BITCOIND = new File(TestUtils.BUILD_DIRECTORY, "bitcoin-0.17.1/bin/bitcoind")
val PATH_BITCOIND_DATADIR = new File(INTEGRATION_TMP_DIR, "datadir-bitcoin")
var bitcoind: Process = null
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..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
@@ -67,7 +74,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 +99,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
}
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
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 efdc8fa73a..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
@@ -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.
@@ -70,7 +71,12 @@ 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 = hex"0200"), Bob.channelParams.copy(localFeatures = hex"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/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala
index 9b603223d7..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._
/**
@@ -45,11 +45,16 @@ 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")) 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)
+
within(30 seconds) {
- alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty)
- bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit)
+ alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams.copy(localFeatures = aliceLocalFeatures), alice2bob.ref, bobInit, ChannelFlags.Empty)
+ bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams.copy(localFeatures = bobLocalFeatures), bob2alice.ref, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
@@ -63,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.version == VersionCommitmentV1
+ })
+ 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.version == VersionSimplifiedCommitment
+ })
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
@@ -92,5 +114,4 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel
bob ! CMD_CLOSE(None)
awaitCond(bob.stateName == CLOSED)
}
-
}
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 1d8b7cd530..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
@@ -22,11 +22,13 @@ 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 scodec.bits.ByteVector
-
+import org.scalatest.{Outcome, Tag}
+import scodec.bits._
import scala.concurrent.duration._
/**
@@ -40,11 +42,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 = hex"0200"), Bob.channelParams.copy(localFeatures = hex"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(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty)
- bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit)
+ alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty)
+ bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
@@ -65,6 +74,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.version == VersionSimplifiedCommitment)
+
+ // 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/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala
index d6486a7a86..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
@@ -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.{ByteVector32, Crypto, Satoshi, ScriptFlags, Transaction}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.UInt64.Conversions._
@@ -289,6 +289,17 @@ 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(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)))
+ // 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
@@ -611,6 +622,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.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)
+ assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.signed.size == 1)
+ }
+
+
test("recv CommitSig (one htlc sent)") { f =>
import f._
val sender = TestProbe()
@@ -634,6 +673,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.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)
+ }
+
test("recv CommitSig (multiple htlcs in both directions)") { f =>
import f._
val sender = TestProbe()
@@ -783,7 +847,6 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
bob2blockchain.expectMsgType[WatchConfirmed]
}
-
test("recv RevokeAndAck (one htlc sent)") { f =>
import f._
val sender = TestProbe()
@@ -802,6 +865,34 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localChanges.acked.size == 1)
}
+ test("recv RevokeAndAck (simplified_commitment - non rotating remote_pubkey)", Tag("simplified_commitment")) { f =>
+ import f._
+ val sender = TestProbe()
+
+ // 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)
+
+ // 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()
@@ -1314,6 +1405,16 @@ 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()
+ 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]
@@ -1324,6 +1425,18 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
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 =>
+ import f._
+ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL]
+ 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(ByteVector32.Zeroes, 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]
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 57505a09c2..705ad6f550 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,9 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
override def withFixture(test: OneArgTest): Outcome = {
val setup = init()
import setup._
+
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))
@@ -94,6 +95,26 @@ 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)
+ alice2bob.forward(bob)
+ awaitCond(bob.stateName == NEGOTIATING)
+
+ // 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)
+
+ 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) = {
import f._
var aliceCloseFee, bobCloseFee = 0L
@@ -121,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.toArray).startsWith("invalid close fee: fee_satoshis=99000"))
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[PublishAsap]
@@ -187,7 +208,6 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
assert(alice.stateName == CLOSING)
}
-
test("recv CMD_CLOSE") { f =>
import f._
val sender = TestProbe()
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 d68b63c48d..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
@@ -26,10 +26,13 @@ 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, randomBytes32}
import org.scalatest.Outcome
import scodec.bits.ByteVector
+import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
+import org.scalatest.{Outcome, Tag}
import scala.concurrent.duration._
@@ -46,7 +49,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)
@@ -257,6 +260,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(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
+ 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/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala
index e521424ebf..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
@@ -29,6 +29,7 @@ import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey}
import org.scalatest.FunSuite
import scodec.bits._
+import scodec.bits.BitVector
/**
* Created by fabrice on 07/02/17.
@@ -38,13 +39,38 @@ 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.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)
+ }
+
+ test("basic serialization test (NORMAL - SimplifiedCommitment)") {
+ val data = normalSimplified
+ 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)
}
+
+ test("backward compatible READING of previously stored commitments") {
+ val state = ChannelCodecs.genericStateDataCodec.decodeValue(BitVector(rawCommitment)).require
+
+ assert(state.commitments.localParams.nodeId.raw == hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")
+ assert(state.commitments.version === VersionCommitmentV1)
+
+ val bin = ChannelCodecs.genericStateDataCodec.encode(state).require
+ val state1 = ChannelCodecs.genericStateDataCodec.decodeValue(bin).require
+
+ assert(state === state1)
+ }
+
}
object ChannelStateSpec {
@@ -101,14 +127,26 @@ 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), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), Scalar(ByteVector.fill(32)(4)).toPoint)
- val commitments = Commitments(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(ByteVector32(ByteVector.fill(32)(42)), 43, 11000000L, 10000000L)),
remoteNextCommitInfo = Right(randomKey.publicKey),
- commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = ByteVector32.Zeroes)
+ 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)),
+ remoteNextCommitInfo = Right(randomKey.publicKey),
+ commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = ByteVector32.Zeroes, version = VersionSimplifiedCommitment)
val channelUpdate = Announcements.makeChannelUpdate(ByteVector32(ByteVector.fill(32)(1)), 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)
+
+ // 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 = hex"000003036D65409C41AB7380A43448F257809E7496B52BF92057C09C4F300CBD61C50D96000429A0E801FFB3E3B20083A3F8C548677F0000000000000222000000012A05F20000000000000008FC00000000000000010090001E800BD48A44296C8BE17C66F9F5675400AE1ADFF2BF4C776F4380000000C501C3277812FEF47DAC3ECC48C36735250C344AF72254935FE1B87161B32CBD1FC78000000000000111000000009502F900000000000000047E00000000000000008048000F0180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A0152D19626E31E85DCC547D6452BFECEA4A58D6421DC3ED9C31EBB5BE25F5EC81301826DC6CF05233C470A78CD05F10719F58CC6E3F3297A86F29F41AD3EC17CD07B81D2E0F42ECE10F90F47068AD225E39205BE9F62234D8217239254D1B149BC91A881BDDB713BD0D5A69215373CB4DD6C082ACCCCA37973FC3ED56486184B4060A08200000001C0404100800000000000000A000000000A5B0000000006CAC68C00000000000FFC3400122B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE87000000000015B8410180000000001100102BC29224A48EBA98852295B2000EE07CBE322503CDAC9E423DFABF5A0D307C790023A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95700AD01000000000080AB6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE870000000000752B8CC00117840000000000000B000A2B55A3F061D2BD8FF8D65643AE2ABBE481B612B32135818000000000110010596BBCBEEA49205A1E44895DBE34810E998C5E3041AF6F154F174B877E08AED0820023982201102713B619608C4602CDCB4820CA2E2D1F18F632E14AD1AFBAA2530B195DB7E4B3811037E26CE7057B4B90C7CC88E0AAFCCA551824239C51DACAEAD6938B56E218264880A418228110804CBDAB73C3F569F5A7D6141639C3A94F923C6D15109F3E7C7019472671261B4201103E15DCB850126C070EF6E513AA9CF6D1B624692F4610DFFC6BBF519754B99DE880A3A9108180884C8F3711CB0FA6009FD387BB18BDE3AE79C6F99FFBDA245A8868E3D84E4A1081B23E89D184700908064F254A89032F9618247C780B06332CA9547A783BBCC52EA95745930A100000000000000000000A000000000A5B00000000000FFC340000000006CAC68C1882C81E7E9B4BAD22DECC021DED07B2A04CF401246F21260608E6D0362BB89981E6F8F85DBFDA1385E5274CBE5B0D21DD45329DE7FB534805C11187456282EE6E000000000000000000000000000000000000000500000000000000000000409D4457D14079D3398F705B263163D4841D8DFB50EF8E8FFFD7D72208E512D884800915B5F5BB68136004E09C3125C27C7AB6D6AD24E794184C27336B36E63398DF4380000000000ADC2080C00000000008800815E1491252475D4C42914AD90007703E5F191281E6D64F211EFD5FAD06983E3C8011D48840C04426479B88E587D3004FE9C3DD8C5EF1D73CE37CCFFDED122D443471EC27250840D91F44E8C2380484032792A5448197CB0C123E3C0583199654AA3D3C1DDE629754AB8000800F00003FFFFFFFFFFC008259C04D32D731B196AED4DC91753BC131B113C3BF619A9D7EB7A3222B3F376B9000F80003FFFFFFFFFFB0020703B9CA4B1B9FB5BA2809C0CF5FB8330E29FB47C27EC40C671E9798BADAAD29580007FFFFFFFFFF62B6BEB76D026C009C138624B84F8F56DAD5A49CF2830984E66D66DCC6731BE8704510980054B800070B821EA2784D9FC32BFEA579D3C6136DF87E6A232947BCC9DF84BC5337794370FF9740C589C29EB6931134B44414343132B149099E5DCA288BFD526B3C4BC888F22EA7DBA63472300DFBF40588AAC14A954A91FA3A7B1E760B9408841FC33B9C2137CC172868DBF6A5B3EA430FF121D3E09DEE03446599B79B33A980BB57721744E873A8774D20F9A26B466939C91BB0C9EA31367A8526F223FC505F5FAE0796C28711C7C0D4FE45184A8A5D284BA86AF7A06C7FBD30717DAAEAE237EBD1E9FD91D93A03FDAE3B23B8A2C360D692276F38C518BA33A18899D9E8C74CFA7F814AA37B0C12942FBD3BC19F24B56C5AB1E72645A06C79737C154F451FA67410A9460000DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E000006DACA81388356E701486891E4AF013CE92D6A57F240AF81389E60197AC38A1B2C070C9DE04BFBD1F6B0FB31230D9CD49430D12BDC89524D7F86E1C586CCB2F47F1E06C8FA274611C02420193C952A240CBE586091F1E02C18CCB2A551E9E0EEF314BA060221323CDC472C3E98027F4E1EEC62F78EB9E71BE67FEF68916A21A38F613929E7E8606E1BCCB0FEBDE1C2692621783F368F36FCB3B4840C7F47FF0EFE011EDCE3F34E089065806CC7E04039166B7EEB7B6B2116CE357A36B7A1DCCD7793B242DFC518156DE366E5834D448D5CC7EE9F263D06CBC2B41138D1AC32000000000011442600152E0000B8FD31EE020001200000000000000002000007D0000000C8000000001B6B0B0000"
}
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 f23766b1e3..c670b63015 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, randomBytes32, Transaction(0, Seq.empty, Seq.empty, 0), Satoshi(42), "mutual")
val e5 = PaymentSent(MilliSatoshi(42000), MilliSatoshi(1000), randomBytes32, randomBytes32, randomBytes32, timestamp = 0)
val e6 = PaymentSent(MilliSatoshi(42000), MilliSatoshi(1000), randomBytes32, randomBytes32, randomBytes32, timestamp = Platform.currentTime * 2)
- val e7 = AvailableBalanceChanged(null, randomBytes32, ShortChannelId(500000, 42, 1), 456123000, ChannelStateSpec.commitments)
+ val e7 = AvailableBalanceChanged(null, randomBytes32, ShortChannelId(500000, 42, 1), 456123000, ChannelStateSpec.commitmentsV1)
val e8 = ChannelLifecycleEvent(randomBytes32, randomKey.publicKey, 456123000, true, false, "mutual")
db.add(e1)
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 d214f9f2b4..f0d9fd3879 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.VersionCommitmentV1
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo}
@@ -116,6 +117,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(hex"022c76692fd70814a8d1ed9dedc833318afaaed8188db4d14727e2e99bc619d325")
+ val delayed_payment_pubkey = Generators.derivePubKey(payment_basepoint, per_commitment_point)
}
val coinbaseTx = Transaction.read("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0100f2052a010000001976a9143ca33c2e4446f4a305f23c80df8ad1afdcf652f988ac00000000")
@@ -189,15 +191,16 @@ 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
- spec)
+ Remote.delayed_payment_pubkey,
+ spec, VersionCommitmentV1)
- 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)
}
- val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)
+ 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}")
@@ -219,11 +222,12 @@ 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
- spec)
+ Remote.delayed_payment_pubkey,
+ spec, VersionCommitmentV1)
- 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 = ${local_sig.dropRight(1).toHex}")
- 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: ${remote_sig.dropRight(1).toHex}")
}
@@ -237,7 +241,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, VersionCommitmentV1)
logger.info(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}")
val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index)
@@ -245,12 +249,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: ${remoteSig.dropRight(1).toHex}")
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: ${remoteSig.dropRight(1).toHex}")
@@ -259,8 +263,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)
@@ -269,8 +273,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 = ${localSig.dropRight(1).toHex}")
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..10e922fecc 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, _}
@@ -62,6 +63,10 @@ class TransactionsSpec extends FunSuite with Logging {
}
test("compute fees") {
+
+ val expectedSizeSimplifiedCommitment = weight2fee(simplifiedFeerateKw , simplifiedCommitWeight + 172 * 4)
+ val expectedSizeCommitmentV1 = Satoshi(5340)
+
// see BOLT #3 specs
val htlcs = Set(
DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(5000000).amount, ByteVector32.Zeroes, 552, ByteVector.empty)),
@@ -70,8 +75,9 @@ class TransactionsSpec extends FunSuite with Logging {
DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(800000).amount, ByteVector32.Zeroes, 551, ByteVector.empty))
)
val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocalMsat = 0, toRemoteMsat = 0)
- val fee = Transactions.commitTxFee(Satoshi(546), spec)
- assert(fee == Satoshi(5340))
+
+ assert(commitTxFee(Satoshi(546), spec, VersionSimplifiedCommitment) == expectedSizeSimplifiedCommitment)
+ assert(commitTxFee(Satoshi(546), spec, VersionCommitmentV1) == expectedSizeCommitmentV1)
}
test("check pre-computed transaction weights") {
@@ -91,7 +97,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, None, VersionCommitmentV1)
// we use dummy signatures to compute the weight
val weight = Transaction.weight(addSigs(claimP2WPKHOutputTx, localPaymentPriv.publicKey, randomBytes(73)).tx)
assert(claimP2WPKHOutputWeight == weight)
@@ -103,7 +109,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, VersionCommitmentV1)
// we use dummy signatures to compute the weight
val weight = Transaction.weight(addSigs(claimHtlcDelayedTx, randomBytes(73)).tx)
assert(claimHtlcDelayedWeight == weight)
@@ -115,7 +121,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, VersionCommitmentV1)
// we use dummy signatures to compute the weight
val weight = Transaction.weight(addSigs(mainPenaltyTx, randomBytes(73)).tx)
assert(mainPenaltyWeight == weight)
@@ -173,6 +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(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))
@@ -204,21 +211,26 @@ 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)
- val localSig = Transactions.sign(txinfo, localPaymentPriv)
- val remoteSig = Transactions.sign(txinfo, remotePaymentPriv)
+ 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)
+ }
+
{
- assert(getCommitTxNumber(commitTx.tx, true, localPaymentPriv.publicKey, remotePaymentPriv.publicKey) == commitTxNumber)
+ 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)
+
+ val (htlcTimeoutTxs, htlcSuccessTxs) = createHtlcTxs(VersionCommitmentV1)
assert(htlcTimeoutTxs.size == 2) // htlc1 and htlc3
assert(htlcSuccessTxs.size == 2) // htlc2 and htlc4
@@ -226,30 +238,55 @@ 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)
}
}
+ {
+ // Simplified commitment
+ 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 expectedToRemoteAmount = Satoshi(spec.toRemoteMsat / 1000)
+
+ // there must be 2 push-me outputs
+ 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))
+
+ // to_remote is delayed with to_self_delay
+ 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)
+ }
+
{
// local spends delayed output of htlc1 timeout tx
- val claimHtlcDelayed = makeClaimDelayedOutputTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)
- val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv)
+ 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)
+ 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
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 claimHtlcSuccessTx = makeClaimHtlcSuccessTx(commitTx().tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw)
+ val localSig = sign(claimHtlcSuccessTx, remoteHtlcPriv, SIGHASH_ALL)
val signed = addSigs(claimHtlcSuccessTx, localSig, paymentPreimage)
assert(checkSpendable(signed).isSuccess)
}
@@ -258,39 +295,39 @@ 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)
- val localSig = sign(claimHtlcDelayed, localDelayedPaymentPriv)
+ 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)
+ makeClaimDelayedOutputTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, VersionCommitmentV1)
}
}
{
// remote spends main output
- val claimP2WPKHOutputTx = makeClaimP2WPKHOutputTx(commitTx.tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw)
- val localSig = sign(claimP2WPKHOutputTx, remotePaymentPriv)
+ 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)
}
{
// 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 claimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx().tx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc2, feeratePerKw)
+ val localSig = sign(claimHtlcTimeoutTx, remoteHtlcPriv, SIGHASH_ALL)
val signed = addSigs(claimHtlcTimeoutTx, localSig)
assert(checkSpendable(signed).isSuccess)
}
@@ -298,8 +335,8 @@ 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 htlcPenaltyTx = makeHtlcPenaltyTx(commitTx().tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw)
+ val sig = sign(htlcPenaltyTx, localRevocationPriv, SIGHASH_ALL)
val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey)
assert(checkSpendable(signed).isSuccess)
}
@@ -307,19 +344,14 @@ 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 htlcPenaltyTx = makeHtlcPenaltyTx(commitTx().tx, outputsAlreadyUsed = Set.empty, script, localDustLimit, finalPubKeyScript, feeratePerKw)
+ val sig = sign(htlcPenaltyTx, localRevocationPriv, SIGHASH_ALL)
val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey)
assert(checkSpendable(signed).isSuccess)
}
}
- 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(ByteVector32.Zeroes, 0, amount.amount * 1000, ByteVector32.Zeroes, 144, ByteVector.empty))
@@ -362,7 +394,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, VersionCommitmentV1)
assert(fee === test.expectedFee)
})
}
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..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.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.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.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)
}
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
-