mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-13 19:37:35 +01:00
Merge branch 'master' into android
This commit is contained in:
commit
801aa11367
13 changed files with 186 additions and 72 deletions
|
@ -79,10 +79,10 @@
|
|||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz
|
||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.17.1/bitcoin-0.17.1-x86_64-linux-gnu.tar.gz
|
||||
</bitcoind.url>
|
||||
<bitcoind.md5>c371e383f024c6c45fb255d528a6beec</bitcoind.md5>
|
||||
<bitcoind.sha1>e6d8ab1f7661a6654fd81e236b9b5fd35a3d4dcb</bitcoind.sha1>
|
||||
<bitcoind.md5>724043999e2b5ed0c088e8db34f15d43</bitcoind.md5>
|
||||
<bitcoind.sha1>546ee35d4089c7ccc040a01cdff3362599b8bc53</bitcoind.sha1>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
|
@ -93,10 +93,10 @@
|
|||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-osx64.tar.gz
|
||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.17.1/bitcoin-0.17.1-osx64.tar.gz
|
||||
</bitcoind.url>
|
||||
<bitcoind.md5>bacd87d0c3f65a5acd666e33d094a59e</bitcoind.md5>
|
||||
<bitcoind.sha1>62cc5bd9ced610bb9e8d4a854396bfe2139e3d0f</bitcoind.sha1>
|
||||
<bitcoind.md5>b5a792c6142995faa42b768273a493bd</bitcoind.md5>
|
||||
<bitcoind.sha1>8bd51c7024d71de07df381055993e9f472013db8</bitcoind.sha1>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
|
@ -107,9 +107,9 @@
|
|||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-win64.zip</bitcoind.url>
|
||||
<bitcoind.md5>bbde9b1206956d19298034319e9f405e</bitcoind.md5>
|
||||
<bitcoind.sha1>85e3dc8a9c6f93b1b20cb79fa5850b5ce81da221</bitcoind.sha1>
|
||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.17.1/bitcoin-0.17.1-win64.zip</bitcoind.url>
|
||||
<bitcoind.md5>b0e824e9dd02580b5b01f073f3c89858</bitcoind.md5>
|
||||
<bitcoind.sha1>4e17bad7d08c465b444143a93cd6eb1c95076e3f</bitcoind.sha1>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -97,8 +97,6 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
|
||||
// this will be used to make sure the current commitment fee is up-to-date
|
||||
context.system.eventStream.subscribe(self, classOf[CurrentFeerates])
|
||||
// we need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network
|
||||
setTimer(TickRefreshChannelUpdate.toString, TickRefreshChannelUpdate, timeout = REFRESH_CHANNEL_UPDATE_INTERVAL, repeat = true)
|
||||
|
||||
/*
|
||||
8888888 888b 888 8888888 88888888888
|
||||
|
@ -201,9 +199,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
blockchain ! WatchSpent(self, data.commitments.commitInput.outPoint.txid, data.commitments.commitInput.outPoint.index.toInt, data.commitments.commitInput.txOut.publicKeyScript, BITCOIN_FUNDING_SPENT)
|
||||
blockchain ! WatchLost(self, data.commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_LOST)
|
||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, normal.channelUpdate.shortChannelId))
|
||||
// we rebuild a channel_update for two reasons:
|
||||
// - we want to reload values from configuration
|
||||
// - if eclair was previously killed, it might not have had time to publish a channel_update with enable=false
|
||||
// we rebuild a new channel_update with values from the configuration because they may have changed while eclair was down
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, normal.channelUpdate.shortChannelId, nodeParams.expiryDeltaBlocks,
|
||||
normal.commitments.remoteParams.htlcMinimumMsat, normal.channelUpdate.feeBaseMsat, normal.channelUpdate.feeProportionalMillionths, normal.commitments.localCommit.spec.totalFunds, enable = false)
|
||||
|
||||
|
@ -820,7 +816,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, shortChannelId))
|
||||
} else None
|
||||
// we use GOTO instead of stay because we want to fire transitions
|
||||
goto(NORMAL) using manualTransition(d, store(d.copy(shortChannelId = shortChannelId, buried = true, channelUpdate = channelUpdate))) sending localAnnSigs_opt.toSeq
|
||||
goto(NORMAL) using manualTransition(NORMAL, NORMAL, d, store(d.copy(shortChannelId = shortChannelId, buried = true, channelUpdate = channelUpdate))) sending localAnnSigs_opt.toSeq
|
||||
|
||||
case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_NORMAL) if d.commitments.announceChannel =>
|
||||
// channels are publicly announced if both parties want it (defined as feature bit)
|
||||
|
@ -835,7 +831,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
import d.commitments.{localParams, remoteParams}
|
||||
val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature)
|
||||
// we use GOTO instead of stay because we want to fire transitions
|
||||
goto(NORMAL) using manualTransition(d, store(d.copy(channelAnnouncement = Some(channelAnn))))
|
||||
goto(NORMAL) using manualTransition(NORMAL, NORMAL, d, store(d.copy(channelAnnouncement = Some(channelAnn))))
|
||||
case Some(_) =>
|
||||
// they have sent their announcement sigs, but we already have a valid channel announcement
|
||||
// this can happen if our announcement_signatures was lost during a disconnection
|
||||
|
@ -858,7 +854,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
log.info(s"sending channel_update announcement (refresh)")
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments))
|
||||
// we use GOTO instead of stay because we want to fire transitions
|
||||
goto(NORMAL) using manualTransition(d, store(d.copy(channelUpdate = channelUpdate)))
|
||||
goto(NORMAL) using manualTransition(NORMAL, NORMAL, d, store(d.copy(channelUpdate = channelUpdate)))
|
||||
|
||||
case Event(CMD_UPDATE_RELAY_FEE(feeBaseMsat, feeProportionalMillionths), d: DATA_NORMAL) =>
|
||||
log.info(s"updating relay fees: prevFeeBaseMsat={} nextFeeBaseMsat={} prevFeeProportionalMillionths={} nextFeeProportionalMillionths={}", d.channelUpdate.feeBaseMsat, feeBaseMsat, d.channelUpdate.feeProportionalMillionths, feeProportionalMillionths)
|
||||
|
@ -879,6 +875,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
d.commitments.localChanges.proposed.collect {
|
||||
case add: UpdateAddHtlc => relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, ChannelUnavailable(d.channelId), d.commitments.originChannels(add.id), Some(channelUpdate), None))
|
||||
}
|
||||
// disable the channel_update refresh timer
|
||||
cancelTimer(TickRefreshChannelUpdate.toString)
|
||||
goto(OFFLINE) using d.copy(channelUpdate = channelUpdate)
|
||||
|
||||
case Event(e: Error, d: DATA_NORMAL) => handleRemoteError(e, d)
|
||||
|
@ -1453,7 +1451,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
}
|
||||
}
|
||||
// re-enable the channel
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments))
|
||||
val timestamp = Platform.currentTime / 1000 match {
|
||||
case ts if ts == d.channelUpdate.timestamp => ts + 1 // corner case: in case of quick reconnection, we bump the timestamp of the new channel_update, otherwise it will get ignored by the network
|
||||
case ts => ts
|
||||
}
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = Helpers.aboveReserve(d.commitments), timestamp = timestamp)
|
||||
// we will refresh need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network
|
||||
setTimer(TickRefreshChannelUpdate.toString, TickRefreshChannelUpdate, timeout = REFRESH_CHANNEL_UPDATE_INTERVAL, repeat = true)
|
||||
|
||||
goto(NORMAL) using d.copy(commitments = commitments1, channelUpdate = channelUpdate)
|
||||
}
|
||||
|
@ -1559,7 +1563,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
// we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream
|
||||
case Event(CurrentFeerates(_), _) => stay
|
||||
|
||||
// we only care about this event in NORMAL state, and the scheduler is never disabled
|
||||
// we only care about this event in NORMAL state, and the scheduler is not cancelled in some cases (e.g. NORMAL->CLOSING)
|
||||
case Event(TickRefreshChannelUpdate, _) => stay
|
||||
|
||||
// we receive this when we send command to ourselves
|
||||
|
@ -1579,7 +1583,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
/**
|
||||
* This is needed because of a bug in akka where onTransition event are not fired for same-state transition
|
||||
*/
|
||||
def manualTransition(stateData: Data, nextStateData: Data): Data = {
|
||||
def manualTransition(state: channel.State, nextState: channel.State, stateData: Data, nextStateData: Data): Data = {
|
||||
// if channel is private, we send the channel_update directly to remote
|
||||
// they need it "to learn the other end's forwarding parameters" (BOLT 7)
|
||||
(stateData, nextStateData) match {
|
||||
|
@ -1592,15 +1596,19 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case _ => ()
|
||||
}
|
||||
|
||||
(stateData, nextStateData) match {
|
||||
case (d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate == d2.channelUpdate && d1.channelAnnouncement == d2.channelAnnouncement =>
|
||||
(state, nextState, stateData, nextStateData) match {
|
||||
// ORDER MATTERS!
|
||||
case (_, _, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate == d2.channelUpdate && d1.channelAnnouncement == d2.channelAnnouncement =>
|
||||
// don't do anything if neither the channel_update nor the channel_announcement didn't change
|
||||
()
|
||||
case (_, normal: DATA_NORMAL) =>
|
||||
// whenever we go to a state with NORMAL data (can be OFFLINE or NORMAL), we send out the new channel_update (most of the time it will just be to enable/disable the channel)
|
||||
case (WAIT_FOR_FUNDING_LOCKED | NORMAL | SYNCING, NORMAL | OFFLINE, _, normal: DATA_NORMAL) =>
|
||||
// when we do WAIT_FOR_FUNDING_LOCKED->NORMAL or NORMAL->NORMAL or SYNCING->NORMAL or NORMAL->OFFLINE, we send out the new channel_update (most of the time it will just be to enable/disable the channel)
|
||||
context.system.eventStream.publish(LocalChannelUpdate(self, normal.commitments.channelId, normal.shortChannelId, normal.commitments.remoteParams.nodeId, normal.channelAnnouncement, normal.channelUpdate, normal.commitments))
|
||||
case (normal: DATA_NORMAL, _) =>
|
||||
// when we finally leave the NORMAL state (or OFFLINE with NORMAL data) to got to SHUTDOWN/NEGOTIATING/CLOSING/ERR*, we advertise the fact that channel can't be used for payments anymore
|
||||
case (_, _, _: DATA_NORMAL, _: DATA_NORMAL) =>
|
||||
// in any other case (e.g. WAIT_FOR_INIT_INTERNAL->OFFLINE) we do nothing
|
||||
()
|
||||
case (_, _, normal: DATA_NORMAL, _) =>
|
||||
// when we finally leave the NORMAL state (or OFFLINE with NORMAL data) to go to SHUTDOWN/NEGOTIATING/CLOSING/ERR*, we advertise the fact that channel can't be used for payments anymore
|
||||
// if the channel is private we don't really need to tell the counterparty because it is already aware that the channel is being closed
|
||||
context.system.eventStream.publish(LocalChannelDown(self, normal.commitments.channelId, normal.shortChannelId, normal.commitments.remoteParams.nodeId))
|
||||
case _ => ()
|
||||
|
@ -1624,7 +1632,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
cancelTimer(RevocationTimeout.toString)
|
||||
}
|
||||
|
||||
manualTransition(stateData, nextStateData)
|
||||
manualTransition(state, nextState, stateData, nextStateData)
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -304,7 +304,7 @@ object Graph {
|
|||
|
||||
// NB we're guaranteed to have weightRatios and factors > 0
|
||||
val factor = (cltvFactor * wr.cltvDeltaFactor) + (ageFactor * wr.ageFactor) + (capFactor * wr.capacityFactor)
|
||||
val edgeWeight = if (isNeighborTarget) prev.weight else edgeCost * factor
|
||||
val edgeWeight = if (isNeighborTarget) prev.weight else prev.weight + edgeCost * factor
|
||||
|
||||
RichWeight(cost = edgeCost, length = prev.length + 1, cltv = prev.cltv + channelCltvDelta, weight = edgeWeight)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
@ -142,10 +182,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") {
|
||||
|
@ -178,7 +217,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)
|
||||
|
@ -213,14 +253,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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -366,4 +366,42 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
channelUpdateListener.expectNoMsg(300 millis)
|
||||
}
|
||||
|
||||
test("bump timestamp in case of quick reconnection") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
|
||||
// we simulate a disconnection
|
||||
sender.send(alice, INPUT_DISCONNECTED)
|
||||
sender.send(bob, INPUT_DISCONNECTED)
|
||||
awaitCond(alice.stateName == OFFLINE)
|
||||
awaitCond(bob.stateName == OFFLINE)
|
||||
|
||||
// alice and bob announce that their channel is OFFLINE
|
||||
val channelUpdate_alice_disabled = channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate
|
||||
val channelUpdate_bob_disabled = channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate
|
||||
assert(Announcements.isEnabled(channelUpdate_alice_disabled.channelFlags) == false)
|
||||
assert(Announcements.isEnabled(channelUpdate_bob_disabled.channelFlags) == false)
|
||||
|
||||
// we immediately reconnect them
|
||||
sender.send(alice, INPUT_RECONNECTED(alice2bob.ref, aliceInit, bobInit))
|
||||
sender.send(bob, INPUT_RECONNECTED(bob2alice.ref, bobInit, aliceInit))
|
||||
|
||||
// peers exchange channel_reestablish messages
|
||||
alice2bob.expectMsgType[ChannelReestablish]
|
||||
bob2alice.expectMsgType[ChannelReestablish]
|
||||
alice2bob.forward(bob)
|
||||
bob2alice.forward(alice)
|
||||
|
||||
// both nodes reach NORMAL state, and broadcast a new channel_update
|
||||
val channelUpdate_alice_enabled = channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate
|
||||
val channelUpdate_bob_enabled = channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate
|
||||
assert(Announcements.isEnabled(channelUpdate_alice_enabled.channelFlags) == true)
|
||||
assert(Announcements.isEnabled(channelUpdate_bob_enabled.channelFlags) == true)
|
||||
|
||||
// let's check that the two successive channel_update have a different timestamp
|
||||
assert(channelUpdate_alice_enabled.timestamp > channelUpdate_alice_disabled.timestamp)
|
||||
assert(channelUpdate_bob_enabled.timestamp > channelUpdate_bob_disabled.timestamp)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -837,6 +837,36 @@ class RouteCalculationSpec extends FunSuite {
|
|||
|
||||
assert(hops2Nodes(routeScoreOptimized) === (a, e) :: (e, f) :: (f, d) :: Nil)
|
||||
}
|
||||
|
||||
test("cost function is monotonic") {
|
||||
|
||||
// This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target,
|
||||
// then if the cost function is not monotonic the path-finding breaks because the result path contains a loop.
|
||||
val updates = List(
|
||||
ChannelDesc(ShortChannelId("565643x1216x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")) -> ChannelUpdate(ByteVector32.Zeroes.bytes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, 1.toByte, 1.toByte, 144, htlcMinimumMsat = 0, feeBaseMsat = 1000, 100, Some(15000000000L)),
|
||||
ChannelDesc(ShortChannelId("565643x1216x0"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector32.Zeroes.bytes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, 1.toByte, 0.toByte, 14, htlcMinimumMsat = 1, 1000, 10, Some(4294967295L)),
|
||||
ChannelDesc(ShortChannelId("542280x2156x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")) -> ChannelUpdate(ByteVector32.Zeroes.bytes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, 1.toByte, 1.toByte, 144, htlcMinimumMsat = 1000, feeBaseMsat = 1000, 100, Some(16777000000L)),
|
||||
ChannelDesc(ShortChannelId("542280x2156x0"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector32.Zeroes.bytes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, 1.toByte, 0.toByte, 144, htlcMinimumMsat = 1, 667, 1, Some(16777000000L)),
|
||||
ChannelDesc(ShortChannelId("565779x2711x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")) -> ChannelUpdate(ByteVector32.Zeroes.bytes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, 1.toByte, 3.toByte, 144, htlcMinimumMsat = 1, 1000, 100, Some(230000000L)),
|
||||
ChannelDesc(ShortChannelId("565779x2711x0"), PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector32.Zeroes.bytes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, 1.toByte, 0.toByte, 144, htlcMinimumMsat = 1, 1000, 100, Some(230000000L))
|
||||
).toMap
|
||||
|
||||
val g = DirectedGraph.makeGraph(updates)
|
||||
|
||||
val params = RouteParams(randomize = false, maxFeeBaseMsat = 21000, maxFeePct = 0.03, routeMaxCltv = 1008, routeMaxLength = 6, ratios = Some(
|
||||
WeightRatios(cltvDeltaFactor = 0.15, ageFactor = 0.35, capacityFactor = 0.5)
|
||||
))
|
||||
val thisNode = PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")
|
||||
val targetNode = PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")
|
||||
val amount = 351000
|
||||
|
||||
Globals.blockCount.set(567634) // simulate mainnet block for heuristic
|
||||
val Success(route) = Router.findRoute(g, thisNode, targetNode, amount, 1, Set.empty, Set.empty, params)
|
||||
|
||||
assert(route.size == 2)
|
||||
assert(route.last.nextNodeId == targetNode)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object RouteCalculationSpec {
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -214,6 +214,7 @@
|
|||
<systemProperties>
|
||||
<buildDirectory>${project.build.directory}</buildDirectory>
|
||||
</systemProperties>
|
||||
<argLine>-Xmx1024m</argLine>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Add table
Reference in a new issue