Add listFundedAddresses call (#1407)

This commit is contained in:
Ben Carman 2020-05-12 07:33:22 -05:00 committed by GitHub
parent 721b28aefd
commit 711f5cb99c
8 changed files with 95 additions and 2 deletions

View file

@ -169,6 +169,9 @@ object ConsoleCli {
.action((_, conf) => conf.copy(command = GetSpentAddresses)) .action((_, conf) => conf.copy(command = GetSpentAddresses))
.text( .text(
"Returns list of all wallet addresses that have received funds and been spent"), "Returns list of all wallet addresses that have received funds and been spent"),
cmd("getfundedaddresses")
.action((_, conf) => conf.copy(command = GetFundedAddresses))
.text("Returns list of all wallet addresses that are holding funds"),
cmd("getaccounts") cmd("getaccounts")
.action((_, conf) => conf.copy(command = GetAccounts)) .action((_, conf) => conf.copy(command = GetAccounts))
.text("Returns list of all wallet accounts"), .text("Returns list of all wallet accounts"),
@ -402,6 +405,8 @@ object ConsoleCli {
RequestParam("getaddresses") RequestParam("getaddresses")
case GetSpentAddresses => case GetSpentAddresses =>
RequestParam("getspentaddresses") RequestParam("getspentaddresses")
case GetFundedAddresses =>
RequestParam("getfundedaddresses")
case GetAccounts => case GetAccounts =>
RequestParam("getaccounts") RequestParam("getaccounts")
case CreateNewAccount => case CreateNewAccount =>
@ -577,6 +582,7 @@ object CliCommand {
case object GetUtxos extends CliCommand case object GetUtxos extends CliCommand
case object GetAddresses extends CliCommand case object GetAddresses extends CliCommand
case object GetSpentAddresses extends CliCommand case object GetSpentAddresses extends CliCommand
case object GetFundedAddresses extends CliCommand
case object GetAccounts extends CliCommand case object GetAccounts extends CliCommand
case object CreateNewAccount extends CliCommand case object CreateNewAccount extends CliCommand
case object IsEmpty extends CliCommand case object IsEmpty extends CliCommand

View file

@ -8,7 +8,7 @@ import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.bitcoins.chain.api.ChainApi import org.bitcoins.chain.api.ChainApi
import org.bitcoins.core.Core import org.bitcoins.core.Core
import org.bitcoins.core.crypto.ExtPublicKey import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit} import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis}
import org.bitcoins.core.hd._ import org.bitcoins.core.hd._
import org.bitcoins.core.protocol.BlockStamp.{ import org.bitcoins.core.protocol.BlockStamp.{
BlockHash, BlockHash,
@ -373,6 +373,31 @@ class RoutesSpec
} }
} }
"return the wallet's funded addresses" in {
val addressDb = LegacyAddressDb(
LegacyHDPath(HDCoinType.Testnet, 0, HDChainType.External, 0),
ECPublicKey.freshPublicKey,
Sha256Hash160Digest.fromBytes(ByteVector.low(20)),
testAddress.asInstanceOf[P2PKHAddress],
testAddress.scriptPubKey
)
(mockWalletApi.listFundedAddresses: () => Future[Vector[(
AddressDb,
CurrencyUnit)]])
.expects()
.returning(Future.successful(Vector((addressDb, Satoshis.zero))))
val route =
walletRoutes.handleCommand(ServerCommand("getfundedaddresses", Arr()))
Get() ~> route ~> check {
contentType shouldEqual `application/json`
responseAs[String] shouldEqual
s"""{"result":["$testAddressStr ${Satoshis.zero}"],"error":null}""".stripMargin
}
}
"return the wallet accounts" in { "return the wallet accounts" in {
val xpub = ExtPublicKey val xpub = ExtPublicKey
.fromString( .fromString(

View file

@ -266,7 +266,7 @@ object SendFromOutpoints extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 3")) s"Bad number of arguments: ${other.length}. Expected: 4"))
} }
} }

View file

@ -189,6 +189,17 @@ case class WalletRoutes(wallet: WalletApi, node: Node)(
} }
} }
case ServerCommand("getfundedaddresses", _) =>
complete {
wallet.listFundedAddresses().map { addressDbs =>
val addressAndValues = addressDbs.map {
case (addressDb, value) => s"${addressDb.address} $value"
}
Server.httpSuccess(addressAndValues)
}
}
case ServerCommand("getaccounts", _) => case ServerCommand("getaccounts", _) =>
complete { complete {
wallet.listAccounts().map { accounts => wallet.listAccounts().map { accounts =>

View file

@ -1,6 +1,7 @@
package org.bitcoins.wallet package org.bitcoins.wallet
import org.bitcoins.core.currency.{Bitcoins, Satoshis} import org.bitcoins.core.currency.{Bitcoins, Satoshis}
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedWallet import org.bitcoins.testkit.wallet.FundWalletUtil.FundedWallet
import org.bitcoins.rpc.util.AsyncUtil import org.bitcoins.rpc.util.AsyncUtil
@ -136,4 +137,19 @@ class AddressHandlingTest extends BitcoinSWalletTest {
assert(diff.isEmpty, s"Extra spent addresses $diff") assert(diff.isEmpty, s"Extra spent addresses $diff")
} }
} }
it must "get the correct funded addresses" in { fundedWallet: FundedWallet =>
val wallet = fundedWallet.wallet
for {
unspentDbs <- wallet.spendingInfoDAO.findAllUnspent()
fundedAddresses <- wallet.listFundedAddresses()
} yield {
val diff = unspentDbs
.map(_.output)
.diff(fundedAddresses.map(tuple =>
TransactionOutput(tuple._2, tuple._1.scriptPubKey)))
assert(diff.isEmpty, s"Extra funded addresses $diff")
}
}
} }

View file

@ -176,6 +176,11 @@ trait WalletApi extends WalletLogger {
def listSpentAddresses(account: HDAccount): Future[Vector[AddressDb]] def listSpentAddresses(account: HDAccount): Future[Vector[AddressDb]]
def listFundedAddresses(): Future[Vector[(AddressDb, CurrencyUnit)]]
def listFundedAddresses(
account: HDAccount): Future[Vector[(AddressDb, CurrencyUnit)]]
def markUTXOsAsReserved( def markUTXOsAsReserved(
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]] utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]]

View file

@ -1,5 +1,6 @@
package org.bitcoins.wallet.internal package org.bitcoins.wallet.internal
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd._ import org.bitcoins.core.hd._
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.BitcoinAddress
@ -70,6 +71,21 @@ private[wallet] trait AddressHandling extends WalletLogger {
} }
} }
override def listFundedAddresses(): Future[
Vector[(AddressDb, CurrencyUnit)]] = {
addressDAO.getFundedAddresses
}
override def listFundedAddresses(
account: HDAccount): Future[Vector[(AddressDb, CurrencyUnit)]] = {
val spentAddressesF = addressDAO.getFundedAddresses
spentAddressesF.map { spentAddresses =>
spentAddresses.filter(addr =>
HDAccount.isSameAccount(addr._1.path, account))
}
}
/** Enumerates the public keys in this wallet */ /** Enumerates the public keys in this wallet */
protected[wallet] def listPubkeys(): Future[Vector[ECPublicKey]] = protected[wallet] def listPubkeys(): Future[Vector[ECPublicKey]] =
addressDAO.findAllPubkeys() addressDAO.findAllPubkeys()

View file

@ -1,5 +1,6 @@
package org.bitcoins.wallet.models package org.bitcoins.wallet.models
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.{ import org.bitcoins.core.hd.{
HDAccount, HDAccount,
HDChainType, HDChainType,
@ -126,6 +127,19 @@ case class AddressDAO()(
safeDatabase.runVec(query.result) safeDatabase.runVec(query.result)
} }
def getFundedAddresses: Future[Vector[(AddressDb, CurrencyUnit)]] = {
val query = table
.join(spendingInfoTable)
.on(_.scriptPubKey === _.scriptPubKey)
.filter(_._2.state.inSet(TxoState.receivedStates))
safeDatabase
.runVec(query.result)
.map(_.map {
case (addrDb, utxoDb) => (addrDb, utxoDb.output.value)
})
}
private def findMostRecentForChain( private def findMostRecentForChain(
account: HDAccount, account: HDAccount,
chain: HDChainType): slick.sql.SqlAction[ chain: HDChainType): slick.sql.SqlAction[