From c1dea2d1cda0a8e4dbdd434d1c6f882189d5b0d1 Mon Sep 17 00:00:00 2001 From: Andrew Steele Date: Mon, 29 Jul 2019 15:54:47 -0700 Subject: [PATCH] Add forced switch to nano prefix. Updates to pending tx detection. --- src/app/app.component.html | 2 +- src/app/app.component.ts | 22 ++++- src/app/components/send/send.component.html | 14 +++- src/app/components/send/send.component.ts | 9 +- src/app/services/address-book.service.ts | 18 ++++ src/app/services/app-settings.service.ts | 3 + src/app/services/nano-block.service.ts | 91 ++++++++++++++++++++- src/app/services/representative.service.ts | 18 ++++ src/app/services/util.service.ts | 2 +- src/app/services/wallet.service.ts | 61 ++++++++++++-- 10 files changed, 227 insertions(+), 13 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 0c2aa066..6b798028 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -34,7 +34,7 @@ -
+
Pending {{ wallet.pendingFiat | fiat: settings.settings.displayCurrency }} {{ wallet.pending | rai: settings.settings.displayDenomination }} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 25184451..f17c4ac3 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -49,8 +49,13 @@ export class AppComponent implements OnInit { async ngOnInit() { this.windowHeight = window.innerHeight; this.settings.loadAppSettings(); + + // New for v19: Patch saved xrb_ prefixes to nano_ + await this.patchXrbToNanoPrefixData(); + this.addressBook.loadAddressBook(); this.workPool.loadWorkCache(); + await this.walletService.loadStoredWallet(); this.websocket.connect(); @@ -107,6 +112,21 @@ export class AppComponent implements OnInit { } + /* + This is important as it looks through saved data using hardcoded xrb_ prefixes + (Your wallet, address book, rep list, etc) and updates them to nano_ prefix for v19 RPC + */ + async patchXrbToNanoPrefixData() { + // If wallet is version 2, data has already been patched. Otherwise, patch all data + if (this.settings.settings.walletVersion >= 2) return; + + await this.walletService.patchOldSavedData(); // Change saved xrb_ addresses to nano_ + this.addressBook.patchXrbPrefixData(); + this.representative.patchXrbPrefixData(); + + this.settings.setAppSetting('walletVersion', 2); // Update wallet version so we do not patch in the future. + } + toggleSearch(mobile = false) { this.showSearchBar = !this.showSearchBar; if (this.showSearchBar) { @@ -118,7 +138,7 @@ export class AppComponent implements OnInit { const searchData = this.searchData.trim(); if (!searchData.length) return; - if (searchData.startsWith('xrb_')) { + if (searchData.startsWith('xrb_') || searchData.startsWith('nano_')) { this.router.navigate(['account', searchData]); } else if (searchData.length === 64) { this.router.navigate(['transaction', searchData]); diff --git a/src/app/components/send/send.component.html b/src/app/components/send/send.component.html index 2e02167d..43cb631b 100644 --- a/src/app/components/send/send.component.html +++ b/src/app/components/send/send.component.html @@ -26,7 +26,7 @@

Send Nano

- +
    @@ -184,6 +184,18 @@

    Send Nano

+ +
+
+
+ You are sending + {{ rawAmount | rai: 'mnano' }} + +{{ amountRaw.toString(10) }} raw + {{ amountFiat | fiat: settings.settings.displayCurrency }} @ {{ price.price.lastPrice | fiat: settings.settings.displayCurrency }} / NANO +
+
+
+
diff --git a/src/app/components/send/send.component.ts b/src/app/components/send/send.component.ts index 8d16632f..3d25fbd6 100644 --- a/src/app/components/send/send.component.ts +++ b/src/app/components/send/send.component.ts @@ -188,7 +188,7 @@ export class SendComponent implements OnInit { if (this.amount < 0 || rawAmount.lessThan(0)) return this.notificationService.sendWarning(`Amount is invalid`); if (nanoAmount.lessThan(1)) return this.notificationService.sendWarning(`Transactions for less than 1 nano will be ignored by the node. Send raw amounts with at least 1 nano.`); - if (from.balanceBN.minus(rawAmount).lessThan(0)) return this.notificationService.sendError(`From account does not have enough XRB`); + if (from.balanceBN.minus(rawAmount).lessThan(0)) return this.notificationService.sendError(`From account does not have enough NANO`); // Determine a proper raw amount to show in the UI, if a decimal was entered this.amountRaw = this.rawAmount.mod(this.nano); @@ -212,6 +212,13 @@ export class SendComponent implements OnInit { this.confirmingTransaction = true; try { + // New stuff to show status of each part of the transaction. + // console.log('Sending sub send command....'); + // this.nanoBlock.subscribeSend(walletAccount, this.toAccountID, this.rawAmount, this.walletService.isLedgerWallet()).subscribe(value => { + // console.log('GOT VALUE!!! ', value) + // }, err => { + // console.log('GOT ERROR!!!: ', err); + // }); const newHash = await this.nanoBlock.generateSend(walletAccount, this.toAccountID, this.rawAmount, this.walletService.isLedgerWallet()); if (newHash) { this.notificationService.sendSuccess(`Successfully sent ${this.amount} ${this.selectedAmount.shortName}!`); diff --git a/src/app/services/address-book.service.ts b/src/app/services/address-book.service.ts index e4ae737f..dd0cdf75 100644 --- a/src/app/services/address-book.service.ts +++ b/src/app/services/address-book.service.ts @@ -30,6 +30,24 @@ export class AddressBookService { return this.addressBook; } + patchXrbPrefixData() { + const addressBookStore = localStorage.getItem(this.storeKey); + if (!addressBookStore) return; + + const addressBook = JSON.parse(addressBookStore); + + const newAddressBook = addressBook.map(entry => { + if (entry.account.indexOf('xrb_') !== -1) { + entry.account = entry.account.replace('xrb_', 'nano_'); + } + return entry; + }); + + localStorage.setItem(this.storeKey, JSON.stringify(newAddressBook)); + + return true; + } + async saveAddress(account, name) { const existingName = this.addressBook.find(a => a.name.toLowerCase() === name.toLowerCase()); if (existingName) throw new Error(`Name already exists in the address book`); diff --git a/src/app/services/app-settings.service.ts b/src/app/services/app-settings.service.ts index 50d38639..2cd44fa7 100644 --- a/src/app/services/app-settings.service.ts +++ b/src/app/services/app-settings.service.ts @@ -18,6 +18,7 @@ interface AppSettings { serverNode: string | null; serverWS: string | null; minimumReceive: string | null; + walletVersion: number | null; } @Injectable() @@ -38,6 +39,7 @@ export class AppSettingsService { serverNode: null, serverWS: null, minimumReceive: null, + walletVersion: 1 }; constructor() { } @@ -91,6 +93,7 @@ export class AppSettingsService { serverAPI: null, serverWS: null, minimumReceive: null, + walletVersion: 1, }; } diff --git a/src/app/services/nano-block.service.ts b/src/app/services/nano-block.service.ts index ffc0a3c5..34d5bbb8 100644 --- a/src/app/services/nano-block.service.ts +++ b/src/app/services/nano-block.service.ts @@ -8,13 +8,14 @@ import {NotificationService} from "./notification.service"; import {AppSettingsService} from "./app-settings.service"; import {WalletService} from "./wallet.service"; import {LedgerService} from "./ledger.service"; +import {Observable} from "rxjs/Observable"; const nacl = window['nacl']; const STATE_BLOCK_PREAMBLE = '0000000000000000000000000000000000000000000000000000000000000006'; @Injectable() export class NanoBlockService { - representativeAccount = 'xrb_3rw4un6ys57hrb39sy1qx8qy5wukst1iiponztrz9qiz6qqa55kxzx4491or'; // NanoVault Representative + representativeAccount = 'nano_3rw4un6ys57hrb39sy1qx8qy5wukst1iiponztrz9qiz6qqa55kxzx4491or'; // NanoVault Representative constructor( private api: ApiService, @@ -83,6 +84,94 @@ export class NanoBlockService { } } + // This might be used in the future to send state changes on the blocks instead of normal true/false + // subscribeSend(walletAccount, toAccountID, rawAmount, ledger = false): Observable { + // const doSend = async (observable) => { + // console.log(`OBS: Promise resolve, running main send logic.`); + // const startTime = Date.now(); + // + // console.log(`Observable: Creation event run`); + // observable.next({ step: 0, startTime: startTime }); + // + // + // const fromAccount = await this.api.accountInfo(walletAccount.id); + // if (!fromAccount) throw new Error(`Unable to get account information for ${walletAccount.id}`); + // + // const remaining = new BigNumber(fromAccount.balance).minus(rawAmount); + // const remainingDecimal = remaining.toString(10); + // let remainingPadded = remaining.toString(16); + // while (remainingPadded.length < 32) remainingPadded = '0' + remainingPadded; // Left pad with 0's + // + // let blockData; + // const representative = fromAccount.representative || (this.settings.settings.defaultRepresentative || this.representativeAccount); + // + // observable.next({ step: 1, startTime: startTime, eventTime: ((Date.now() - startTime) / 1000).toFixed(3) }); + // + // let signature = null; + // if (ledger) { + // const ledgerBlock = { + // previousBlock: fromAccount.frontier, + // representative: representative, + // balance: remainingDecimal, + // recipient: toAccountID, + // }; + // try { + // this.sendLedgerNotification(); + // await this.ledgerService.updateCache(walletAccount.index, fromAccount.frontier); + // const sig = await this.ledgerService.signBlock(walletAccount.index, ledgerBlock); + // this.clearLedgerNotification(); + // signature = sig.signature; + // + // observable.next({ step: 2, startTime: startTime, eventTime: ((Date.now() - startTime) / 1000).toFixed(3) }); + // } catch (err) { + // this.clearLedgerNotification(); + // this.sendLedgerDeniedNotification(err); + // return; + // } + // } else { + // signature = this.signSendBlock(walletAccount, fromAccount, representative, remainingPadded, toAccountID); + // observable.next({ step: 2, startTime: startTime, eventTime: ((Date.now() - startTime) / 1000).toFixed(3) }); + // } + // + // if (!this.workPool.workExists(fromAccount.frontier)) { + // this.notifications.sendInfo(`Generating Proof of Work...`); + // } + // + // blockData = { + // type: 'state', + // account: walletAccount.id, + // previous: fromAccount.frontier, + // representative: representative, + // balance: remainingDecimal, + // link: this.util.account.getAccountPublicKey(toAccountID), + // work: await this.workPool.getWork(fromAccount.frontier), + // signature: signature, + // }; + // + // observable.next({ step: 3, startTime: startTime, eventTime: ((Date.now() - startTime) / 1000).toFixed(3) }); + // + // const processResponse = await this.api.process(blockData); + // if (!processResponse || !processResponse.hash) throw new Error(processResponse.error || `Node returned an error`); + // + // observable.next({ step: 4, startTime: startTime, eventTime: ((Date.now() - startTime) / 1000).toFixed(3) }); + // + // walletAccount.frontier = processResponse.hash; + // this.workPool.addWorkToCache(processResponse.hash); // Add new hash into the work pool + // this.workPool.removeFromCache(fromAccount.frontier); + // + // observable.complete(); + // }; + // + // + // console.log(`Creating observable... on send...`); + // // Create an observable that can be returned instantly. + // return new Observable(observable => { + // + // doSend(observable).then(val => console.log(val)); + // }); + // + // } + async generateSend(walletAccount, toAccountID, rawAmount, ledger = false) { const fromAccount = await this.api.accountInfo(walletAccount.id); if (!fromAccount) throw new Error(`Unable to get account information for ${walletAccount.id}`); diff --git a/src/app/services/representative.service.ts b/src/app/services/representative.service.ts index f6ecd34a..5550b927 100644 --- a/src/app/services/representative.service.ts +++ b/src/app/services/representative.service.ts @@ -242,6 +242,24 @@ export class RepresentativeService { return list; } + patchXrbPrefixData() { + const representativeStore = localStorage.getItem(this.storeKey); + if (!representativeStore) return; + + const list = JSON.parse(representativeStore); + + const newRepList = list.map(entry => { + if (entry.id.indexOf('xrb_') !== -1) { + entry.id = entry.id.replace('xrb_', 'nano_'); + } + return entry; + }); + + localStorage.setItem(this.storeKey, JSON.stringify(newRepList)); + + return true; + } + getRepresentative(id): StoredRepresentative | undefined { return this.representatives.find(rep => rep.id == id); } diff --git a/src/app/services/util.service.ts b/src/app/services/util.service.ts index 74aba87f..acc2eb40 100644 --- a/src/app/services/util.service.ts +++ b/src/app/services/util.service.ts @@ -206,7 +206,7 @@ function generateAccountKeyPair(accountSecretKeyBytes) { return nacl.sign.keyPair.fromSecretKey(accountSecretKeyBytes); } -function getPublicAccountID(accountPublicKeyBytes, prefix = 'xrb') { +function getPublicAccountID(accountPublicKeyBytes, prefix = 'nano') { const accountHex = util.uint8.toHex(accountPublicKeyBytes); const keyBytes = util.uint4.toUint8(util.hex.toUint4(accountHex)); // For some reason here we go from u, to hex, to 4, to 8?? const checksum = util.uint5.toString(util.uint4.toUint5(util.uint8.toUint4(blake.blake2b(keyBytes, null, 5).reverse()))); diff --git a/src/app/services/wallet.service.ts b/src/app/services/wallet.service.ts index de351749..14522980 100644 --- a/src/app/services/wallet.service.ts +++ b/src/app/services/wallet.service.ts @@ -106,6 +106,8 @@ export class WalletService { // Find out if this is a send, with our account as a destination or not const walletAccountIDs = this.wallet.accounts.map(a => a.id); + // If we have a minimum receive, once we know the account... add the amount to wallet pending? set pending to true + if (transaction.block.type == 'send' && walletAccountIDs.indexOf(transaction.block.destination) !== -1) { // Perform an automatic receive const walletAccount = this.wallet.accounts.find(a => a.id === transaction.block.destination); @@ -118,12 +120,15 @@ export class WalletService { await this.processPendingBlocks(); } } else if (transaction.block.type == 'state') { + if (this.wallet.locked) { + this.notifications.sendWarning(`New incoming transaction - unlock the wallet to receive it!`, { length: 0, identifier: 'pending-locked' }); + } + await this.processStateBlock(transaction); } // TODO: We don't really need to call to update balances, we should be able to balance on our own from here - - await this.reloadBalances(); + await this.reloadBalances(false); }); this.addressBook.addressBook$.subscribe(newAddressBook => { @@ -132,21 +137,32 @@ export class WalletService { } async processStateBlock(transaction) { + // If we have a minimum receive, once we know the account... add the amount to wallet pending? set pending to true if (transaction.is_send === 'true' && transaction.block.link_as_account) { // This is an incoming send block, we want to perform a receive const walletAccount = this.wallet.accounts.find(a => a.id === transaction.block.link_as_account); if (!walletAccount) return; // Not for our wallet? // Check for a min receive + const txAmount = new BigNumber(transaction.amount); + + if (this.wallet.pending.lte(0)) { + this.wallet.pending = this.wallet.pending.plus(txAmount); + this.wallet.pendingRaw = this.wallet.pendingRaw.plus(txAmount.mod(this.nano)); + this.wallet.pendingFiat += this.util.nano.rawToMnano(txAmount).times(this.price.price.lastPrice).toNumber(); + this.wallet.hasPending = true; + } + if (this.appSettings.settings.minimumReceive) { const minAmount = this.util.nano.mnanoToRaw(this.appSettings.settings.minimumReceive); - if (new BigNumber(transaction.amount).gt(minAmount)) { - this.addPendingBlock(walletAccount.id, transaction.hash, new BigNumber(0)); + + if (txAmount.gt(minAmount)) { + this.addPendingBlock(walletAccount.id, transaction.hash, txAmount); } else { console.log(`Found new pending block that was below minimum receive amount: `, transaction.amount, this.appSettings.settings.minimumReceive); } } else { - this.addPendingBlock(walletAccount.id, transaction.hash, new BigNumber(0)); + this.addPendingBlock(walletAccount.id, transaction.hash, txAmount); } await this.processPendingBlocks(); @@ -167,6 +183,30 @@ export class WalletService { return this.wallet.accounts.find(a => a.id == accountID); } + + async patchOldSavedData() { + // Look for saved accounts using an xrb_ prefix + const walletData = localStorage.getItem(this.storeKey); + if (!walletData) return true; + + const walletJson = JSON.parse(walletData); + + if (walletJson.accounts) { + const newAccounts = walletJson.accounts.map(account => { + if (account.id.indexOf('xrb_') !== -1) { + account.id = account.id.replace('xrb_', 'nano_'); + } + return account; + }); + + walletJson.accounts = newAccounts; + } + + localStorage.setItem(this.storeKey, JSON.stringify(walletJson)); + + return true; + } + async loadStoredWallet() { this.resetWallet(); @@ -557,6 +597,11 @@ export class WalletService { let hasPending: boolean = false; + // If this is just a normal reload.... do not use the minimum receive setting? + if (!reloadPending && walletPending.gt(0)) { + hasPending = true; // Temporary override? New incoming transaction on half reload? skip? + } + // Check if there is a pending balance at all if (walletPending.gt(0)) { // If we have a minimum receive amount, check accounts for actual receivable transactions @@ -566,10 +611,13 @@ export class WalletService { if (pending && pending.blocks) { for (let block in pending.blocks) { - if (!pending.blocks.hasOwnProperty(block)) continue; + if (!pending.blocks.hasOwnProperty(block)) { + continue; + } if (pending.blocks[block]) { hasPending = true; } else { + console.log('Pendling loop - no match, skipping? ', block); } } } @@ -725,7 +773,6 @@ export class WalletService { } } - // Now, only if we have results, do a unique on the account names, and run account info on all of them? if (this.pendingBlocks.length) { this.processPendingBlocks(); }