Release Vexta Wallet v0.6.8 hotfix
This commit is contained in:
parent
3e2a7150f9
commit
45d9b111fb
6 changed files with 348 additions and 57 deletions
|
|
@ -22,8 +22,8 @@ android {
|
||||||
applicationId = "org.vextaproject.wallet"
|
applicationId = "org.vextaproject.wallet"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 37
|
versionCode = 38
|
||||||
versionName = "0.6.7"
|
versionName = "0.6.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ object BlockScanner {
|
||||||
val height: Int,
|
val height: Int,
|
||||||
val netSatoshis: Long,
|
val netSatoshis: Long,
|
||||||
val blockTime: Long,
|
val blockTime: Long,
|
||||||
|
val isCoinbase: Boolean = false,
|
||||||
val isPending: Boolean = false,
|
val isPending: Boolean = false,
|
||||||
val feeSatoshis: Long? = null,
|
val feeSatoshis: Long? = null,
|
||||||
val recipientAddress: String? = null
|
val recipientAddress: String? = null
|
||||||
|
|
@ -263,7 +264,8 @@ object BlockScanner {
|
||||||
.toHex(),
|
.toHex(),
|
||||||
height = height,
|
height = height,
|
||||||
netSatoshis = netSatoshis,
|
netSatoshis = netSatoshis,
|
||||||
blockTime = blockTime
|
blockTime = blockTime,
|
||||||
|
isCoinbase = transaction.isCoinbase
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ object HeaderSync {
|
||||||
var versionInfo: VersionInfo? = null
|
var versionInfo: VersionInfo? = null
|
||||||
var gotVerack = false
|
var gotVerack = false
|
||||||
var requested = false
|
var requested = false
|
||||||
|
var totalReceived = 0
|
||||||
val cachedBefore = chain.last().height
|
val cachedBefore = chain.last().height
|
||||||
|
|
||||||
repeat(200) {
|
repeat(200) {
|
||||||
|
|
@ -153,6 +154,8 @@ object HeaderSync {
|
||||||
|
|
||||||
"headers" -> {
|
"headers" -> {
|
||||||
val received = parseHeaders(message.second, chain)
|
val received = parseHeaders(message.second, chain)
|
||||||
|
totalReceived += received
|
||||||
|
|
||||||
val remote = versionInfo
|
val remote = versionInfo
|
||||||
?: throw IllegalStateException("missing node version")
|
?: throw IllegalStateException("missing node version")
|
||||||
|
|
||||||
|
|
@ -162,8 +165,25 @@ object HeaderSync {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chain.last().height < remote.height) {
|
||||||
|
if (received == 0) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"peer advertises height ${remote.height} " +
|
||||||
|
"but returned no additional headers"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(
|
||||||
|
output,
|
||||||
|
"getheaders",
|
||||||
|
createGetHeadersPayload(chain)
|
||||||
|
)
|
||||||
|
|
||||||
|
return@repeat
|
||||||
|
}
|
||||||
|
|
||||||
return SyncResult(
|
return SyncResult(
|
||||||
received = received,
|
received = totalReceived,
|
||||||
cachedBefore = cachedBefore,
|
cachedBefore = cachedBefore,
|
||||||
chainHeight = chain.last().height,
|
chainHeight = chain.last().height,
|
||||||
lastHash = chain.last().hashDisplay
|
lastHash = chain.last().hashDisplay
|
||||||
|
|
@ -175,7 +195,7 @@ object HeaderSync {
|
||||||
sendMessage(
|
sendMessage(
|
||||||
output,
|
output,
|
||||||
"getheaders",
|
"getheaders",
|
||||||
createGetHeadersPayload(chain.last().hashWire)
|
createGetHeadersPayload(chain)
|
||||||
)
|
)
|
||||||
requested = true
|
requested = true
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +221,8 @@ object HeaderSync {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val receivedHeaders = mutableListOf<ByteArray>()
|
||||||
|
|
||||||
repeat(count) {
|
repeat(count) {
|
||||||
if (buffer.remaining() < 81) {
|
if (buffer.remaining() < 81) {
|
||||||
throw IllegalStateException("truncated headers message")
|
throw IllegalStateException("truncated headers message")
|
||||||
|
|
@ -217,7 +239,33 @@ object HeaderSync {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendVerifiedHeader(chain, raw)
|
receivedHeaders.add(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedHeaders.isNotEmpty()) {
|
||||||
|
val firstPreviousHash =
|
||||||
|
receivedHeaders.first().copyOfRange(4, 36)
|
||||||
|
|
||||||
|
if (!firstPreviousHash.contentEquals(chain.last().hashWire)) {
|
||||||
|
val ancestorIndex =
|
||||||
|
chain.indexOfLast {
|
||||||
|
it.hashWire.contentEquals(firstPreviousHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ancestorIndex < 0) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"peer returned headers with no common ancestor"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
while (chain.lastIndex > ancestorIndex) {
|
||||||
|
chain.removeAt(chain.lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedHeaders.forEach { raw ->
|
||||||
|
appendVerifiedHeader(chain, raw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
@ -375,12 +423,38 @@ object HeaderSync {
|
||||||
return compact
|
return compact
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createGetHeadersPayload(locatorHash: ByteArray): ByteArray {
|
fun createGetHeadersPayload(chain: List<ChainHeader>): ByteArray {
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
|
val locator = mutableListOf<ByteArray>()
|
||||||
|
|
||||||
|
var index = chain.lastIndex
|
||||||
|
var step = 1
|
||||||
|
|
||||||
|
while (index >= 0) {
|
||||||
|
locator.add(chain[index].hashWire)
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (index - step).coerceAtLeast(0)
|
||||||
|
|
||||||
|
if (locator.size >= 10) {
|
||||||
|
step *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!locator.last().contentEquals(chain.first().hashWire)) {
|
||||||
|
locator.add(chain.first().hashWire)
|
||||||
|
}
|
||||||
|
|
||||||
writeInt32LE(out, PROTOCOL_VERSION)
|
writeInt32LE(out, PROTOCOL_VERSION)
|
||||||
writeCompactSize(out, 1)
|
writeCompactSize(out, locator.size)
|
||||||
out.write(locatorHash)
|
|
||||||
|
locator.forEach { hash ->
|
||||||
|
out.write(hash)
|
||||||
|
}
|
||||||
|
|
||||||
out.write(ByteArray(32))
|
out.write(ByteArray(32))
|
||||||
|
|
||||||
return out.toByteArray()
|
return out.toByteArray()
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ class MainActivity : Activity() {
|
||||||
)
|
)
|
||||||
|
|
||||||
val results = mutableListOf<String>()
|
val results = mutableListOf<String>()
|
||||||
|
var successfulPeerSyncs = 0
|
||||||
|
|
||||||
for ((index, peer) in peers.withIndex()) {
|
for ((index, peer) in peers.withIndex()) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -128,6 +129,8 @@ class MainActivity : Activity() {
|
||||||
saveChain(chain)
|
saveChain(chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
successfulPeerSyncs++
|
||||||
|
|
||||||
results.add(
|
results.add(
|
||||||
"$peer:$PORT\n" +
|
"$peer:$PORT\n" +
|
||||||
"Handshake: successful\n" +
|
"Handshake: successful\n" +
|
||||||
|
|
@ -148,7 +151,10 @@ class MainActivity : Activity() {
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (intent.getBooleanExtra("sync_only", false)) {
|
if (intent.getBooleanExtra("sync_only", false)) {
|
||||||
if (chain.last().height > 0) {
|
if (
|
||||||
|
successfulPeerSyncs > 0 &&
|
||||||
|
chain.last().height > 0
|
||||||
|
) {
|
||||||
setResult(
|
setResult(
|
||||||
RESULT_OK,
|
RESULT_OK,
|
||||||
android.content.Intent().putExtra(
|
android.content.Intent().putExtra(
|
||||||
|
|
@ -271,6 +277,7 @@ class MainActivity : Activity() {
|
||||||
var versionInfo: VersionInfo? = null
|
var versionInfo: VersionInfo? = null
|
||||||
var gotVerack = false
|
var gotVerack = false
|
||||||
var requested = false
|
var requested = false
|
||||||
|
var totalReceived = 0
|
||||||
val cachedBefore = chain.last().height
|
val cachedBefore = chain.last().height
|
||||||
|
|
||||||
repeat(200) {
|
repeat(200) {
|
||||||
|
|
@ -288,6 +295,8 @@ class MainActivity : Activity() {
|
||||||
|
|
||||||
"headers" -> {
|
"headers" -> {
|
||||||
val received = parseHeaders(message.second, chain)
|
val received = parseHeaders(message.second, chain)
|
||||||
|
totalReceived += received
|
||||||
|
|
||||||
val remote = versionInfo
|
val remote = versionInfo
|
||||||
?: throw IllegalStateException("missing node version")
|
?: throw IllegalStateException("missing node version")
|
||||||
|
|
||||||
|
|
@ -297,8 +306,25 @@ class MainActivity : Activity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chain.last().height < remote.height) {
|
||||||
|
if (received == 0) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"peer advertises height ${remote.height} " +
|
||||||
|
"but returned no additional headers"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(
|
||||||
|
output,
|
||||||
|
"getheaders",
|
||||||
|
createGetHeadersPayload(chain)
|
||||||
|
)
|
||||||
|
|
||||||
|
return@repeat
|
||||||
|
}
|
||||||
|
|
||||||
return SyncResult(
|
return SyncResult(
|
||||||
received = received,
|
received = totalReceived,
|
||||||
cachedBefore = cachedBefore,
|
cachedBefore = cachedBefore,
|
||||||
chainHeight = chain.last().height,
|
chainHeight = chain.last().height,
|
||||||
lastHash = chain.last().hashDisplay
|
lastHash = chain.last().hashDisplay
|
||||||
|
|
@ -310,7 +336,7 @@ class MainActivity : Activity() {
|
||||||
sendMessage(
|
sendMessage(
|
||||||
output,
|
output,
|
||||||
"getheaders",
|
"getheaders",
|
||||||
createGetHeadersPayload(chain.last().hashWire)
|
createGetHeadersPayload(chain)
|
||||||
)
|
)
|
||||||
requested = true
|
requested = true
|
||||||
}
|
}
|
||||||
|
|
@ -336,6 +362,8 @@ class MainActivity : Activity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val receivedHeaders = mutableListOf<ByteArray>()
|
||||||
|
|
||||||
repeat(count) {
|
repeat(count) {
|
||||||
if (buffer.remaining() < 81) {
|
if (buffer.remaining() < 81) {
|
||||||
throw IllegalStateException("truncated headers message")
|
throw IllegalStateException("truncated headers message")
|
||||||
|
|
@ -352,7 +380,33 @@ class MainActivity : Activity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendVerifiedHeader(chain, raw)
|
receivedHeaders.add(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedHeaders.isNotEmpty()) {
|
||||||
|
val firstPreviousHash =
|
||||||
|
receivedHeaders.first().copyOfRange(4, 36)
|
||||||
|
|
||||||
|
if (!firstPreviousHash.contentEquals(chain.last().hashWire)) {
|
||||||
|
val ancestorIndex =
|
||||||
|
chain.indexOfLast {
|
||||||
|
it.hashWire.contentEquals(firstPreviousHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ancestorIndex < 0) {
|
||||||
|
throw IllegalStateException(
|
||||||
|
"peer returned headers with no common ancestor"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
while (chain.lastIndex > ancestorIndex) {
|
||||||
|
chain.removeAt(chain.lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedHeaders.forEach { raw ->
|
||||||
|
appendVerifiedHeader(chain, raw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|
@ -510,12 +564,38 @@ class MainActivity : Activity() {
|
||||||
return compact
|
return compact
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createGetHeadersPayload(locatorHash: ByteArray): ByteArray {
|
private fun createGetHeadersPayload(chain: List<ChainHeader>): ByteArray {
|
||||||
val out = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
|
val locator = mutableListOf<ByteArray>()
|
||||||
|
|
||||||
|
var index = chain.lastIndex
|
||||||
|
var step = 1
|
||||||
|
|
||||||
|
while (index >= 0) {
|
||||||
|
locator.add(chain[index].hashWire)
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (index - step).coerceAtLeast(0)
|
||||||
|
|
||||||
|
if (locator.size >= 10) {
|
||||||
|
step *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!locator.last().contentEquals(chain.first().hashWire)) {
|
||||||
|
locator.add(chain.first().hashWire)
|
||||||
|
}
|
||||||
|
|
||||||
writeInt32LE(out, PROTOCOL_VERSION)
|
writeInt32LE(out, PROTOCOL_VERSION)
|
||||||
writeCompactSize(out, 1)
|
writeCompactSize(out, locator.size)
|
||||||
out.write(locatorHash)
|
|
||||||
|
locator.forEach { hash ->
|
||||||
|
out.write(hash)
|
||||||
|
}
|
||||||
|
|
||||||
out.write(ByteArray(32))
|
out.write(ByteArray(32))
|
||||||
|
|
||||||
return out.toByteArray()
|
return out.toByteArray()
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,11 @@ class WalletActivity : FragmentActivity() {
|
||||||
"background_transaction_history"
|
"background_transaction_history"
|
||||||
private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT =
|
private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT =
|
||||||
"last_background_scan_height"
|
"last_background_scan_height"
|
||||||
|
private const val PREF_LAST_HEADER_HEIGHT =
|
||||||
|
"last_header_height"
|
||||||
|
private const val PREF_BACKGROUND_TX_HISTORY_VERSION =
|
||||||
|
"background_transaction_history_version"
|
||||||
|
private const val BACKGROUND_TX_HISTORY_VERSION = 4
|
||||||
private const val PREF_FIAT_CURRENCY =
|
private const val PREF_FIAT_CURRENCY =
|
||||||
"preferred_fiat_currency"
|
"preferred_fiat_currency"
|
||||||
|
|
||||||
|
|
@ -147,6 +152,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
private var latestSpendableUtxos =
|
private var latestSpendableUtxos =
|
||||||
emptyList<BlockScanner.SpendableUtxo>()
|
emptyList<BlockScanner.SpendableUtxo>()
|
||||||
private var latestChainHeight = 0
|
private var latestChainHeight = 0
|
||||||
|
private var walletFiatRefresh: ((Long) -> Unit)? = null
|
||||||
|
|
||||||
private fun spendableUtxos(
|
private fun spendableUtxos(
|
||||||
utxos: List<BlockScanner.SpendableUtxo>,
|
utxos: List<BlockScanner.SpendableUtxo>,
|
||||||
|
|
@ -193,7 +199,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
latestChainHeight =
|
latestChainHeight =
|
||||||
getSharedPreferences(PREFS, MODE_PRIVATE)
|
getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||||
.getInt(
|
.getInt(
|
||||||
PREF_LAST_BACKGROUND_SCAN_HEIGHT,
|
PREF_LAST_HEADER_HEIGHT,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
.coerceAtLeast(0)
|
.coerceAtLeast(0)
|
||||||
|
|
@ -217,6 +223,10 @@ class WalletActivity : FragmentActivity() {
|
||||||
100_000_000.0
|
100_000_000.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
walletFiatRefresh?.invoke(
|
||||||
|
balanceSatoshis
|
||||||
|
)
|
||||||
|
|
||||||
walletHistoryView?.text =
|
walletHistoryView?.text =
|
||||||
formatTransactionHistory(
|
formatTransactionHistory(
|
||||||
cachedTransactions,
|
cachedTransactions,
|
||||||
|
|
@ -429,7 +439,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
mainWalletVisible &&
|
mainWalletVisible &&
|
||||||
!blockchainScanRunning
|
!blockchainScanRunning
|
||||||
) {
|
) {
|
||||||
startHeaderSyncAndScan()
|
startBackgroundHeaderSyncAndScan()
|
||||||
}
|
}
|
||||||
}, 400L)
|
}, 400L)
|
||||||
}
|
}
|
||||||
|
|
@ -870,12 +880,21 @@ class WalletActivity : FragmentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
blockchainScanRunning = true
|
blockchainScanRunning = true
|
||||||
|
blockchainScanStatus?.text =
|
||||||
|
"Synchronizing block headers..."
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
try {
|
try {
|
||||||
val chain = HeaderSync.loadCachedChain(this)
|
val chain = HeaderSync.loadCachedChain(this)
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
blockchainScanStatus?.text =
|
||||||
|
"Synchronizing block headers...\n" +
|
||||||
|
"Cached height: ${chain.last().height}"
|
||||||
|
}
|
||||||
|
|
||||||
val syncErrors = mutableListOf<String>()
|
val syncErrors = mutableListOf<String>()
|
||||||
|
var successfulPeerSyncs = 0
|
||||||
|
|
||||||
for (peer in listOf("87.106.99.23", "74.208.53.160")) {
|
for (peer in listOf("87.106.99.23", "74.208.53.160")) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -884,6 +903,14 @@ class WalletActivity : FragmentActivity() {
|
||||||
if (result.received > 0) {
|
if (result.received > 0) {
|
||||||
HeaderSync.saveChain(this, chain)
|
HeaderSync.saveChain(this, chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
successfulPeerSyncs++
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
blockchainScanStatus?.text =
|
||||||
|
"Header sync OK: ${result.chainHeight}\n" +
|
||||||
|
"Peer: $peer"
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
syncErrors.add(
|
syncErrors.add(
|
||||||
"$peer: ${e.message ?: e.javaClass.simpleName}"
|
"$peer: ${e.message ?: e.javaClass.simpleName}"
|
||||||
|
|
@ -891,7 +918,10 @@ class WalletActivity : FragmentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chain.last().height <= 0) {
|
if (
|
||||||
|
successfulPeerSyncs == 0 ||
|
||||||
|
chain.last().height <= 0
|
||||||
|
) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
blockchainScanRunning = false
|
blockchainScanRunning = false
|
||||||
blockchainScanStatus?.text =
|
blockchainScanStatus?.text =
|
||||||
|
|
@ -904,9 +934,12 @@ class WalletActivity : FragmentActivity() {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
runBlockchainScan()
|
runBlockchainScan()
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
blockchainScanRunning = false
|
blockchainScanRunning = false
|
||||||
|
blockchainScanStatus?.text =
|
||||||
|
"Background synchronization failed:\n" +
|
||||||
|
(e.message ?: e.javaClass.simpleName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
|
|
@ -947,6 +980,14 @@ class WalletActivity : FragmentActivity() {
|
||||||
-1
|
-1
|
||||||
) ?: -1
|
) ?: -1
|
||||||
|
|
||||||
|
if (height >= 0) {
|
||||||
|
latestChainHeight = height
|
||||||
|
getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putInt(PREF_LAST_HEADER_HEIGHT, height)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
blockchainScanStatus?.text = if (height >= 0) {
|
blockchainScanStatus?.text = if (height >= 0) {
|
||||||
"Headers synchronized to block $height.\n" +
|
"Headers synchronized to block $height.\n" +
|
||||||
"Scanning compact filters..."
|
"Scanning compact filters..."
|
||||||
|
|
@ -1073,7 +1114,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
try {
|
try {
|
||||||
val parts = value.split("|")
|
val parts = value.split("|")
|
||||||
|
|
||||||
if (parts.size != 4) {
|
if (parts.size != 5) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1090,6 +1131,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
parts[2].toLong()
|
parts[2].toLong()
|
||||||
},
|
},
|
||||||
blockTime = parts[3].toLong(),
|
blockTime = parts[3].toLong(),
|
||||||
|
isCoinbase = parts[4].toBooleanStrict(),
|
||||||
feeSatoshis = outgoing?.feeSatoshis,
|
feeSatoshis = outgoing?.feeSatoshis,
|
||||||
recipientAddress =
|
recipientAddress =
|
||||||
outgoing?.recipientAddress
|
outgoing?.recipientAddress
|
||||||
|
|
@ -1135,11 +1177,23 @@ class WalletActivity : FragmentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasCachedWalletState(): Boolean {
|
private fun hasCachedWalletState(): Boolean {
|
||||||
return getSharedPreferences(PREFS, MODE_PRIVATE)
|
val preferences =
|
||||||
.getInt(
|
getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||||
|
|
||||||
|
val scanHeight =
|
||||||
|
preferences.getInt(
|
||||||
PREF_LAST_BACKGROUND_SCAN_HEIGHT,
|
PREF_LAST_BACKGROUND_SCAN_HEIGHT,
|
||||||
0
|
0
|
||||||
) > 0
|
)
|
||||||
|
|
||||||
|
val cacheVersion =
|
||||||
|
preferences.getInt(
|
||||||
|
PREF_BACKGROUND_TX_HISTORY_VERSION,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
return scanHeight > 0 &&
|
||||||
|
cacheVersion == BACKGROUND_TX_HISTORY_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun preferredFiatCurrency(): String {
|
private fun preferredFiatCurrency(): String {
|
||||||
|
|
@ -1395,6 +1449,9 @@ class WalletActivity : FragmentActivity() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
blockTime = newest.blockTime,
|
blockTime = newest.blockTime,
|
||||||
|
isCoinbase = transactions.any {
|
||||||
|
it.isCoinbase
|
||||||
|
},
|
||||||
feeSatoshis =
|
feeSatoshis =
|
||||||
outgoing?.feeSatoshis,
|
outgoing?.feeSatoshis,
|
||||||
recipientAddress =
|
recipientAddress =
|
||||||
|
|
@ -1586,8 +1643,22 @@ class WalletActivity : FragmentActivity() {
|
||||||
1
|
1
|
||||||
).coerceAtLeast(1)
|
).coerceAtLeast(1)
|
||||||
|
|
||||||
"Block ${transaction.height}\n" +
|
if (transaction.isCoinbase) {
|
||||||
"Confirmations: $confirmations"
|
val maturityStatus =
|
||||||
|
if (confirmations >= COINBASE_MATURITY) {
|
||||||
|
"Coinbase reward — Mature"
|
||||||
|
} else {
|
||||||
|
"Coinbase reward — Immature"
|
||||||
|
}
|
||||||
|
|
||||||
|
"Block ${transaction.height}\n" +
|
||||||
|
"$maturityStatus\n" +
|
||||||
|
"Confirmations: $confirmations / " +
|
||||||
|
"$COINBASE_MATURITY"
|
||||||
|
} else {
|
||||||
|
"Block ${transaction.height}\n" +
|
||||||
|
"Confirmations: $confirmations"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"$direction $sign$amount VTX\n" +
|
"$direction $sign$amount VTX\n" +
|
||||||
|
|
@ -1637,7 +1708,14 @@ class WalletActivity : FragmentActivity() {
|
||||||
|
|
||||||
val details = StringBuilder()
|
val details = StringBuilder()
|
||||||
|
|
||||||
details.append("Type: $direction\n")
|
details.append(
|
||||||
|
"Type: " +
|
||||||
|
if (transaction.isCoinbase) {
|
||||||
|
"Coinbase reward\n"
|
||||||
|
} else {
|
||||||
|
"$direction\n"
|
||||||
|
}
|
||||||
|
)
|
||||||
details.append("Amount: $amount VTX\n")
|
details.append("Amount: $amount VTX\n")
|
||||||
details.append("Date & time: $dateTime\n")
|
details.append("Date & time: $dateTime\n")
|
||||||
|
|
||||||
|
|
@ -1645,9 +1723,30 @@ class WalletActivity : FragmentActivity() {
|
||||||
details.append("Status: Pending\n")
|
details.append("Status: Pending\n")
|
||||||
details.append("Confirmations: 0\n")
|
details.append("Confirmations: 0\n")
|
||||||
} else {
|
} else {
|
||||||
details.append("Status: Confirmed\n")
|
if (transaction.isCoinbase) {
|
||||||
details.append("Block: ${transaction.height}\n")
|
val mature =
|
||||||
details.append("Confirmations: $confirmations\n")
|
confirmations >= COINBASE_MATURITY
|
||||||
|
|
||||||
|
details.append(
|
||||||
|
"Status: " +
|
||||||
|
if (mature) {
|
||||||
|
"Mature\n"
|
||||||
|
} else {
|
||||||
|
"Immature\n"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
details.append("Block: ${transaction.height}\n")
|
||||||
|
details.append(
|
||||||
|
"Confirmations: $confirmations / " +
|
||||||
|
"$COINBASE_MATURITY\n"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
details.append("Status: Confirmed\n")
|
||||||
|
details.append("Block: ${transaction.height}\n")
|
||||||
|
details.append(
|
||||||
|
"Confirmations: $confirmations\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.feeSatoshis?.let { fee ->
|
transaction.feeSatoshis?.let { fee ->
|
||||||
|
|
@ -2144,7 +2243,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
latestChainHeight =
|
latestChainHeight =
|
||||||
getSharedPreferences(PREFS, MODE_PRIVATE)
|
getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||||
.getInt(
|
.getInt(
|
||||||
PREF_LAST_BACKGROUND_SCAN_HEIGHT,
|
PREF_LAST_HEADER_HEIGHT,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
.coerceAtLeast(0)
|
.coerceAtLeast(0)
|
||||||
|
|
@ -2337,30 +2436,37 @@ class WalletActivity : FragmentActivity() {
|
||||||
|
|
||||||
addView(alternativesContainer)
|
addView(alternativesContainer)
|
||||||
|
|
||||||
loadFiatValues(
|
walletFiatRefresh = {
|
||||||
cachedBalance
|
balanceSatoshis ->
|
||||||
) { values ->
|
loadFiatValues(
|
||||||
fiatValues = values
|
balanceSatoshis
|
||||||
|
) { values ->
|
||||||
|
fiatValues = values
|
||||||
|
|
||||||
preferredFiatView.text =
|
preferredFiatView.text =
|
||||||
preferredFiatText(
|
preferredFiatText(
|
||||||
alternativesContainer.visibility ==
|
alternativesContainer.visibility ==
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
)
|
)
|
||||||
|
|
||||||
alternativeViews.forEach {
|
alternativeViews.forEach {
|
||||||
(currency, view) ->
|
(currency, view) ->
|
||||||
values[currency]?.let {
|
values[currency]?.let {
|
||||||
value ->
|
value ->
|
||||||
view.text =
|
view.text =
|
||||||
formatFiatValue(
|
formatFiatValue(
|
||||||
currency,
|
currency,
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walletFiatRefresh?.invoke(
|
||||||
|
cachedBalance
|
||||||
|
)
|
||||||
|
|
||||||
addView(space(8))
|
addView(space(8))
|
||||||
addView(
|
addView(
|
||||||
TextView(this@WalletActivity).apply {
|
TextView(this@WalletActivity).apply {
|
||||||
|
|
@ -2422,7 +2528,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
symbol = "↻",
|
symbol = "↻",
|
||||||
label = "Scan"
|
label = "Scan"
|
||||||
) {
|
) {
|
||||||
startHeaderSyncAndScan()
|
startBackgroundHeaderSyncAndScan()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2527,7 +2633,7 @@ class WalletActivity : FragmentActivity() {
|
||||||
mainWalletVisible &&
|
mainWalletVisible &&
|
||||||
!blockchainScanRunning
|
!blockchainScanRunning
|
||||||
) {
|
) {
|
||||||
startHeaderSyncAndScan()
|
startBackgroundHeaderSyncAndScan()
|
||||||
}
|
}
|
||||||
}, 500L)
|
}, 500L)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,15 @@ class WalletMonitoringService : Service() {
|
||||||
"tx_notifications_initialized"
|
"tx_notifications_initialized"
|
||||||
private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT =
|
private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT =
|
||||||
"last_background_scan_height"
|
"last_background_scan_height"
|
||||||
|
private const val PREF_LAST_HEADER_HEIGHT =
|
||||||
|
"last_header_height"
|
||||||
private const val PREF_BACKGROUND_UTXO_PREFIX =
|
private const val PREF_BACKGROUND_UTXO_PREFIX =
|
||||||
"background_utxos_"
|
"background_utxos_"
|
||||||
private const val PREF_BACKGROUND_TX_HISTORY =
|
private const val PREF_BACKGROUND_TX_HISTORY =
|
||||||
"background_transaction_history"
|
"background_transaction_history"
|
||||||
private const val PREF_BACKGROUND_TX_HISTORY_VERSION =
|
private const val PREF_BACKGROUND_TX_HISTORY_VERSION =
|
||||||
"background_transaction_history_version"
|
"background_transaction_history_version"
|
||||||
private const val BACKGROUND_TX_HISTORY_VERSION = 2
|
private const val BACKGROUND_TX_HISTORY_VERSION = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
@ -97,10 +99,24 @@ class WalletMonitoringService : Service() {
|
||||||
try {
|
try {
|
||||||
val chain = HeaderSync.loadCachedChain(this)
|
val chain = HeaderSync.loadCachedChain(this)
|
||||||
|
|
||||||
|
val cachedTipHeightBeforeSync = chain.last().height
|
||||||
|
val cachedTipHashBeforeSync = chain.last().hashDisplay
|
||||||
|
var reorgDetected = false
|
||||||
|
|
||||||
for (peer in listOf("87.106.99.23", "74.208.53.160")) {
|
for (peer in listOf("87.106.99.23", "74.208.53.160")) {
|
||||||
try {
|
try {
|
||||||
val result = HeaderSync.syncFromPeer(peer, chain)
|
val result = HeaderSync.syncFromPeer(peer, chain)
|
||||||
|
|
||||||
|
if (cachedTipHeightBeforeSync > 0) {
|
||||||
|
val originalTipStillCanonical =
|
||||||
|
chain.getOrNull(cachedTipHeightBeforeSync)
|
||||||
|
?.hashDisplay == cachedTipHashBeforeSync
|
||||||
|
|
||||||
|
if (!originalTipStillCanonical) {
|
||||||
|
reorgDetected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result.received > 0) {
|
if (result.received > 0) {
|
||||||
HeaderSync.saveChain(this, chain)
|
HeaderSync.saveChain(this, chain)
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +138,13 @@ class WalletMonitoringService : Service() {
|
||||||
|
|
||||||
val localHeight = chain.last().height
|
val localHeight = chain.last().height
|
||||||
|
|
||||||
|
preferences.edit()
|
||||||
|
.putInt(
|
||||||
|
PREF_LAST_HEADER_HEIGHT,
|
||||||
|
localHeight
|
||||||
|
)
|
||||||
|
.apply()
|
||||||
|
|
||||||
val previousHeight =
|
val previousHeight =
|
||||||
preferences
|
preferences
|
||||||
.getInt(
|
.getInt(
|
||||||
|
|
@ -145,7 +168,8 @@ class WalletMonitoringService : Service() {
|
||||||
) == BACKGROUND_TX_HISTORY_VERSION
|
) == BACKGROUND_TX_HISTORY_VERSION
|
||||||
|
|
||||||
val fullScanRequired =
|
val fullScanRequired =
|
||||||
previousHeight <= 0 ||
|
reorgDetected ||
|
||||||
|
previousHeight <= 0 ||
|
||||||
previousHeight > localHeight ||
|
previousHeight > localHeight ||
|
||||||
!backgroundCacheAvailable ||
|
!backgroundCacheAvailable ||
|
||||||
!transactionHistoryCurrent
|
!transactionHistoryCurrent
|
||||||
|
|
@ -226,7 +250,10 @@ class WalletMonitoringService : Service() {
|
||||||
netSatoshis = transactions.sumOf {
|
netSatoshis = transactions.sumOf {
|
||||||
it.netSatoshis
|
it.netSatoshis
|
||||||
},
|
},
|
||||||
blockTime = newest.blockTime
|
blockTime = newest.blockTime,
|
||||||
|
isCoinbase = transactions.any {
|
||||||
|
it.isCoinbase
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.filter { it.netSatoshis != 0L }
|
.filter { it.netSatoshis != 0L }
|
||||||
|
|
@ -308,7 +335,8 @@ class WalletMonitoringService : Service() {
|
||||||
transaction.txid,
|
transaction.txid,
|
||||||
transaction.height.toString(),
|
transaction.height.toString(),
|
||||||
transaction.netSatoshis.toString(),
|
transaction.netSatoshis.toString(),
|
||||||
transaction.blockTime.toString()
|
transaction.blockTime.toString(),
|
||||||
|
transaction.isCoinbase.toString()
|
||||||
).joinToString("|")
|
).joinToString("|")
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
|
|
@ -325,7 +353,7 @@ class WalletMonitoringService : Service() {
|
||||||
try {
|
try {
|
||||||
val parts = value.split("|")
|
val parts = value.split("|")
|
||||||
|
|
||||||
if (parts.size != 4) {
|
if (parts.size != 5) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,7 +361,8 @@ class WalletMonitoringService : Service() {
|
||||||
txid = parts[0],
|
txid = parts[0],
|
||||||
height = parts[1].toInt(),
|
height = parts[1].toInt(),
|
||||||
netSatoshis = parts[2].toLong(),
|
netSatoshis = parts[2].toLong(),
|
||||||
blockTime = parts[3].toLong()
|
blockTime = parts[3].toLong(),
|
||||||
|
isCoinbase = parts[4].toBooleanStrict()
|
||||||
)
|
)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue