mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
2022 03 09 label refactor (#4175)
* Rename existing getaddresslabels -> getaddresslabel * Fix missing rename of GetAddressLabel * Modify pk constraint on wallet_address_tags to be tag_name rather than tag_type * Add dropaddresslabel for a specific address * Fix migrations * Add unit tests and fix existing tests * Add docs
This commit is contained in:
parent
c379cf4a73
commit
668ab21ca1
14 changed files with 254 additions and 43 deletions
|
@ -320,8 +320,8 @@ object ConsoleCli {
|
||||||
case other => other
|
case other => other
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
cmd("getaddresslabels")
|
cmd("getaddresslabel")
|
||||||
.action((_, conf) => conf.copy(command = GetAddressLabels(null)))
|
.action((_, conf) => conf.copy(command = GetAddressLabel(null)))
|
||||||
.text("Get all the labels associated with this address")
|
.text("Get all the labels associated with this address")
|
||||||
.children(
|
.children(
|
||||||
arg[BitcoinAddress]("address")
|
arg[BitcoinAddress]("address")
|
||||||
|
@ -329,14 +329,17 @@ object ConsoleCli {
|
||||||
.required()
|
.required()
|
||||||
.action((addr, conf) =>
|
.action((addr, conf) =>
|
||||||
conf.copy(command = conf.command match {
|
conf.copy(command = conf.command match {
|
||||||
case getAddressLabels: GetAddressLabels =>
|
case getAddressLabels: GetAddressLabel =>
|
||||||
getAddressLabels.copy(address = addr)
|
getAddressLabels.copy(address = addr)
|
||||||
case other => other
|
case other => other
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
|
cmd("getaddresslabels")
|
||||||
|
.action((_, conf) => conf.copy(command = GetAddressLabels))
|
||||||
|
.text("Returns all labels in wallet"),
|
||||||
cmd("dropaddresslabels")
|
cmd("dropaddresslabels")
|
||||||
.action((_, conf) => conf.copy(command = DropAddressLabels(null)))
|
.action((_, conf) => conf.copy(command = DropAddressLabels(null)))
|
||||||
.text("Drop all the labels associated with this address")
|
.text("Drop the label associated with the address")
|
||||||
.children(
|
.children(
|
||||||
arg[BitcoinAddress]("address")
|
arg[BitcoinAddress]("address")
|
||||||
.text("The address to drop the associated labels of")
|
.text("The address to drop the associated labels of")
|
||||||
|
@ -348,6 +351,29 @@ object ConsoleCli {
|
||||||
case other => other
|
case other => other
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
|
cmd("dropaddresslabel")
|
||||||
|
.action((_, conf) => conf.copy(command = DropAddressLabel(null, null)))
|
||||||
|
.text("Drop all the labels associated with this address")
|
||||||
|
.children(
|
||||||
|
arg[BitcoinAddress]("address")
|
||||||
|
.text("The address to drop the associated labels of")
|
||||||
|
.required()
|
||||||
|
.action((addr, conf) =>
|
||||||
|
conf.copy(command = conf.command match {
|
||||||
|
case dropAddressLabel: DropAddressLabel =>
|
||||||
|
dropAddressLabel.copy(address = addr)
|
||||||
|
case other => other
|
||||||
|
})),
|
||||||
|
arg[String]("label")
|
||||||
|
.text("The label to drop")
|
||||||
|
.required()
|
||||||
|
.action((label, conf) =>
|
||||||
|
conf.copy(command = conf.command match {
|
||||||
|
case dropAddressLabel: DropAddressLabel =>
|
||||||
|
dropAddressLabel.copy(label = label)
|
||||||
|
case other => other
|
||||||
|
}))
|
||||||
|
),
|
||||||
cmd("sendtoaddress")
|
cmd("sendtoaddress")
|
||||||
.action(
|
.action(
|
||||||
// TODO how to handle null here?
|
// TODO how to handle null here?
|
||||||
|
@ -1852,8 +1878,13 @@ object ConsoleCli {
|
||||||
Seq(up.writeJs(address), up.writeJs(label)))
|
Seq(up.writeJs(address), up.writeJs(label)))
|
||||||
case GetAddressTags(address) =>
|
case GetAddressTags(address) =>
|
||||||
RequestParam("getaddresstags", Seq(up.writeJs(address)))
|
RequestParam("getaddresstags", Seq(up.writeJs(address)))
|
||||||
case GetAddressLabels(address) =>
|
case GetAddressLabel(address) =>
|
||||||
RequestParam("getaddresslabels", Seq(up.writeJs(address)))
|
RequestParam("getaddresslabel", Seq(up.writeJs(address)))
|
||||||
|
case GetAddressLabels =>
|
||||||
|
RequestParam("getaddresslabels")
|
||||||
|
case DropAddressLabel(address, label) =>
|
||||||
|
RequestParam("dropaddresslabel",
|
||||||
|
Seq(up.writeJs(address), ujson.Str(label)))
|
||||||
case DropAddressLabels(address) =>
|
case DropAddressLabels(address) =>
|
||||||
RequestParam("dropaddresslabels", Seq(up.writeJs(address)))
|
RequestParam("dropaddresslabels", Seq(up.writeJs(address)))
|
||||||
case Rescan(addressBatchSize,
|
case Rescan(addressBatchSize,
|
||||||
|
@ -2355,7 +2386,12 @@ object CliCommand {
|
||||||
|
|
||||||
case class GetAddressTags(address: BitcoinAddress) extends AppServerCliCommand
|
case class GetAddressTags(address: BitcoinAddress) extends AppServerCliCommand
|
||||||
|
|
||||||
case class GetAddressLabels(address: BitcoinAddress)
|
case class GetAddressLabel(address: BitcoinAddress)
|
||||||
|
extends AppServerCliCommand
|
||||||
|
|
||||||
|
case object GetAddressLabels extends AppServerCliCommand
|
||||||
|
|
||||||
|
case class DropAddressLabel(address: BitcoinAddress, label: String)
|
||||||
extends AppServerCliCommand
|
extends AppServerCliCommand
|
||||||
|
|
||||||
case class DropAddressLabels(address: BitcoinAddress)
|
case class DropAddressLabels(address: BitcoinAddress)
|
||||||
|
|
|
@ -744,7 +744,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"get address labels" in {
|
"get address label" in {
|
||||||
(mockWalletApi
|
(mockWalletApi
|
||||||
.getAddressTags(_: BitcoinAddress, _: AddressTagType))
|
.getAddressTags(_: BitcoinAddress, _: AddressTagType))
|
||||||
.expects(testAddress, AddressLabelTagType)
|
.expects(testAddress, AddressLabelTagType)
|
||||||
|
@ -753,7 +753,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
||||||
|
|
||||||
val route =
|
val route =
|
||||||
walletRoutes.handleCommand(
|
walletRoutes.handleCommand(
|
||||||
ServerCommand("getaddresslabels", Arr(Str(testAddressStr))))
|
ServerCommand("getaddresslabel", Arr(Str(testAddressStr))))
|
||||||
|
|
||||||
Get() ~> route ~> check {
|
Get() ~> route ~> check {
|
||||||
assert(contentType == `application/json`)
|
assert(contentType == `application/json`)
|
||||||
|
@ -762,6 +762,41 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"get address labels" in {
|
||||||
|
(mockWalletApi.getAddressTags: () => Future[Vector[AddressTagDb]])
|
||||||
|
.expects()
|
||||||
|
.returning(
|
||||||
|
Future.successful(Vector(AddressTagDb(testAddress, testLabel))))
|
||||||
|
|
||||||
|
val route =
|
||||||
|
walletRoutes.handleCommand(ServerCommand("getaddresslabels", Arr()))
|
||||||
|
|
||||||
|
Get() ~> route ~> check {
|
||||||
|
assert(contentType == `application/json`)
|
||||||
|
assert(
|
||||||
|
responseAs[String] == """{"result":[{"address":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","labels":["test"]}],"error":null}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"drop address label" in {
|
||||||
|
val labelName = "label"
|
||||||
|
(mockWalletApi
|
||||||
|
.dropAddressTagName(_: BitcoinAddress, _: AddressTagName))
|
||||||
|
.expects(testAddress, AddressLabelTagName(labelName))
|
||||||
|
.returning(Future.successful(1))
|
||||||
|
|
||||||
|
val route =
|
||||||
|
walletRoutes.handleCommand(
|
||||||
|
ServerCommand("dropaddresslabel",
|
||||||
|
Arr(Str(testAddressStr), Str(labelName))))
|
||||||
|
|
||||||
|
Get() ~> route ~> check {
|
||||||
|
assert(contentType == `application/json`)
|
||||||
|
assert(
|
||||||
|
responseAs[String] == """{"result":"""" + "1 label dropped" + """","error":null}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"drop address labels with no labels" in {
|
"drop address labels with no labels" in {
|
||||||
(mockWalletApi
|
(mockWalletApi
|
||||||
.dropAddressTagType(_: BitcoinAddress, _: AddressTagType))
|
.dropAddressTagType(_: BitcoinAddress, _: AddressTagType))
|
||||||
|
|
|
@ -114,17 +114,17 @@ object GetAddressTags extends ServerJsonModels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case class GetAddressLabels(address: BitcoinAddress)
|
case class GetAddressLabel(address: BitcoinAddress)
|
||||||
|
|
||||||
object GetAddressLabels extends ServerJsonModels {
|
object GetAddressLabel extends ServerJsonModels {
|
||||||
|
|
||||||
def fromJsArr(jsArr: ujson.Arr): Try[GetAddressLabels] = {
|
def fromJsArr(jsArr: ujson.Arr): Try[GetAddressLabel] = {
|
||||||
jsArr.arr.toList match {
|
jsArr.arr.toList match {
|
||||||
case addrJs :: Nil =>
|
case addrJs :: Nil =>
|
||||||
Try {
|
Try {
|
||||||
val addr = jsToBitcoinAddress(addrJs)
|
val addr = jsToBitcoinAddress(addrJs)
|
||||||
|
|
||||||
GetAddressLabels(addr)
|
GetAddressLabel(addr)
|
||||||
}
|
}
|
||||||
case other =>
|
case other =>
|
||||||
Failure(
|
Failure(
|
||||||
|
@ -134,6 +134,25 @@ object GetAddressLabels extends ServerJsonModels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class DropAddressLabel(address: BitcoinAddress, label: String)
|
||||||
|
|
||||||
|
object DropAddressLabel extends ServerJsonModels {
|
||||||
|
|
||||||
|
def fromJsArr(jsonArr: ujson.Arr): Try[DropAddressLabel] = {
|
||||||
|
jsonArr.arr.toList match {
|
||||||
|
case address :: label :: Nil =>
|
||||||
|
Try {
|
||||||
|
val addr = jsToBitcoinAddress(address)
|
||||||
|
DropAddressLabel(addr, label.str)
|
||||||
|
}
|
||||||
|
case other =>
|
||||||
|
Failure(
|
||||||
|
new IllegalArgumentException(
|
||||||
|
s"Bad number of arguments: ${other.length}. Expected: 2"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class DropAddressLabels(address: BitcoinAddress)
|
case class DropAddressLabels(address: BitcoinAddress)
|
||||||
|
|
||||||
object DropAddressLabels extends ServerJsonModels {
|
object DropAddressLabels extends ServerJsonModels {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.bitcoins.server
|
package org.bitcoins.server
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.model.HttpEntity
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server._
|
import akka.http.scaladsl.server._
|
||||||
import akka.stream.Materializer
|
import akka.stream.Materializer
|
||||||
|
@ -12,7 +13,11 @@ import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.core.protocol.tlv._
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.core.protocol.transaction.Transaction
|
import org.bitcoins.core.protocol.transaction.Transaction
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||||
import org.bitcoins.core.wallet.utxo.{AddressLabelTagType, TxoState}
|
import org.bitcoins.core.wallet.utxo.{
|
||||||
|
AddressLabelTagName,
|
||||||
|
AddressLabelTagType,
|
||||||
|
TxoState
|
||||||
|
}
|
||||||
import org.bitcoins.crypto.NetworkElement
|
import org.bitcoins.crypto.NetworkElement
|
||||||
import org.bitcoins.keymanager._
|
import org.bitcoins.keymanager._
|
||||||
import org.bitcoins.keymanager.config.KeyManagerAppConfig
|
import org.bitcoins.keymanager.config.KeyManagerAppConfig
|
||||||
|
@ -228,9 +233,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case ServerCommand("getaddresslabels", arr) =>
|
case ServerCommand("getaddresslabel", arr) =>
|
||||||
withValidServerCommand(GetAddressLabels.fromJsArr(arr)) {
|
withValidServerCommand(GetAddressLabel.fromJsArr(arr)) {
|
||||||
case GetAddressLabels(address) =>
|
case GetAddressLabel(address) =>
|
||||||
complete {
|
complete {
|
||||||
wallet.getAddressTags(address, AddressLabelTagType).map { tagDbs =>
|
wallet.getAddressTags(address, AddressLabelTagType).map { tagDbs =>
|
||||||
val retStr = tagDbs.map(_.tagName.name)
|
val retStr = tagDbs.map(_.tagName.name)
|
||||||
|
@ -239,20 +244,38 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ServerCommand("getaddresslabels", _) =>
|
||||||
|
complete {
|
||||||
|
val allTagsF = wallet.getAddressTags()
|
||||||
|
for {
|
||||||
|
allTags <- allTagsF
|
||||||
|
grouped = allTags.groupBy(_.address)
|
||||||
|
} yield {
|
||||||
|
val json: Vector[ujson.Obj] = grouped.map { case (address, labels) =>
|
||||||
|
val tagNames: Vector[ujson.Str] =
|
||||||
|
labels.map(l => ujson.Str(l.tagName.name))
|
||||||
|
ujson.Obj(("address", address.toString),
|
||||||
|
("labels", ujson.Arr.from(tagNames)))
|
||||||
|
}.toVector
|
||||||
|
Server.httpSuccess(ujson.Arr.from(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ServerCommand("dropaddresslabel", arr) =>
|
||||||
|
withValidServerCommand(DropAddressLabel.fromJsArr(arr)) {
|
||||||
|
case DropAddressLabel(address, label) =>
|
||||||
|
complete {
|
||||||
|
val tagName = AddressLabelTagName(label)
|
||||||
|
val droppedF = wallet.dropAddressTagName(address, tagName)
|
||||||
|
droppedF.map(handleTagResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
case ServerCommand("dropaddresslabels", arr) =>
|
case ServerCommand("dropaddresslabels", arr) =>
|
||||||
withValidServerCommand(DropAddressLabels.fromJsArr(arr)) {
|
withValidServerCommand(DropAddressLabels.fromJsArr(arr)) {
|
||||||
case DropAddressLabels(address) =>
|
case DropAddressLabels(address) =>
|
||||||
complete {
|
complete {
|
||||||
wallet.dropAddressTagType(address, AddressLabelTagType).map {
|
val droppedF =
|
||||||
numDropped =>
|
wallet.dropAddressTagType(address, AddressLabelTagType)
|
||||||
if (numDropped <= 0) {
|
droppedF.map(handleTagResponse)
|
||||||
Server.httpSuccess(s"Address had no labels")
|
|
||||||
} else if (numDropped == 1) {
|
|
||||||
Server.httpSuccess(s"$numDropped label dropped")
|
|
||||||
} else {
|
|
||||||
Server.httpSuccess(s"$numDropped labels dropped")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -898,4 +921,14 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
Bitcoins(currencyUnit.satoshis).toBigDecimal.toDouble
|
Bitcoins(currencyUnit.satoshis).toBigDecimal.toDouble
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def handleTagResponse(numDropped: Int): HttpEntity.Strict = {
|
||||||
|
if (numDropped <= 0) {
|
||||||
|
Server.httpSuccess(s"Address had no labels")
|
||||||
|
} else if (numDropped == 1) {
|
||||||
|
Server.httpSuccess(s"$numDropped label dropped")
|
||||||
|
} else {
|
||||||
|
Server.httpSuccess(s"$numDropped labels dropped")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,12 @@ import org.bitcoins.core.protocol.transaction.{
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.util.{FutureUtil, StartStopAsync}
|
import org.bitcoins.core.util.{FutureUtil, StartStopAsync}
|
||||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||||
import org.bitcoins.core.wallet.utxo.{AddressTag, AddressTagType, TxoState}
|
import org.bitcoins.core.wallet.utxo.{
|
||||||
|
AddressTag,
|
||||||
|
AddressTagName,
|
||||||
|
AddressTagType,
|
||||||
|
TxoState
|
||||||
|
}
|
||||||
import org.bitcoins.crypto.DoubleSha256DigestBE
|
import org.bitcoins.crypto.DoubleSha256DigestBE
|
||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -218,7 +223,7 @@ trait WalletApi extends StartStopAsync[WalletApi] {
|
||||||
address: BitcoinAddress,
|
address: BitcoinAddress,
|
||||||
tagType: AddressTagType): Future[Vector[AddressTagDb]]
|
tagType: AddressTagType): Future[Vector[AddressTagDb]]
|
||||||
|
|
||||||
def getAddressTags: Future[Vector[AddressTagDb]]
|
def getAddressTags(): Future[Vector[AddressTagDb]]
|
||||||
|
|
||||||
def getAddressTags(tagType: AddressTagType): Future[Vector[AddressTagDb]]
|
def getAddressTags(tagType: AddressTagType): Future[Vector[AddressTagDb]]
|
||||||
|
|
||||||
|
@ -230,6 +235,10 @@ trait WalletApi extends StartStopAsync[WalletApi] {
|
||||||
address: BitcoinAddress,
|
address: BitcoinAddress,
|
||||||
addressTagType: AddressTagType): Future[Int]
|
addressTagType: AddressTagType): Future[Int]
|
||||||
|
|
||||||
|
def dropAddressTagName(
|
||||||
|
address: BitcoinAddress,
|
||||||
|
tagName: AddressTagName): Future[Int]
|
||||||
|
|
||||||
/** Generates a new change address */
|
/** Generates a new change address */
|
||||||
protected[wallet] def getNewChangeAddress()(implicit
|
protected[wallet] def getNewChangeAddress()(implicit
|
||||||
ec: ExecutionContext): Future[BitcoinAddress]
|
ec: ExecutionContext): Future[BitcoinAddress]
|
||||||
|
|
|
@ -106,13 +106,13 @@ class DbManagementTest extends BitcoinSAsyncTest with EmbeddedPg {
|
||||||
val result = walletDbManagement.migrate()
|
val result = walletDbManagement.migrate()
|
||||||
walletAppConfig.driver match {
|
walletAppConfig.driver match {
|
||||||
case SQLite =>
|
case SQLite =>
|
||||||
val expected = 14
|
val expected = 15
|
||||||
assert(result == expected)
|
assert(result == expected)
|
||||||
val flywayInfo = walletDbManagement.info()
|
val flywayInfo = walletDbManagement.info()
|
||||||
assert(flywayInfo.applied().length == expected)
|
assert(flywayInfo.applied().length == expected)
|
||||||
assert(flywayInfo.pending().length == 0)
|
assert(flywayInfo.pending().length == 0)
|
||||||
case PostgreSQL =>
|
case PostgreSQL =>
|
||||||
val expected = 12
|
val expected = 13
|
||||||
assert(result == expected)
|
assert(result == expected)
|
||||||
val flywayInfo = walletDbManagement.info()
|
val flywayInfo = walletDbManagement.info()
|
||||||
|
|
||||||
|
|
|
@ -309,6 +309,10 @@ the `-p 9999:9999` port mapping on the docker container to adjust for this.
|
||||||
- `offer-send` `offerOrTempContractId` `peerAddress` `message` - Sends an offer to a peer. `offerOrTempContractId` is either an offer TLV or a temporary contract ID.
|
- `offer-send` `offerOrTempContractId` `peerAddress` `message` - Sends an offer to a peer. `offerOrTempContractId` is either an offer TLV or a temporary contract ID.
|
||||||
- `offers-list` - List all incoming offers from the inbox
|
- `offers-list` - List all incoming offers from the inbox
|
||||||
- `getdlcoffer` `tempContractId` - Gets a DLC offer by temporary contract ID.
|
- `getdlcoffer` `tempContractId` - Gets a DLC offer by temporary contract ID.
|
||||||
|
- `getaddresslabel` `address` - gets all labels for an address
|
||||||
|
- `getaddresslabels` - returns all addresses with labels in the wallet
|
||||||
|
- `dropaddresslabel` `address` `label` - drops the label for a given address
|
||||||
|
- `dropaddresslabels` `address` - drops all labels for the given address
|
||||||
|
|
||||||
### Network
|
### Network
|
||||||
- `getpeers` - List the connected peers
|
- `getpeers` - List the connected peers
|
||||||
|
|
|
@ -283,7 +283,7 @@ class AddressHandlingTest extends BitcoinSWalletTest {
|
||||||
for {
|
for {
|
||||||
_ <- addressF
|
_ <- addressF
|
||||||
_ <- wallet.clearAllUtxosAndAddresses()
|
_ <- wallet.clearAllUtxosAndAddresses()
|
||||||
tags <- wallet.getAddressTags
|
tags <- wallet.getAddressTags()
|
||||||
} yield {
|
} yield {
|
||||||
assert(tags.isEmpty)
|
assert(tags.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import org.bitcoins.testkit.wallet.BitcoinSWalletTest
|
||||||
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedWallet
|
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedWallet
|
||||||
import org.scalatest.FutureOutcome
|
import org.scalatest.FutureOutcome
|
||||||
|
|
||||||
|
import java.sql.SQLException
|
||||||
|
|
||||||
class AddressLabelTest extends BitcoinSWalletTest {
|
class AddressLabelTest extends BitcoinSWalletTest {
|
||||||
type FixtureParam = FundedWallet
|
type FixtureParam = FundedWallet
|
||||||
|
|
||||||
|
@ -14,18 +16,18 @@ class AddressLabelTest extends BitcoinSWalletTest {
|
||||||
|
|
||||||
behavior of "Address tags"
|
behavior of "Address tags"
|
||||||
|
|
||||||
it must "add two labels to the database" in { fundedWallet =>
|
it must "add two tags to the database" in { fundedWallet =>
|
||||||
val wallet = fundedWallet.wallet
|
val wallet = fundedWallet.wallet
|
||||||
val tag1 = UnknownAddressTag("test_tag_name_1", "test_tag_type_1")
|
val tag1 = UnknownAddressTag("test_tag_name_1", "test_tag_type_1")
|
||||||
val tag2 = UnknownAddressTag("test_tag_name_2", "test_tag_type_2")
|
val tag2 = UnknownAddressTag("test_tag_name_2", "test_tag_type_2")
|
||||||
val addressF = for {
|
val addressF = for {
|
||||||
address <- wallet.getNewAddress(Vector(tag1))
|
address <- wallet.getNewAddress(Vector(tag1))
|
||||||
//add another tag to address
|
//add another tag to address
|
||||||
tagDb1 <- wallet.tagAddress(address, tag1)
|
tagDb1 <- wallet.getAddressTags(address)
|
||||||
tagDb2 <- wallet.tagAddress(address, tag2)
|
tagDb2 <- wallet.tagAddress(address, tag2)
|
||||||
} yield {
|
} yield {
|
||||||
assert(tagDb1.address == address)
|
assert(tagDb1.head.address == address)
|
||||||
assert(tagDb1.tagName == tag1.tagName)
|
assert(tagDb1.head.tagName == tag1.tagName)
|
||||||
|
|
||||||
assert(tagDb2.tagName == tag2.tagName)
|
assert(tagDb2.tagName == tag2.tagName)
|
||||||
assert(tagDb2.address == address)
|
assert(tagDb2.address == address)
|
||||||
|
@ -33,4 +35,21 @@ class AddressLabelTest extends BitcoinSWalletTest {
|
||||||
|
|
||||||
addressF
|
addressF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "fail if we tag the address with the same tag twice" in {
|
||||||
|
fundedWallet =>
|
||||||
|
val wallet = fundedWallet.wallet
|
||||||
|
val tag1 = UnknownAddressTag(tagName = "test_tag_name_1",
|
||||||
|
tagType = "test_tag_type_1")
|
||||||
|
val tag2 = UnknownAddressTag(tagName = "test_tag_name_1",
|
||||||
|
tagType = "test_tag_type_2")
|
||||||
|
val resultF = for {
|
||||||
|
address <- wallet.getNewAddress()
|
||||||
|
//add another tag to address
|
||||||
|
_ <- wallet.tagAddress(address, tag1)
|
||||||
|
_ <- wallet.tagAddress(address, tag2)
|
||||||
|
} yield ()
|
||||||
|
|
||||||
|
recoverToSucceededIf[SQLException](resultF)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,4 +83,29 @@ class AddressTagDAOTest extends WalletDAOFixture {
|
||||||
daos =>
|
daos =>
|
||||||
testInsertion(daos, HotStorage)
|
testInsertion(daos, HotStorage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "delete a tag by name" in { daos =>
|
||||||
|
val accountDAO = daos.accountDAO
|
||||||
|
val addressDAO = daos.addressDAO
|
||||||
|
val addressTagDAO = daos.addressTagDAO
|
||||||
|
for {
|
||||||
|
createdAccount <- {
|
||||||
|
val account = WalletTestUtil.firstAccountDb
|
||||||
|
accountDAO.create(account)
|
||||||
|
}
|
||||||
|
createdAddress <- {
|
||||||
|
val addressDb = WalletTestUtil.getAddressDb(createdAccount)
|
||||||
|
addressDAO.create(addressDb)
|
||||||
|
}
|
||||||
|
createdAddressTag <- {
|
||||||
|
val tagDb =
|
||||||
|
AddressTagDb(createdAddress.address, exampleTag)
|
||||||
|
addressTagDAO.create(tagDb)
|
||||||
|
}
|
||||||
|
dropped <- addressTagDAO.dropByAddressAndName(createdAddress.address,
|
||||||
|
createdAddressTag.tagName)
|
||||||
|
} yield {
|
||||||
|
assert(dropped == 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
--changes pk to (address,tag_name) rather than (address,tag_type)
|
||||||
|
ALTER TABLE wallet_address_tags DROP CONSTRAINT IF EXISTS "pk_address_tags";
|
||||||
|
|
||||||
|
ALTER TABLE wallet_address_tags ADD CONSTRAINT pk_address_tags PRIMARY KEY (address, tag_name);
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- This changes the primary key to (address, tag_name)
|
||||||
|
CREATE TABLE "wallet_address_tags_temp" ("address" VARCHAR(254) NOT NULL,"tag_name" VARCHAR(254) NOT NULL,"tag_type" VARCHAR(254) NOT NULL);
|
||||||
|
INSERT INTO "wallet_address_tags_temp" SELECT "address", "tag_name", "tag_type" FROM "wallet_address_tags";
|
||||||
|
DROP TABLE "wallet_address_tags";
|
||||||
|
CREATE TABLE "wallet_address_tags" ("address" VARCHAR(254) NOT NULL,"tag_name" VARCHAR(254) NOT NULL,"tag_type" VARCHAR(254) NOT NULL,constraint "pk_address_tags" primary key ("address", "tag_name"), constraint "fk_address" foreign key("address") references "addresses"("address") on update NO ACTION on delete NO ACTION);
|
||||||
|
INSERT INTO "wallet_address_tags" SELECT "address", "tag_name", "tag_type" FROM "wallet_address_tags_temp";
|
||||||
|
DROP TABLE "wallet_address_tags_temp";
|
|
@ -13,7 +13,11 @@ import org.bitcoins.core.protocol.transaction.{
|
||||||
TransactionOutPoint,
|
TransactionOutPoint,
|
||||||
TransactionOutput
|
TransactionOutput
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.wallet.utxo.{AddressTag, AddressTagType}
|
import org.bitcoins.core.wallet.utxo.{
|
||||||
|
AddressTag,
|
||||||
|
AddressTagName,
|
||||||
|
AddressTagType
|
||||||
|
}
|
||||||
import org.bitcoins.crypto.ECPublicKey
|
import org.bitcoins.crypto.ECPublicKey
|
||||||
import org.bitcoins.wallet._
|
import org.bitcoins.wallet._
|
||||||
|
|
||||||
|
@ -414,7 +418,7 @@ private[wallet] trait AddressHandling extends WalletLogger {
|
||||||
address: BitcoinAddress,
|
address: BitcoinAddress,
|
||||||
tag: AddressTag): Future[AddressTagDb] = {
|
tag: AddressTag): Future[AddressTagDb] = {
|
||||||
val addressTagDb = AddressTagDb(address, tag)
|
val addressTagDb = AddressTagDb(address, tag)
|
||||||
val f = addressTagDAO.upsert(addressTagDb)
|
val f = addressTagDAO.create(addressTagDb)
|
||||||
f
|
f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,7 +432,7 @@ private[wallet] trait AddressHandling extends WalletLogger {
|
||||||
addressTagDAO.findByAddressAndTag(address, tagType)
|
addressTagDAO.findByAddressAndTag(address, tagType)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getAddressTags: Future[Vector[AddressTagDb]] = {
|
def getAddressTags(): Future[Vector[AddressTagDb]] = {
|
||||||
addressTagDAO.findAll()
|
addressTagDAO.findAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,6 +455,12 @@ private[wallet] trait AddressHandling extends WalletLogger {
|
||||||
addressTagDAO.dropByAddressAndTag(address, addressTagType)
|
addressTagDAO.dropByAddressAndTag(address, addressTagType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def dropAddressTagName(
|
||||||
|
address: BitcoinAddress,
|
||||||
|
addressTagName: AddressTagName): Future[Int] = {
|
||||||
|
addressTagDAO.dropByAddressAndName(address, addressTagName)
|
||||||
|
}
|
||||||
|
|
||||||
private lazy val addressRequestQueue = {
|
private lazy val addressRequestQueue = {
|
||||||
val queue = new java.util.concurrent.ArrayBlockingQueue[AddressRequest](
|
val queue = new java.util.concurrent.ArrayBlockingQueue[AddressRequest](
|
||||||
walletConfig.addressQueueSize
|
walletConfig.addressQueueSize
|
||||||
|
|
|
@ -125,6 +125,16 @@ case class AddressTagDAO()(implicit
|
||||||
safeDatabase.run(query.delete)
|
safeDatabase.run(query.delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def dropByAddressAndName(
|
||||||
|
address: BitcoinAddress,
|
||||||
|
tagName: AddressTagName): Future[Int] = {
|
||||||
|
val query = table
|
||||||
|
.filter(_.address === address)
|
||||||
|
.filter(_.tagName === tagName)
|
||||||
|
|
||||||
|
safeDatabase.run(query.delete)
|
||||||
|
}
|
||||||
|
|
||||||
def findTx(
|
def findTx(
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
network: NetworkParameters): Future[Vector[AddressTagDb]] = {
|
network: NetworkParameters): Future[Vector[AddressTagDb]] = {
|
||||||
|
@ -201,7 +211,7 @@ case class AddressTagDAO()(implicit
|
||||||
(address, tagName, tagType).<>(fromTuple, toTuple)
|
(address, tagName, tagType).<>(fromTuple, toTuple)
|
||||||
|
|
||||||
def primaryKey: PrimaryKey =
|
def primaryKey: PrimaryKey =
|
||||||
primaryKey("pk_address_tags", sourceColumns = (address, tagType))
|
primaryKey("pk_address_tags", sourceColumns = (address, tagName))
|
||||||
|
|
||||||
/** All tags must have an associated address */
|
/** All tags must have an associated address */
|
||||||
def fk_address = {
|
def fk_address = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue