feat(app): flag transactions above the newest checkpoint as not anchored
Blocks mined after the newest hardcoded checkpoint cannot be chained to it, so only the Merkle proof against the server's header backs them. The history list and the transaction detail now show a 'not anchored' label for those transactions. Informational only: spending is not blocked. See ADR 0041.
This commit is contained in:
1 parent
24368a07b5
commit
305f22ed0e
13 files changed
+119
-7
No files matched your search
@@ -436,6 +436,8 @@ public sealed class Loc
|
||||
["msg.notspendable"] = ["non ancora spendibile", "not yet spendable", "aún no gastable", "pas encore dépensable", "ainda não gastável", "noch nicht verwendbar", "尚不可花费"],
|
||||
["msg.immature"] = ["in maturazione", "maturing", "en maduración", "en maturation", "em maturação", "in Reifung", "成熟中"],
|
||||
["msg.verifying"] = ["in verifica SPV", "SPV-verifying", "en verificación SPV", "en cours de vérification SPV", "em verificação SPV", "SPV-Prüfung läuft", "正在进行 SPV 验证"],
|
||||
["history.unanchored"] = ["non ancorata", "not anchored", "sin anclar", "non ancrée", "não ancorada", "nicht verankert", "未锚定"],
|
||||
["history.unanchored.tip"] = ["Blocco successivo all'ultimo checkpoint della versione: la sua intestazione e la prova Merkle arrivano dal server e non si collegano a un checkpoint. Aggiorna il wallet per ancorarla.", "Block after the newest checkpoint of this version: its header and Merkle proof come from the server and do not chain to a checkpoint. Update the wallet to anchor it.", "Bloque posterior al último punto de control de esta versión: su cabecera y la prueba Merkle vienen del servidor y no se enlazan a un punto de control. Actualiza la cartera para anclarlo.", "Bloc postérieur au dernier point de contrôle de cette version : son en-tête et la preuve Merkle viennent du serveur et ne se rattachent à aucun point de contrôle. Mettez le portefeuille à jour pour l'ancrer.", "Bloco posterior ao último checkpoint desta versão: o cabeçalho e a prova Merkle vêm do servidor e não se ligam a um checkpoint. Atualize a carteira para ancorá-lo.", "Block nach dem neuesten Checkpoint dieser Version: Header und Merkle-Beweis stammen vom Server und sind an keinen Checkpoint gekettet. Aktualisieren Sie die Wallet, um ihn zu verankern.", "此版本最新检查点之后的区块:其区块头和 Merkle 证明来自服务器,未链接到任何检查点。请更新钱包以完成锚定。"],
|
||||
["history.unverified"] = ["in verifica…", "verifying…", "verificando…", "vérification…", "verificando…", "wird geprüft…", "验证中…"],
|
||||
["msg.settings.saved"] = ["Impostazioni salvate.", "Settings saved.", "Configuración guardada.", "Paramètres enregistrés.", "Configurações salvas.", "Einstellungen gespeichert.", "设置已保存。"],
|
||||
["msg.certreset"] = [
|
||||
|
||||
@@ -240,6 +240,7 @@ public partial class MainWindowViewModel
|
||||
var transactions = _lastTransactions;
|
||||
var loc = _loc;
|
||||
var unit = _config.Unit;
|
||||
var unanchored = Profile.IsAboveNewestCheckpoint(height);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -247,7 +248,7 @@ public partial class MainWindowViewModel
|
||||
{
|
||||
var details = await TransactionInspector.FetchAsync(
|
||||
client, network, txid, tipHeight, height, owned, delta, verified, transactions, ct);
|
||||
return new TransactionDetailsViewModel(details, loc, unit);
|
||||
return new TransactionDetailsViewModel(details, loc, unit, unanchored);
|
||||
}, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -310,7 +311,8 @@ public partial class MainWindowViewModel
|
||||
tx.Height > 0 ? tx.Height.ToString() : "mempool",
|
||||
(tx.DeltaSats >= 0 ? "+" : "") + Fmt(tx.DeltaSats, withLabel: false),
|
||||
tx.Txid,
|
||||
tx.Verified));
|
||||
tx.Verified,
|
||||
Profile.IsAboveNewestCheckpoint(tx.Height)));
|
||||
|
||||
Addresses.Clear();
|
||||
foreach (var a in cache.Addresses)
|
||||
|
||||
@@ -17,7 +17,8 @@ using PalladiumWallet.Core.Wallet;
|
||||
namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
/// <summary>Transaction history row for the view.</summary>
|
||||
public sealed record HistoryRow(string Conferma, string Importo, string Txid, bool Verified = true);
|
||||
public sealed record HistoryRow(string Conferma, string Importo, string Txid, bool Verified = true,
|
||||
bool Unanchored = false);
|
||||
|
||||
/// <summary>
|
||||
/// Address view row: public data and derivation path only. The private key is never held here;
|
||||
|
||||
@@ -21,14 +21,14 @@ public sealed class TransactionDetailsViewModel
|
||||
|
||||
public Loc Loc { get; }
|
||||
|
||||
public TransactionDetailsViewModel(TransactionDetails d, Loc loc, string unit)
|
||||
public TransactionDetailsViewModel(TransactionDetails d, Loc loc, string unit, bool unanchored = false)
|
||||
{
|
||||
Loc = loc;
|
||||
_unit = unit;
|
||||
|
||||
Txid = d.Txid;
|
||||
IsCoinbase = d.IsCoinbase;
|
||||
StatusText = BuildStatus(d, loc);
|
||||
StatusText = BuildStatus(d, loc) + (unanchored ? $" — {loc["history.unanchored"]}" : "");
|
||||
DateText = d.BlockTime is { } t
|
||||
? t.ToLocalTime().ToString("dd/MM/yyyy HH:mm")
|
||||
: loc["tx.mempool"];
|
||||
|
||||
@@ -402,6 +402,10 @@
|
||||
<TextBlock Grid.Column="3" Text="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).Loc[history.unverified]}"
|
||||
Foreground="#FCD34D" FontSize="11" Margin="6,0,0,0"
|
||||
IsVisible="{Binding !Verified}"/>
|
||||
<TextBlock Grid.Column="3" Text="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).Loc[history.unanchored]}"
|
||||
Foreground="#FB923C" FontSize="11" Margin="6,0,0,0"
|
||||
ToolTip.Tip="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).Loc[history.unanchored.tip]}"
|
||||
IsVisible="{Binding Unanchored}"/>
|
||||
</Grid>
|
||||
<!-- Mobile: vertical card -->
|
||||
<StackPanel Spacing="2"
|
||||
@@ -414,6 +418,9 @@
|
||||
<TextBlock Text="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).Loc[history.unverified]}"
|
||||
Foreground="#FCD34D" FontSize="11"
|
||||
IsVisible="{Binding !Verified}"/>
|
||||
<TextBlock Text="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).Loc[history.unanchored]}"
|
||||
Foreground="#FB923C" FontSize="11"
|
||||
IsVisible="{Binding Unanchored}"/>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -140,4 +140,13 @@ public sealed record ChainProfile
|
||||
|
||||
/// <summary>Hardcoded checkpoints, updated at every release (§7.3).</summary>
|
||||
public required IReadOnlyList<Checkpoint> Checkpoints { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// True for a block above the newest checkpoint of a profile that has checkpoints: its header
|
||||
/// cannot be anchored (ADR 0040), so only the server-supplied header and the Merkle proof back
|
||||
/// it. False for confirmed history the anchoring covers, and for profiles without any
|
||||
/// checkpoint (testnet/regtest), where nothing is anchored by design and the notion does not apply.
|
||||
/// </summary>
|
||||
public bool IsAboveNewestCheckpoint(int height) =>
|
||||
height > 0 && Checkpoints.Count > 0 && height > Checkpoints.Max(c => c.Height);
|
||||
}
|
||||
Reference in new issue
Block a user