1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 14:22:39 +01:00

Electrum: do not persist transaction locks (#953)

Locks held on utxos that are used in unpublished funding transactions should not be persisted.
If the app is stopped before the funding transaction has been published the channel is forgotten
and so should be locks on its funding tx utxos.
This commit is contained in:
Fabrice Drouin 2019-04-17 19:10:14 +02:00 committed by GitHub
parent 9032da5326
commit 6afe28d147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 36 deletions

View file

@ -211,7 +211,7 @@ object SqliteWalletDb {
("history" | historyCodec) :: ("history" | historyCodec) ::
("proofs" | proofsCodec) :: ("proofs" | proofsCodec) ::
("pendingTransactions" | listOfN(uint16, txCodec)) :: ("pendingTransactions" | listOfN(uint16, txCodec)) ::
("locks" | setCodec(txCodec))).as[PersistentData] ("locks" | provide(Set.empty[Transaction]))).as[PersistentData]
def serialize(data: PersistentData): Array[Byte] = persistentDataCodec.encode(data).require.toByteArray def serialize(data: PersistentData): Array[Byte] = persistentDataCodec.encode(data).require.toByteArray

View file

@ -17,11 +17,16 @@
package fr.acinq.eclair.blockchain.electrum.db.sqlite package fr.acinq.eclair.blockchain.electrum.db.sqlite
import fr.acinq.bitcoin.{Block, BlockHeader, OutPoint, Satoshi, Transaction, TxIn, TxOut} import fr.acinq.bitcoin.{Block, BlockHeader, OutPoint, Satoshi, Transaction, TxIn, TxOut}
import fr.acinq.eclair.TestConstants import fr.acinq.eclair.{TestConstants, randomBytes, randomBytes32}
import fr.acinq.eclair.blockchain.electrum.ElectrumClient import fr.acinq.eclair.blockchain.electrum.ElectrumClient
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.GetMerkleResponse import fr.acinq.eclair.blockchain.electrum.ElectrumClient.GetMerkleResponse
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet.PersistentData import fr.acinq.eclair.blockchain.electrum.ElectrumWallet.PersistentData
import fr.acinq.eclair.blockchain.electrum.db.sqlite.SqliteWalletDb.version
import fr.acinq.eclair.wire.ChannelCodecs.txCodec
import org.scalatest.FunSuite import org.scalatest.FunSuite
import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs.{constant, listOfN, provide, uint16}
import scala.util.Random import scala.util.Random
@ -34,6 +39,36 @@ class SqliteWalletDbSpec extends FunSuite {
if (acc.size == n) acc else makeHeaders(n, acc :+ makeChildHeader(acc.last)) if (acc.size == n) acc else makeHeaders(n, acc :+ makeChildHeader(acc.last))
} }
def randomTransaction = Transaction(version = 2,
txIn = TxIn(OutPoint(randomBytes32, random.nextInt(100)), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
txOut = TxOut(Satoshi(random.nextInt(10000000)), randomBytes(20)) :: Nil,
0L
)
def randomHeight = if (random.nextBoolean()) random.nextInt(500000) else -1
def randomHistoryItem = ElectrumClient.TransactionHistoryItem(randomHeight, randomBytes32)
def randomHistoryItems = (0 to random.nextInt(100)).map(_ => randomHistoryItem).toList
def randomProof = GetMerkleResponse(randomBytes32, ((0 until 10).map(_ => randomBytes32)).toList, random.nextInt(100000), 0)
def randomPersistentData = {
val transactions = for (i <- 0 until random.nextInt(100)) yield randomTransaction
PersistentData(
accountKeysCount = 10,
changeKeysCount = 10,
status = (for (i <- 0 until random.nextInt(100)) yield randomBytes32 -> random.nextInt(100000).toHexString).toMap,
transactions = transactions.map(tx => tx.hash -> tx).toMap,
heights = transactions.map(tx => tx.hash -> randomHeight).toMap,
history = (for (i <- 0 until random.nextInt(100)) yield randomBytes32 -> randomHistoryItems).toMap,
proofs = (for (i <- 0 until random.nextInt(100)) yield randomBytes32 -> randomProof).toMap,
pendingTransactions = transactions.toList,
locks = (for (i <- 0 until random.nextInt(10)) yield randomTransaction).toSet
)
}
test("add/get/list headers") { test("add/get/list headers") {
val db = new SqliteWalletDb(TestConstants.sqliteInMemory()) val db = new SqliteWalletDb(TestConstants.sqliteInMemory())
val headers = makeHeaders(100) val headers = makeHeaders(100)
@ -59,46 +94,38 @@ class SqliteWalletDbSpec extends FunSuite {
test("serialize persistent data") { test("serialize persistent data") {
val db = new SqliteWalletDb(TestConstants.sqliteInMemory()) val db = new SqliteWalletDb(TestConstants.sqliteInMemory())
import fr.acinq.eclair.{randomBytes, randomBytes32}
def randomTransaction = Transaction(version = 2,
txIn = TxIn(OutPoint(randomBytes32, random.nextInt(100)), signatureScript = Nil, sequence = TxIn.SEQUENCE_FINAL) :: Nil,
txOut = TxOut(Satoshi(random.nextInt(10000000)), randomBytes(20)) :: Nil,
0L
)
def randomHeight = if (random.nextBoolean()) random.nextInt(500000) else -1
def randomHistoryItem = ElectrumClient.TransactionHistoryItem(randomHeight, randomBytes32)
def randomHistoryItems = (0 to random.nextInt(100)).map(_ => randomHistoryItem).toList
def randomProof = GetMerkleResponse(randomBytes32, ((0 until 10).map(_ => randomBytes32)).toList, random.nextInt(100000), 0)
def randomPersistentData = {
val transactions = for (i <- 0 until random.nextInt(100)) yield randomTransaction
PersistentData(
accountKeysCount = 10,
changeKeysCount = 10,
status = (for (i <- 0 until random.nextInt(100)) yield randomBytes32 -> random.nextInt(100000).toHexString).toMap,
transactions = transactions.map(tx => tx.hash -> tx).toMap,
heights = transactions.map(tx => tx.hash -> randomHeight).toMap,
history = (for (i <- 0 until random.nextInt(100)) yield randomBytes32 -> randomHistoryItems).toMap,
proofs = (for (i <- 0 until random.nextInt(100)) yield randomBytes32 -> randomProof).toMap,
pendingTransactions = transactions.toList,
locks = (for (i <- 0 until random.nextInt(10)) yield randomTransaction).toSet
)
}
assert(db.readPersistentData() == None) assert(db.readPersistentData() == None)
for (i <- 0 until 50) { for (i <- 0 until 50) {
val data = randomPersistentData val data = randomPersistentData
db.persist(data) db.persist(data)
val Some(check) = db.readPersistentData() val Some(check) = db.readPersistentData()
assert(check === data) assert(check === data.copy(locks = Set.empty[Transaction]))
}
}
test("read old persistent data") {
import scodec.codecs._
import SqliteWalletDb._
import fr.acinq.eclair.wire.ChannelCodecs._
val oldPersistentDataCodec: Codec[PersistentData] = (
("version" | constant(BitVector.fromInt(version))) ::
("accountKeysCount" | int32) ::
("changeKeysCount" | int32) ::
("status" | statusCodec) ::
("transactions" | transactionsCodec) ::
("heights" | heightsCodec) ::
("history" | historyCodec) ::
("proofs" | proofsCodec) ::
("pendingTransactions" | listOfN(uint16, txCodec)) ::
("locks" | setCodec(txCodec))).as[PersistentData]
for (i <- 0 until 50) {
val data = randomPersistentData
val encoded = oldPersistentDataCodec.encode(data).require
val decoded = persistentDataCodec.decode(encoded).require.value
assert(decoded === data.copy(locks = Set.empty[Transaction]))
} }
} }
} }