diff --git a/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala b/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala index 022ce70..7a0f922 100644 --- a/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala +++ b/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala @@ -108,12 +108,7 @@ class LedgerPostgresDataHandler @Inject() ( transactions .map(_.inputs) .flatMap { inputs => - inputs.flatMap { input => - for { - address <- input.address - value <- input.value - } yield address -> value - } + inputs.map { input => input.address -> input.value } } .groupBy(_._1) .mapValues { list => list.map(_._2).sum } diff --git a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala index cda4dc1..8732792 100644 --- a/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala +++ b/server/app/com/xsn/explorer/data/anorm/dao/TransactionPostgresDAO.scala @@ -188,20 +188,24 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi SQL( """ |INSERT INTO transaction_inputs - | (txid, index, value, address) + | (txid, index, from_txid, from_output_index, value, address) |VALUES - | ({txid}, {index}, {value}, {address}) + | ({txid}, {index}, {from_txid}, {from_output_index}, {value}, {address}) |ON CONFLICT (txid, index) DO UPDATE | SET value = EXCLUDED.value, - | address = EXCLUDED.address - |RETURNING index, value, address + | address = EXCLUDED.address, + | from_txid = EXCLUDED.from_txid, + | from_output_index = EXCLUDED.from_output_index + |RETURNING txid, index, from_txid, from_output_index, value, address """.stripMargin ).on( 'txid -> transactionId.string, 'index -> input.index, + 'from_txid -> input.fromTxid.string, + 'from_output_index -> input.fromOutputIndex, 'value -> input.value, - 'address -> input.address.map(_.string) - ).as(parseTransactionInput.singleOpt) + 'address -> input.address.string + ).as(parseTransactionInput.singleOpt).flatten } private def upsertOutputs( @@ -228,21 +232,23 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi SQL( """ |INSERT INTO transaction_outputs - | (txid, index, value, address, tpos_owner_address, tpos_merchant_address) + | (txid, index, value, address, hex_script, tpos_owner_address, tpos_merchant_address) |VALUES - | ({txid}, {index}, {value}, {address}, {tpos_owner_address}, {tpos_merchant_address}) + | ({txid}, {index}, {value}, {address}, {hex_script}, {tpos_owner_address}, {tpos_merchant_address}) |ON CONFLICT (txid, index) DO UPDATE | SET value = EXCLUDED.value, | address = EXCLUDED.address, + | hex_script = EXCLUDED.hex_script, | tpos_owner_address = EXCLUDED.tpos_owner_address, | tpos_merchant_address = EXCLUDED.tpos_merchant_address - |RETURNING index, value, address, tpos_owner_address, tpos_merchant_address + |RETURNING txid, index, value, address, hex_script, tpos_owner_address, tpos_merchant_address """.stripMargin ).on( 'txid -> transactionId.string, 'index -> output.index, 'value -> output.value, 'address -> output.address.string, + 'hex_script -> output.script.string, 'tpos_owner_address -> output.tposOwnerAddress.map(_.string), 'tpos_merchant_address -> output.tposMerchantAddress.map(_.string) ).as(parseTransactionOutput.singleOpt).flatten @@ -253,11 +259,11 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi """ |DELETE FROM transaction_inputs |WHERE txid = {txid} - |RETURNING index, value, address + |RETURNING txid, index, from_txid, from_output_index, value, address """.stripMargin ).on( 'txid -> txid.string - ).as(parseTransactionInput.*) + ).as(parseTransactionInput.*).flatten } private def deleteOutputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Output] = { @@ -265,7 +271,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi """ |DELETE FROM transaction_outputs |WHERE txid = {txid} - |RETURNING index, value, address, tpos_owner_address, tpos_merchant_address + |RETURNING txid, index, hex_script, value, address, tpos_owner_address, tpos_merchant_address """.stripMargin ).on( 'txid -> txid.string diff --git a/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala b/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala index 8622c04..f6eebbb 100644 --- a/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala +++ b/server/app/com/xsn/explorer/data/anorm/parsers/TransactionParsers.scala @@ -2,19 +2,22 @@ package com.xsn.explorer.data.anorm.parsers import anorm.SqlParser.{get, str} import anorm.~ -import com.xsn.explorer.models.{Address, Transaction, TransactionId, TransactionWithValues} +import com.xsn.explorer.models._ object TransactionParsers { import CommonParsers._ val parseTransactionId = str("txid").map(TransactionId.from) + val parseFromTxid = str("from_txid").map(TransactionId.from) + val parseFromOutputIndex = get[Int]("from_output_index") val parseReceived = get[BigDecimal]("received") val parseSpent = get[BigDecimal]("spent") val parseSent = get[BigDecimal]("sent") val parseIndex = get[Int]("index") val parseValue = get[BigDecimal]("value") + val parseHexString = get[String]("hex_script").map(HexString.from) val parseTposOwnerAddress = str("tpos_owner_address").map(Address.from) val parseTposMerchantAddress = str("tpos_merchant_address").map(Address.from) @@ -42,19 +45,24 @@ object TransactionParsers { } yield TransactionWithValues(txid, blockhash, time, size, sent, received) } - val parseTransactionInput = (parseIndex ~ parseValue.? ~ parseAddress.?).map { case index ~ value ~ address => - Transaction.Input(index, value, address.flatten) - } + val parseTransactionInput = (parseFromTxid ~ parseFromOutputIndex ~ parseIndex ~ parseValue ~ parseAddress) + .map { case fromTxidMaybe ~ fromOutputIndex ~ index ~ value ~ addressMaybe => + for { + from <- fromTxidMaybe + address <- addressMaybe + } yield Transaction.Input(from, fromOutputIndex, index, value, address) + } val parseTransactionOutput = ( parseIndex ~ parseValue ~ parseAddress ~ + parseHexString ~ parseTposOwnerAddress.? ~ parseTposMerchantAddress.?).map { - case index ~ value ~ addressMaybe ~ tposOwnerAddress ~ tposMerchantAddress => - for (address <- addressMaybe) - yield Transaction.Output(index, value, address, tposOwnerAddress.flatten, tposMerchantAddress.flatten) + case index ~ value ~ addressMaybe ~ scriptMaybe ~ tposOwnerAddress ~ tposMerchantAddress => + for (address <- addressMaybe; script <- scriptMaybe) + yield Transaction.Output(index, value, address, script, tposOwnerAddress.flatten, tposMerchantAddress.flatten) } } diff --git a/server/app/com/xsn/explorer/models/Transaction.scala b/server/app/com/xsn/explorer/models/Transaction.scala index 3f55441..710a5e1 100644 --- a/server/app/com/xsn/explorer/models/Transaction.scala +++ b/server/app/com/xsn/explorer/models/Transaction.scala @@ -10,15 +10,21 @@ case class Transaction( object Transaction { + /** + * The coins where generated on the given output index of the given txid (from). + */ case class Input( + fromTxid: TransactionId, + fromOutputIndex: Int, index: Int, - value: Option[BigDecimal], - address: Option[Address]) + value: BigDecimal, + address: Address) case class Output( index: Int, value: BigDecimal, address: Address, + script: HexString, tposOwnerAddress: Option[Address], tposMerchantAddress: Option[Address]) @@ -29,15 +35,20 @@ object Transaction { * the utxo index or the getTransaction method from the TransactionService.. */ def fromRPC(tx: rpc.Transaction): Transaction = { - val inputs = tx.vin.zipWithIndex.map { case (vin, index) => - Transaction.Input(index, vin.value, vin.address) + val inputs = tx.vin.zipWithIndex.flatMap { case (vin, index) => + for { + value <- vin.value + address <- vin.address + } yield Transaction.Input(vin.txid, vin.voutIndex, index, value, address) } val outputs = tx.vout.flatMap { vout => val tposAddresses = vout.scriptPubKey.flatMap(_.getTPoSAddresses) + val scriptMaybe = vout.scriptPubKey.map(_.hex) for { address <- vout.address - } yield Transaction.Output(vout.n, vout.value, address, tposAddresses.map(_._1), tposAddresses.map(_._2)) + script <- scriptMaybe + } yield Transaction.Output(vout.n, vout.value, address, script, tposAddresses.map(_._1), tposAddresses.map(_._2)) } Transaction( diff --git a/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala b/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala index 5b84b34..34591a2 100644 --- a/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala +++ b/server/app/com/xsn/explorer/models/rpc/ScriptPubKey.scala @@ -1,12 +1,13 @@ package com.xsn.explorer.models.rpc -import com.xsn.explorer.models.Address +import com.xsn.explorer.models.{Address, HexString} import play.api.libs.functional.syntax._ import play.api.libs.json.{Reads, __} case class ScriptPubKey( `type`: String, asm: String, + hex: HexString, addresses: List[Address]) { /** @@ -27,7 +28,7 @@ case class ScriptPubKey( .filter(_.size >= 4) // relax size check .map(_.toList) .flatMap { - case op :: owner :: merchant :: commission if op == "OP_RETURN" => + case op :: owner :: merchant :: _ if op == "OP_RETURN" => for { ownerAddress <- Address.fromHex(owner) merchantAddress <- Address.fromHex(merchant) @@ -40,13 +41,16 @@ case class ScriptPubKey( object ScriptPubKey { - implicit val reads: Reads[ScriptPubKey] = { + implicit val reads: Reads[Option[ScriptPubKey]] = { val builder = (__ \ 'type).read[String] and (__ \ 'asm).read[String] and + (__ \ 'hex).read[String].map(HexString.from) and (__ \ 'addresses).readNullable[List[Address]].map(_ getOrElse List.empty) - builder.apply { (t, asm, addresses) => - ScriptPubKey(t, asm, addresses) + builder.apply { (t, asm, hexString, addresses) => + for { + hex <- hexString + } yield ScriptPubKey(t, asm = asm, hex = hex, addresses = addresses) } } } diff --git a/server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala b/server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala index b6aa095..bc1dc70 100644 --- a/server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala +++ b/server/app/com/xsn/explorer/models/rpc/TransactionVOUT.scala @@ -17,7 +17,7 @@ object TransactionVOUT { implicit val reads: Reads[TransactionVOUT] = { val builder = (__ \ 'value).read[BigDecimal] and (__ \ 'n).read[Int] and - (__ \ 'scriptPubKey).readNullable[ScriptPubKey] + (__ \ 'scriptPubKey).read[Option[ScriptPubKey]] builder.apply { (value, n, script) => TransactionVOUT(value, n, script) diff --git a/server/app/com/xsn/explorer/util/Extensions.scala b/server/app/com/xsn/explorer/util/Extensions.scala index 703f10f..e8a5c31 100644 --- a/server/app/com/xsn/explorer/util/Extensions.scala +++ b/server/app/com/xsn/explorer/util/Extensions.scala @@ -14,6 +14,10 @@ object Extensions { def fromSatoshis: BigDecimal = { inner / SatoshiScale } + + def toSatoshis: BigInt = { + (inner * SatoshiScale).toBigInt() + } } implicit class ListOptionExt[+A](val inner: List[Option[A]]) extends AnyVal { diff --git a/server/conf/evolutions/default/4.sql b/server/conf/evolutions/default/4.sql index d02338c..c740ac4 100644 --- a/server/conf/evolutions/default/4.sql +++ b/server/conf/evolutions/default/4.sql @@ -14,15 +14,18 @@ CREATE TABLE transactions( CREATE INDEX transactions_blockhash_index ON transactions USING BTREE (blockhash); CREATE INDEX transactions_time_index ON transactions USING BTREE (time); - +-- TODO: it might be worth to add a unique constraint based for (from_txid, from_output_index) CREATE TABLE transaction_inputs( txid TXID_TYPE NOT NULL, index NON_NEGATIVE_INT_TYPE NOT NULL, + from_txid TXID_TYPE NOT NULL, + from_output_index NON_NEGATIVE_INT_TYPE NOT NULL, value DECIMAL(30, 15) NULL, address ADDRESS_TYPE NULL, -- constraints CONSTRAINT transaction_inputs_txid_index_pk PRIMARY KEY (txid, index), - CONSTRAINT transaction_inputs_txid_fk FOREIGN KEY (txid) REFERENCES transactions (txid) + CONSTRAINT transaction_inputs_txid_fk FOREIGN KEY (txid) REFERENCES transactions (txid), + CONSTRAINT transaction_inputs_from_txid_fk FOREIGN KEY (from_txid) REFERENCES transactions (txid) ); CREATE INDEX transaction_inputs_address_index ON transaction_inputs USING BTREE (address); @@ -32,16 +35,19 @@ CREATE TABLE transaction_outputs( txid TXID_TYPE NOT NULL, index NON_NEGATIVE_INT_TYPE NOT NULL, value DECIMAL(30, 15) NOT NULL, - address ADDRESS_TYPE NULL, + address ADDRESS_TYPE NOT NULL, + hex_script TEXT NOT NULL, + spent_on TXID_TYPE NULL, tpos_owner_address ADDRESS_TYPE NULL, tpos_merchant_address ADDRESS_TYPE NULL, -- constraints CONSTRAINT transaction_outputs_txid_index_pk PRIMARY KEY (txid, index), - CONSTRAINT transaction_outputs_txid_fk FOREIGN KEY (txid) REFERENCES transactions (txid) + CONSTRAINT transaction_outputs_txid_fk FOREIGN KEY (txid) REFERENCES transactions (txid), + CONSTRAINT transaction_outputs_spent_on_fk FOREIGN KEY (spent_on) REFERENCES transactions (txid) ON DELETE SET NULL ); CREATE INDEX transaction_outputs_address_index ON transaction_outputs USING BTREE (address); - +CREATE INDEX transaction_outputs_spent_on ON transaction_outputs USING BTREE (spent_on) WHERE spent_on IS NULL; # --- !Downs diff --git a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala index 39034ce..2ffa6f2 100644 --- a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala @@ -38,17 +38,26 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be tposContract = None ) + val dummyTransaction = Transaction( + createTransactionId("ad9320dcea2fdaa357aac6eab00695cf07b487e34113598909f625c24629c981"), + block.hash, + 12312312L, + Size(1000), + List.empty, + List.empty + ) + val inputs = List( - Transaction.Input(0, None, None), - Transaction.Input(1, Some(BigDecimal(100)), Some(createAddress("XxQ7j37LfuXgsLd5DZAwFKhT3s2ZMkW85F"))) + Transaction.Input(dummyTransaction.id, 0, 1, BigDecimal(100), createAddress("XxQ7j37LfuXgsLd5DZAwFKhT3s2ZMkW85F")) ) val outputs = List( - Transaction.Output(0, BigDecimal(50), createAddress("XxQ7j37LfuXgsLd5DZAwFKhT3s2ZMkW85F"), None, None), + Transaction.Output(0, BigDecimal(50), createAddress("XxQ7j37LfuXgsLd5DZAwFKhT3s2ZMkW85F"), HexString.from("00").get, None, None), Transaction.Output( 1, BigDecimal(150), createAddress("Xbh5pJdBNm8J9PxnEmwVcuQKRmZZ7DkpcF"), + HexString.from("00").get, Some(createAddress("XfAATXtkRgCdMTrj2fxHvLsKLLmqAjhEAt")), Some(createAddress("XjfNeGJhLgW3egmsZqdbpCNGfysPs7jTNm"))) ) @@ -61,7 +70,7 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be inputs, outputs) - before { + private def prepareBlock(block: Block) = { val dao = new BlockPostgresDAO try { database.withConnection { implicit conn => @@ -73,6 +82,20 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be } } + private def prepareTransaction(transaction: Transaction) = { + try { + dataHandler.upsert(transaction) + } catch { + case _ => () + } + } + + before { + clearDatabase() + prepareBlock(block) + prepareTransaction(dummyTransaction) + } + "upsert" should { "add a new transaction" in { val result = dataHandler.upsert(transaction) @@ -109,26 +132,25 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be dataHandler.upsert(transaction).isGood mustEqual true val result = dataHandler.deleteBy(transaction.blockhash) - println(result) result.isGood mustEqual true - result.get mustEqual List(transaction) + result.get.contains(transaction) mustEqual true } } "getBy address" should { val address = createAddress("XxQ7j37LfuXgsLd5DZAwFKhT3s2ZMkW86F") val inputs = List( - Transaction.Input(0, None, None), - Transaction.Input(1, Some(BigDecimal(100)), Some(address)), - Transaction.Input(2, Some(BigDecimal(200)), Some(createAddress("XxQ7j37LfuXgsLD5DZAwFKhT3s2ZMkW86F"))) + Transaction.Input(dummyTransaction.id, 0, 1, 100, address), + Transaction.Input(dummyTransaction.id, 0, 2, 200, createAddress("XxQ7j37LfuXgsLD5DZAwFKhT3s2ZMkW86F")) ) val outputs = List( - Transaction.Output(0, BigDecimal(50), address, None, None), + Transaction.Output(0, BigDecimal(50), address, HexString.from("00").get, None, None), Transaction.Output( 1, BigDecimal(250), createAddress("Xbh5pJdBNm8J9PxnEmwVcuQKRmZZ7DkpcF"), + HexString.from("00").get, None, None) ) diff --git a/server/test/com/xsn/explorer/helpers/DataHelper.scala b/server/test/com/xsn/explorer/helpers/DataHelper.scala index 91daca0..5f6acd2 100644 --- a/server/test/com/xsn/explorer/helpers/DataHelper.scala +++ b/server/test/com/xsn/explorer/helpers/DataHelper.scala @@ -1,6 +1,5 @@ package com.xsn.explorer.helpers -import com.xsn.explorer.models.rpc.{ScriptPubKey, TransactionVOUT} import com.xsn.explorer.models.{Address, Blockhash, TransactionId} object DataHelper { @@ -10,19 +9,4 @@ object DataHelper { def createBlockhash(string: String) = Blockhash.from(string).get def createTransactionId(string: String) = TransactionId.from(string).get - - def createTransactionVOUT(n: Int, value: BigDecimal, scriptPubKey: ScriptPubKey) = { - TransactionVOUT( - n = n, - value = value, - scriptPubKey = Some(scriptPubKey)) - } - - def createScriptPubKey(scriptType: String, address: Address) = { - ScriptPubKey(scriptType, "", List(address)) - } - - def createScriptPubKey(scriptType: String, asm: String, address: Option[Address] = None) = { - ScriptPubKey(scriptType, asm, address.toList) - } } diff --git a/server/test/com/xsn/explorer/models/rpc/ScriptPubKeySpec.scala b/server/test/com/xsn/explorer/models/rpc/ScriptPubKeySpec.scala index b06ac11..3971eba 100644 --- a/server/test/com/xsn/explorer/models/rpc/ScriptPubKeySpec.scala +++ b/server/test/com/xsn/explorer/models/rpc/ScriptPubKeySpec.scala @@ -1,14 +1,16 @@ package com.xsn.explorer.models.rpc -import com.xsn.explorer.models.Address +import com.xsn.explorer.models.{Address, HexString} import org.scalatest.{MustMatchers, OptionValues, WordSpec} class ScriptPubKeySpec extends WordSpec with MustMatchers with OptionValues { + private val dummyScript = HexString.from("00").get + "getTPoSAddresses" should { "parse the addresses" in { - val script = ScriptPubKey("nulldata", "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99", List.empty) + val script = ScriptPubKey("nulldata", "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99", dummyScript, List.empty) val expected = ( Address.from("Xi3sQfMQsy2CzMZTrnKW6HFGp1VqFThdLw").get, Address.from("XyJC8xnfFrHNcMinh6gxuPRYY9HCaY9DAo").get) @@ -18,35 +20,35 @@ class ScriptPubKeySpec extends WordSpec with MustMatchers with OptionValues { } "support more than 4 values if we have the addresses" in { - val script = ScriptPubKey("nulldata", "OP_RETURN 586a55587938507a55464d78534c37594135767866574a587365746b354d5638676f 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99 1f60a6a385a4e5163ffef65dd873f17452bb0d9f89da701ffcc5a0f72287273c0571485c29123fef880d2d8169cfdb884bf95a18a0b36461517acda390ce4cf441", List.empty) + val script = ScriptPubKey("nulldata", "OP_RETURN 586a55587938507a55464d78534c37594135767866574a587365746b354d5638676f 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99 1f60a6a385a4e5163ffef65dd873f17452bb0d9f89da701ffcc5a0f72287273c0571485c29123fef880d2d8169cfdb884bf95a18a0b36461517acda390ce4cf441", dummyScript, List.empty) val result = script.getTPoSAddresses result.nonEmpty mustEqual true } "fail if OP_RETURN is not present" in { - val script = ScriptPubKey("nulldata", "OP_RTURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99", List.empty) + val script = ScriptPubKey("nulldata", "OP_RTURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99", dummyScript, List.empty) val result = script.getTPoSAddresses result.isEmpty mustEqual true } "fail if the comission is missing" in { - val script = ScriptPubKey("nulldata", "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f ", List.empty) + val script = ScriptPubKey("nulldata", "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f ", dummyScript, List.empty) val result = script.getTPoSAddresses result.isEmpty mustEqual true } "fail if the owner address is malformed" in { - val script = ScriptPubKey("nulldata", "OP_RETURN 586933735164d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99", List.empty) + val script = ScriptPubKey("nulldata", "OP_RETURN 586933735164d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99", dummyScript, List.empty) val result = script.getTPoSAddresses result.isEmpty mustEqual true } "fail if the merchant address is malformed" in { - val script = ScriptPubKey("nulldata", "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786664672484e634d696e68366778755052595939484361593944416f 99", List.empty) + val script = ScriptPubKey("nulldata", "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786664672484e634d696e68366778755052595939484361593944416f 99", dummyScript, List.empty) val result = script.getTPoSAddresses result.isEmpty mustEqual true diff --git a/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala b/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala index ffd6e9c..92de679 100644 --- a/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala +++ b/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala @@ -4,7 +4,7 @@ import com.alexitc.playsonify.core.FutureApplicationResult import com.alexitc.playsonify.validators.PaginatedQueryValidator import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} import com.xsn.explorer.data.anorm.interpreters.FieldOrderingSQLInterpreter -import com.xsn.explorer.data.anorm.{BalancePostgresDataHandler, BlockPostgresDataHandler, LedgerPostgresDataHandler, TransactionPostgresDataHandler} +import com.xsn.explorer.data.anorm.{BlockPostgresDataHandler, LedgerPostgresDataHandler, TransactionPostgresDataHandler} import com.xsn.explorer.data.async.{BlockFutureDataHandler, LedgerFutureDataHandler, TransactionFutureDataHandler} import com.xsn.explorer.data.common.PostgresDataHandlerSpec import com.xsn.explorer.errors.BlockNotFoundError @@ -169,21 +169,6 @@ class LedgerSynchronizerServiceSpec extends PostgresDataHandlerSpec with BeforeA verifyLedger(finalBlocks: _*) } } - - "process a block without spent index on transactions" in { - val block = BlockLoader.get("000001ff95f22b8d82db14a5c5e9f725e8239e548be43c668766e7ddaee81924") - .copy(previousBlockhash = None, height = Height(0)) - - val synchronizer = ledgerSynchronizerService(block) - whenReady(synchronizer.synchronize(block.hash)) { result => - result.isGood mustEqual true - - val balanceDataHandler = new BalancePostgresDataHandler(database, new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) - val balance = balanceDataHandler.getBy(DataHelper.createAddress("XdJnCKYNwzCz8ATv8Eu75gonaHyfr9qXg9")) - - balance.get.spent mustEqual BigDecimal("76500000.000000000000000") - } - } } private def verifyLedger(blocks: Block*) = { diff --git a/server/test/com/xsn/explorer/util/BigDecimalExtSpec.scala b/server/test/com/xsn/explorer/util/BigDecimalExtSpec.scala new file mode 100644 index 0000000..ad015b3 --- /dev/null +++ b/server/test/com/xsn/explorer/util/BigDecimalExtSpec.scala @@ -0,0 +1,24 @@ +package com.xsn.explorer.util + +import org.scalatest.{MustMatchers, WordSpec} + +class BigDecimalExtSpec extends WordSpec with MustMatchers { + + import Extensions.BigDecimalExt + + "fromSatoshis" should { + "work" in { + val input = BigDecimal(40409891838L) + val result = input.fromSatoshis + result mustEqual BigDecimal(404.09891838) + } + } + + "toSatoshis" should { + "work" in { + val input = BigDecimal(404.09891838) + val result = input.toSatoshis + result mustEqual BigDecimal(40409891838L) + } + } +}