mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-20 02:27:32 +01:00
Test preimage extraction from mempool (#1387)
Instead of waiting for htlc-success txs to be confirmed, eclair also looks at mempool txs to detect preimages as soon as possible. This has been the case for a very long time, but our integration tests didn't showcase this correctly. Refactored common watcher test helpers and added tests to ZmqWatcherSpec.
This commit is contained in:
parent
22d476774d
commit
3809b023c0
@ -16,28 +16,96 @@
|
||||
|
||||
package fr.acinq.eclair.blockchain
|
||||
|
||||
import fr.acinq.bitcoin.Transaction
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import akka.actor.{ActorRef, ActorSystem}
|
||||
import akka.testkit.TestProbe
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.bitcoin.{Base58, OutPoint, SIGHASH_ALL, Script, ScriptFlags, ScriptWitness, SigVersion, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.eclair.LongToBtcAmount
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
|
||||
import org.json4s.JsonAST.{JString, JValue}
|
||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||
|
||||
/**
|
||||
* Created by PM on 27/01/2017.
|
||||
*/
|
||||
* Created by PM on 27/01/2017.
|
||||
*/
|
||||
|
||||
class WatcherSpec extends AnyFunSuite {
|
||||
class WatcherSpec extends AnyFunSuiteLike {
|
||||
|
||||
test("extract pay2wpkh pubkey script") {
|
||||
val commitTx = Transaction.read("020000000001010ba75314a116c1e585d1454d079598c5f00edc8a21ebd9e4f3b64e5c318ff2a30100000000e832a680012e850100000000001600147d2a3fc37dba8e946e0238d7eeb6fb602be658200400473044022010d4f249861bb9828ddfd2cda91dc10b8f8ffd0f15c8a4a85a2d373d52f5e0ff02205356242878121676e3e823ceb3dc075d18fed015053badc8f8d754b8959a9178014730440220521002cf241311facf541b689e7229977bfceffa0e4ded785b4e6197af80bfa202204a168d1f7ee59c73ae09c3e0a854b20262b9969fe4ed69b15796dca3ea286582014752210365375134360808be0b4756ba8a2995488310ac4c69571f2b600aaba3ec6cc2d32103a0d9c18794f16dfe01d6d6716bcd1e97ecff2f39451ec48e1899af40f20a18bc52aec3dd9520")
|
||||
val claimMainTx = Transaction.read("020000000001012537488e9d066a8f3550cc9adc141a11668425e046e69e07f53bb831f3296cbf00000000000000000001bf8401000000000017a9143f398d81d3c42367b779ea869c7dd3b6826fbb7487024730440220477b961f6360ef6cb62a76898dcecbb130627c7e6a452646e3be601f04627c1f02202572313d0c0afecbfb0c7d0e47ba689427a54f3debaded6d406daa1f5da4918c01210291ed78158810ad867465377f5920036ea865a29b3a39a1b1808d0c3c351a4b4100000000")
|
||||
|
||||
assert(commitTx.txOut.head.publicKeyScript === WatchConfirmed.extractPublicKeyScript(claimMainTx.txIn.head.witness))
|
||||
}
|
||||
|
||||
test("extract pay2wsh pubkey script") {
|
||||
val commitTx = Transaction.read("02000000000101fb98507ff5f47bcc5b4497a145e631f68b2b5fcf2752598bc54c8f33696e1c73000000000017f15b80015b3f0f0000000000220020345fc26988f6252d9d93ee95f2198e820db1a4d7c7ec557e4cc5d7e60750cc21040047304402202fd9cbc8446a10193f378269bf12d321aa972743c0a011089aff522de2a1414d02204dd65bf43e41fe911c7180e5e036d609646a798fa5c3f288ede73679978df36b01483045022100fced8966c2527cb175521c4eb41aaaee96838420fa5fce3d4730c0da37f6253502202dc9667530a9f79bc6444b54335467d2043c4b996da5fbca7496e0fa64ccc1bd0147522103a16c06d8626bad5d6d8ea8fee980c287590b9dedeb5857a3d0cd6c4b4e95631c2103d872e26e43f723523d2d8eff5f93a1b344fe51eb76bcfd4906315ae2fe35389a52ae620acc20")
|
||||
val claimMainDelayedTx = Transaction.read("02000000000101b285ffeb84c366f621fe33b6ff77a9b7578075b65e69c363d12c35aa422d98fd00000000009000000001e03e0f000000000017a9147407522166f1ed3030788b1b6a48803867d1797f8703483045022100fe9eefd010a80411ccae87590db3f54c1c04605170bdcd83c1e04222d474ef41022036db7fd3c07c0523c2cf72d80c7fe3bdc2d5028a8bc2864b478a707e8af627dc01004d63210298f7dada89d882c4ab971e7e914f4953249bad70333b29aa504bb67e5ce9239c67029000b275210328170f7e781c70ea679efc30383d3e03451ca350e2a8690f8ed3db9dabb3866768ac00000000")
|
||||
|
||||
assert(commitTx.txOut.head.publicKeyScript === WatchConfirmed.extractPublicKeyScript(claimMainDelayedTx.txIn.head.witness))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object WatcherSpec {
|
||||
|
||||
/**
|
||||
* Create a new address and dumps its private key.
|
||||
*/
|
||||
def getNewAddress(bitcoincli: ActorRef)(implicit system: ActorSystem): (String, PrivateKey) = {
|
||||
val probe = TestProbe()
|
||||
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||
val JString(address) = probe.expectMsgType[JValue]
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("dumpprivkey", address))
|
||||
val JString(wif) = probe.expectMsgType[JValue]
|
||||
val (priv, true) = PrivateKey.fromBase58(wif, Base58.Prefix.SecretKeyTestnet)
|
||||
(address, priv)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to a given address, without generating blocks to confirm.
|
||||
*
|
||||
* @return the corresponding transaction.
|
||||
*/
|
||||
def sendToAddress(bitcoincli: ActorRef, address: String, amount: Double)(implicit system: ActorSystem): Transaction = {
|
||||
val probe = TestProbe()
|
||||
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, amount))
|
||||
val JString(txid) = probe.expectMsgType[JValue]
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("getrawtransaction", txid))
|
||||
val JString(hex) = probe.expectMsgType[JValue]
|
||||
Transaction.read(hex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a chain of unspent txs.
|
||||
*
|
||||
* @param tx tx that sends funds to a p2wpkh of priv
|
||||
* @param priv private key that tx sends funds to
|
||||
* @return a (tx1, tx2) tuple where tx2 spends tx1 which spends tx
|
||||
*/
|
||||
def createUnspentTxChain(tx: Transaction, priv: PrivateKey): (Transaction, Transaction) = {
|
||||
// tx sends funds to our key
|
||||
val pub = priv.publicKey
|
||||
val outputIndex = tx.txOut.indexWhere(_.publicKeyScript == Script.write(Script.pay2wpkh(pub)))
|
||||
|
||||
val fee = 10000 sat
|
||||
// tx1 spends tx
|
||||
val tx1 = {
|
||||
val tmp = Transaction(version = 2, txIn = TxIn(OutPoint(tx, outputIndex), Nil, TxIn.SEQUENCE_FINAL) :: Nil, txOut = TxOut(tx.txOut(outputIndex).amount - fee, Script.pay2wpkh(pub)) :: Nil, lockTime = 0)
|
||||
val sig = Transaction.signInput(tmp, 0, Script.pay2pkh(pub), SIGHASH_ALL, tx.txOut(outputIndex).amount, SigVersion.SIGVERSION_WITNESS_V0, priv)
|
||||
val tmp1 = tmp.updateWitness(0, ScriptWitness(sig :: pub.value :: Nil))
|
||||
Transaction.correctlySpends(tmp1, tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
tmp1
|
||||
}
|
||||
// and tx2 spends tx1
|
||||
val tx2 = {
|
||||
val tmp = Transaction(version = 2, txIn = TxIn(OutPoint(tx1, 0), Nil, TxIn.SEQUENCE_FINAL) :: Nil, txOut = TxOut(tx1.txOut.head.amount - fee, Script.pay2wpkh(pub)) :: Nil, lockTime = 0)
|
||||
val sig = Transaction.signInput(tmp, 0, Script.pay2pkh(pub), SIGHASH_ALL, tx1.txOut.head.amount, SigVersion.SIGVERSION_WITNESS_V0, priv)
|
||||
val tmp1 = tmp.updateWitness(0, ScriptWitness(sig :: pub.value :: Nil))
|
||||
Transaction.correctlySpends(tmp1, tx1 :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
tmp1
|
||||
}
|
||||
(tx1, tx2)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.typesafe.config.ConfigFactory
|
||||
import fr.acinq.bitcoin.{Block, ByteVector32, 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.BitcoindService.BitcoinReq
|
||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, JsonRPCError}
|
||||
import fr.acinq.eclair.transactions.Scripts
|
||||
import fr.acinq.eclair.{LongToBtcAmount, addressToPublicKeyScript, randomKey}
|
||||
|
@ -17,7 +17,7 @@
|
||||
package fr.acinq.eclair.blockchain.bitcoind
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.{Files, StandardCopyOption}
|
||||
import java.nio.file.Files
|
||||
import java.util.UUID
|
||||
|
||||
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
|
||||
@ -28,7 +28,7 @@ import fr.acinq.eclair.TestUtils
|
||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinJsonRPCClient}
|
||||
import fr.acinq.eclair.integration.IntegrationSpec
|
||||
import grizzled.slf4j.Logging
|
||||
import org.json4s.JsonAST.{JArray, JDecimal, JInt, JString, JValue}
|
||||
import org.json4s.JsonAST._
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
@ -38,7 +38,7 @@ trait BitcoindService extends Logging {
|
||||
self: TestKitBase =>
|
||||
|
||||
implicit val system: ActorSystem
|
||||
implicit val sttpBackend = OkHttpFutureBackend()
|
||||
implicit val sttpBackend = OkHttpFutureBackend()
|
||||
|
||||
val bitcoindPort: Int = TestUtils.availablePort
|
||||
|
||||
@ -48,6 +48,8 @@ trait BitcoindService extends Logging {
|
||||
|
||||
val bitcoindZmqTxPort: Int = TestUtils.availablePort
|
||||
|
||||
import BitcoindService._
|
||||
|
||||
import scala.sys.process._
|
||||
|
||||
val INTEGRATION_TMP_DIR = new File(TestUtils.BUILD_DIRECTORY, s"integration-${UUID.randomUUID()}")
|
||||
@ -60,17 +62,15 @@ trait BitcoindService extends Logging {
|
||||
var bitcoinrpcclient: BitcoinJsonRPCClient = null
|
||||
var bitcoincli: ActorRef = null
|
||||
|
||||
case class BitcoinReq(method: String, params: Any*)
|
||||
|
||||
def startBitcoind(): Unit = {
|
||||
Files.createDirectories(PATH_BITCOIND_DATADIR.toPath)
|
||||
if (!Files.exists(new File(PATH_BITCOIND_DATADIR.toString, "bitcoin.conf").toPath)) {
|
||||
val is = classOf[IntegrationSpec].getResourceAsStream("/integration/bitcoin.conf")
|
||||
val conf = Source.fromInputStream(is).mkString
|
||||
.replace("28333", bitcoindPort.toString)
|
||||
.replace("28332", bitcoindRpcPort.toString)
|
||||
.replace("28334", bitcoindZmqBlockPort.toString)
|
||||
.replace("28335", bitcoindZmqTxPort.toString)
|
||||
.replace("28333", bitcoindPort.toString)
|
||||
.replace("28332", bitcoindRpcPort.toString)
|
||||
.replace("28334", bitcoindZmqBlockPort.toString)
|
||||
.replace("28335", bitcoindZmqTxPort.toString)
|
||||
Files.writeString(new File(PATH_BITCOIND_DATADIR.toString, "bitcoin.conf").toPath, conf)
|
||||
}
|
||||
|
||||
@ -129,4 +129,10 @@ trait BitcoindService extends Logging {
|
||||
assert(blocks.size == blockCount)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object BitcoindService {
|
||||
|
||||
case class BitcoinReq(method: String, params: Any*)
|
||||
|
||||
}
|
@ -22,17 +22,17 @@ import akka.pattern.pipe
|
||||
import akka.testkit.{TestKit, TestProbe}
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import fr.acinq.bitcoin.Transaction
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
|
||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, ExtendedBitcoinClient}
|
||||
import grizzled.slf4j.Logging
|
||||
import org.json4s.DefaultFormats
|
||||
import org.json4s.JsonAST.{JString, _}
|
||||
import org.json4s.JsonAST._
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
|
||||
class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with BitcoindService with AnyFunSuiteLike with BeforeAndAfterAll with Logging {
|
||||
|
||||
val commonConfig = ConfigFactory.parseMap(Map(
|
||||
@ -107,7 +107,7 @@ class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with Bitcoi
|
||||
signedTx
|
||||
}
|
||||
bitcoinClient.invoke("sendrawtransaction", spendingTx).pipeTo(sender.ref)
|
||||
val JString(spendingTxid) = sender.expectMsgType[JValue]
|
||||
sender.expectMsgType[JValue]
|
||||
|
||||
// and publish the tx a fourth time to test idempotence
|
||||
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||
|
@ -16,22 +16,59 @@
|
||||
|
||||
package fr.acinq.eclair.blockchain.bitcoind
|
||||
|
||||
import fr.acinq.bitcoin.OutPoint
|
||||
import fr.acinq.eclair.blockchain.{Watch, WatchConfirmed, WatchSpent, WatchSpentBasic}
|
||||
import fr.acinq.eclair.channel.BITCOIN_FUNDING_SPENT
|
||||
import fr.acinq.eclair.randomBytes32
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class ZmqWatcherSpec extends AnyFunSuite {
|
||||
import akka.Done
|
||||
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||
import akka.testkit.{TestKit, TestProbe}
|
||||
import fr.acinq.bitcoin.{OutPoint, Script}
|
||||
import fr.acinq.eclair.blockchain.WatcherSpec._
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
|
||||
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
|
||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.ExtendedBitcoinClient
|
||||
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor
|
||||
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, BITCOIN_FUNDING_SPENT}
|
||||
import fr.acinq.eclair.randomBytes32
|
||||
import grizzled.slf4j.Logging
|
||||
import org.json4s.JsonAST.JValue
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||
|
||||
import scala.concurrent.Promise
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class ZmqWatcherSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteLike with BitcoindService with BeforeAndAfterAll with Logging {
|
||||
|
||||
var zmqBlock: ActorRef = _
|
||||
var zmqTx: ActorRef = _
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
logger.info("starting bitcoind")
|
||||
startBitcoind()
|
||||
waitForBitcoindReady()
|
||||
logger.info("starting zmq actors")
|
||||
val (zmqBlockConnected, zmqTxConnected) = (Promise[Done](), Promise[Done]())
|
||||
zmqBlock = system.actorOf(Props(new ZMQActor(s"tcp://127.0.0.1:$bitcoindZmqBlockPort", Some(zmqBlockConnected))))
|
||||
zmqTx = system.actorOf(Props(new ZMQActor(s"tcp://127.0.0.1:$bitcoindZmqTxPort", Some(zmqTxConnected))))
|
||||
awaitCond(zmqBlockConnected.isCompleted && zmqTxConnected.isCompleted)
|
||||
super.beforeAll()
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
logger.info("stopping zmq actors")
|
||||
system.stop(zmqBlock)
|
||||
system.stop(zmqTx)
|
||||
logger.info("stopping bitcoind")
|
||||
stopBitcoind()
|
||||
super.afterAll()
|
||||
TestKit.shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
test("add/remove watches from/to utxo map") {
|
||||
import ZmqWatcher._
|
||||
|
||||
val m0 = Map.empty[OutPoint, Set[Watch]]
|
||||
|
||||
val txid = randomBytes32
|
||||
val outputIndex = 42
|
||||
|
||||
val utxo = OutPoint(txid.reverse, outputIndex)
|
||||
|
||||
val w1 = WatchSpent(null, txid, outputIndex, randomBytes32, BITCOIN_FUNDING_SPENT)
|
||||
@ -63,5 +100,55 @@ class ZmqWatcherSpec extends AnyFunSuite {
|
||||
assert(m10.isEmpty)
|
||||
}
|
||||
|
||||
test("watch for confirmed transactions") {
|
||||
val probe = TestProbe()
|
||||
val blockCount = new AtomicLong()
|
||||
val watcher = system.actorOf(ZmqWatcher.props(blockCount, new ExtendedBitcoinClient(bitcoinrpcclient)))
|
||||
val (address, _) = getNewAddress(bitcoincli)
|
||||
val tx = sendToAddress(bitcoincli, address, 1.0)
|
||||
|
||||
val listener = TestProbe()
|
||||
probe.send(watcher, WatchConfirmed(listener.ref, tx.txid, tx.txOut.head.publicKeyScript, 4, BITCOIN_FUNDING_DEPTHOK))
|
||||
probe.send(watcher, WatchConfirmed(listener.ref, tx.txid, tx.txOut.head.publicKeyScript, 4, BITCOIN_FUNDING_DEPTHOK)) // setting the watch multiple times should be a no-op
|
||||
generateBlocks(bitcoincli, 5)
|
||||
assert(listener.expectMsgType[WatchEventConfirmed].tx.txid === tx.txid)
|
||||
listener.expectNoMsg(1 second)
|
||||
|
||||
// If we try to watch a transaction that has already been confirmed, we should immediately receive a WatchEventConfirmed.
|
||||
probe.send(watcher, WatchConfirmed(listener.ref, tx.txid, tx.txOut.head.publicKeyScript, 4, BITCOIN_FUNDING_DEPTHOK))
|
||||
assert(listener.expectMsgType[WatchEventConfirmed].tx.txid === tx.txid)
|
||||
listener.expectNoMsg(1 second)
|
||||
system.stop(watcher)
|
||||
}
|
||||
|
||||
test("watch for spent transactions") {
|
||||
val probe = TestProbe()
|
||||
val blockCount = new AtomicLong()
|
||||
val watcher = system.actorOf(ZmqWatcher.props(blockCount, new ExtendedBitcoinClient(bitcoinrpcclient)))
|
||||
val (address, priv) = getNewAddress(bitcoincli)
|
||||
val tx = sendToAddress(bitcoincli, address, 1.0)
|
||||
val outputIndex = tx.txOut.indexWhere(_.publicKeyScript == Script.write(Script.pay2wpkh(priv.publicKey)))
|
||||
val (tx1, tx2) = createUnspentTxChain(tx, priv)
|
||||
|
||||
val listener = TestProbe()
|
||||
probe.send(watcher, WatchSpent(listener.ref, tx, outputIndex, BITCOIN_FUNDING_SPENT))
|
||||
listener.expectNoMsg(1 second)
|
||||
probe.send(bitcoincli, BitcoinReq("sendrawtransaction", tx1.toString()))
|
||||
probe.expectMsgType[JValue]
|
||||
// tx and tx1 aren't confirmed yet, but we trigger the WatchEventSpent when we see tx1 in the mempool.
|
||||
listener.expectMsg(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx1))
|
||||
// Let's confirm tx and tx1: seeing tx1 in a block should trigger WatchEventSpent again.
|
||||
generateBlocks(bitcoincli, 2)
|
||||
listener.expectMsg(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx1))
|
||||
|
||||
// Let's submit tx2, and set a watch after it has been confirmed this time.
|
||||
probe.send(bitcoincli, BitcoinReq("sendrawtransaction", tx2.toString()))
|
||||
probe.expectMsgType[JValue]
|
||||
listener.expectNoMsg(1 second)
|
||||
generateBlocks(bitcoincli, 1)
|
||||
probe.send(watcher, WatchSpent(listener.ref, tx1, 0, BITCOIN_FUNDING_SPENT))
|
||||
listener.expectMsg(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx2))
|
||||
system.stop(watcher)
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,7 @@ import com.whisk.docker.DockerReadyChecker
|
||||
import fr.acinq.bitcoin.{Block, Btc, ByteVector32, DeterministicWallet, MnemonicCode, OutPoint, Satoshi, Script, ScriptFlags, ScriptWitness, SigVersion, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.eclair.LongToBtcAmount
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.{FundTransactionResponse, SignTransactionResponse}
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
|
||||
import fr.acinq.eclair.blockchain.bitcoind.{BitcoinCoreWallet, BitcoindService}
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{BroadcastTransaction, BroadcastTransactionResponse, SSL}
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClientPool.ElectrumServerAddress
|
||||
|
@ -21,16 +21,17 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
import akka.actor.{ActorSystem, Props}
|
||||
import akka.testkit.{TestKit, TestProbe}
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.bitcoin.{Base58, Bech32, ByteVector32, OutPoint, SIGHASH_ALL, Script, ScriptFlags, ScriptWitness, SigVersion, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.bitcoin.{ByteVector32, OutPoint, SIGHASH_ALL, Script, ScriptFlags, ScriptWitness, SigVersion, Transaction, TxIn, TxOut}
|
||||
import fr.acinq.eclair.blockchain.WatcherSpec._
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.SSL
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClientPool.ElectrumServerAddress
|
||||
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, BITCOIN_FUNDING_SPENT}
|
||||
import fr.acinq.eclair.{LongToBtcAmount, randomBytes32}
|
||||
import grizzled.slf4j.Logging
|
||||
import org.json4s.JsonAST.{JString, JValue}
|
||||
import org.json4s.JsonAST.JValue
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||
import scodec.bits._
|
||||
@ -61,21 +62,14 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteL
|
||||
val electrumClient = system.actorOf(Props(new ElectrumClientPool(blockCount, Set(electrumAddress))))
|
||||
val watcher = system.actorOf(Props(new ElectrumWatcher(blockCount, electrumClient)))
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||
val JString(address) = probe.expectMsgType[JValue]
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
|
||||
val JString(txid) = probe.expectMsgType[JValue](3000 seconds)
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("getrawtransaction", txid))
|
||||
val JString(hex) = probe.expectMsgType[JValue]
|
||||
val tx = Transaction.read(hex)
|
||||
val (address, _) = getNewAddress(bitcoincli)
|
||||
val tx = sendToAddress(bitcoincli, address, 1.0)
|
||||
|
||||
val listener = TestProbe()
|
||||
probe.send(watcher, WatchConfirmed(listener.ref, tx.txid, tx.txOut(0).publicKeyScript, 4, BITCOIN_FUNDING_DEPTHOK))
|
||||
generateBlocks(bitcoincli, 5)
|
||||
val confirmed = listener.expectMsgType[WatchEventConfirmed](20 seconds)
|
||||
assert(confirmed.tx.txid.toHex === txid)
|
||||
assert(confirmed.tx.txid === tx.txid)
|
||||
system.stop(watcher)
|
||||
}
|
||||
|
||||
@ -85,19 +79,8 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteL
|
||||
val electrumClient = system.actorOf(Props(new ElectrumClientPool(blockCount, Set(electrumAddress))))
|
||||
val watcher = system.actorOf(Props(new ElectrumWatcher(blockCount, electrumClient)))
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||
val JString(address) = probe.expectMsgType[JValue]
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("dumpprivkey", address))
|
||||
val JString(wif) = probe.expectMsgType[JValue]
|
||||
val (priv, true) = PrivateKey.fromBase58(wif, Base58.Prefix.SecretKeyTestnet)
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
|
||||
val JString(txid) = probe.expectMsgType[JValue](30 seconds)
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("getrawtransaction", txid))
|
||||
val JString(hex) = probe.expectMsgType[JValue]
|
||||
val tx = Transaction.read(hex)
|
||||
val (address, priv) = getNewAddress(bitcoincli)
|
||||
val tx = sendToAddress(bitcoincli, address, 1.0)
|
||||
|
||||
// find the output for the address we generated and create a tx that spends it
|
||||
val pos = tx.txOut.indexWhere(_.publicKeyScript == Script.write(Script.pay2wpkh(priv.publicKey)))
|
||||
@ -123,65 +106,22 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteL
|
||||
system.stop(watcher)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a chain of unspent txs
|
||||
*
|
||||
* @param tx tx that sends funds to a p2wpkh of priv
|
||||
* @param priv private key that tx sends funds to
|
||||
* @return a (tx1, tx2) tuple where tx2 spends tx1 which spends tx
|
||||
*/
|
||||
def createUnspentTxChain(tx: Transaction, priv: PrivateKey): (Transaction, Transaction) = {
|
||||
// tx sends funds to our key
|
||||
val pub = priv.publicKey
|
||||
val outputIndex = tx.txOut.indexWhere(_.publicKeyScript == Script.write(Script.pay2wpkh(pub)))
|
||||
|
||||
val fee = 10000 sat
|
||||
val tx1 = {
|
||||
val tmp = Transaction(version = 2, txIn = TxIn(OutPoint(tx, outputIndex), Nil, TxIn.SEQUENCE_FINAL) :: Nil, txOut = TxOut(tx.txOut(outputIndex).amount - fee, Script.pay2wpkh(pub)) :: Nil, lockTime = 0)
|
||||
val sig = Transaction.signInput(tmp, 0, Script.pay2pkh(pub), SIGHASH_ALL, tx.txOut(outputIndex).amount, SigVersion.SIGVERSION_WITNESS_V0, priv)
|
||||
val tmp1 = tmp.updateWitness(0, ScriptWitness(sig :: pub.value :: Nil))
|
||||
Transaction.correctlySpends(tmp1, tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
tmp1
|
||||
}
|
||||
// tx1 spends tx
|
||||
|
||||
val tx2 = {
|
||||
val tmp = Transaction(version = 2, txIn = TxIn(OutPoint(tx1, 0), Nil, TxIn.SEQUENCE_FINAL) :: Nil, txOut = TxOut(tx1.txOut(0).amount - fee, Script.pay2wpkh(pub)) :: Nil, lockTime = 0)
|
||||
val sig = Transaction.signInput(tmp, 0, Script.pay2pkh(pub), SIGHASH_ALL, tx1.txOut(0).amount, SigVersion.SIGVERSION_WITNESS_V0, priv)
|
||||
val tmp1 = tmp.updateWitness(0, ScriptWitness(sig :: pub.value :: Nil))
|
||||
Transaction.correctlySpends(tmp1, tx1 :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
tmp1
|
||||
}
|
||||
// and tx2 spends tx1
|
||||
(tx1, tx2)
|
||||
}
|
||||
|
||||
test("watch for mempool transactions (txs in mempool before we set the watch)") {
|
||||
val probe = TestProbe()
|
||||
val blockCount = new AtomicLong()
|
||||
val electrumClient = system.actorOf(Props(new ElectrumClientPool(blockCount, Set(electrumAddress))))
|
||||
probe.send(electrumClient, ElectrumClient.AddStatusListener(probe.ref))
|
||||
probe.expectMsgType[ElectrumClient.ElectrumReady]
|
||||
|
||||
val watcher = system.actorOf(Props(new ElectrumWatcher(blockCount, electrumClient)))
|
||||
|
||||
val priv = PrivateKey(ByteVector32.fromValidHex("01" * 32))
|
||||
val pub = priv.publicKey
|
||||
val address = Bech32.encodeWitnessAddress("bcrt", 0, pub.hash160)
|
||||
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
|
||||
val JString(txid) = probe.expectMsgType[JValue](3000 seconds)
|
||||
probe.send(bitcoincli, BitcoinReq("getrawtransaction", txid))
|
||||
val JString(hex) = probe.expectMsgType[JValue]
|
||||
val tx = Transaction.read(hex)
|
||||
|
||||
val (address, priv) = getNewAddress(bitcoincli)
|
||||
val tx = sendToAddress(bitcoincli, address, 1.0)
|
||||
val (tx1, tx2) = createUnspentTxChain(tx, priv)
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("sendrawtransaction", tx1.toString()))
|
||||
probe.expectMsgType[JValue]
|
||||
probe.send(bitcoincli, BitcoinReq("sendrawtransaction", tx2.toString()))
|
||||
probe.expectMsgType[JValue]
|
||||
|
||||
|
||||
// wait until tx1 and tx2 are in the mempool (as seen by our ElectrumX server)
|
||||
awaitCond({
|
||||
probe.send(electrumClient, ElectrumClient.GetScriptHashHistory(ElectrumClient.computeScriptHash(tx2.txOut(0).publicKeyScript)))
|
||||
@ -205,21 +145,13 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteL
|
||||
probe.expectMsgType[ElectrumClient.ElectrumReady]
|
||||
val watcher = system.actorOf(Props(new ElectrumWatcher(blockCount, electrumClient)))
|
||||
|
||||
val priv = PrivateKey(ByteVector32.fromValidHex("01" * 32))
|
||||
val pub = priv.publicKey
|
||||
val address = Bech32.encodeWitnessAddress("bcrt", 0, pub.hash160)
|
||||
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
|
||||
val JString(txid) = probe.expectMsgType[JValue](3000 seconds)
|
||||
probe.send(bitcoincli, BitcoinReq("getrawtransaction", txid))
|
||||
val JString(hex) = probe.expectMsgType[JValue]
|
||||
val tx = Transaction.read(hex)
|
||||
|
||||
val (address, priv) = getNewAddress(bitcoincli)
|
||||
val tx = sendToAddress(bitcoincli, address, 1.0)
|
||||
val (tx1, tx2) = createUnspentTxChain(tx, priv)
|
||||
|
||||
// here we set the watch * before * we publish our transactions
|
||||
val listener = TestProbe()
|
||||
probe.send(watcher, WatchConfirmed(listener.ref, tx2.txid, tx2.txOut(0).publicKeyScript, 0, BITCOIN_FUNDING_DEPTHOK))
|
||||
|
||||
probe.send(bitcoincli, BitcoinReq("sendrawtransaction", tx1.toString()))
|
||||
probe.expectMsgType[JValue]
|
||||
probe.send(bitcoincli, BitcoinReq("sendrawtransaction", tx2.toString()))
|
||||
@ -247,7 +179,6 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with AnyFunSuiteL
|
||||
val mainnetAddress = ElectrumServerAddress(new InetSocketAddress("electrum.acinq.co", 50002), SSL.STRICT)
|
||||
val electrumClient = system.actorOf(Props(new ElectrumClientPool(blockCount, Set(mainnetAddress))))
|
||||
val watcher = system.actorOf(Props(new ElectrumWatcher(blockCount, electrumClient)))
|
||||
//Thread.sleep(10000)
|
||||
val probe = TestProbe()
|
||||
|
||||
{
|
||||
|
@ -26,6 +26,7 @@ import com.typesafe.config.{Config, ConfigFactory}
|
||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||
import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, Block, ByteVector32, Crypto, OP_0, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi, Script, ScriptFlags, Transaction}
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
|
||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq
|
||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.ExtendedBitcoinClient
|
||||
import fr.acinq.eclair.blockchain.{Watch, WatchConfirmed}
|
||||
import fr.acinq.eclair.channel.Channel.{BroadcastChannelUpdate, PeriodicRefresh}
|
||||
@ -886,16 +887,15 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||
}, max = 20 seconds, interval = 1 second)
|
||||
// we then fulfill the htlc, which will make F redeem it on-chain
|
||||
sender.send(nodes("F1").register, Forward(htlc.channelId, CMD_FULFILL_HTLC(htlc.id, preimage)))
|
||||
// we then generate one block so that the htlc success tx gets written to the blockchain
|
||||
generateBlocks(bitcoincli, 1)
|
||||
// C will extract the preimage from the blockchain and fulfill the payment upstream
|
||||
paymentSender.expectMsgType[PaymentSent](30 seconds)
|
||||
// at this point F should have 1 recv transactions: the redeemed htlc
|
||||
awaitCond({
|
||||
sender.send(bitcoincli, BitcoinReq("listreceivedbyaddress", 0))
|
||||
val res = sender.expectMsgType[JValue](10 seconds)
|
||||
res.filter(_ \ "address" == JString(finalAddressF)).flatMap(_ \ "txids" \\ classOf[JString]).size == 1
|
||||
}, max = 30 seconds, interval = 1 second)
|
||||
// we don't need to generate blocks to confirm the htlc-success; C should extract the preimage as soon as it enters
|
||||
// the mempool and fulfill the payment upstream.
|
||||
paymentSender.expectMsgType[PaymentSent](30 seconds)
|
||||
// we then generate enough blocks so that C gets its main delayed output
|
||||
generateBlocks(bitcoincli, 145)
|
||||
// and C will have its main output
|
||||
@ -937,7 +937,6 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||
val paymentSender = TestProbe()
|
||||
paymentSender.send(nodes("A").paymentInitiator, paymentReq)
|
||||
paymentSender.expectMsgType[UUID](30 seconds)
|
||||
|
||||
// F gets the htlc
|
||||
val htlc = htlcReceiver.expectMsgType[IncomingPacket.FinalPacket].add
|
||||
// now that we have the channel id, we retrieve channels default final addresses
|
||||
@ -964,14 +963,13 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||
sender.expectMsg(ChannelCommandResponse.Ok)
|
||||
// we then fulfill the htlc (it won't be sent to C, and will be used to pull funds on-chain)
|
||||
sender.send(nodes("F2").register, Forward(htlc.channelId, CMD_FULFILL_HTLC(htlc.id, preimage)))
|
||||
// we then generate one block so that the htlc success tx gets written to the blockchain
|
||||
sender.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||
val JString(address) = sender.expectMsgType[JValue]
|
||||
generateBlocks(bitcoincli, 1, Some(address))
|
||||
// C will extract the preimage from the blockchain and fulfill the payment upstream
|
||||
// we don't need to generate blocks to confirm the htlc-success; C should extract the preimage as soon as it enters
|
||||
// the mempool and fulfill the payment upstream.
|
||||
paymentSender.expectMsgType[PaymentSent](30 seconds)
|
||||
// at this point F should have 1 recv transactions: the redeemed htlc
|
||||
// we then generate enough blocks so that F gets its htlc-success delayed output
|
||||
sender.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||
val JString(address) = sender.expectMsgType[JValue]
|
||||
generateBlocks(bitcoincli, 145, Some(address))
|
||||
// at this point F should have 1 recv transactions: the redeemed htlc
|
||||
awaitCond({
|
||||
|
Loading…
Reference in New Issue
Block a user