fix: some fixes for stored silent payment data, and fee usage

This commit is contained in:
Rafael Saes 2023-12-08 17:56:48 -03:00
parent ea2161010f
commit c74e335876
4 changed files with 149 additions and 50 deletions

View file

@ -8,16 +8,30 @@ class BitcoinUnspent extends Unspent {
: bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord? address, Map<String, dynamic> json) =>
BitcoinUnspent(
address,
address ?? BitcoinAddressRecord.fromJSON(json['address_record'] as String),
json['tx_hash'] as String,
json['value'] as int,
json['tx_pos'] as int,
silentPaymentTweak: json['silent_payment_tweak'] as String?,
type: json['type'] == null ? null : AddressType.values[json['type'] as int],
type: json['type'] == null
? null
: AddressType.values.firstWhere((e) => e.toString() == json['type']),
);
Map<String, dynamic> toJson() {
final json = <String, dynamic>{
'address_record': bitcoinAddressRecord.toJSON(),
'tx_hash': hash,
'value': value,
'tx_pos': vout,
'silent_payment_tweak': silentPaymentTweak,
'type': type.toString(),
};
return json;
}
final BitcoinAddressRecord bitcoinAddressRecord;
String? silentPaymentTweak;
AddressType? type;

View file

@ -3,6 +3,7 @@ import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart';
@ -18,6 +19,8 @@ class ElectrumTransactionBundle {
}
class ElectrumTransactionInfo extends TransactionInfo {
BitcoinUnspent? unspent;
ElectrumTransactionInfo(this.type,
{required String id,
required int height,
@ -26,7 +29,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
required TransactionDirection direction,
required bool isPending,
required DateTime date,
required int confirmations}) {
required int confirmations,
String? to,
this.unspent}) {
this.id = id;
this.height = height;
this.amount = amount;
@ -35,6 +40,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.date = date;
this.isPending = isPending;
this.confirmations = confirmations;
this.to = to;
}
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
@ -168,7 +174,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
}
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type,
return ElectrumTransactionInfo(
type,
id: data['id'] as String,
height: data['height'] as int,
amount: data['amount'] as int,
@ -176,7 +183,12 @@ class ElectrumTransactionInfo extends TransactionInfo {
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int);
confirmations: data['confirmations'] as int,
to: data['to'] as String?,
unspent: data['unspent'] != null
? BitcoinUnspent.fromJSON(null, data['unspent'] as Map<String, dynamic>)
: null,
);
}
final WalletType type;
@ -220,6 +232,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['isPending'] = isPending;
m['confirmations'] = confirmations;
m['fee'] = fee;
m['to'] = to;
m['unspent'] = unspent?.toJson() ?? <String, dynamic>{};
return m;
}
}

View file

@ -4,6 +4,8 @@ import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:http/http.dart' as http;
import 'package:cw_core/unspent_coins_info.dart';
import 'package:hive/hive.dart';
@ -165,10 +167,7 @@ abstract class ElectrumWalletBase
startRefresh,
ScanData(
sendPort: receivePort.sendPort,
scanPrivkeyCompressed:
walletAddresses.primarySilentAddress!.scanPrivkey.toCompressedHex().fromHex,
spendPubkeyCompressed:
walletAddresses.primarySilentAddress!.spendPubkey.toCompressedHex().fromHex,
primarySilentAddress: walletAddresses.primarySilentAddress!,
networkType: networkType,
height: height,
chainTip: currentChainTip,
@ -180,13 +179,31 @@ abstract class ElectrumWalletBase
await for (var message in receivePort) {
if (message is BitcoinUnspent) {
if (!unspentCoins.any((utx) =>
utx.hash.contains(message.hash) &&
utx.vout == message.vout &&
utx.address.contains(message.address))) {
unspentCoins.add(message);
await _addCoinInfo(message);
balance[currency] = await _fetchBalances();
if (unspentCoinsInfo.values.any((element) =>
element.walletId.contains(id) &&
element.hash.contains(message.hash) &&
element.address.contains(message.address))) {
_addCoinInfo(message);
await walletInfo.save();
await save();
}
balance[currency] = await _fetchBalances();
}
}
if (message is Map<String, ElectrumTransactionInfo>) {
transactionHistory.addMany(message);
await transactionHistory.save();
}
// check if is a SyncStatus type since "is SyncStatus" doesn't work here
if (message is SyncResponse) {
syncStatus = message.syncStatus;
@ -268,7 +285,7 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionNoInputsException();
}
final minAmount = networkType == bitcoin.testnet ? 0 : 546;
final minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final outputs = transactionCredentials.outputs;
final hasMultiDestination = outputs.length > 1;
@ -284,7 +301,7 @@ abstract class ElectrumWalletBase
var fee = 0;
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 1)) {
throw BitcoinTransactionWrongBalanceException(currency);
}
@ -324,15 +341,10 @@ abstract class ElectrumWalletBase
}
}
if (fee == 0 && networkType == bitcoin.bitcoin) {
if (fee == 0) {
throw BitcoinTransactionWrongBalanceException(currency);
}
if (networkType == bitcoin.testnet) {
fee += 50;
amount -= 50;
}
final totalAmount = amount + fee;
if ((totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) &&
@ -656,6 +668,18 @@ abstract class ElectrumWalletBase
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> updateUnspent() async {
// Update unspents stored from scanned silent payment transactions
transactionHistory.transactions.values.forEach((tx) {
if (tx.unspent != null) {
if (!unspentCoins.any((utx) =>
utx.hash.contains(tx.unspent!.hash) &&
utx.vout == tx.unspent!.vout &&
utx.address.contains(tx.to!))) {
unspentCoins.add(tx.unspent!);
}
}
});
final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent.map((unspent) {
@ -763,6 +787,7 @@ abstract class ElectrumWalletBase
}
});
});
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
try {
return fetchTransactionInfo(
@ -776,12 +801,25 @@ abstract class ElectrumWalletBase
return Future.value(null);
}
}));
transactionHistory.transactions.values.forEach((tx) {
if (tx.direction == TransactionDirection.incoming &&
!walletAddresses.addresses.any((addr) => tx.to?.contains(addr.address) ?? false))
historiesWithDetails.add(tx);
});
return historiesWithDetails
.fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
if (tx == null) {
return acc;
}
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
if (tx.to != null && tx.to!.isNotEmpty && acc[tx.id] != null) {
final updatedConf = acc[tx.id]!;
updatedConf.confirmations = walletInfo.restoreHeight - tx.height;
acc[tx.id] = updatedConf;
}
return acc;
});
}
@ -824,6 +862,7 @@ abstract class ElectrumWalletBase
}
});
});
await _chainTipUpdateSubject?.close();
_chainTipUpdateSubject = electrumClient.chainTipUpdate();
_chainTipUpdateSubject?.listen((_) async {
@ -856,15 +895,18 @@ abstract class ElectrumWalletBase
var totalConfirmed = 0;
var totalUnconfirmed = 0;
// Add values from unspent coins that are not fetched by the address list
// i.e. scanned silent payments
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
if (info.isFrozen) totalFrozen += element.value;
if (element.bitcoinAddressRecord.silentPaymentTweak != null)
if (element.bitcoinAddressRecord.silentPaymentTweak != null) {
totalConfirmed += element.value;
}
}
});
});
@ -982,8 +1024,7 @@ Future<ElectrumTransactionBundle> getTransactionExpanded(
class ScanData {
final SendPort sendPort;
final Uint8List scanPrivkeyCompressed;
final Uint8List spendPubkeyCompressed;
final bitcoin.SilentPaymentReceiver primarySilentAddress;
final int height;
final String node;
final bitcoin.NetworkType networkType;
@ -994,8 +1035,7 @@ class ScanData {
ScanData({
required this.sendPort,
required this.scanPrivkeyCompressed,
required this.spendPubkeyCompressed,
required this.primarySilentAddress,
required this.height,
required this.node,
required this.networkType,
@ -1008,8 +1048,7 @@ class ScanData {
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
return ScanData(
sendPort: scanData.sendPort,
scanPrivkeyCompressed: scanData.scanPrivkeyCompressed,
spendPubkeyCompressed: scanData.spendPubkeyCompressed,
primarySilentAddress: scanData.primarySilentAddress,
height: newHeight,
node: scanData.node,
networkType: scanData.networkType,
@ -1179,10 +1218,10 @@ Future<void> startRefresh(ScanData scanData) async {
// break;
// }
// final p2tr = bitcoin.P2trAddress(program: script.sublist(2).hex);
// final address = p2tr.toAddress(scanData.networkType);
final p2tr = bitcoin.P2trAddress(program: script.sublist(2).hex);
final address = p2tr.toAddress(scanData.networkType);
// print(["Verifying taproot address:", address]);
print(["Verifying taproot address:", address]);
outpointsByP2TRpubkey[script.sublist(2).hex] =
bitcoin.Outpoint(txid: txid, index: i, value: output["value"] as int);
@ -1198,8 +1237,10 @@ Future<void> startRefresh(ScanData scanData) async {
final curve = bitcoin.getSecp256k1();
final result = bitcoin.scanOutputs(
bitcoin.PrivateKey.fromHex(curve, scanData.scanPrivkeyCompressed.hex),
bitcoin.PublicKey.fromHex(curve, scanData.spendPubkeyCompressed.hex),
bitcoin.PrivateKey.fromHex(
curve, scanData.primarySilentAddress.scanPrivkey.toCompressedHex()),
bitcoin.PublicKey.fromHex(
curve, scanData.primarySilentAddress.spendPubkey.toCompressedHex()),
bitcoin.getSumInputPubKeys(pubkeys),
outpointHash,
outpointsByP2TRpubkey.keys.map((e) => e.fromHex).toList(),
@ -1225,24 +1266,54 @@ Future<void> startRefresh(ScanData scanData) async {
return;
}
// found utxo for tx
scanData.sendPort.send(BitcoinUnspent(
final tweak = value[0];
String? label;
if (value.length > 1) label = value[1];
final unspent = BitcoinUnspent(
BitcoinAddressRecord(
bitcoin.P2trAddress(program: key, network: scanData.networkType)
.toAddress(scanData.networkType),
index: 0,
isHidden: false,
isHidden: true,
isUsed: true,
silentAddressLabel: null,
silentPaymentTweak: value,
silentPaymentTweak: tweak,
type: bitcoin.AddressType.p2tr,
),
outpoint.txid,
txid,
outpoint.value!,
outpoint.index,
silentPaymentTweak: value,
silentPaymentTweak: tweak,
type: bitcoin.AddressType.p2tr,
));
);
// found utxo for tx, send unspent coin to main isolate
scanData.sendPort.send(unspent);
// also send tx data for tx history
scanData.sendPort.send({
txid: ElectrumTransactionInfo(
WalletType.bitcoin,
id: txid,
height: syncHeight,
amount: outpoint.value!,
fee: 0,
direction: TransactionDirection.incoming,
isPending: false,
date:
DateTime.fromMillisecondsSinceEpoch((blockJson["timestamp"] as int) * 1000),
confirmations: currentChainTip - syncHeight,
to: bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress(
scanData.primarySilentAddress.scanPubkey,
scanData.primarySilentAddress.spendPubkey,
label != null ? label.fromHex : "0".fromHex,
hrp: scanData.primarySilentAddress.hrp,
version: scanData.primarySilentAddress.version)
.toString(),
unspent: unspent,
)
});
});
} catch (_) {}
}

View file

@ -743,7 +743,7 @@
"unspent_change": "Mudar",
"Block_remaining": "${status} bloco restante",
"labeled_silent_addresses": "Endereços silenciosos rotulados",
"use_testnet": "Use testNet",
"use_testnet": "Use Testnet",
"tor_connection": "Conexão Tor",
"seed_hex_form": "Semente de carteira (forma hexadecimal)",
"seedtype": "SeedType",