diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a8376fe0..349893bce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: ${{ runner.os }}-pods- - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '13.2.1' + xcode-version: '15.4' - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim - name: Dependencies diff --git a/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/ChildControllers/DSPassphraseChildViewController.m b/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/ChildControllers/DSPassphraseChildViewController.m index 61b2aebab..10c0aca01 100644 --- a/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/ChildControllers/DSPassphraseChildViewController.m +++ b/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/ChildControllers/DSPassphraseChildViewController.m @@ -98,7 +98,7 @@ - (void)verifySeedPharse { oldData = getKeychainData(EXTENDED_0_PUBKEY_KEY_BIP44_V0, nil); } - NSData *seed = [bip39Mnemonic deriveKeyFromPhrase:[bip39Mnemonic normalizePhrase:phrase] withPassphrase:nil]; + //NSData *seed = [bip39Mnemonic deriveKeyFromPhrase:[bip39Mnemonic normalizePhrase:phrase] withPassphrase:nil]; DSWallet *transientWallet = [DSWallet standardWalletWithSeedPhrase:phrase setCreationDate:[NSDate timeIntervalSince1970] forChain:chain storeSeedPhrase:NO isTransient:YES]; DSAccount *transientAccount = [transientWallet accountWithNumber:0]; DSDerivationPath *transientDerivationPath = [transientAccount bip44DerivationPath]; diff --git a/DashSync/shared/Categories/NSData/NSData+Dash.m b/DashSync/shared/Categories/NSData/NSData+Dash.m index 8efea528a..ae9ddb2a4 100644 --- a/DashSync/shared/Categories/NSData/NSData+Dash.m +++ b/DashSync/shared/Categories/NSData/NSData+Dash.m @@ -1188,17 +1188,23 @@ - (uint8_t)UInt8AtOffset:(NSUInteger)offset { - (uint16_t)UInt16AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint16_t)) return 0; - return CFSwapInt16LittleToHost(*(const uint16_t *)((const uint8_t *)self.bytes + offset)); + uint16_t value; + memcpy(&value, (const uint8_t *)self.bytes + offset, sizeof(uint16_t)); + return CFSwapInt16LittleToHost(value); } - (uint16_t)UInt16BigToHostAtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint16_t)) return 0; - return CFSwapInt16BigToHost(*(const uint16_t *)((const uint8_t *)self.bytes + offset)); + uint16_t value; + memcpy(&value, (const uint8_t *)self.bytes + offset, sizeof(uint16_t)); + return CFSwapInt16BigToHost(value); } - (uint32_t)UInt32AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint32_t)) return 0; - return CFSwapInt32LittleToHost(*(const uint32_t *)((const uint8_t *)self.bytes + offset)); + uint32_t value; + memcpy(&value, (const uint8_t *)self.bytes + offset, sizeof(uint32_t)); + return CFSwapInt32LittleToHost(value); } - (uint32_t)UInt32BigToHostAtOffset:(NSUInteger)offset { @@ -1208,12 +1214,16 @@ - (uint32_t)UInt32BigToHostAtOffset:(NSUInteger)offset { - (uint64_t)UInt64AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(uint64_t)) return 0; - return CFSwapInt64LittleToHost(*(const uint64_t *)((const uint8_t *)self.bytes + offset)); + uint64_t value; + memcpy(&value, (const uint8_t *)self.bytes + offset, sizeof(uint64_t)); + return CFSwapInt64LittleToHost(value); } - (int64_t)Int64AtOffset:(NSUInteger)offset { if (self.length < offset + sizeof(int64_t)) return 0; - return CFSwapInt64LittleToHost(*(const int64_t *)((const uint8_t *)self.bytes + offset)); + uint64_t value; + memcpy(&value, (const uint8_t *)self.bytes + offset, sizeof(uint64_t)); + return CFSwapInt64LittleToHost(value); } - (UInt128)UInt128AtOffset:(NSUInteger)offset { diff --git a/DashSync/shared/Categories/NSString+Bitcoin.m b/DashSync/shared/Categories/NSString+Bitcoin.m index bf6578016..b48e2f8a2 100644 --- a/DashSync/shared/Categories/NSString+Bitcoin.m +++ b/DashSync/shared/Categories/NSString+Bitcoin.m @@ -354,13 +354,11 @@ - (NSData *)base58ToData { - (NSData *)base58checkToData { NSData *d = self.base58ToData; - if (d.length < 4) return nil; - NSData *data = CFBridgingRelease(CFDataCreate(SecureAllocator(), d.bytes, d.length - 4)); - - // verify checksum - if (*(uint32_t *)((const uint8_t *)d.bytes + d.length - 4) != data.SHA256_2.u32[0]) return nil; + uint32_t checksum; + memcpy(&checksum, (const uint8_t *)d.bytes + d.length - 4, sizeof(uint32_t)); + if (checksum != data.SHA256_2.u32[0]) return nil; return data; } diff --git a/DashSync/shared/DashSync.h b/DashSync/shared/DashSync.h index 69f1fb352..7f28d2347 100644 --- a/DashSync/shared/DashSync.h +++ b/DashSync/shared/DashSync.h @@ -55,6 +55,7 @@ #import "DSPriceManager.h" #import "DSShapeshiftManager.h" #import "DSSporkManager.h" +#import "DSSyncState.h" #import "DSTransactionManager.h" #import "DSVersionManager.h" #import "DSWallet.h" diff --git a/DashSync/shared/DashSync.m b/DashSync/shared/DashSync.m index e97d7c9a0..25d8f807b 100644 --- a/DashSync/shared/DashSync.m +++ b/DashSync/shared/DashSync.m @@ -18,6 +18,7 @@ #import "DSMerkleBlockEntity+CoreDataClass.h" #import "DSPeerEntity+CoreDataClass.h" #import "DSPeerManager+Protected.h" +#import "DSSyncState.h" #import "DSQuorumEntryEntity+CoreDataClass.h" #import "DSQuorumSnapshotEntity+CoreDataClass.h" #import "DSSporkManager+Protected.h" @@ -168,12 +169,10 @@ - (void)wipeBlockchainDataForChain:(DSChain *)chain inContext:(NSManagedObjectCo [DSDashpayUserEntity deleteContactsOnChainEntity:chainEntity]; // this must move after wipeBlockchainInfo where blockchain identities are removed [context ds_save]; [chain reloadDerivationPaths]; - [chain.chainManager assignSyncWeights]; + [chain.chainManager notifySyncStateChanged]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSWalletBalanceDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainChainSyncBlocksDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainTerminalBlocksDidChangeNotification object:nil]; }); }]; } @@ -198,12 +197,10 @@ - (void)wipeBlockchainNonTerminalDataForChain:(DSChain *)chain inContext:(NSMana [DSDashpayUserEntity deleteContactsOnChainEntity:chainEntity]; // this must move after wipeBlockchainInfo where blockchain identities are removed [context ds_save]; [chain reloadDerivationPaths]; - [chain.chainManager assignSyncWeights]; + [chain.chainManager notifySyncStateChanged]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSWalletBalanceDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainChainSyncBlocksDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainTerminalBlocksDidChangeNotification object:nil]; }); }]; } @@ -222,7 +219,7 @@ - (void)wipeMasternodeDataForChain:(DSChain *)chain inContext:(NSManagedObjectCo DSChainManager *chainManager = [[DSChainsManager sharedInstance] chainManagerForChain:chain]; [chainManager wipeMasternodeInfo]; [context ds_save]; - [chain.chainManager assignSyncWeights]; + [chain.chainManager notifySyncStateChanged]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:[NSString stringWithFormat:@"%@_%@", chain.uniqueID, LAST_SYNCED_MASTERNODE_LIST]]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSMasternodeListDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: chain}]; @@ -325,7 +322,7 @@ - (void)scheduleBackgroundFetch { - (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { DSChainManager *mainnetManager = [[DSChainsManager sharedInstance] mainnetManager]; - if (mainnetManager.chainSyncProgress >= 1.0) { + if (mainnetManager.syncState.chainSyncProgress >= 1.0) { DSLog(@"Background fetch: already synced"); if (completionHandler) { @@ -374,7 +371,7 @@ - (void)performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))com } - (void)backgroundFetchTimedOut { - const double syncProgress = [[DSChainsManager sharedInstance] mainnetManager].chainSyncProgress; + const double syncProgress = [[DSChainsManager sharedInstance] mainnetManager].syncState.chainSyncProgress; DSLog(@"Background fetch timeout with progress: %f", syncProgress); const UIBackgroundFetchResult fetchResult = syncProgress > 0.1 ? UIBackgroundFetchResultNewData : UIBackgroundFetchResultFailed; diff --git a/DashSync/shared/MainnetFixedPeers.plist b/DashSync/shared/MainnetFixedPeers.plist new file mode 100644 index 000000000..6420dffcc --- /dev/null +++ b/DashSync/shared/MainnetFixedPeers.plist @@ -0,0 +1,32 @@ + + + + + 3263651171 + 148804577 + 759996581 + 2994784866 + 2673411509 + 2297634243 + 1588652144 + 2178245180 + 2671888933 + 94492658 + 3156785217 + 759997248 + 3156779795 + 3156782919 + 3156743658 + 1389566409 + 2461642608 + 2297663013 + 1097723841 + 1389566384 + 2335935255 + 822870522 + 1608023587 + 1311700836 + 759130970 + 2178245360 + + diff --git a/DashSync/shared/Models/Chain/DSChain.h b/DashSync/shared/Models/Chain/DSChain.h index f28612750..4537474e8 100644 --- a/DashSync/shared/Models/Chain/DSChain.h +++ b/DashSync/shared/Models/Chain/DSChain.h @@ -34,15 +34,12 @@ NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const DSChainWalletsDidChangeNotification; FOUNDATION_EXPORT NSString *const DSChainStandaloneDerivationPathsDidChangeNotification; FOUNDATION_EXPORT NSString *const DSChainStandaloneAddressesDidChangeNotification; -FOUNDATION_EXPORT NSString *const DSChainChainSyncBlocksDidChangeNotification; FOUNDATION_EXPORT NSString *const DSChainBlockWasLockedNotification; FOUNDATION_EXPORT NSString *const DSChainNotificationBlockKey; // For improved performance DSChainInitialHeadersDidChangeNotification is not garanteed to trigger on every initial headers change. -FOUNDATION_EXPORT NSString *const DSChainTerminalBlocksDidChangeNotification; FOUNDATION_EXPORT NSString *const DSChainInitialHeadersDidFinishSyncingNotification; FOUNDATION_EXPORT NSString *const DSChainBlocksDidFinishSyncingNotification; -FOUNDATION_EXPORT NSString *const DSChainNewChainTipBlockNotification; typedef NS_ENUM(NSUInteger, DSTransactionDirection) { @@ -504,6 +501,7 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) @required +- (void)chainWillStartConnectingToPeers:(DSChain *)chain; - (void)chainWillStartSyncingBlockchain:(DSChain *)chain; - (void)chainShouldStartSyncingBlockchain:(DSChain *)chain onPeer:(DSPeer *)peer; - (void)chainFinishedSyncingTransactionsAndBlocks:(DSChain *)chain fromPeer:(DSPeer *_Nullable)peer onMainChain:(BOOL)onMainChain; diff --git a/DashSync/shared/Models/Chain/DSChain.m b/DashSync/shared/Models/Chain/DSChain.m index d9b0e0d2a..834465ffc 100644 --- a/DashSync/shared/Models/Chain/DSChain.m +++ b/DashSync/shared/Models/Chain/DSChain.m @@ -162,8 +162,6 @@ @interface DSChain () @property (nonatomic, strong) DSCheckpoint *terminalHeadersOverrideUseCheckpoint; @property (nonatomic, strong) DSCheckpoint *syncHeadersOverrideUseCheckpoint; @property (nonatomic, strong) DSCheckpoint *lastCheckpoint; -@property (nonatomic, assign) NSTimeInterval lastNotifiedBlockDidChange; -@property (nonatomic, strong) NSTimer *lastNotifiedBlockDidChangeTimer; @property (nonatomic, assign, getter=isTransient) BOOL transient; @property (nonatomic, assign) BOOL cachedIsQuorumRotationPresented; @property (nonatomic, readonly) NSString *chainWalletsKey; @@ -185,9 +183,7 @@ - (instancetype)init { self.transactionHashHeights = [NSMutableDictionary dictionary]; self.transactionHashTimestamps = [NSMutableDictionary dictionary]; - - self.lastNotifiedBlockDidChange = 0; - + if (self.checkpoints) { self.genesisHash = self.checkpoints[0].blockHash; dispatch_sync(self.networkingQueue, ^{ @@ -1201,9 +1197,7 @@ - (void)unregisterStandaloneDerivationPath:(DSDerivationPath *)derivationPath { [keyChainArray removeObject:derivationPath.standaloneExtendedPublicKeyUniqueID]; setKeychainArray(keyChainArray, self.chainStandaloneDerivationPathsKey, NO); [self.viewingAccount removeDerivationPath:derivationPath]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainStandaloneDerivationPathsDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); + [self notify:DSChainStandaloneDerivationPathsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; } - (void)addStandaloneDerivationPath:(DSDerivationPath *)derivationPath { [self.viewingAccount addDerivationPath:derivationPath]; @@ -1218,9 +1212,7 @@ - (void)registerStandaloneDerivationPath:(DSDerivationPath *)derivationPath { if (!keyChainArray) keyChainArray = [NSMutableArray array]; [keyChainArray addObject:derivationPath.standaloneExtendedPublicKeyUniqueID]; setKeychainArray(keyChainArray, self.chainStandaloneDerivationPathsKey, NO); - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainStandaloneDerivationPathsDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); + [self notify:DSChainStandaloneDerivationPathsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; } - (NSArray *)standaloneDerivationPaths { @@ -1414,9 +1406,7 @@ - (void)unregisterWallet:(DSWallet *)wallet { if (!keyChainArray) keyChainArray = [NSMutableArray array]; [keyChainArray removeObject:wallet.uniqueIDString]; setKeychainArray(keyChainArray, self.chainWalletsKey, NO); - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainWalletsDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); + [self notify:DSChainWalletsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; } - (BOOL)addWallet:(DSWallet *)walletToAdd { @@ -1450,9 +1440,7 @@ - (void)registerWallet:(DSWallet *)wallet { if (![keyChainArray containsObject:wallet.uniqueIDString]) { [keyChainArray addObject:wallet.uniqueIDString]; setKeychainArray(keyChainArray, self.chainWalletsKey, NO); - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainWalletsDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); + [self notify:DSChainWalletsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; } } @@ -1544,7 +1532,7 @@ - (void)setLastSyncBlockFromCheckpoints { } if (_lastSyncBlock) { - DSLog(@"[%@] last sync block at height %d chosen from checkpoints (hash is %@)", self.name, _lastSyncBlock.height, self.name, [NSData dataWithUInt256:_lastSyncBlock.blockHash].hexString); + DSLog(@"[%@] last sync block at height %d chosen from checkpoints (hash is %@)", self.name, _lastSyncBlock.height, [NSData dataWithUInt256:_lastSyncBlock.blockHash].hexString); } } @@ -1779,9 +1767,9 @@ - (BOOL)addMinedFullBlock:(DSFullBlock *)block { } [self saveBlockLocators]; [self saveTerminalBlocks]; - + self.chainManager.syncState.estimatedBlockHeight = _bestEstimatedBlockHeight; // notify that transaction confirmations may have changed - [self notifyBlocksChanged]; + [self.chainManager notifySyncStateChanged]; } return TRUE; @@ -1949,13 +1937,14 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( [block setChainLockedWithEquivalentBlock:equivalentTerminalBlock]; } self.lastSyncBlock = block; - + self.chainManager.syncState.lastSyncBlockHeight = block.height; if (!equivalentTerminalBlock && uint256_eq(block.prevBlock, self.lastTerminalBlock.blockHash)) { if ((h % 1000) == 0 || txHashes.count > 0 || h > peer.lastBlockHeight) { DSLog(@"%@ + terminal block (caught up) at: %d: %@", prefix, h, uint256_hex(block.blockHash)); } self.mTerminalBlocks[blockHash] = block; self.lastTerminalBlock = block; + self.chainManager.syncState.lastTerminalBlockHeight = block.height; } @synchronized(peer) { if (peer) { @@ -1977,6 +1966,9 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( } self.mTerminalBlocks[blockHash] = block; self.lastTerminalBlock = block; + self.chainManager.syncState.estimatedBlockHeight = self.estimatedBlockHeight; + self.chainManager.syncState.lastTerminalBlockHeight = block.height; + @synchronized(peer) { if (peer) { peer.currentBlockHeight = h; //might be download peer instead @@ -2005,7 +1997,11 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( if (b != nil && uint256_eq(b.blockHash, block.blockHash)) { // if it's not on a fork, set block heights for its transactions [self setBlockHeight:h andTimestamp:txTime forTransactionHashes:txHashes]; - if (h == self.lastSyncBlockHeight) self.lastSyncBlock = block; + if (h == self.lastSyncBlockHeight) { + self.lastSyncBlock = block; + self.chainManager.syncState.estimatedBlockHeight = self.estimatedBlockHeight; + self.chainManager.syncState.lastSyncBlockHeight = block.height; + } } } else if (self.mTerminalBlocks[blockHash] != nil && (blockPosition & DSBlockPosition_Terminal)) { // we already have the block (or at least the header) if ((h % 1) == 0 || txHashes.count > 0 || h > peer.lastBlockHeight) { @@ -2024,7 +2020,10 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( if (b != nil && uint256_eq(b.blockHash, block.blockHash)) { // if it's not on a fork, set block heights for its transactions [self setBlockHeight:h andTimestamp:txTime forTransactionHashes:txHashes]; - if (h == self.lastTerminalBlockHeight) self.lastTerminalBlock = block; + if (h == self.lastTerminalBlockHeight) { + self.lastTerminalBlock = block; + self.chainManager.syncState.lastTerminalBlockHeight = block.height; + } } } else { // new block is on a fork if (h <= [self lastCheckpoint].height) { // fork is older than last checkpoint @@ -2059,6 +2058,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( DSLog(@"%@ reorganizing terminal chain from height %d, new height is %d", prefix, b.height, h); self.lastTerminalBlock = block; + self.chainManager.syncState.lastTerminalBlockHeight = block.height; @synchronized(peer) { if (peer) { peer.currentBlockHeight = h; //might be download peer instead @@ -2090,6 +2090,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( } else { DSLog(@"%@ reorganizing terminal chain from height %d, new height is %d", prefix, b.height, h); self.lastTerminalBlock = block; + self.chainManager.syncState.lastTerminalBlockHeight = block.height; @synchronized(peer) { if (peer) { peer.currentBlockHeight = h; //might be download peer instead @@ -2131,6 +2132,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( } self.lastSyncBlock = block; + self.chainManager.syncState.lastSyncBlockHeight = block.height; if (h == self.estimatedBlockHeight) syncDone = YES; } } @@ -2148,9 +2150,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( if (peer) { [self.chainManager chainFinishedSyncingInitialHeaders:self fromPeer:peer onMainChain:onMainChain]; } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainInitialHeadersDidFinishSyncingNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); + [self notify:DSChainInitialHeadersDidFinishSyncingNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; } if ((blockPosition & DSBlockPosition_Sync) && (phase == DSChainSyncPhase_ChainSync || phase == DSChainSyncPhase_Synced)) { //we should only save @@ -2159,15 +2159,14 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( if (peer) { [self.chainManager chainFinishedSyncingTransactionsAndBlocks:self fromPeer:peer onMainChain:onMainChain]; } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainBlocksDidFinishSyncingNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); + [self notify:DSChainBlocksDidFinishSyncingNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; } } if (((blockPosition & DSBlockPosition_Terminal) && block.height > self.estimatedBlockHeight) || ((blockPosition & DSBlockPosition_Sync) && block.height >= self.lastTerminalBlockHeight)) { @synchronized (self) { _bestEstimatedBlockHeight = block.height; + self.chainManager.syncState.estimatedBlockHeight = _bestEstimatedBlockHeight; } if (peer && (blockPosition & DSBlockPosition_Sync) && !savedBlockLocators) { [self saveBlockLocators]; @@ -2178,18 +2177,8 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( if (peer) { [self.chainManager chain:self wasExtendedWithBlock:block fromPeer:peer]; } - - // notify that transaction confirmations may have changed - [self setupBlockChangeTimer:^{ - [self notifyBlocksChanged]; - }]; - } else { - //we should avoid dispatching this message too frequently - [self setupBlockChangeTimer:^{ - [self notifyBlocksChanged:blockPosition]; - }]; } - + [self.chainManager notifySyncStateChanged]; // check if the next block was received as an orphan if (block == self.lastTerminalBlock && self.mOrphans[blockHash]) { DSBlock *b = self.mOrphans[blockHash]; @@ -2200,45 +2189,15 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( return TRUE; } -- (void)setupBlockChangeTimer:(void (^ __nullable)(void))completion { - //we should avoid dispatching this message too frequently - NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970]; - if (!self.lastNotifiedBlockDidChange || (timestamp - self.lastNotifiedBlockDidChange > 0.1)) { - self.lastNotifiedBlockDidChange = timestamp; - if (self.lastNotifiedBlockDidChangeTimer) { - [self.lastNotifiedBlockDidChangeTimer invalidate]; - self.lastNotifiedBlockDidChangeTimer = nil; - } - completion(); - } else if (!self.lastNotifiedBlockDidChangeTimer) { - self.lastNotifiedBlockDidChangeTimer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer *_Nonnull timer) { - completion(); - }]; - [[NSRunLoop mainRunLoop] addTimer:self.lastNotifiedBlockDidChangeTimer forMode:NSRunLoopCommonModes]; - } -} - -- (void)notifyBlocksChanged { +- (void)notify:(NSNotificationName)name userInfo:(NSDictionary *_Nullable)userInfo { dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainNewChainTipBlockNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainChainSyncBlocksDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainTerminalBlocksDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; + [[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:userInfo]; }); } -- (void)notifyBlocksChanged:(DSBlockPosition)blockPosition { - dispatch_async(dispatch_get_main_queue(), ^{ - if (blockPosition & DSBlockPosition_Terminal) { - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainTerminalBlocksDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - } - if (blockPosition & DSBlockPosition_Sync) { - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainChainSyncBlocksDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - } - - }); -} // MARK: Terminal Blocks + - (NSMutableDictionary *)mTerminalBlocks { @synchronized (_mTerminalBlocks) { if (_mTerminalBlocks.count > 0) { @@ -2371,6 +2330,8 @@ - (BOOL)addChainLock:(DSChainLock *)chainLock { DSLog(@"[%@] Reorginizing to height %d", self.name, clb.height); self.lastTerminalBlock = terminalBlock; + self.chainManager.syncState.lastTerminalBlockHeight = terminalBlock.height; + [self.chainManager notifySyncStateChanged]; NSMutableDictionary *forkChainsTerminalBlocks = [[self forkChainsTerminalBlocks] mutableCopy]; NSMutableArray *addedBlocks = [NSMutableArray array]; BOOL done = FALSE; @@ -2422,7 +2383,8 @@ - (BOOL)addChainLock:(DSChainLock *)chainLock { DSLog(@"[%@] Cancelling sync reorg because block %@ is already chain locked", self.name, sbmc); } else { self.lastSyncBlock = syncBlock; - + self.chainManager.syncState.lastSyncBlockHeight = syncBlock.height; + [self.chainManager notifySyncStateChanged]; DSLog(@"[%@] Reorginizing to height %d (last sync block %@)", self.name, clb.height, self.lastSyncBlock); @@ -2724,17 +2686,10 @@ - (void)setEstimatedBlockHeight:(uint32_t)estimatedBlockHeight fromPeer:(DSPeer uint32_t finalEstimatedBlockHeight = [self decideFromPeerSoftConsensusEstimatedBlockHeight]; if (finalEstimatedBlockHeight > oldEstimatedBlockHeight) { _bestEstimatedBlockHeight = finalEstimatedBlockHeight; - dispatch_once(&onceToken, ^{ - [self.chainManager assignSyncWeights]; - }); - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerSyncParametersUpdatedNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self}]; - }); - } else { - dispatch_once(&onceToken, ^{ - [self.chainManager assignSyncWeights]; - }); } + dispatch_once(&onceToken, ^{ + self.chainManager.syncState.estimatedBlockHeight = finalEstimatedBlockHeight; + }); } } @@ -2750,6 +2705,7 @@ - (void)removeEstimatedBlockHeightOfPeer:(DSPeer *)peer { //keep best estimate if no other peers reporting on estimate if ([self.estimatedBlockHeights count] && ([height intValue] == _bestEstimatedBlockHeight)) { _bestEstimatedBlockHeight = 0; + self.chainManager.syncState.estimatedBlockHeight = 0; } } } diff --git a/DashSync/shared/Models/Chain/DSChainConstants.h b/DashSync/shared/Models/Chain/DSChainConstants.h index 5bc90d373..4420efbfc 100644 --- a/DashSync/shared/Models/Chain/DSChainConstants.h +++ b/DashSync/shared/Models/Chain/DSChainConstants.h @@ -31,13 +31,13 @@ #define TESTNET_DAPI_GRPC_STANDARD_PORT 3010 #define DEVNET_DAPI_GRPC_STANDARD_PORT 3010 -#define PROTOCOL_VERSION_MAINNET 70231 +#define PROTOCOL_VERSION_MAINNET 70232 #define DEFAULT_MIN_PROTOCOL_VERSION_MAINNET 70228 -#define PROTOCOL_VERSION_TESTNET 70231 +#define PROTOCOL_VERSION_TESTNET 70232 #define DEFAULT_MIN_PROTOCOL_VERSION_TESTNET 70228 -#define PROTOCOL_VERSION_DEVNET 70231 +#define PROTOCOL_VERSION_DEVNET 70232 #define DEFAULT_MIN_PROTOCOL_VERSION_DEVNET 70228 #define PLATFORM_PROTOCOL_VERSION_MAINNET 1 diff --git a/DashSync/shared/Models/Entities/DSCoinbaseTransactionEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSCoinbaseTransactionEntity+CoreDataClass.m index ef06f6804..18cbc6745 100644 --- a/DashSync/shared/Models/Entities/DSCoinbaseTransactionEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSCoinbaseTransactionEntity+CoreDataClass.m @@ -23,7 +23,7 @@ - (instancetype)setAttributesFromTransaction:(DSTransaction *)tx { if (self.specialTransactionVersion >= COINBASE_TX_CORE_19) { self.merkleRootLLMQList = uint256_data(coinbaseTransaction.merkleRootLLMQList); if (self.specialTransactionVersion >= COINBASE_TX_CORE_20) { - self.bestCLHeightDiff = coinbaseTransaction.bestCLHeightDiff; + self.bestCLHeightDiff = (uint32_t) coinbaseTransaction.bestCLHeightDiff; self.bestCLSignature = uint768_data(coinbaseTransaction.bestCLSignature); self.creditPoolBalance = coinbaseTransaction.creditPoolBalance; } diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSBackgroundManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSBackgroundManager.h new file mode 100644 index 000000000..e947359ef --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSBackgroundManager.h @@ -0,0 +1,35 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DSChain.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSBackgroundManager : NSObject + +- (instancetype)initWithChain:(DSChain *)chain; + +- (void)createBlockLocatorsTask:(void(^ __nullable)(void))handler; +- (void)createTerminalHeadersTask:(void(^ __nullable)(void))handler; +- (void)stopBackgroundActivities; + +- (BOOL)hasValidHeadersTask; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSBackgroundManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSBackgroundManager.m new file mode 100644 index 000000000..e579e6a1f --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSBackgroundManager.m @@ -0,0 +1,92 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DSBackgroundManager.h" +#import "DSChainManager.h" +#import "DSPeerManager+Protected.h" + +@interface DSBackgroundManager () + +@property (nonatomic, strong) DSChain *chain; + +#if TARGET_OS_IOS + +@property (nonatomic, strong) id backgroundObserver; +@property (nonatomic, assign) NSUInteger terminalHeadersSaveTaskId, blockLocatorsSaveTaskId; + +#endif + +@end + +@implementation DSBackgroundManager + +- (instancetype)initWithChain:(DSChain *)chain { + if (!(self = [super init])) return nil; +#if TARGET_OS_IOS + self.terminalHeadersSaveTaskId = UIBackgroundTaskInvalid; + self.backgroundObserver = + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [chain.chainManager.peerManager startBackgroundMode:self.terminalHeadersSaveTaskId == UIBackgroundTaskInvalid]; + }]; +#endif + + return self; +} + +- (void)dealloc { +#if TARGET_OS_IOS + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + if (self.backgroundObserver) + [[NSNotificationCenter defaultCenter] removeObserver:self.backgroundObserver]; +#endif +} + + +- (void)createBlockLocatorsTask:(void(^ __nullable)(void))handler { +#if TARGET_OS_IOS + if (self.blockLocatorsSaveTaskId == UIBackgroundTaskInvalid) { // start a background task for the chain sync + self.blockLocatorsSaveTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:handler]; + } +#endif +} +- (void)createTerminalHeadersTask:(void(^ __nullable)(void))handler { +#if TARGET_OS_IOS + if (self.terminalHeadersSaveTaskId == UIBackgroundTaskInvalid) { // start a background task for the chain sync + self.terminalHeadersSaveTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:handler]; + } +#endif +} + +- (void)stopBackgroundActivities { +#if TARGET_OS_IOS + if (self.terminalHeadersSaveTaskId != UIBackgroundTaskInvalid) { + [[UIApplication sharedApplication] endBackgroundTask:self.terminalHeadersSaveTaskId]; + self.terminalHeadersSaveTaskId = UIBackgroundTaskInvalid; + } + + if (self.blockLocatorsSaveTaskId != UIBackgroundTaskInvalid) { + [[UIApplication sharedApplication] endBackgroundTask:self.blockLocatorsSaveTaskId]; + self.blockLocatorsSaveTaskId = UIBackgroundTaskInvalid; + } +#endif +} + +- (BOOL)hasValidHeadersTask { + return self.terminalHeadersSaveTaskId != UIBackgroundTaskInvalid || [UIApplication sharedApplication].applicationState != UIApplicationStateBackground; +} + +@end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Mining.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Mining.h new file mode 100644 index 000000000..e1bdddfdb --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Mining.h @@ -0,0 +1,54 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DSChainManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSChainManager (Mining) + +// MARK: - Mining + +- (void)mineEmptyBlocks:(uint32_t)blockCount + toPaymentAddress:(NSString *)paymentAddress + withTimeout:(NSTimeInterval)timeout + completion:(MultipleBlockMiningCompletionBlock)completion; + +- (void)mineEmptyBlocks:(uint32_t)blockCount + toPaymentAddress:(NSString *)paymentAddress + afterBlock:(DSBlock *)block + previousBlocks:(NSDictionary *)previousBlocks + withTimeout:(NSTimeInterval)timeout + completion:(MultipleBlockMiningCompletionBlock)completion; + +- (void)mineBlockToPaymentAddress:(NSString *)paymentAddress + withTransactions:(NSArray *_Nullable)transactions + withTimeout:(NSTimeInterval)timeout + completion:(BlockMiningCompletionBlock)completion; + +- (void)mineBlockAfterBlock:(DSBlock *)block + toPaymentAddress:(NSString *)paymentAddress + withTransactions:(NSArray *_Nullable)transactions + previousBlocks:(NSDictionary *)previousBlocks + nonceOffset:(uint32_t)nonceOffset + withTimeout:(NSTimeInterval)timeout + completion:(BlockMiningCompletionBlock)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Mining.m b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Mining.m new file mode 100644 index 000000000..8a92dea9c --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Mining.m @@ -0,0 +1,104 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DSChain+Protected.h" +#import "DSChainManager+Protected.h" +#import "DSChainManager+Mining.h" +#import "DSFullBlock.h" +#import "NSError+Dash.h" + +@implementation DSChainManager (Mining) + +- (void)mineEmptyBlocks:(uint32_t)blockCount + toPaymentAddress:(NSString *)paymentAddress + withTimeout:(NSTimeInterval)timeout + completion:(MultipleBlockMiningCompletionBlock)completion { + [self mineEmptyBlocks:blockCount toPaymentAddress:paymentAddress afterBlock:self.chain.lastTerminalBlock previousBlocks:self.chain.terminalBlocks withTimeout:timeout completion:completion]; +} + +- (void)mineEmptyBlocks:(uint32_t)blockCount + toPaymentAddress:(NSString *)paymentAddress + afterBlock:(DSBlock *)previousBlock + previousBlocks:(NSDictionary *)previousBlocks + withTimeout:(NSTimeInterval)timeout + completion:(MultipleBlockMiningCompletionBlock)completion { + dispatch_async(self.miningQueue, ^{ + NSTimeInterval start = [[NSDate date] timeIntervalSince1970]; + NSTimeInterval end = [[[NSDate alloc] initWithTimeIntervalSinceNow:timeout] timeIntervalSince1970]; + NSMutableArray *blocksArray = [NSMutableArray array]; + NSMutableArray *attemptsArray = [NSMutableArray array]; + __block uint32_t blocksRemaining = blockCount; + __block NSMutableDictionary *mPreviousBlocks = [previousBlocks mutableCopy]; + __block DSBlock *currentBlock = previousBlock; + while ([[NSDate date] timeIntervalSince1970] < end && blocksRemaining > 0) { + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + [self mineBlockAfterBlock:currentBlock + toPaymentAddress:paymentAddress + withTransactions:[NSArray array] + previousBlocks:mPreviousBlocks + nonceOffset:0 + withTimeout:timeout + completion:^(DSFullBlock *_Nullable block, NSUInteger attempts, NSTimeInterval timeUsed, NSError *_Nullable error) { + NSAssert(uint256_is_not_zero(block.blockHash), @"Block hash must not be empty"); + dispatch_semaphore_signal(sem); + [blocksArray addObject:block]; + [mPreviousBlocks setObject:block forKey:uint256_obj(block.blockHash)]; + currentBlock = block; + blocksRemaining--; + [attemptsArray addObject:@(attempts)]; + }]; + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + } + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(blocksArray, attemptsArray, [[NSDate date] timeIntervalSince1970] - start, nil); + }); + } + }); +} + +- (void)mineBlockToPaymentAddress:(NSString *)paymentAddress + withTransactions:(NSArray *)transactions + withTimeout:(NSTimeInterval)timeout + completion:(BlockMiningCompletionBlock)completion { + [self mineBlockAfterBlock:self.chain.lastTerminalBlock toPaymentAddress:paymentAddress withTransactions:transactions previousBlocks:self.chain.terminalBlocks nonceOffset:0 withTimeout:timeout completion:completion]; +} + +- (void)mineBlockAfterBlock:(DSBlock *)block + toPaymentAddress:(NSString *)paymentAddress + withTransactions:(NSArray *)transactions + previousBlocks:(NSDictionary *)previousBlocks + nonceOffset:(uint32_t)nonceOffset + withTimeout:(NSTimeInterval)timeout + completion:(nonnull BlockMiningCompletionBlock)completion { + DSCoinbaseTransaction *coinbaseTransaction = [[DSCoinbaseTransaction alloc] initWithCoinbaseMessage:@"From iOS" paymentAddresses:@[paymentAddress] atHeight:block.height + 1 onChain:block.chain]; + DSFullBlock *fullblock = [[DSFullBlock alloc] initWithCoinbaseTransaction:coinbaseTransaction transactions:[NSSet set] previousBlockHash:block.blockHash previousBlocks:previousBlocks timestamp:[[NSDate date] timeIntervalSince1970] height:block.height + 1 onChain:self.chain]; + uint64_t attempts = 0; + NSDate *startTime = [NSDate date]; + if ([fullblock mineBlockAfterBlock:block withNonceOffset:nonceOffset withTimeout:timeout rAttempts:&attempts]) { + if (completion) { + completion(fullblock, attempts, -[startTime timeIntervalSinceNow], nil); + } + } else { + if (completion) { + NSError *error = [NSError errorWithCode:500 localizedDescriptionKey:@"A block could not be mined in the selected time interval."]; + completion(nil, attempts, -[startTime timeIntervalSinceNow], error); + } + } +} + +@end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h index 722e49ed1..e24ddb3fc 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h @@ -5,32 +5,37 @@ // Created by Sam Westrich on 11/21/18. // +#import "DSChain.h" #import "DSChainManager.h" NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(uint16_t, DSChainNotificationType) { + DSChainNotificationType_Headers = 0, + DSChainNotificationType_Blocks = 1, + DSChainNotificationType_SyncState = 2, +}; + @interface DSChainManager () @property (nonatomic, assign) NSTimeInterval lastChainRelayTime; +@property (nonatomic, assign) DSChainSyncPhase syncPhase; +@property (nonatomic, strong) dispatch_queue_t miningQueue; -- (instancetype)initWithChain:(DSChain *)chain; -- (void)resetSyncCountInfo:(DSSyncCountInfo)masternodeSyncCountInfo inContext:(NSManagedObjectContext *)context; - (void)resetChainSyncStartHeight; - (void)restartChainSyncStartHeight; - (void)resetTerminalSyncStartHeight; - (void)restartTerminalSyncStartHeight; +- (instancetype)initWithChain:(DSChain *)chain; +- (void)resetSyncCountInfo:(DSSyncCountInfo)masternodeSyncCountInfo inContext:(NSManagedObjectContext *)context; - (void)relayedNewItem; -- (void)resetLastRelayedItemTime; - (void)setCount:(uint32_t)count forSyncCountInfo:(DSSyncCountInfo)masternodeSyncCountInfo inContext:(NSManagedObjectContext *)context; -- (BOOL)shouldRequestMerkleBlocksForZoneBetweenHeight:(uint32_t)blockHeight andEndHeight:(uint32_t)endBlockHeight; -- (BOOL)shouldRequestMerkleBlocksForZoneAfterHeight:(uint32_t)blockHeight; - - (void)wipeMasternodeInfo; -@property (nonatomic, assign) DSChainSyncPhase syncPhase; +- (void)notify:(NSNotificationName)name userInfo:(NSDictionary *_Nullable)userInfo; +- (void)notifySyncStateChanged; -- (void)assignSyncWeights; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h new file mode 100644 index 000000000..7e17e178f --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h @@ -0,0 +1,30 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DSChainManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSChainManager (Transactions) + +- (BOOL)shouldRequestMerkleBlocksForZoneBetweenHeight:(uint32_t)blockHeight andEndHeight:(uint32_t)endBlockHeight; +- (BOOL)shouldRequestMerkleBlocksForZoneAfterHeight:(uint32_t)blockHeight; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m new file mode 100644 index 000000000..0ef4efefd --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m @@ -0,0 +1,208 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DSChainManager+Protected.h" +#import "DSChainManager+Transactions.h" +#import "DSWallet+Protected.h" +#import "RHIntervalTree.h" +#import + +NSString const *maxTransactionsInfoDataKey = @"maxTransactionsInfoDataKey"; +NSString const *heightTransactionZonesKey = @"heightTransactionZonesKey"; +NSString const *maxTransactionsInfoDataFirstHeightKey = @"maxTransactionsInfoDataFirstHeightKey"; +NSString const *maxTransactionsInfoDataLastHeightKey = @"maxTransactionsInfoDataLastHeightKey"; +NSString const *chainSynchronizationFingerprintKey = @"chainSynchronizationFingerprintKey"; +NSString const *chainSynchronizationBlockZonesKey = @"chainSynchronizationBlockZonesKey"; + + +@interface DSChainManager () + +@property (nonatomic, strong) NSData *maxTransactionsInfoData; +@property (nonatomic, strong) RHIntervalTree *heightTransactionZones; +@property (nonatomic, assign) uint32_t maxTransactionsInfoDataFirstHeight; +@property (nonatomic, assign) uint32_t maxTransactionsInfoDataLastHeight; +@property (nonatomic, strong) NSData *chainSynchronizationFingerprint; +@property (nonatomic, strong) NSOrderedSet *chainSynchronizationBlockZones; + +@end + +@implementation DSChainManager (Transactions) + +- (void)setMaxTransactionsInfoData:(NSData *)maxTransactionsInfoData { + objc_setAssociatedObject(self, &maxTransactionsInfoDataKey, maxTransactionsInfoData, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +- (NSData *)maxTransactionsInfoData { + return objc_getAssociatedObject(self, &maxTransactionsInfoDataKey); +} + +- (void)setHeightTransactionZones:(RHIntervalTree *)heightTransactionZones { + objc_setAssociatedObject(self, &heightTransactionZonesKey, heightTransactionZones, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +- (RHIntervalTree *)heightTransactionZones { + return objc_getAssociatedObject(self, &heightTransactionZonesKey); +} + + +- (void)setChainSynchronizationBlockZones:(NSOrderedSet *)chainSynchronizationBlockZones { + objc_setAssociatedObject(self, &chainSynchronizationBlockZonesKey, chainSynchronizationBlockZones, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +- (NSOrderedSet *)chainSynchronizationBlockZones { + NSOrderedSet *obj = objc_getAssociatedObject(self, &chainSynchronizationBlockZonesKey); + if (!obj) { + obj = [DSWallet blockZonesFromChainSynchronizationFingerprint:self.chainSynchronizationFingerprint rVersion:0 rChainHeight:0]; + [self setChainSynchronizationBlockZones:obj]; + } + return obj; + +} + +- (void)loadHeightTransactionZones { + NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; + NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; + NSString *filePath = [bundle pathForResource:[NSString stringWithFormat:@"HeightTransactionZones_%@", self.chain.name] ofType:@"dat"]; + NSData *heightTransactionZonesData = [NSData dataWithContentsOfFile:filePath]; + if (heightTransactionZonesData) { + NSMutableArray *intervals = [NSMutableArray array]; + for (uint16_t i = 0; i < heightTransactionZonesData.length - 4; i += 4) { + uint32_t intervalStartHeight = [heightTransactionZonesData UInt16AtOffset:i] * 500; + uint16_t average = [heightTransactionZonesData UInt16AtOffset:i + 2]; + uint32_t intervalEndHeight = [heightTransactionZonesData UInt16AtOffset:i + 4] * 500; + [intervals addObject:[RHInterval intervalWithStart:intervalStartHeight stop:intervalEndHeight - 1 object:@(average)]]; + } + self.heightTransactionZones = [[RHIntervalTree alloc] initWithIntervalObjects:intervals]; + } +} + +- (uint16_t)averageTransactionsInZoneForStartHeight:(uint32_t)startHeight endHeight:(uint32_t)endHeight { + NSArray *intervals = [self.heightTransactionZones overlappingObjectsForStart:startHeight andStop:endHeight]; + if (!intervals.count) return 0; + if (intervals.count == 1) return [(NSNumber *)[intervals[0] object] unsignedShortValue]; + uint64_t aggregate = 0; + for (RHInterval *interval in intervals) { + uint64_t value = [(NSNumber *)interval.object unsignedLongValue]; + if (interval == [intervals firstObject]) { + aggregate += value * (interval.stop - startHeight + 1); + } else if (interval == [intervals lastObject]) { + aggregate += value * (endHeight - interval.start + 1); + } else { + aggregate += value * (interval.stop - interval.start + 1); + } + } + return aggregate / (endHeight - startHeight); +} + +- (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height rAverage:(float *)rAverage { + return [self firstHeightOutOfAverageRangeWithStart500RangeHeight:height startingVarianceLevel:1 endingVarianceLevel:0.2 convergencePolynomial:0.33 rAverage:rAverage]; +} + +- (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height startingVarianceLevel:(float)startingVarianceLevel endingVarianceLevel:(float)endingVarianceLevel convergencePolynomial:(float)convergencePolynomial rAverage:(float *)rAverage { + return [self firstHeightOutOfAverageRangeWithStart500RangeHeight:height startingVarianceLevel:startingVarianceLevel endingVarianceLevel:endingVarianceLevel convergencePolynomial:convergencePolynomial recursionLevel:0 recursionMaxLevel:2 rAverage:rAverage rAverages:nil]; +} + +- (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height startingVarianceLevel:(float)startingVarianceLevel endingVarianceLevel:(float)endingVarianceLevel convergencePolynomial:(float)convergencePolynomial recursionLevel:(uint16_t)recursionLevel recursionMaxLevel:(uint16_t)recursionMaxLevel rAverage:(float *)rAverage rAverages:(NSArray **)rAverages { + NSMutableArray *averagesAtHeights = [NSMutableArray array]; + float currentAverage = 0; + uint32_t checkHeight = height; + uint16_t i = 0; + float internalVarianceParameter = ((startingVarianceLevel - endingVarianceLevel) / endingVarianceLevel); + while (checkHeight < self.maxTransactionsInfoDataLastHeight) { + uint16_t averageValue = [self averageTransactionsFor500RangeAtHeight:checkHeight]; + + if (i != 0 && averageValue > 10) { //before 12 just ignore + float maxVariance = endingVarianceLevel * (powf((float)i, convergencePolynomial) + internalVarianceParameter) / powf((float)i, convergencePolynomial); + //NSLog(@"height %d averageValue %hu currentAverage %.2f variance %.2f",checkHeight,averageValue,currentAverage,fabsf(averageValue - currentAverage)/currentAverage); + if (fabsf(averageValue - currentAverage) > maxVariance * currentAverage) { + //there was a big change in variance + if (recursionLevel > recursionMaxLevel) break; //don't recurse again + //We need to make sure that this wasn't a 1 time variance + float nextAverage = 0; + NSArray *nextAverages = nil; + + uint32_t nextHeight = [self firstHeightOutOfAverageRangeWithStart500RangeHeight:checkHeight startingVarianceLevel:startingVarianceLevel endingVarianceLevel:endingVarianceLevel convergencePolynomial:convergencePolynomial recursionLevel:recursionLevel + 1 recursionMaxLevel:recursionMaxLevel rAverage:&nextAverage rAverages:&nextAverages]; + if (fabsf(nextAverage - currentAverage) > endingVarianceLevel * currentAverage) { + break; + } else { + [averagesAtHeights addObjectsFromArray:nextAverages]; + checkHeight = nextHeight; + } + } else { + [averagesAtHeights addObject:@(averageValue)]; + currentAverage = [[averagesAtHeights valueForKeyPath:@"@avg.self"] floatValue]; + checkHeight += 500; + } + } else { + [averagesAtHeights addObject:@(averageValue)]; + currentAverage = [[averagesAtHeights valueForKeyPath:@"@avg.self"] floatValue]; + checkHeight += 500; + } + i++; + } + if (rAverage) { + *rAverage = currentAverage; + } + if (rAverages) { + *rAverages = averagesAtHeights; + } + return checkHeight; +} + +- (uint16_t)averageTransactionsFor500RangeAtHeight:(uint32_t)height { + if (height < self.maxTransactionsInfoDataFirstHeight) return 0; + if (height > self.maxTransactionsInfoDataFirstHeight + self.maxTransactionsInfoData.length * 500 / 6) return 0; + uint32_t offset = floor(((double)height - self.maxTransactionsInfoDataFirstHeight) * 2.0 / 500.0) * 3; + //uint32_t checkHeight = [self.maxTransactionsInfoData UInt16AtOffset:offset]*500; + uint16_t average = [self.maxTransactionsInfoData UInt16AtOffset:offset + 2]; + uint16_t max = [self.maxTransactionsInfoData UInt16AtOffset:offset + 4]; + NSAssert(average < max, @"Sanity check that average < max"); + return average; +} + +- (uint16_t)maxTransactionsFor500RangeAtHeight:(uint32_t)height { + if (height < self.maxTransactionsInfoDataFirstHeight) return 0; + if (height > self.maxTransactionsInfoDataFirstHeight + self.maxTransactionsInfoData.length * 500 / 6) return 0; + uint32_t offset = floor(((double)height - self.maxTransactionsInfoDataFirstHeight) * 2.0 / 500.0) * 3; + //uint32_t checkHeight = [self.maxTransactionsInfoData UInt16AtOffset:offset]*500; + uint16_t average = [self.maxTransactionsInfoData UInt16AtOffset:offset + 2]; + uint16_t max = [self.maxTransactionsInfoData UInt16AtOffset:offset + 4]; + NSAssert(average < max, @"Sanity check that average < max"); + return max; +} + +- (BOOL)shouldRequestMerkleBlocksForZoneBetweenHeight:(uint32_t)blockHeight andEndHeight:(uint32_t)endBlockHeight { + uint16_t blockZone = blockHeight / 500; + uint16_t endBlockZone = endBlockHeight / 500 + (endBlockHeight % 500 ? 1 : 0); + if (self.chainSynchronizationFingerprint) { + while (blockZone < endBlockZone) { + if ([[self chainSynchronizationBlockZones] containsObject:@(blockZone)]) return TRUE; + } + return NO; + } else { + return YES; + } +} + +- (BOOL)shouldRequestMerkleBlocksForZoneAfterHeight:(uint32_t)blockHeight { + uint16_t blockZone = blockHeight / 500; + uint16_t leftOver = blockHeight % 500; + if (self.chainSynchronizationFingerprint) { + return [[self chainSynchronizationBlockZones] containsObject:@(blockZone)] || [[self chainSynchronizationBlockZones] containsObject:@(blockZone + 1)] || [[self chainSynchronizationBlockZones] containsObject:@(blockZone + 2)] || [[self chainSynchronizationBlockZones] containsObject:@(blockZone + 3)] || (!leftOver && [self shouldRequestMerkleBlocksForZoneAfterHeight:(blockZone + 1) * 500]); + } else { + return YES; + } +} + +@end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h index 500977193..0d0ba4bbe 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h @@ -23,9 +23,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#import "DSBackgroundManager.h" #import "DSChain.h" #import "DSKeyManager.h" #import "DSPeer.h" +#import "DSSyncState.h" #import NS_ASSUME_NONNULL_BEGIN @@ -43,26 +45,22 @@ typedef NS_ENUM(uint32_t, DSSyncCountInfo) FOUNDATION_EXPORT NSString *const DSChainManagerNotificationChainKey; FOUNDATION_EXPORT NSString *const DSChainManagerNotificationWalletKey; FOUNDATION_EXPORT NSString *const DSChainManagerNotificationAccountKey; +FOUNDATION_EXPORT NSString *const DSChainManagerNotificationSyncStateKey; FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncWillStartNotification; FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerChainSyncDidStartNotification; -FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncParametersUpdatedNotification; FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncFinishedNotification; FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncFailedNotification; +FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncStateDidChangeNotification; -@class DSGovernanceSyncManager, DSMasternodeManager, DSSporkManager, DSPeerManager, DSGovernanceVote, DSDAPIClient, DSTransactionManager, DSIdentitiesManager, DSBloomFilter, DSBlock, DSFullBlock, DSKeyManager; +@class DSGovernanceSyncManager, DSMasternodeManager, DSSporkManager, DSPeerManager, DSGovernanceVote, DSDAPIClient, DSTransactionManager, DSIdentitiesManager, DSBackgroundManager, DSBloomFilter, DSBlock, DSFullBlock, DSKeyManager, DSSyncState; typedef void (^BlockMiningCompletionBlock)(DSFullBlock *_Nullable block, NSUInteger attempts, NSTimeInterval timeUsed, NSError *_Nullable error); typedef void (^MultipleBlockMiningCompletionBlock)(NSArray *block, NSArray *attempts, NSTimeInterval timeUsed, NSError *_Nullable error); @interface DSChainManager : NSObject -@property (nonatomic, readonly) double chainSyncProgress; -@property (nonatomic, readonly) double terminalHeaderSyncProgress; -@property (nonatomic, readonly) double combinedSyncProgress; -@property (nonatomic, readonly) double chainSyncWeight; -@property (nonatomic, readonly) double terminalHeaderSyncWeight; -@property (nonatomic, readonly) double masternodeListSyncWeight; +@property (nonatomic, readonly) DSBackgroundManager *backgroundManager; @property (nonatomic, readonly) DSSporkManager *sporkManager; @property (nonatomic, readonly) DSMasternodeManager *masternodeManager; @property (nonatomic, readonly) DSGovernanceSyncManager *governanceSyncManager; @@ -73,29 +71,21 @@ typedef void (^MultipleBlockMiningCompletionBlock)(NSArray *block @property (nonatomic, readonly) DSKeyManager *keyManager; @property (nonatomic, readonly) DSChain *chain; @property (nonatomic, readonly) NSData *chainSynchronizationFingerprint; +@property (nonatomic, readonly, getter = isSynced) BOOL synced; +@property (nonatomic, readonly) double combinedSyncProgress; /*! @brief Returns the sync phase that the chain is currently in. */ @property (nonatomic, readonly) DSChainSyncPhase syncPhase; -- (void)startSync; +/*! @brief Returns determined chain sync state. */ +@property (nonatomic, readonly) DSSyncState *syncState; +- (void)startSync; - (void)stopSync; - - (void)syncBlocksRescan; - - (void)masternodeListAndBlocksRescan; - - (void)masternodeListRescan; -// MARK: - Mining - -- (void)mineEmptyBlocks:(uint32_t)blockCount toPaymentAddress:(NSString *)paymentAddress withTimeout:(NSTimeInterval)timeout completion:(MultipleBlockMiningCompletionBlock)completion; - -- (void)mineEmptyBlocks:(uint32_t)blockCount toPaymentAddress:(NSString *)paymentAddress afterBlock:(DSBlock *)block previousBlocks:(NSDictionary *)previousBlocks withTimeout:(NSTimeInterval)timeout completion:(MultipleBlockMiningCompletionBlock)completion; - -- (void)mineBlockToPaymentAddress:(NSString *)paymentAddress withTransactions:(NSArray *_Nullable)transactions withTimeout:(NSTimeInterval)timeout completion:(BlockMiningCompletionBlock)completion; - -- (void)mineBlockAfterBlock:(DSBlock *)block toPaymentAddress:(NSString *)paymentAddress withTransactions:(NSArray *_Nullable)transactions previousBlocks:(NSDictionary *)previousBlocks nonceOffset:(uint32_t)nonceOffset withTimeout:(NSTimeInterval)timeout completion:(BlockMiningCompletionBlock)completion; - (DSChainLock * _Nullable)chainLockForBlockHash:(UInt256)blockHash; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m index 18830a575..2672588d6 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m @@ -26,7 +26,9 @@ #import "DSBloomFilter.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataClass.h" +#import "DSChainManager+Mining.h" #import "DSChainManager+Protected.h" +#import "DSChainManager+Transactions.h" #import "DSCheckpoint.h" #import "DSDerivationPath.h" #import "DSEventManager.h" @@ -53,10 +55,8 @@ @interface DSChainManager () -@property (nonatomic, assign) double chainSyncWeight; -@property (nonatomic, assign) double terminalHeaderSyncWeight; -@property (nonatomic, assign) double masternodeListSyncWeight; @property (nonatomic, strong) DSChain *chain; +@property (nonatomic, strong) DSBackgroundManager *backgroundManager; @property (nonatomic, strong) DSSporkManager *sporkManager; @property (nonatomic, strong) DSMasternodeManager *masternodeManager; @property (nonatomic, strong) DSKeyManager *keyManager; @@ -65,17 +65,13 @@ @interface DSChainManager () @property (nonatomic, strong) DSDAPIClient *DAPIClient; @property (nonatomic, strong) DSTransactionManager *transactionManager; @property (nonatomic, strong) DSPeerManager *peerManager; -@property (nonatomic, assign) uint32_t chainSyncStartHeight; -@property (nonatomic, assign) uint32_t terminalSyncStartHeight; @property (nonatomic, assign) uint64_t sessionConnectivityNonce; @property (nonatomic, assign) BOOL gotSporksAtChainSyncStart; -@property (nonatomic, strong) NSData *maxTransactionsInfoData; -@property (nonatomic, strong) RHIntervalTree *heightTransactionZones; -@property (nonatomic, assign) uint32_t maxTransactionsInfoDataFirstHeight; -@property (nonatomic, assign) uint32_t maxTransactionsInfoDataLastHeight; -@property (nonatomic, strong) NSData *chainSynchronizationFingerprint; -@property (nonatomic, strong) NSOrderedSet *chainSynchronizationBlockZones; -@property (nonatomic, strong) dispatch_queue_t miningQueue; + +@property (nonatomic, strong) DSSyncState *syncState; +@property (nonatomic, assign) NSTimeInterval lastNotifiedBlockDidChange; +@property (nonatomic, strong) NSTimer *lastNotifiedBlockDidChangeTimer; + @end @@ -85,9 +81,10 @@ - (instancetype)initWithChain:(DSChain *)chain { if (!(self = [super init])) return nil; self.chain = chain; - self.syncPhase = DSChainSyncPhase_Offline; chain.chainManager = self; + self.syncState = [[DSSyncState alloc] initWithSyncPhase:DSChainSyncPhase_Offline]; self.keyManager = [[DSKeyManager alloc] initWithChain:chain]; + self.backgroundManager = [[DSBackgroundManager alloc] initWithChain:chain]; self.sporkManager = [[DSSporkManager alloc] initWithChain:chain]; self.masternodeManager = [[DSMasternodeManager alloc] initWithChain:chain]; self.DAPIClient = [[DSDAPIClient alloc] initWithChain:chain]; //this must be @@ -97,7 +94,8 @@ - (instancetype)initWithChain:(DSChain *)chain { self.peerManager = [[DSPeerManager alloc] initWithChain:chain]; self.identitiesManager = [[DSIdentitiesManager alloc] initWithChain:chain]; self.gotSporksAtChainSyncStart = FALSE; - self.sessionConnectivityNonce = (long long)arc4random() << 32 | arc4random(); + self.sessionConnectivityNonce = ((uint64_t)arc4random() << 32) | arc4random(); + self.lastNotifiedBlockDidChange = 0; if ([self.masternodeManager hasCurrentMasternodeListInLast30Days]) { [self.peerManager useMasternodeList:self.masternodeManager.currentMasternodeList withConnectivityNonce:self.sessionConnectivityNonce]; @@ -106,369 +104,29 @@ - (instancetype)initWithChain:(DSChain *)chain { //[self loadMaxTransactionInfo]; //[self loadHeightTransactionZones]; - _miningQueue = dispatch_queue_create([[NSString stringWithFormat:@"org.dashcore.dashsync.mining.%@", self.chain.uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL); + self.miningQueue = dispatch_queue_create([[NSString stringWithFormat:@"org.dashcore.dashsync.mining.%@", self.chain.uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL); DSLog(@"[%@] DSChainManager.initWithChain %@", chain.name, chain); return self; } -// MARK: - Max transaction info - -- (void)loadMaxTransactionInfo { - NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; - NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; - NSString *filePath = [bundle pathForResource:[NSString stringWithFormat:@"MaxTransactionInfo_%@", self.chain.name] ofType:@"dat"]; - self.maxTransactionsInfoData = [NSData dataWithContentsOfFile:filePath]; - if (self.maxTransactionsInfoData) { - self.maxTransactionsInfoDataFirstHeight = [self.maxTransactionsInfoData UInt16AtOffset:0] * 500; - self.maxTransactionsInfoDataLastHeight = [self.maxTransactionsInfoData UInt16AtOffset:self.maxTransactionsInfoData.length - 6] * 500; - //We need MaxTransactionsInfoDataLastHeight to be after the last checkpoint so there is no gap in info. We can gather Max Transactions after the last checkpoint from the initial terminal sync. - NSAssert(self.maxTransactionsInfoDataLastHeight > self.chain.checkpoints.lastObject.height, @"MaxTransactionsInfoDataLastHeight should always be after the last checkpoint for the system to work"); - } - - ////Some code to log checkpoints, keep it here for some testing in the future. - // for (DSCheckpoint * checkpoint in self.chain.checkpoints) { - // if (checkpoint.height > 340000) { - // NSLog(@"%d:%d",checkpoint.height,[self averageTransactionsFor500RangeAtHeight:checkpoint.height]); - // } - // } - // float average = 0; - // uint32_t startRange = self.maxTransactionsInfoDataFirstHeight; - // NSMutableData * data = [NSMutableData data]; - // [data appendUInt16:startRange/500]; - // while (startRange < self.maxTransactionsInfoDataLastHeight) { - // uint32_t endRange = [self firstHeightOutOfAverageRangeWithStart500RangeHeight:startRange rAverage:&average]; - // NSLog(@"heights %d-%d averageTransactions %.1f",startRange,endRange,average); - // startRange = endRange; - // [data appendUInt16:(unsigned short)average]; - // [data appendUInt16:endRange/500]; - // } - // - // NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - // NSString *documentsDirectory = [paths objectAtIndex:0]; - // NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"HeightTransactionZones_%@.dat",self.chain.name]]; - // [data writeToFile:dataPath atomically:YES]; - // -} - -- (void)loadHeightTransactionZones { - NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; - NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; - NSString *filePath = [bundle pathForResource:[NSString stringWithFormat:@"HeightTransactionZones_%@", self.chain.name] ofType:@"dat"]; - NSData *heightTransactionZonesData = [NSData dataWithContentsOfFile:filePath]; - if (heightTransactionZonesData) { - NSMutableArray *intervals = [NSMutableArray array]; - for (uint16_t i = 0; i < heightTransactionZonesData.length - 4; i += 4) { - uint32_t intervalStartHeight = [heightTransactionZonesData UInt16AtOffset:i] * 500; - uint16_t average = [heightTransactionZonesData UInt16AtOffset:i + 2]; - uint32_t intervalEndHeight = [heightTransactionZonesData UInt16AtOffset:i + 4] * 500; - [intervals addObject:[RHInterval intervalWithStart:intervalStartHeight stop:intervalEndHeight - 1 object:@(average)]]; - } - self.heightTransactionZones = [[RHIntervalTree alloc] initWithIntervalObjects:intervals]; - } -} - -- (uint16_t)averageTransactionsInZoneForStartHeight:(uint32_t)startHeight endHeight:(uint32_t)endHeight { - NSArray *intervals = [self.heightTransactionZones overlappingObjectsForStart:startHeight andStop:endHeight]; - if (!intervals.count) return 0; - if (intervals.count == 1) return [(NSNumber *)[intervals[0] object] unsignedShortValue]; - uint64_t aggregate = 0; - for (RHInterval *interval in intervals) { - uint64_t value = [(NSNumber *)interval.object unsignedLongValue]; - if (interval == [intervals firstObject]) { - aggregate += value * (interval.stop - startHeight + 1); - } else if (interval == [intervals lastObject]) { - aggregate += value * (endHeight - interval.start + 1); - } else { - aggregate += value * (interval.stop - interval.start + 1); - } - } - return aggregate / (endHeight - startHeight); -} - -- (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height rAverage:(float *)rAverage { - return [self firstHeightOutOfAverageRangeWithStart500RangeHeight:height startingVarianceLevel:1 endingVarianceLevel:0.2 convergencePolynomial:0.33 rAverage:rAverage]; -} - -- (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height startingVarianceLevel:(float)startingVarianceLevel endingVarianceLevel:(float)endingVarianceLevel convergencePolynomial:(float)convergencePolynomial rAverage:(float *)rAverage { - return [self firstHeightOutOfAverageRangeWithStart500RangeHeight:height startingVarianceLevel:startingVarianceLevel endingVarianceLevel:endingVarianceLevel convergencePolynomial:convergencePolynomial recursionLevel:0 recursionMaxLevel:2 rAverage:rAverage rAverages:nil]; -} - -- (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height startingVarianceLevel:(float)startingVarianceLevel endingVarianceLevel:(float)endingVarianceLevel convergencePolynomial:(float)convergencePolynomial recursionLevel:(uint16_t)recursionLevel recursionMaxLevel:(uint16_t)recursionMaxLevel rAverage:(float *)rAverage rAverages:(NSArray **)rAverages { - NSMutableArray *averagesAtHeights = [NSMutableArray array]; - float currentAverage = 0; - uint32_t checkHeight = height; - uint16_t i = 0; - float internalVarianceParameter = ((startingVarianceLevel - endingVarianceLevel) / endingVarianceLevel); - while (checkHeight < self.maxTransactionsInfoDataLastHeight) { - uint16_t averageValue = [self averageTransactionsFor500RangeAtHeight:checkHeight]; - - if (i != 0 && averageValue > 10) { //before 12 just ignore - float maxVariance = endingVarianceLevel * (powf((float)i, convergencePolynomial) + internalVarianceParameter) / powf((float)i, convergencePolynomial); - //NSLog(@"height %d averageValue %hu currentAverage %.2f variance %.2f",checkHeight,averageValue,currentAverage,fabsf(averageValue - currentAverage)/currentAverage); - if (fabsf(averageValue - currentAverage) > maxVariance * currentAverage) { - //there was a big change in variance - if (recursionLevel > recursionMaxLevel) break; //don't recurse again - //We need to make sure that this wasn't a 1 time variance - float nextAverage = 0; - NSArray *nextAverages = nil; - - uint32_t nextHeight = [self firstHeightOutOfAverageRangeWithStart500RangeHeight:checkHeight startingVarianceLevel:startingVarianceLevel endingVarianceLevel:endingVarianceLevel convergencePolynomial:convergencePolynomial recursionLevel:recursionLevel + 1 recursionMaxLevel:recursionMaxLevel rAverage:&nextAverage rAverages:&nextAverages]; - if (fabsf(nextAverage - currentAverage) > endingVarianceLevel * currentAverage) { - break; - } else { - [averagesAtHeights addObjectsFromArray:nextAverages]; - checkHeight = nextHeight; - } - } else { - [averagesAtHeights addObject:@(averageValue)]; - currentAverage = [[averagesAtHeights valueForKeyPath:@"@avg.self"] floatValue]; - checkHeight += 500; - } - } else { - [averagesAtHeights addObject:@(averageValue)]; - currentAverage = [[averagesAtHeights valueForKeyPath:@"@avg.self"] floatValue]; - checkHeight += 500; - } - i++; - } - if (rAverage) { - *rAverage = currentAverage; - } - if (rAverages) { - *rAverages = averagesAtHeights; - } - return checkHeight; -} - -- (uint16_t)averageTransactionsFor500RangeAtHeight:(uint32_t)height { - if (height < self.maxTransactionsInfoDataFirstHeight) return 0; - if (height > self.maxTransactionsInfoDataFirstHeight + self.maxTransactionsInfoData.length * 500 / 6) return 0; - uint32_t offset = floor(((double)height - self.maxTransactionsInfoDataFirstHeight) * 2.0 / 500.0) * 3; - //uint32_t checkHeight = [self.maxTransactionsInfoData UInt16AtOffset:offset]*500; - uint16_t average = [self.maxTransactionsInfoData UInt16AtOffset:offset + 2]; - uint16_t max = [self.maxTransactionsInfoData UInt16AtOffset:offset + 4]; - NSAssert(average < max, @"Sanity check that average < max"); - return average; -} - -- (uint16_t)maxTransactionsFor500RangeAtHeight:(uint32_t)height { - if (height < self.maxTransactionsInfoDataFirstHeight) return 0; - if (height > self.maxTransactionsInfoDataFirstHeight + self.maxTransactionsInfoData.length * 500 / 6) return 0; - uint32_t offset = floor(((double)height - self.maxTransactionsInfoDataFirstHeight) * 2.0 / 500.0) * 3; - //uint32_t checkHeight = [self.maxTransactionsInfoData UInt16AtOffset:offset]*500; - uint16_t average = [self.maxTransactionsInfoData UInt16AtOffset:offset + 2]; - uint16_t max = [self.maxTransactionsInfoData UInt16AtOffset:offset + 4]; - NSAssert(average < max, @"Sanity check that average < max"); - return max; -} - -// MARK: - Info - -- (NSString *)chainSyncStartHeightKey { - return [NSString stringWithFormat:@"%@_%@", SYNC_STARTHEIGHT_KEY, [self.chain uniqueID]]; -} - -- (NSString *)terminalSyncStartHeightKey { - return [NSString stringWithFormat:@"%@_%@", TERMINAL_SYNC_STARTHEIGHT_KEY, [self.chain uniqueID]]; -} - -- (void)assignSyncWeights { - uint32_t chainBlocks = [self chainBlocksToSync]; - uint32_t terminalBlocks = [self terminalHeadersToSync]; - uint32_t masternodeListsToSync = self.masternodeManager.estimatedMasternodeListsToSync; - //a unit of weight is the time it would take to sync 1000 blocks; - //terminal headers are 4 times faster the blocks - //the first masternode list is worth 20000 blocks - //each masternode list after that is worth 2000 blocks - uint32_t chainWeight = chainBlocks; - uint32_t terminalWeight = terminalBlocks / 4; - uint32_t masternodeWeight = masternodeListsToSync ? (20000 + 2000 * (masternodeListsToSync - 1)) : 0; - uint32_t totalWeight = chainWeight + terminalWeight + masternodeWeight; - if (totalWeight == 0) { - self.terminalHeaderSyncWeight = 0; - self.masternodeListSyncWeight = 0; - self.chainSyncWeight = 1; - } else { - self.chainSyncWeight = ((float)chainWeight) / totalWeight; - self.terminalHeaderSyncWeight = ((float)terminalWeight) / totalWeight; - self.masternodeListSyncWeight = ((float)masternodeWeight) / totalWeight; - } -} - -- (uint32_t)chainBlocksToSync { - if (self.chain.lastSyncBlockHeight >= self.chain.estimatedBlockHeight) return 0; - return self.chain.estimatedBlockHeight - self.chain.lastSyncBlockHeight; -} - -- (double)chainSyncProgress { - if (!self.peerManager.downloadPeer && self.chainSyncStartHeight == 0) return 0.0; - if (self.chain.lastSyncBlockHeight >= self.chain.estimatedBlockHeight) return 1.0; - - double lastBlockHeight = self.chain.lastSyncBlockHeight; - double estimatedBlockHeight = self.chain.estimatedBlockHeight; - double syncStartHeight = self.chainSyncStartHeight; - double progress; - if (estimatedBlockHeight == 0) return 0; - if (syncStartHeight > lastBlockHeight) { - progress = lastBlockHeight / estimatedBlockHeight; - } else { - if (estimatedBlockHeight - syncStartHeight == 0) return 0; - progress = (lastBlockHeight - syncStartHeight) / (estimatedBlockHeight - syncStartHeight); - } - return MIN(1.0, MAX(0.0, 0.1 + 0.9 * progress)); +- (BOOL)isSynced { + return self.syncState.combinedSyncProgress == 1.0; } - -- (uint32_t)terminalHeadersToSync { - if (self.chain.lastTerminalBlockHeight >= self.chain.estimatedBlockHeight) return 0; - return self.chain.estimatedBlockHeight - self.chain.lastTerminalBlockHeight; -} - -- (double)terminalHeaderSyncProgress { - if (!self.peerManager.downloadPeer && self.terminalSyncStartHeight == 0) return 0.0; - if (self.chain.lastTerminalBlockHeight >= self.chain.estimatedBlockHeight) return 1.0; - - double lastBlockHeight = self.chain.lastTerminalBlockHeight; - double estimatedBlockHeight = self.chain.estimatedBlockHeight; - double syncStartHeight = self.terminalSyncStartHeight; - double progress; - if (syncStartHeight > lastBlockHeight) { - progress = lastBlockHeight / estimatedBlockHeight; - } else { - progress = (lastBlockHeight - syncStartHeight) / (estimatedBlockHeight - syncStartHeight); - } - return MIN(1.0, MAX(0.0, 0.1 + 0.9 * progress)); -} - -#define LOG_COMBINED_SYNC_PROGRESS 0 - - (double)combinedSyncProgress { -#if LOG_COMBINED_SYNC_PROGRESS - DSLog(@"[%@] combinedSyncProgress breakdown %f %f %f", self.chain.name, self.terminalHeaderSyncProgress, self.masternodeManager.masternodeListAndQuorumsSyncProgress, self.chainSyncProgress); -#endif - if ((self.terminalHeaderSyncWeight + self.chainSyncWeight + self.masternodeListSyncWeight) == 0) { - @synchronized (self.peerManager) { - return self.peerManager.connected ? 1 : 0; - } - } else { - double progress = self.terminalHeaderSyncProgress * self.terminalHeaderSyncWeight + self.masternodeManager.masternodeListAndQuorumsSyncProgress * self.masternodeListSyncWeight + self.chainSyncProgress * self.chainSyncWeight; - if (progress < 0.99995) { - return progress; - } else { - return 1; - } - } + return self.syncState.combinedSyncProgress; } -- (void)resetChainSyncStartHeight { - NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; - if (self.chainSyncStartHeight == 0) self.chainSyncStartHeight = (uint32_t)[userDefaults integerForKey:self.chainSyncStartHeightKey]; - - if (self.chainSyncStartHeight == 0) { - self.chainSyncStartHeight = self.chain.lastSyncBlockHeight; - [[NSUserDefaults standardUserDefaults] setInteger:self.chainSyncStartHeight forKey:self.chainSyncStartHeightKey]; - } -} - -- (void)restartChainSyncStartHeight { - self.chainSyncStartHeight = 0; - [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:self.chainSyncStartHeightKey]; -} - - -- (void)resetTerminalSyncStartHeight { - NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; - if (self.terminalSyncStartHeight == 0) self.terminalSyncStartHeight = (uint32_t)[userDefaults integerForKey:self.terminalSyncStartHeightKey]; - - if (self.terminalSyncStartHeight == 0) { - self.terminalSyncStartHeight = self.chain.lastTerminalBlockHeight; - [[NSUserDefaults standardUserDefaults] setInteger:self.terminalSyncStartHeight forKey:self.terminalSyncStartHeightKey]; - } -} -- (void)restartTerminalSyncStartHeight { - self.terminalSyncStartHeight = 0; - [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:self.terminalSyncStartHeightKey]; -} +// MARK: - Info - (void)relayedNewItem { self.lastChainRelayTime = [NSDate timeIntervalSince1970]; } -- (void)resetLastRelayedItemTime { - self.lastChainRelayTime = 0; -} - -// MARK: - Mining - -- (void)mineEmptyBlocks:(uint32_t)blockCount toPaymentAddress:(NSString *)paymentAddress withTimeout:(NSTimeInterval)timeout completion:(MultipleBlockMiningCompletionBlock)completion { - [self mineEmptyBlocks:blockCount toPaymentAddress:paymentAddress afterBlock:self.chain.lastTerminalBlock previousBlocks:self.chain.terminalBlocks withTimeout:timeout completion:completion]; -} - -- (void)mineEmptyBlocks:(uint32_t)blockCount toPaymentAddress:(NSString *)paymentAddress afterBlock:(DSBlock *)previousBlock previousBlocks:(NSDictionary *)previousBlocks withTimeout:(NSTimeInterval)timeout completion:(MultipleBlockMiningCompletionBlock)completion { - dispatch_async(_miningQueue, ^{ - NSTimeInterval start = [[NSDate date] timeIntervalSince1970]; - NSTimeInterval end = [[[NSDate alloc] initWithTimeIntervalSinceNow:timeout] timeIntervalSince1970]; - NSMutableArray *blocksArray = [NSMutableArray array]; - NSMutableArray *attemptsArray = [NSMutableArray array]; - __block uint32_t blocksRemaining = blockCount; - __block NSMutableDictionary *mPreviousBlocks = [previousBlocks mutableCopy]; - __block DSBlock *currentBlock = previousBlock; - while ([[NSDate date] timeIntervalSince1970] < end && blocksRemaining > 0) { - dispatch_semaphore_t sem = dispatch_semaphore_create(0); - [self mineBlockAfterBlock:currentBlock - toPaymentAddress:paymentAddress - withTransactions:[NSArray array] - previousBlocks:mPreviousBlocks - nonceOffset:0 - withTimeout:timeout - completion:^(DSFullBlock *_Nullable block, NSUInteger attempts, NSTimeInterval timeUsed, NSError *_Nullable error) { - NSAssert(uint256_is_not_zero(block.blockHash), @"Block hash must not be empty"); - dispatch_semaphore_signal(sem); - [blocksArray addObject:block]; - [mPreviousBlocks setObject:block forKey:uint256_obj(block.blockHash)]; - currentBlock = block; - blocksRemaining--; - [attemptsArray addObject:@(attempts)]; - }]; - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - } - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(blocksArray, attemptsArray, [[NSDate date] timeIntervalSince1970] - start, nil); - }); - } - }); -} - -- (void)mineBlockToPaymentAddress:(NSString *)paymentAddress withTransactions:(NSArray *)transactions withTimeout:(NSTimeInterval)timeout completion:(BlockMiningCompletionBlock)completion { - [self mineBlockAfterBlock:self.chain.lastTerminalBlock toPaymentAddress:paymentAddress withTransactions:transactions previousBlocks:self.chain.terminalBlocks nonceOffset:0 withTimeout:timeout completion:completion]; -} - -- (void)mineBlockAfterBlock:(DSBlock *)block toPaymentAddress:(NSString *)paymentAddress withTransactions:(NSArray *)transactions previousBlocks:(NSDictionary *)previousBlocks nonceOffset:(uint32_t)nonceOffset withTimeout:(NSTimeInterval)timeout completion:(nonnull BlockMiningCompletionBlock)completion { - DSCoinbaseTransaction *coinbaseTransaction = [[DSCoinbaseTransaction alloc] initWithCoinbaseMessage:@"From iOS" paymentAddresses:@[paymentAddress] atHeight:block.height + 1 onChain:block.chain]; - DSFullBlock *fullblock = [[DSFullBlock alloc] initWithCoinbaseTransaction:coinbaseTransaction transactions:[NSSet set] previousBlockHash:block.blockHash previousBlocks:previousBlocks timestamp:[[NSDate date] timeIntervalSince1970] height:block.height + 1 onChain:self.chain]; - uint64_t attempts = 0; - NSDate *startTime = [NSDate date]; - if ([fullblock mineBlockAfterBlock:block withNonceOffset:nonceOffset withTimeout:timeout rAttempts:&attempts]) { - if (completion) { - completion(fullblock, attempts, -[startTime timeIntervalSinceNow], nil); - } - } else { - if (completion) { - NSError *error = [NSError errorWithCode:500 localizedDescriptionKey:@"A block could not be mined in the selected time interval."]; - completion(nil, attempts, -[startTime timeIntervalSinceNow], error); - } - } -} - // MARK: - Blockchain Sync - (void)startSync { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerSyncWillStartNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; DSLog(@"[%@] startSync -> peerManager::connect", self.chain.name); [self.peerManager connect]; } @@ -477,7 +135,8 @@ - (void)stopSync { DSLog(@"[%@] stopSync (chain switch)", self.chain.name); [self.masternodeManager stopSync]; [self.peerManager disconnect:DSDisconnectReason_ChainSwitch]; - self.syncPhase = DSChainSyncPhase_Offline; + self.syncState.syncPhase = DSChainSyncPhase_Offline; + [self notifySyncStateChanged]; } - (void)removeNonMainnetTrustedPeer { @@ -493,11 +152,7 @@ - (void)disconnectedMasternodeListAndBlocksRescan { [[DashSync sharedSyncController] wipeBlockchainDataForChain:self.chain inContext:chainContext]; [self removeNonMainnetTrustedPeer]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerSyncWillStartNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; DSLog(@"[%@] disconnectedMasternodeListAndBlocksRescan -> peerManager::connect", self.chain.name); [self.peerManager connect]; } @@ -507,11 +162,7 @@ - (void)disconnectedMasternodeListRescan { [[DashSync sharedSyncController] wipeMasternodeDataForChain:self.chain inContext:chainContext]; [self removeNonMainnetTrustedPeer]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerSyncWillStartNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; DSLog(@"[%@] disconnectedMasternodeListRescan -> peerManager::connect", self.chain.name); [self.peerManager connect]; } @@ -521,11 +172,7 @@ - (void)disconnectedSyncBlocksRescan { [[DashSync sharedSyncController] wipeBlockchainNonTerminalDataForChain:self.chain inContext:chainContext]; [self removeNonMainnetTrustedPeer]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerSyncWillStartNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; DSLog(@"[%@] disconnectedSyncBlocksRescan -> peerManager::connect", self.chain.name); [self.peerManager connect]; } @@ -583,65 +230,30 @@ - (void)chainWasWiped:(DSChain *)chain { } - (void)chainWillStartSyncingBlockchain:(DSChain *)chain { + self.lastChainRelayTime = 0; if (!self.gotSporksAtChainSyncStart) { [self.sporkManager getSporks]; //get the sporks early on } } -- (NSData *)chainSynchronizationFingerprint { - // if (!_chainSynchronizationFingerprint) { - // _chainSynchronizationFingerprint = @"".hexToData; - // } - return _chainSynchronizationFingerprint; -} - - -- (NSOrderedSet *)chainSynchronizationBlockZones { - if (!_chainSynchronizationBlockZones) { - _chainSynchronizationBlockZones = [DSWallet blockZonesFromChainSynchronizationFingerprint:self.chainSynchronizationFingerprint rVersion:0 rChainHeight:0]; - } - return _chainSynchronizationBlockZones; -} - -- (BOOL)shouldRequestMerkleBlocksForZoneBetweenHeight:(uint32_t)blockHeight andEndHeight:(uint32_t)endBlockHeight { - uint16_t blockZone = blockHeight / 500; - uint16_t endBlockZone = endBlockHeight / 500 + (endBlockHeight % 500 ? 1 : 0); - if (self.chainSynchronizationFingerprint) { - while (blockZone < endBlockZone) { - if ([[self chainSynchronizationBlockZones] containsObject:@(blockZone)]) return TRUE; - } - return NO; - } else { - return YES; - } -} - -- (BOOL)shouldRequestMerkleBlocksForZoneAfterHeight:(uint32_t)blockHeight { - uint16_t blockZone = blockHeight / 500; - uint16_t leftOver = blockHeight % 500; - if (self.chainSynchronizationFingerprint) { - return [[self chainSynchronizationBlockZones] containsObject:@(blockZone)] || [[self chainSynchronizationBlockZones] containsObject:@(blockZone + 1)] || [[self chainSynchronizationBlockZones] containsObject:@(blockZone + 2)] || [[self chainSynchronizationBlockZones] containsObject:@(blockZone + 3)] || (!leftOver && [self shouldRequestMerkleBlocksForZoneAfterHeight:(blockZone + 1) * 500]); - } else { - return YES; - } +- (void)chainWillStartConnectingToPeers:(DSChain *)chain { + } - (void)chainShouldStartSyncingBlockchain:(DSChain *)chain onPeer:(DSPeer *)peer { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerChainSyncDidStartNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSPeerManagerNotificationPeerKey: peer ? peer : [NSNull null]}]; - }); + [self notify:DSChainManagerChainSyncDidStartNotification userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSPeerManagerNotificationPeerKey: peer ? peer : [NSNull null]}]; dispatch_async(self.chain.networkingQueue, ^{ if ((self.syncPhase != DSChainSyncPhase_ChainSync && self.syncPhase != DSChainSyncPhase_Synced) && self.chain.needsInitialTerminalHeadersSync) { //masternode list should be synced first and the masternode list is old - self.syncPhase = DSChainSyncPhase_InitialTerminalBlocks; + self.syncState.syncPhase = DSChainSyncPhase_InitialTerminalBlocks; [peer sendGetheadersMessageWithLocators:[self.chain terminalBlocksLocatorArray] andHashStop:UINT256_ZERO]; - } else if (([[DSOptionsManager sharedInstance] syncType] & DSSyncType_MasternodeList) && ((self.masternodeManager.lastMasternodeListBlockHeight < self.chain.lastTerminalBlockHeight - 8) || (self.masternodeManager.lastMasternodeListBlockHeight == UINT32_MAX))) { - self.syncPhase = DSChainSyncPhase_InitialTerminalBlocks; + } else if (([[DSOptionsManager sharedInstance] syncType] & DSSyncType_MasternodeList) && [self.masternodeManager isMasternodeListOutdated]) { + self.syncState.syncPhase = DSChainSyncPhase_InitialTerminalBlocks; [self.masternodeManager startSync]; } else { - self.syncPhase = DSChainSyncPhase_ChainSync; + self.syncState.syncPhase = DSChainSyncPhase_ChainSync; BOOL startingDevnetSync = [self.chain isDevnetAny] && self.chain.lastSyncBlockHeight < 5; NSTimeInterval cutoffTime = self.chain.earliestWalletCreationTime - HEADER_WINDOW_BUFFER_TIME; if (startingDevnetSync || (self.chain.lastSyncBlockTimestamp >= cutoffTime && [self shouldRequestMerkleBlocksForZoneAfterHeight:[self.chain lastSyncBlockHeight]])) { @@ -650,6 +262,7 @@ - (void)chainShouldStartSyncingBlockchain:(DSChain *)chain onPeer:(DSPeer *)peer [peer sendGetheadersMessageWithLocators:[self.chain chainSyncBlockLocatorArray] andHashStop:UINT256_ZERO]; } } + [self notifySyncStateChanged]; }); } @@ -666,8 +279,9 @@ - (void)chainFinishedSyncingInitialHeaders:(DSChain *)chain fromPeer:(DSPeer *)p - (void)chainFinishedSyncingTransactionsAndBlocks:(DSChain *)chain fromPeer:(DSPeer *)peer onMainChain:(BOOL)onMainChain { if (onMainChain && peer && (peer == self.peerManager.downloadPeer)) [self relayedNewItem]; DSLog(@"[%@] finished syncing", self.chain.name); - self.chainSyncStartHeight = 0; - self.syncPhase = DSChainSyncPhase_Synced; + + self.syncState.chainSyncStartHeight = 0; + self.syncState.syncPhase = DSChainSyncPhase_Synced; [self.transactionManager fetchMempoolFromNetwork]; [self.sporkManager getSporks]; [self.governanceSyncManager startGovernanceSync]; @@ -675,20 +289,31 @@ - (void)chainFinishedSyncingTransactionsAndBlocks:(DSChain *)chain fromPeer:(DSP // make sure we care about masternode lists [self.masternodeManager startSync]; } + [self notifySyncStateChanged]; +} + +- (DSChainSyncPhase)syncPhase { + return [self.syncState syncPhase]; +} + +- (void)setSyncPhase:(DSChainSyncPhase)syncPhase { + self.syncState.syncPhase = syncPhase; } - (void)syncBlockchain { DSLog(@"[%@] syncBlockchain connected peers: %lu phase: %d", self.chain.name, self.peerManager.connectedPeerCount, self.syncPhase); if (self.peerManager.connectedPeerCount == 0) { if (self.syncPhase == DSChainSyncPhase_InitialTerminalBlocks) { - self.syncPhase = DSChainSyncPhase_ChainSync; + self.syncState.syncPhase = DSChainSyncPhase_ChainSync; + [self notifySyncStateChanged]; } DSLog(@"[%@] syncBlockchain -> peerManager::connect", self.chain.name); [self.peerManager connect]; } else if (!self.peerManager.masternodeList && self.masternodeManager.currentMasternodeList) { [self.peerManager useMasternodeList:self.masternodeManager.currentMasternodeList withConnectivityNonce:self.sessionConnectivityNonce]; } else if (self.syncPhase == DSChainSyncPhase_InitialTerminalBlocks) { - self.syncPhase = DSChainSyncPhase_ChainSync; + self.syncState.syncPhase = DSChainSyncPhase_ChainSync; + [self notifySyncStateChanged]; [self chainShouldStartSyncingBlockchain:self.chain onPeer:self.peerManager.downloadPeer]; } } @@ -759,12 +384,7 @@ - (void)peer:(DSPeer *)peer relayedSyncInfo:(DSSyncCountInfo)syncCountInfo count break; } case DSSyncCountInfo_GovernanceObject: { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSGovernanceObjectCountUpdateNotification - object:nil - userInfo:@{@(syncCountInfo): @(count), - DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSGovernanceObjectCountUpdateNotification userInfo:@{@(syncCountInfo): @(count), DSChainManagerNotificationChainKey: self.chain}]; break; } case DSSyncCountInfo_GovernanceObjectVote: { @@ -775,12 +395,7 @@ - (void)peer:(DSPeer *)peer relayedSyncInfo:(DSSyncCountInfo)syncCountInfo count peer.governanceRequestState = DSGovernanceRequestState_GovernanceObjectVotes; [self.governanceSyncManager finishedGovernanceVoteSyncWithPeer:peer]; } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSGovernanceVoteCountUpdateNotification - object:nil - userInfo:@{@(syncCountInfo): @(count), - DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSGovernanceVoteCountUpdateNotification userInfo:@{@(syncCountInfo): @(count), DSChainManagerNotificationChainKey: self.chain}]; } } @@ -800,4 +415,93 @@ - (DSChainLock * _Nullable)chainLockForBlockHash:(UInt256)blockHash { return [self.transactionManager chainLockForBlockHash:blockHash]; } +- (NSString *)chainSyncStartHeightKey { + return [NSString stringWithFormat:@"%@_%@", SYNC_STARTHEIGHT_KEY, [self.chain uniqueID]]; +} + +- (NSString *)terminalSyncStartHeightKey { + return [NSString stringWithFormat:@"%@_%@", TERMINAL_SYNC_STARTHEIGHT_KEY, [self.chain uniqueID]]; +} + + +- (void)resetChainSyncStartHeight { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + BOOL changed = NO; + if (self.syncState.chainSyncStartHeight == 0) { + self.syncState.chainSyncStartHeight = (uint32_t)[userDefaults integerForKey:self.chainSyncStartHeightKey]; + changed = YES; + } + if (self.syncState.chainSyncStartHeight == 0) { + self.syncState.chainSyncStartHeight = self.chain.lastSyncBlockHeight; + changed = YES; + [[NSUserDefaults standardUserDefaults] setInteger:self.syncState.chainSyncStartHeight forKey:self.chainSyncStartHeightKey]; + } + if (changed) + [self notifySyncStateChanged]; +} + +- (void)restartChainSyncStartHeight { + self.syncState.chainSyncStartHeight = 0; + [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:self.chainSyncStartHeightKey]; + [self notifySyncStateChanged]; + +} + + +- (void)resetTerminalSyncStartHeight { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + if (self.syncState.terminalSyncStartHeight == 0) + self.syncState.terminalSyncStartHeight = (uint32_t)[userDefaults integerForKey:self.terminalSyncStartHeightKey]; + + if (self.syncState.terminalSyncStartHeight == 0) { + self.syncState.terminalSyncStartHeight = self.chain.lastTerminalBlockHeight; + [[NSUserDefaults standardUserDefaults] setInteger:self.syncState.terminalSyncStartHeight forKey:self.terminalSyncStartHeightKey]; + } +} + +- (void)restartTerminalSyncStartHeight { + self.syncState.terminalSyncStartHeight = 0; + [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:self.terminalSyncStartHeightKey]; +} + + +// MARK: Notifications + +- (void)setupNotificationTimer:(void (^ __nullable)(void))completion { + //we should avoid dispatching this message too frequently + NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970]; + if (!self.lastNotifiedBlockDidChange || (timestamp - self.lastNotifiedBlockDidChange > 0.1)) { + self.lastNotifiedBlockDidChange = timestamp; + if (self.lastNotifiedBlockDidChangeTimer) { + [self.lastNotifiedBlockDidChangeTimer invalidate]; + self.lastNotifiedBlockDidChangeTimer = nil; + } + completion(); + } else if (!self.lastNotifiedBlockDidChangeTimer) { + self.lastNotifiedBlockDidChangeTimer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer *_Nonnull timer) { + completion(); + }]; + [[NSRunLoop mainRunLoop] addTimer:self.lastNotifiedBlockDidChangeTimer forMode:NSRunLoopCommonModes]; + } +} + +- (void)notifySyncStateChanged { + [self setupNotificationTimer:^{ + @synchronized (self) { +// NSLog(@"[%@] Sync: %@", self.chain.name, self.syncState); + [self notify:DSChainManagerSyncStateDidChangeNotification + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSChainManagerNotificationSyncStateKey: [self.syncState copy] + }]; + } + }]; + +} + +- (void)notify:(NSNotificationName)name userInfo:(NSDictionary *_Nullable)userInfo { + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:userInfo]; + }); +} @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m index 108456cdc..c17220628 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m @@ -25,13 +25,13 @@ NSString const *localMasternodesDictionaryKey = @"localMasternodesDictionaryKey"; -@interface DSMasternodeManager (LocalMasternode) +@interface DSMasternodeManager () @property (nonatomic, strong) NSMutableDictionary *localMasternodesDictionaryByRegistrationTransactionHash; @end @implementation DSMasternodeManager (LocalMasternode) -@dynamic localMasternodesDictionaryByRegistrationTransactionHash; +//@dynamic localMasternodesDictionaryByRegistrationTransactionHash; - (void)setLocalMasternodesDictionaryByRegistrationTransactionHash:(NSMutableDictionary *)dictionary { objc_setAssociatedObject(self, &localMasternodesDictionaryKey, dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h index c5b813f93..e9ab3ff59 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h @@ -47,6 +47,7 @@ NS_ASSUME_NONNULL_BEGIN - (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry claimedWithOwnerWallet:(DSWallet *)wallet ownerKeyIndex:(uint32_t)ownerKeyIndex; + (void)saveMasternodeList:(DSMasternodeList *)masternodeList toChain:(DSChain *)chain havingModifiedMasternodes:(NSDictionary *)modifiedMasternodes createUnknownBlocks:(BOOL)createUnknownBlocks inContext:(NSManagedObjectContext *)context completion:(void (^)(NSError *error))completion; +- (BOOL)isMasternodeListOutdated; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h index 0af7fe899..6259fb70b 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h @@ -48,9 +48,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSUInteger knownMasternodeListsCount; @property (nonatomic, readonly) uint32_t earliestMasternodeListBlockHeight; @property (nonatomic, readonly) uint32_t lastMasternodeListBlockHeight; -@property (nonatomic, readonly) uint32_t estimatedMasternodeListsToSync; @property (nonatomic, readonly) DSMasternodeList *currentMasternodeList; -@property (nonatomic, readonly) double masternodeListAndQuorumsSyncProgress; @property (nonatomic, readonly) NSUInteger masternodeListRetrievalQueueCount; @property (nonatomic, readonly) NSUInteger masternodeListRetrievalQueueMaxAmount; @property (nonatomic, readonly) BOOL hasMasternodeListCurrentlyBeingSaved; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m index dca4860ca..92cf5bd40 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m @@ -35,6 +35,7 @@ #import "DSMasternodeListStore+Protected.h" #import "DSMasternodeManager+LocalMasternode.h" #import "DSMasternodeManager+Mndiff.h" +#import "DSMasternodeManager+Protected.h" #import "DSMerkleBlock.h" #import "DSMnDiffProcessingResult.h" #import "DSOperationQueue.h" @@ -46,7 +47,7 @@ #import "DSTransactionManager+Protected.h" #import "NSError+Dash.h" -#define SAVE_MASTERNODE_DIFF_TO_FILE (1 && DEBUG) +#define SAVE_MASTERNODE_DIFF_TO_FILE (0 && DEBUG) #define DSFullLog(FORMAT, ...) printf("%s\n", [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]) @@ -142,6 +143,11 @@ - (uint32_t)heightForBlockHash:(UInt256)blockhash { return [self.store heightForBlockHash:blockhash]; } +- (BOOL)isMasternodeListOutdated { + uint32_t lastHeight = self.lastMasternodeListBlockHeight; + return lastHeight == UINT32_MAX || lastHeight < self.chain.lastTerminalBlockHeight - 8; +} + - (DSSimplifiedMasternodeEntry *)masternodeHavingProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash { NSParameterAssert(providerRegistrationTransactionHash); return [self.currentMasternodeList.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash objectForKey:providerRegistrationTransactionHash]; @@ -172,31 +178,6 @@ - (NSUInteger)masternodeListRetrievalQueueMaxAmount { return [self.masternodeListDiffService retrievalQueueMaxAmount]; } -- (uint32_t)estimatedMasternodeListsToSync { - BOOL syncMasternodeLists = ([[DSOptionsManager sharedInstance] syncType] & DSSyncType_MasternodeList); - if (!syncMasternodeLists) { - return 0; - } - double amountLeft = self.masternodeListRetrievalQueueCount; - double maxAmount = self.masternodeListRetrievalQueueMaxAmount; - if (!maxAmount || self.store.masternodeListsByBlockHash.count <= 1) { //1 because there might be a default - return self.store.masternodeListsToSync; - } - return amountLeft; -} - -- (double)masternodeListAndQuorumsSyncProgress { - @synchronized (self) { - double amountLeft = self.masternodeListRetrievalQueueCount; - double maxAmount = self.masternodeListRetrievalQueueMaxAmount; - if (!amountLeft) { - return self.store.masternodeListsAndQuorumsIsSynced; - } - double progress = MAX(MIN((maxAmount - amountLeft) / maxAmount, 1), 0); - return progress; - } -} - - (BOOL)currentMasternodeListIsInLast24Hours { if (!self.currentMasternodeList) { return NO; @@ -206,7 +187,6 @@ - (BOOL)currentMasternodeListIsInLast24Hours { NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; NSTimeInterval delta = currentTimestamp - block.timestamp; return fabs(delta) < DAY_TIME_INTERVAL; - } @@ -329,8 +309,15 @@ - (BOOL)saveCLSignature:(NSData *)blockHashData signatureData:(NSData *)signatur - (BOOL)saveMasternodeList:(DSMasternodeList *)masternodeList forBlockHash:(UInt256)blockHash { /// TODO: need to properly store in CoreData or wait for rust SQLite - DSLog(@"[%@] ••• cache mnlist -> %@: %@", self.chain.name, uint256_hex(blockHash), masternodeList); + //DSLog(@"[%@] ••• cache mnlist -> %@: %@", self.chain.name, uint256_hex(blockHash), masternodeList); [self.store.masternodeListsByBlockHash setObject:masternodeList forKey:uint256_data(blockHash)]; + uint32_t lastHeight = self.lastMasternodeListBlockHeight; + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.lastBlockHeight = lastHeight; + self.chain.chainManager.syncState.masternodeListSyncInfo.storedCount = self.store.masternodeListsByBlockHash.count; + DSLog(@"[%@] [DSMasternodeManager] New List Stored: %u/%lu", self.chain.name, lastHeight, self.store.masternodeListsByBlockHash.count); + [self.chain.chainManager notifySyncStateChanged]; + } return YES; } @@ -765,10 +752,10 @@ - (void)peer:(DSPeer *)peer relayedMasternodeDiffMessage:(NSData *)message { return lastBlock.merkleRoot; }]; [self processMasternodeDiffWith:message context:ctx completion:^(DSMnDiffProcessingResult * _Nonnull result) { + #if SAVE_MASTERNODE_DIFF_TO_FILE UInt256 baseBlockHash = result.baseBlockHash; UInt256 blockHash = result.blockHash; DSLog(@"[%@] •••• -> processed mnlistdiff %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:baseBlockHash], [self heightForBlockHash:blockHash], uint256_hex(baseBlockHash), uint256_hex(blockHash)); - #if SAVE_MASTERNODE_DIFF_TO_FILE NSString *fileName = [NSString stringWithFormat:@"MNL_%@_%@__%d.dat", @([self heightForBlockHash:baseBlockHash]), @([self heightForBlockHash:blockHash]), peer.version]; DSLog(@"[%@] •-• File %@ saved", self.chain.name, fileName); [message saveToFile:fileName inDirectory:NSCachesDirectory]; @@ -808,6 +795,7 @@ - (void)peer:(DSPeer *)peer relayedQuorumRotationInfoMessage:(NSData *)message { dispatch_group_leave(self.processingGroup); return; } + #if SAVE_MASTERNODE_DIFF_TO_FILE UInt256 baseBlockHash = result.mnListDiffResultAtTip.baseBlockHash; UInt256 blockHash = result.mnListDiffResultAtTip.blockHash; DSLog(@"[%@] •••• -> processed qrinfo tip %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:baseBlockHash], [self heightForBlockHash:blockHash], uint256_hex(baseBlockHash), uint256_hex(blockHash)); @@ -818,7 +806,6 @@ - (void)peer:(DSPeer *)peer relayedQuorumRotationInfoMessage:(NSData *)message { if (result.extraShare) { DSLog(@"[%@] •••• -> processed qrinfo h-4c %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:result.mnListDiffResultAtH4C.baseBlockHash], [self heightForBlockHash:result.mnListDiffResultAtH4C.blockHash], uint256_hex(result.mnListDiffResultAtH4C.baseBlockHash), uint256_hex(result.mnListDiffResultAtH4C.blockHash)); } - #if SAVE_MASTERNODE_DIFF_TO_FILE NSString *fileName = [NSString stringWithFormat:@"QRINFO_%@_%@__%d.dat", @([self heightForBlockHash:baseBlockHash]), @([self heightForBlockHash:blockHash]), peer.version]; DSLog(@"[%@] •-• File %@ saved", self.chain.name, fileName); [message saveToFile:fileName inDirectory:NSCachesDirectory]; @@ -914,4 +901,5 @@ - (UInt256)buildLLMQHashFor:(DSQuorumEntry *)quorum { return [DSKeyManager NSDataFrom:quorum_build_llmq_hash(quorum.llmqType, quorum.quorumHash.u8)].UInt256; } } + @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager+Protected.h b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager+Protected.h index b3b7189b2..5852a45d1 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager+Protected.h @@ -59,6 +59,8 @@ typedef NS_ENUM(NSUInteger, DSPeerManagerDesiredState) - (void)clearRegisteredPeers; - (void)registerPeerAtLocation:(UInt128)IPAddress port:(uint32_t)port dapiPort:(uint32_t)dapiPort; +- (void)startBackgroundMode:(BOOL)performDisconnects; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.m index 1a8a53a8c..cdfb882d0 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.m @@ -27,6 +27,7 @@ // THE SOFTWARE. #import "DSAccount.h" +#import "DSBackgroundManager.h" #import "DSBloomFilter.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataClass.h" @@ -44,6 +45,7 @@ #import "DSPeer.h" #import "DSPeerEntity+CoreDataClass.h" #import "DSPeerManager+Protected.h" +#import "DSSyncState.h" #import "DSSpork.h" #import "DSSporkManager.h" #import "DSTransaction.h" @@ -70,7 +72,7 @@ #define TESTNET_MAIN_PEER @"" //@"52.36.64.148:19999" -#define FIXED_PEERS @"FixedPeers" +#define FIXED_PEERS @"MainnetFixedPeers" #define TESTNET_FIXED_PEERS @"TestnetFixedPeers" #define SYNC_COUNT_INFO @"SYNC_COUNT_INFO" @@ -83,17 +85,13 @@ @interface DSPeerManager () @property (nonatomic, strong) DSPeer *downloadPeer, *fixedPeer; @property (nonatomic, assign) NSUInteger connectFailures, misbehavingCount, maxConnectCount; -@property (nonatomic, strong) id backgroundObserver, walletAddedObserver; +@property (nonatomic, strong) id walletAddedObserver; @property (nonatomic, strong) DSChain *chain; @property (nonatomic, assign) DSPeerManagerDesiredState desiredState; @property (nonatomic, assign) uint64_t masternodeListConnectivityNonce; @property (nonatomic, strong) DSMasternodeList *masternodeList; @property (nonatomic, readonly) dispatch_queue_t networkingQueue; -#if TARGET_OS_IOS -@property (nonatomic, assign) NSUInteger terminalHeadersSaveTaskId, blockLocatorsSaveTaskId; -#endif - @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; @end @@ -111,27 +109,6 @@ - (instancetype)initWithChain:(DSChain *)chain { self.maxConnectCount = PEER_MAX_CONNECTIONS; -#if TARGET_OS_IOS - self.terminalHeadersSaveTaskId = UIBackgroundTaskInvalid; - - self.backgroundObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:nil - usingBlock:^(NSNotification *note) { - dispatch_async(self.networkingQueue, ^{ - [self savePeers]; - [self.chain saveTerminalBlocks]; - }); - if (self.terminalHeadersSaveTaskId == UIBackgroundTaskInvalid) { - self.misbehavingCount = 0; - dispatch_async(self.networkingQueue, ^{ - [self.connectedPeers makeObjectsPerformSelector:@selector(disconnect)]; - }); - } - }]; -#endif - self.walletAddedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:DSChainWalletsDidChangeNotification object:nil @@ -149,7 +126,6 @@ - (instancetype)initWithChain:(DSChain *)chain { - (void)dealloc { [NSObject cancelPreviousPerformRequestsWithTarget:self]; - if (self.backgroundObserver) [[NSNotificationCenter defaultCenter] removeObserver:self.backgroundObserver]; if (self.walletAddedObserver) [[NSNotificationCenter defaultCenter] removeObserver:self.walletAddedObserver]; } @@ -190,6 +166,9 @@ - (DSSporkManager *)sporkManager { - (DSChainManager *)chainManager { return self.chain.chainManager; } +- (DSBackgroundManager *)backgroundManager { + return self.chain.chainManager.backgroundManager; +} // MARK: - Info @@ -694,6 +673,23 @@ - (void)useMasternodeList:(DSMasternodeList *)masternodeList withConnectivityNon }); } +// MARK: - Background Ops (iOS) + +- (void)startBackgroundMode:(BOOL)performDisconnects { + dispatch_async(self.networkingQueue, ^{ + [self savePeers]; + [self.chain saveTerminalBlocks]; + }); + if (performDisconnects) { + self.misbehavingCount = 0; + dispatch_async(self.networkingQueue, ^{ + [self.connectedPeers makeObjectsPerformSelector:@selector(disconnect)]; + }); + } +} + + + // MARK: - Connectivity - (void)connect { @@ -702,55 +698,34 @@ - (void)connect { dispatch_async(self.networkingQueue, ^{ if ([self.chain syncsBlockchain] && ![self.chain canConstructAFilter]) return; // check to make sure the wallet has been created if only are a basic wallet with no dash features if (self.connectFailures >= MAX_CONNECT_FAILURES) self.connectFailures = 0; // this attempt is a manual retry + @synchronized (self.chainManager) { - if (self.chainManager.terminalHeaderSyncProgress < 1.0) { + if (self.chainManager.syncState.terminalHeaderSyncProgress < 1.0) { [self.chainManager resetTerminalSyncStartHeight]; - #if TARGET_OS_IOS - if (self.blockLocatorsSaveTaskId == UIBackgroundTaskInvalid) { // start a background task for the chain sync - self.blockLocatorsSaveTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ - dispatch_async(self.networkingQueue, ^{ - [self.chain saveBlockLocators]; - }); - - [self chainSyncStopped]; - }]; - } - #endif + [self.backgroundManager createBlockLocatorsTask:^{ + dispatch_async(self.networkingQueue, ^{ [self.chain saveBlockLocators]; }); + [self chainSyncStopped]; + }]; } - if (self.chainManager.chainSyncProgress < 1.0) { + if (self.chainManager.syncState.chainSyncProgress < 1.0) { [self.chainManager resetChainSyncStartHeight]; - #if TARGET_OS_IOS - if (self.terminalHeadersSaveTaskId == UIBackgroundTaskInvalid) { // start a background task for the chain sync - self.terminalHeadersSaveTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ - dispatch_async(self.networkingQueue, ^{ - [self.chain saveTerminalBlocks]; - }); - - [self chainSyncStopped]; - }]; - } - #endif + [self.backgroundManager createTerminalHeadersTask:^{ + dispatch_async(self.networkingQueue, ^{ [self.chain saveTerminalBlocks]; }); + [self chainSyncStopped]; + }]; } } -// @synchronized(self.mutableConnectedPeers) { -// [self.mutableConnectedPeers minusSet:[self.connectedPeers objectsPassingTest:^BOOL(id obj, BOOL *stop) { -// return ([obj status] == DSPeerStatus_Disconnected) ? YES : NO; -// }]]; -// } @synchronized(self.mutableConnectedPeers) { NSMutableSet *disconnectedPeers = [NSMutableSet set]; for (DSPeer *peer in self.mutableConnectedPeers) { -// @synchronized(peer) { - if (peer.status == DSPeerStatus_Disconnected) { - [disconnectedPeers addObject:peer]; - } -// } + if (peer.status == DSPeerStatus_Disconnected) { + [disconnectedPeers addObject:peer]; + } } [self.mutableConnectedPeers minusSet:disconnectedPeers]; } - self.fixedPeer = [self trustedPeerHost] ? [DSPeer peerWithHost:[self trustedPeerHost] onChain:self.chain] : nil; self.maxConnectCount = (self.fixedPeer) ? 1 : PEER_MAX_CONNECTIONS; if (self.connectedPeers.count >= self.maxConnectCount) return; // already connected to maxConnectCount peers @@ -768,7 +743,7 @@ - (void)connect { DSPeer *peer = peers[(NSUInteger)(pow(arc4random_uniform((uint32_t)peers.count), 2) / peers.count)]; if (peer && ![self.connectedPeers containsObject:peer]) { - [peer setChainDelegate:self.chain.chainManager peerDelegate:self transactionDelegate:self.transactionManager governanceDelegate:self.governanceSyncManager sporkDelegate:self.sporkManager masternodeDelegate:self.masternodeManager queue:self.networkingQueue]; + [peer setChainDelegate:self.chainManager peerDelegate:self transactionDelegate:self.transactionManager governanceDelegate:self.governanceSyncManager sporkDelegate:self.sporkManager masternodeDelegate:self.masternodeManager queue:self.networkingQueue]; peer.earliestKeyTime = earliestWalletCreationTime; [self.mutableConnectedPeers addObject:peer]; @@ -785,6 +760,7 @@ - (void)connect { if (peers.count == 0) { [self chainSyncStopped]; + DSLog(@"[%@] [DSPeerManager] No peers found -> SyncFailed", self.chain.name); dispatch_async(dispatch_get_main_queue(), ^{ NSError *error = [NSError errorWithCode:1 localizedDescriptionKey:@"No peers found"]; [[NSNotificationCenter defaultCenter] postNotificationName:DSChainManagerSyncFailedNotification @@ -817,35 +793,30 @@ - (void)disconnectDownloadPeerForError:(NSError *)error withCompletion:(void (^_ } - (void)syncTimeout { - @synchronized (self.chainManager) { + dispatch_async(self.networkingQueue, ^{ NSTimeInterval now = [NSDate timeIntervalSince1970]; NSTimeInterval delta = now - self.chainManager.lastChainRelayTime; if (delta < PROTOCOL_TIMEOUT) { // the download peer relayed something in time, so restart timer - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(syncTimeout) object:nil]; - [self performSelector:@selector(syncTimeout) - withObject:nil - afterDelay:PROTOCOL_TIMEOUT - delta]; + [self restartSyncTimeout:PROTOCOL_TIMEOUT - delta]; return; } - } + }); [self disconnectDownloadPeerForError:[NSError errorWithCode:500 descriptionKey:DSLocalizedString(@"Synchronization Timeout", @"An error message for notifying that chain sync has timed out")] withCompletion:nil]; } +- (void)restartSyncTimeout:(NSTimeInterval)afterDelay { + [self cancelSyncTimeout]; + [self performSelector:@selector(syncTimeout) withObject:nil afterDelay:afterDelay]; +} + +- (void)cancelSyncTimeout { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(syncTimeout) object:nil]; +} - (void)chainSyncStopped { dispatch_async(dispatch_get_main_queue(), ^{ - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(syncTimeout) object:nil]; -#if TARGET_OS_IOS - if (self.terminalHeadersSaveTaskId != UIBackgroundTaskInvalid) { - [[UIApplication sharedApplication] endBackgroundTask:self.terminalHeadersSaveTaskId]; - self.terminalHeadersSaveTaskId = UIBackgroundTaskInvalid; - } - - if (self.blockLocatorsSaveTaskId != UIBackgroundTaskInvalid) { - [[UIApplication sharedApplication] endBackgroundTask:self.blockLocatorsSaveTaskId]; - self.blockLocatorsSaveTaskId = UIBackgroundTaskInvalid; - } -#endif + [self cancelSyncTimeout]; + [self.backgroundManager stopBackgroundActivities]; }); } @@ -910,7 +881,9 @@ - (void)peerConnected:(DSPeer *)peer { [peer sendGetaddrMessage]; // request a list of other dash peers } dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil + userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; }); }]; }]; @@ -949,24 +922,31 @@ - (void)peerConnected:(DSPeer *)peer { @synchronized (self) { _connected = YES; } + if ([self.chain syncsBlockchain] && [self.chain canConstructAFilter]) { [bestPeer sendFilterloadMessage:[self.transactionManager transactionsBloomFilterForPeer:bestPeer].data]; } - bestPeer.currentBlockHeight = self.chain.lastSyncBlockHeight; + + uint32_t estimatedBlockHeight = self.chain.estimatedBlockHeight; + uint32_t lastSyncBlockHeight = self.chain.lastSyncBlockHeight; + uint32_t lastTerminalBlockHeight = self.chain.lastTerminalBlockHeight; + uint32_t bestPeerLastBlockHeight = bestPeer.lastBlockHeight; + bestPeer.currentBlockHeight = lastSyncBlockHeight; dispatch_async(dispatch_get_main_queue(), ^{ // setup a timer to detect if the sync stalls - [self.chainManager assignSyncWeights]; - if ([self.chain syncsBlockchain] && - ((self.chain.lastSyncBlockHeight != self.chain.lastTerminalBlockHeight) || - (self.chain.lastSyncBlockHeight < bestPeer.lastBlockHeight))) { // start blockchain sync - [self.chainManager resetLastRelayedItemTime]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(syncTimeout) object:nil]; - [self performSelector:@selector(syncTimeout) withObject:nil afterDelay:PROTOCOL_TIMEOUT]; - - [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - - [self.chainManager chainWillStartSyncingBlockchain:self.chain]; - [self.chainManager chainShouldStartSyncingBlockchain:self.chain onPeer:bestPeer]; + self.chainManager.syncState.hasDownloadPeer = bestPeer; + self.chainManager.syncState.peerManagerConnected = YES; + self.chainManager.syncState.estimatedBlockHeight = estimatedBlockHeight; + self.chainManager.syncState.lastTerminalBlockHeight = lastTerminalBlockHeight; + self.chainManager.syncState.lastSyncBlockHeight = lastSyncBlockHeight; + if ([self.chain syncsBlockchain] && ((lastSyncBlockHeight != lastTerminalBlockHeight) || (lastSyncBlockHeight < bestPeerLastBlockHeight))) { + // start blockchain sync + [self restartSyncTimeout:PROTOCOL_TIMEOUT]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil + userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; + [self.chainManager chainWillStartSyncingBlockchain:self.chain]; + [self.chainManager chainShouldStartSyncingBlockchain:self.chain onPeer:bestPeer]; } else { // we're already synced [self.chainManager chainFinishedSyncingTransactionsAndBlocks:self.chain fromPeer:nil onMainChain:TRUE]; } @@ -996,6 +976,10 @@ - (void)peer:(DSPeer *)peer disconnectedWithError:(NSError *)error { _connected = NO; [self.chain removeEstimatedBlockHeightOfPeer:peer]; self.downloadPeer = nil; + + self.chainManager.syncState.hasDownloadPeer = NO; + [self.chainManager notifySyncStateChanged]; + if (self.connectFailures > MAX_CONNECT_FAILURES) self.connectFailures = MAX_CONNECT_FAILURES; } @@ -1025,8 +1009,7 @@ - (void)peer:(DSPeer *)peer disconnectedWithError:(NSError *)error { } else if (self.connectFailures < MAX_CONNECT_FAILURES) { dispatch_async(dispatch_get_main_queue(), ^{ #if TARGET_OS_IOS - if ((self.desiredState == DSPeerManagerDesiredState_Connected) && (self.terminalHeadersSaveTaskId != UIBackgroundTaskInvalid || - [UIApplication sharedApplication].applicationState != UIApplicationStateBackground)) { + if ((self.desiredState == DSPeerManagerDesiredState_Connected) && [self.backgroundManager hasValidHeadersTask]) { DSLog(@"[%@: %@:%d] [DSPeerManager] peer disconnectedWithError -> peerManager::connect", self.chain.name, peer.host, peer.port); if (!banned) [self connect]; // try connecting to another peer } @@ -1077,4 +1060,5 @@ - (void)sendRequest:(DSMessageRequest *)request { [self.downloadPeer sendRequest:request]; } + @end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListService.m b/DashSync/shared/Models/Masternode/DSMasternodeListService.m index ef62bf210..3c8c15700 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListService.m +++ b/DashSync/shared/Models/Masternode/DSMasternodeListService.m @@ -21,8 +21,10 @@ #import "DSMasternodeListStore+Protected.h" #import "DSChain+Protected.h" #import "DSChainManager.h" +#import "DSChainManager+Protected.h" #import "DSGetMNListDiffRequest.h" #import "DSGetQRInfoRequest.h" +#import "DSMasternodeManager+Protected.h" #import "DSMerkleBlock.h" #import "DSPeerManager+Protected.h" #import "DSSimplifiedMasternodeEntry.h" @@ -212,7 +214,7 @@ - (BOOL)shouldProcessDiffResult:(DSMnDiffProcessingResult *)diffResult skipPrese UInt256 masternodeListBlockHash = masternodeList.blockHash; NSData *masternodeListBlockHashData = uint256_data(masternodeListBlockHash); BOOL hasInRetrieval = [self.retrievalQueue containsObject:masternodeListBlockHashData]; - uint32_t masternodeListBlockHeight = [self.store heightForBlockHash:masternodeListBlockHash]; +// uint32_t masternodeListBlockHeight = [self.store heightForBlockHash:masternodeListBlockHash]; BOOL shouldNot = !hasInRetrieval && !skipPresenceInRetrieval; //DSLog(@"•••• shouldProcessDiffResult: %d: %@ %d", masternodeListBlockHeight, uint256_reverse_hex(masternodeListBlockHash), !shouldNot); if (shouldNot) { @@ -256,6 +258,13 @@ - (void)addToRetrievalQueueArray:(NSArray *)masternodeBlockHashDataArr - (void)removeFromRetrievalQueue:(NSData *)masternodeBlockHashData { [self.retrievalQueue removeObject:masternodeBlockHashData]; + double count = self.retrievalQueue.count; + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueCount = count; + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueMaxAmount = self.retrievalQueueMaxAmount; + DSLog(@"[%@] Masternode list queue updated: %f/%lu", self.chain.name, count, self.retrievalQueueMaxAmount); + [self.chain.chainManager notifySyncStateChanged]; + } } - (void)cleanRequestsInRetrieval { @@ -264,6 +273,12 @@ - (void)cleanRequestsInRetrieval { - (void)cleanListsRetrievalQueue { [self.retrievalQueue removeAllObjects]; + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueCount = 0; + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueMaxAmount = self.retrievalQueueMaxAmount; + DSLog(@"[%@] Masternode list queue cleaned up: 0/%lu", self.chain.name, self.retrievalQueueMaxAmount); + [self.chain.chainManager notifySyncStateChanged]; + } } - (void)cleanAllLists { @@ -282,12 +297,16 @@ - (NSUInteger)retrievalQueueCount { - (void)updateMasternodeRetrievalQueue { NSUInteger currentCount = self.retrievalQueue.count; - dispatch_async(dispatch_get_main_queue(), ^{ - self.retrievalQueueMaxAmount = MAX(self.retrievalQueueMaxAmount, currentCount); - }); + self.retrievalQueueMaxAmount = MAX(self.retrievalQueueMaxAmount, currentCount); [self.retrievalQueue sortUsingComparator:^NSComparisonResult(NSData *_Nonnull obj1, NSData *_Nonnull obj2) { return [self.store heightForBlockHash:obj1.UInt256] < [self.store heightForBlockHash:obj2.UInt256] ? NSOrderedAscending : NSOrderedDescending; }]; + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueCount = currentCount; + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueMaxAmount = self.retrievalQueueMaxAmount; + DSLog(@"[%@] Masternode list queue updated: %lu/%lu", self.chain.name, currentCount, self.retrievalQueueMaxAmount); + [self.chain.chainManager notifySyncStateChanged]; + } } - (void)fetchMasternodeListsToRetrieve:(void (^)(NSOrderedSet *listsToRetrieve))completion { @@ -378,9 +397,7 @@ - (void)issueWithMasternodeListFromPeer:(DSPeer *)peer { [[NSUserDefaults standardUserDefaults] setObject:faultyPeers forKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; [self dequeueMasternodeListRequest]; } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSMasternodeListDiffValidationErrorNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); + [self.chain.chainManager notify:DSMasternodeListDiffValidationErrorNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; } - (void)sendMasternodeListRequest:(DSMasternodeListRequest *)request { diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListStore.h b/DashSync/shared/Models/Masternode/DSMasternodeListStore.h index 50092f0c8..89085e3aa 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListStore.h +++ b/DashSync/shared/Models/Masternode/DSMasternodeListStore.h @@ -32,7 +32,6 @@ FOUNDATION_EXPORT NSString *const DSQuorumListDidChangeNotification; @interface DSMasternodeListStore : NSObject -//@property (nonatomic, nullable) DSMasternodeList *currentMasternodeList; @property (nonatomic, readonly) NSUInteger knownMasternodeListsCount; @property (nonatomic, readonly) NSArray *recentMasternodeLists; @property (nonatomic, readonly) uint32_t earliestMasternodeListBlockHeight; @@ -40,8 +39,6 @@ FOUNDATION_EXPORT NSString *const DSQuorumListDidChangeNotification; @property (nonatomic, readonly) NSMutableDictionary *masternodeListsByBlockHash; @property (nonatomic, readonly) NSMutableSet *masternodeListsBlockHashStubs; @property (nonatomic, readonly) NSMutableOrderedSet *activeQuorums; -@property (nonatomic, readonly) uint32_t masternodeListsToSync; -@property (nonatomic, readonly) BOOL masternodeListsAndQuorumsIsSynced; @property (nonatomic, readonly) NSMutableDictionary *cachedQuorumSnapshots; @property (nonatomic, readonly) NSMutableDictionary *cachedCLSignatures; diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListStore.m b/DashSync/shared/Models/Masternode/DSMasternodeListStore.m index b886769c3..3211e1729 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListStore.m +++ b/DashSync/shared/Models/Masternode/DSMasternodeListStore.m @@ -21,6 +21,7 @@ #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataProperties.h" #import "DSChainManager.h" +#import "DSChainManager+Protected.h" #import "DSCheckpoint.h" #import "DSDAPIClient.h" #import "DSLocalMasternodeEntity+CoreDataClass.h" @@ -218,25 +219,6 @@ - (BOOL)hasMasternodeListCurrentlyBeingSaved { return !!self.masternodeListCurrentlyBeingSavedCount; } -- (uint32_t)masternodeListsToSync { - if (self.lastMasternodeListBlockHeight == UINT32_MAX) { - return 32; - } else { - float diff = self.chain.estimatedBlockHeight - self.lastMasternodeListBlockHeight; - if (diff < 0) return 32; - return MIN(32, (uint32_t)ceil(diff / 24.0f)); - } -} - -- (BOOL)masternodeListsAndQuorumsIsSynced { - if (self.lastMasternodeListBlockHeight == UINT32_MAX || - self.lastMasternodeListBlockHeight < self.chain.estimatedBlockHeight - 16) { - return NO; - } else { - return YES; - } -} - - (void)loadLocalMasternodes { NSFetchRequest *fetchRequest = [[DSLocalMasternodeEntity fetchRequest] copy]; [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"providerRegistrationTransaction.transactionHash.chain == %@", [self.chain chainEntityInContext:self.managedObjectContext]]]; @@ -256,12 +238,20 @@ - (DSMasternodeList *)loadMasternodeListAtBlockHash:(NSData *)blockHash withBloc masternodeList = [masternodeListEntity masternodeListWithSimplifiedMasternodeEntryPool:[simplifiedMasternodeEntryPool copy] quorumEntryPool:quorumEntryPool withBlockHeightLookup:blockHeightLookup]; if (masternodeList) { DSLog(@"[%@] ••• addMasternodeList (loadMasternodeListAtBlockHash) -> %@: %@", self.chain.name, blockHash.hexString, masternodeList); + double count; @synchronized (self.masternodeListsByBlockHash) { [self.masternodeListsByBlockHash setObject:masternodeList forKey:blockHash]; + count = self.masternodeListsByBlockHash.count; } - @synchronized (self.masternodeListsByBlockHash) { + @synchronized (self.masternodeListsBlockHashStubs) { [self.masternodeListsBlockHashStubs removeObject:blockHash]; } + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.storedCount = count; + self.chain.chainManager.syncState.masternodeListSyncInfo.lastBlockHeight = self.lastMasternodeListBlockHeight; + [self.chain.chainManager notifySyncStateChanged]; + [self.chain.chainManager notifySyncStateChanged]; + } DSLog(@"[%@] Loading Masternode List at height %u for blockHash %@ with %lu entries", self.chain.name, masternodeList.height, uint256_hex(masternodeList.blockHash), (unsigned long)masternodeList.simplifiedMasternodeEntries.count); } }]; @@ -285,6 +275,13 @@ - (DSMasternodeList *)loadMasternodeListsWithBlockHeightLookup:(BlockHeightFinde //we only need a few in memory as new quorums will mostly be verified against recent masternode lists DSMasternodeList *masternodeList = [masternodeListEntity masternodeListWithSimplifiedMasternodeEntryPool:[simplifiedMasternodeEntryPool copy] quorumEntryPool:quorumEntryPool withBlockHeightLookup:blockHeightLookup]; [self.masternodeListsByBlockHash setObject:masternodeList forKey:uint256_data(masternodeList.blockHash)]; + double listCount = self.masternodeListsByBlockHash.count; + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.storedCount = listCount; + self.chain.chainManager.syncState.masternodeListSyncInfo.lastBlockHeight = self.lastMasternodeListBlockHeight; + [self.chain.chainManager notifySyncStateChanged]; + } + [self.cachedBlockHashHeights setObject:@(masternodeListEntity.block.height) forKey:uint256_data(masternodeList.blockHash)]; [simplifiedMasternodeEntryPool addEntriesFromDictionary:masternodeList.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash]; [quorumEntryPool addEntriesFromDictionary:masternodeList.quorums]; @@ -347,6 +344,12 @@ - (void)removeAllMasternodeLists { [self.masternodeListsBlockHashStubs removeAllObjects]; } self.masternodeListAwaitingQuorumValidation = nil; + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.lastBlockHeight = UINT32_MAX; + self.chain.chainManager.syncState.masternodeListSyncInfo.storedCount = 0; + DSLog(@"[%@] [DSMasternodeManager] All List Removed: %u/%u", self.chain.name, UINT32_MAX, 0); + [self.chain.chainManager notifySyncStateChanged]; + } } - (void)removeOldMasternodeLists:(uint32_t)lastBlockHeight { @@ -370,6 +373,7 @@ - (void)removeOldMasternodeLists:(uint32_t)lastBlockHeight { } } if (removedItems) { + //Now we should delete old quorums //To do this, first get the last 24 active masternode lists //Then check for quorums not referenced by them, and delete those @@ -381,6 +385,12 @@ - (void)removeOldMasternodeLists:(uint32_t)lastBlockHeight { [self.managedObjectContext deleteObject:unusedQuorumEntryEntity]; } [self.managedObjectContext ds_save]; + + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.storedCount = self.masternodeListsByBlockHash.count; + self.chain.chainManager.syncState.masternodeListSyncInfo.lastBlockHeight = self.lastMasternodeListBlockHeight; + [self.chain.chainManager notifySyncStateChanged]; + } } } }]; @@ -438,8 +448,15 @@ - (void)saveMasternodeList:(DSMasternodeList *)masternodeList addedMasternodes:( } NSArray *updatedSimplifiedMasternodeEntries = [addedMasternodes.allValues arrayByAddingObjectsFromArray:modifiedMasternodes.allValues]; [self.chain updateAddressUsageOfSimplifiedMasternodeEntries:updatedSimplifiedMasternodeEntries]; + double count; @synchronized (self.masternodeListsByBlockHash) { [self.masternodeListsByBlockHash setObject:masternodeList forKey:blockHashData]; + count = self.masternodeListsByBlockHash.count; + } + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.storedCount = count; + self.chain.chainManager.syncState.masternodeListSyncInfo.lastBlockHeight = self.lastMasternodeListBlockHeight; + [self.chain.chainManager notifySyncStateChanged]; } [self notifyMasternodeListUpdate]; dispatch_group_enter(self.savingGroup); diff --git a/DashSync/shared/Models/Network/DSPeer.m b/DashSync/shared/Models/Network/DSPeer.m index 1f7f9ee61..62f4b48f2 100644 --- a/DashSync/shared/Models/Network/DSPeer.m +++ b/DashSync/shared/Models/Network/DSPeer.m @@ -34,6 +34,7 @@ #import "DSChainEntity+CoreDataClass.h" #import "DSChainLock.h" #import "DSChainManager+Protected.h" +#import "DSChainManager+Transactions.h" #import "DSChainManager.h" #import "DSFilterLoadRequest.h" #import "DSGetBlocksRequest.h" @@ -576,9 +577,9 @@ - (void)sendGetblocksMessageWithLocators:(NSArray *)locators andHashStop:(UInt25 NSMutableArray *locatorHexes = [NSMutableArray arrayWithCapacity:[locators count]]; [locators enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { uint32_t knownHeight = [self.chain quickHeightForBlockHash:((NSData *)obj).UInt256]; - [locatorHexes addObject:[NSString stringWithFormat:@"%@ (block height %@)", + [locatorHexes addObject:[NSString stringWithFormat:@"%@ (block height %d)", ((NSData *)obj).reverse.hexString, - knownHeight == UINT32_MAX ? @"unknown" : @"%d", knownHeight]]; + knownHeight == UINT32_MAX ? 0 : knownHeight]]; }]; #if DEBUG DSLogPrivateWithLocation(self, @"%@sending getblocks with locators %@", self.peerDelegate.downloadPeer == self ? @"(download peer) " : @"", locatorHexes); @@ -663,7 +664,7 @@ - (void)sendGetdataMessageWithTxHashes:(NSArray *)txHashes instantSendLockHashes if (!([[DSOptionsManager sharedInstance] syncType] & DSSyncType_GetsNewBlocks)) return; NSUInteger totalCount = txHashes.count + instantSendLockHashes.count + instantSendLockDHashes.count + blockHashes.count + chainLockHashes.count; if (totalCount > MAX_GETDATA_HASHES) { // limit total hash count to MAX_GETDATA_HASHES - DSLogWithLocation(self, @"couldn't send getdata, %u is too many items, max is %u", totalCount, MAX_GETDATA_HASHES); + DSLogWithLocation(self, @"couldn't send getdata, %lu is too many items, max is %u", totalCount, MAX_GETDATA_HASHES); return; } else if (totalCount == 0) return; @@ -682,7 +683,7 @@ - (void)sendGetdataMessageWithTxHashes:(NSArray *)txHashes instantSendLockHashes - (void)sendGovernanceRequest:(DSGovernanceHashesRequest *)request { if (request.hashes.count > MAX_GETDATA_HASHES) { // limit total hash count to MAX_GETDATA_HASHES - DSLogWithLocation(self, @"couldn't send governance votes getdata, %u is too many items, max is %u", request.hashes.count, MAX_GETDATA_HASHES); + DSLogWithLocation(self, @"couldn't send governance votes getdata, %lu is too many items, max is %u", request.hashes.count, MAX_GETDATA_HASHES); return; } else if (request.hashes.count == 0) { DSLogWithLocation(self, @"couldn't send governance getdata, there is no items"); @@ -722,7 +723,7 @@ - (void)rerequestBlocksFrom:(UInt256)blockHash { if (i != NSNotFound) { [self.knownBlockHashes removeObjectsInRange:NSMakeRange(0, i)]; - DSLogWithLocation(self, @"re-requesting %u blocks", self.knownBlockHashes.count); + DSLogWithLocation(self, @"re-requesting %lu blocks", self.knownBlockHashes.count); [self sendGetdataMessageWithTxHashes:nil instantSendLockHashes:nil instantSendLockDHashes:nil blockHashes:self.knownBlockHashes.array chainLockHashes:nil]; } } @@ -928,14 +929,14 @@ - (void)acceptAddrMessage:(NSData *)message { NSMutableArray *peers = [NSMutableArray array]; if (count > 1000) { - DSLogWithLocation(self, @"dropping addr message, %u is too many addresses (max 1000)", count); + DSLogWithLocation(self, @"dropping addr message, %lu is too many addresses (max 1000)", count); return; } else if (message.length < l.unsignedIntegerValue + count * 30) { [self error:@"malformed addr message, length is %u, should be %u for %u addresses", (int)message.length, (int)(l.unsignedIntegerValue + count * 30), (int)count]; return; } else - DSLogWithLocation(self, @"got addr with %u addresses", count); + DSLogWithLocation(self, @"got addr with %lu addresses", count); for (NSUInteger off = l.unsignedIntegerValue; off < l.unsignedIntegerValue + 30 * count; off += 30) { NSTimeInterval timestamp = [message UInt32AtOffset:off]; @@ -964,7 +965,7 @@ - (void)acceptAddrMessage:(NSData *)message { } - (void)acceptAddrV2Message:(NSData *)message { - DSLogWithLocation(self, @"sendaddrv2, len:%u, (not implemented)", message.length); + DSLogWithLocation(self, @"sendaddrv2, len:%lu, (not implemented)", message.length); } - (NSString *)nameOfInvMessage:(DSInvType)type { @@ -1037,7 +1038,7 @@ - (void)acceptInvMessage:(NSData *)message { (int)(((l.unsignedIntegerValue == 0) ? 1 : l.unsignedIntegerValue) + count * 36), (int)count]; return; } else if (count > MAX_GETDATA_HASHES) { - DSLogWithLocation(self, @"dropping inv message, %u is too many items, max is %u", count, MAX_GETDATA_HASHES); + DSLogWithLocation(self, @"dropping inv message, %lu is too many items, max is %u", count, MAX_GETDATA_HASHES); return; } #if MESSAGE_LOGGING @@ -1409,7 +1410,7 @@ - (void)acceptHeadersMessage:(NSData *)message { DSLogWithLocation(self, @"got 0 headers (%@)", @""); #endif } else { - DSLogWithLocation(self, @"got %u headers", count); + DSLogWithLocation(self, @"got %lu headers", count); } #if LOG_ALL_HEADERS_IN_ACCEPT_HEADERS @@ -1501,11 +1502,11 @@ - (void)acceptGetdataMessage:(NSData *)message { (int)(((l == 0) ? 1 : l) + count * 36), (int)count]; return; } else if (count > MAX_GETDATA_HASHES) { - DSLogWithLocation(self, @"dropping getdata message, %u is too many items, max is %u", count, MAX_GETDATA_HASHES); + DSLogWithLocation(self, @"dropping getdata message, %lu is too many items, max is %u", count, MAX_GETDATA_HASHES); return; } - DSLogWithLocation(self, @"%@got getdata for %u item%@", self.peerDelegate.downloadPeer == self ? @"(download peer)" : @"", count, count == 1 ? @"" : @"s"); + DSLogWithLocation(self, @"%@got getdata for %lu item%@", self.peerDelegate.downloadPeer == self ? @"(download peer)" : @"", count, count == 1 ? @"" : @"s"); [self dispatchAsyncInDelegateQueue:^{ NSMutableData *notfound = [NSMutableData data]; @@ -1583,7 +1584,7 @@ - (void)acceptNotfoundMessage:(NSData *)message { return; } - DSLogWithLocation(self, @"got notfound with %u item%@ (first item %@)", count, count == 1 ? @"" : @"s", [self nameOfInvMessage:[message UInt32AtOffset:l]]); + DSLogWithLocation(self, @"got notfound with %lu item%@ (first item %@)", count, count == 1 ? @"" : @"s", [self nameOfInvMessage:[message UInt32AtOffset:l]]); for (NSUInteger off = l; off < l + 36 * count; off += 36) { if ([message UInt32AtOffset:off] == DSInvType_Tx) { @@ -1637,11 +1638,16 @@ - (void)acceptPongMessage:(NSData *)message { DSLogWithLocation(self, @"got pong in %fs", self.pingTime); #endif if (self->_status == DSPeerStatus_Connected && self.pongHandlers.count) { - void (^handler)(BOOL) = [self.pongHandlers objectAtIndex:0]; - [self.pongHandlers removeObjectAtIndex:0]; - [self dispatchAsyncInDelegateQueue:^{ - handler(YES); - }]; + void (^handler)(BOOL) = nil; + @synchronized(self.pongHandlers) { + if (self.pongHandlers.count > 0) { + handler = [self.pongHandlers objectAtIndex:0]; + [self.pongHandlers removeObjectAtIndex:0]; + } + } + if (handler) { + [self dispatchAsyncInDelegateQueue:^{ handler(YES); }]; + } } } @@ -2068,7 +2074,7 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { break; default: - DSLogWithLocation(self, @"unknown network stream eventCode:%u", eventCode); + DSLogWithLocation(self, @"unknown network stream eventCode:%lu", eventCode); } } diff --git a/DashSync/shared/Models/Notifications/DSSyncState.h b/DashSync/shared/Models/Notifications/DSSyncState.h new file mode 100644 index 000000000..b3a775d28 --- /dev/null +++ b/DashSync/shared/Models/Notifications/DSSyncState.h @@ -0,0 +1,69 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "DSChain.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(uint16_t, DSSyncStateKind) { + DSSyncStateKind_Chain = 0, + DSSyncStateKind_Headers = 1, + DSSyncStateKind_Masternodes = 2, +}; + + +@interface DSMasternodeListSyncState : NSObject + +@property (nonatomic, assign) uint32_t retrievalQueueCount; +@property (nonatomic, assign) uint32_t retrievalQueueMaxAmount; +@property (nonatomic, assign) double storedCount; +@property (nonatomic, assign) uint32_t lastBlockHeight; + +@end + + +@interface DSSyncState : NSObject + +@property (nonatomic, assign) DSChainSyncPhase syncPhase; +@property (nonatomic, assign) BOOL hasDownloadPeer; +@property (nonatomic, assign) BOOL peerManagerConnected; + +@property (nonatomic, assign) uint32_t estimatedBlockHeight; + +@property (nonatomic, assign) uint32_t lastSyncBlockHeight; +@property (nonatomic, assign) uint32_t chainSyncStartHeight; + +@property (nonatomic, assign) uint32_t lastTerminalBlockHeight; +@property (nonatomic, assign) uint32_t terminalSyncStartHeight; +@property (nonatomic, strong) DSMasternodeListSyncState *masternodeListSyncInfo; + +// MARK: Read-only + +@property (nonatomic, readonly) double masternodeListProgress; +@property (nonatomic, readonly) double chainSyncProgress; +@property (nonatomic, readonly) double terminalHeaderSyncProgress; +@property (nonatomic, readonly) double combinedSyncProgress; +@property (nonatomic, readonly) DSSyncStateKind kind; + +// MARK: Constructor + +- (instancetype)initWithSyncPhase:(DSChainSyncPhase)phase; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Notifications/DSSyncState.m b/DashSync/shared/Models/Notifications/DSSyncState.m new file mode 100644 index 000000000..8b5481480 --- /dev/null +++ b/DashSync/shared/Models/Notifications/DSSyncState.m @@ -0,0 +1,191 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2024 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DSOptionsManager.h" +#import "DSSyncState.h" + +@implementation DSMasternodeListSyncState +- (id)copyWithZone:(NSZone *)zone { + DSMasternodeListSyncState *copy = [[[self class] alloc] init]; + copy.retrievalQueueCount = self.retrievalQueueCount; + copy.retrievalQueueMaxAmount = self.retrievalQueueMaxAmount; + copy.storedCount = self.storedCount; + copy.lastBlockHeight = self.lastBlockHeight; + return copy; +} +- (NSString *)description { + return [NSString stringWithFormat:@"%u/%u/%u/%u", + self.retrievalQueueCount, + self.retrievalQueueMaxAmount, + self.storedCount, + self.lastBlockHeight]; +} +@end + +@implementation DSSyncState + +- (instancetype)initWithSyncPhase:(DSChainSyncPhase)phase { + if (!(self = [super init])) return nil; + self.syncPhase = phase; + self.masternodeListSyncInfo = [[DSMasternodeListSyncState alloc] init]; + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + DSSyncState *copy = [[[self class] alloc] init]; + copy.syncPhase = self.syncPhase; + copy.hasDownloadPeer = self.hasDownloadPeer; + copy.peerManagerConnected = self.peerManagerConnected; + copy.estimatedBlockHeight = self.estimatedBlockHeight; + copy.chainSyncStartHeight = self.chainSyncStartHeight; + copy.lastSyncBlockHeight = self.lastSyncBlockHeight; + copy.terminalSyncStartHeight = self.terminalSyncStartHeight; + copy.lastTerminalBlockHeight = self.lastTerminalBlockHeight; + copy.masternodeListSyncInfo = [self.masternodeListSyncInfo copy]; + return copy; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"SyncState: { phase: %u, peer: %u, connected: %u, estimated: %u, chain: [%u/%u/%f] headers: [%u/%u/%f], mn: [%@/%u/%f] == %f", + self.syncPhase, + self.hasDownloadPeer, + self.peerManagerConnected, + self.estimatedBlockHeight, + self.chainSyncStartHeight, + self.lastSyncBlockHeight, + self.chainSyncProgress, + self.terminalSyncStartHeight, + self.lastTerminalBlockHeight, + self.terminalHeaderSyncProgress, + self.masternodeListSyncInfo, + self.masternodeListsToSync, + self.masternodeListProgress, + self.combinedSyncProgress + ]; +} + +- (double)masternodeListProgress { + uint32_t amountLeft = self.masternodeListSyncInfo.retrievalQueueCount; + uint32_t maxAmount = self.masternodeListSyncInfo.retrievalQueueMaxAmount; + uint32_t lastBlockHeight = self.masternodeListSyncInfo.lastBlockHeight; + uint32_t estimatedBlockHeight = self.estimatedBlockHeight; + return amountLeft ? MAX(MIN((maxAmount - amountLeft) / maxAmount, 1), 0) : lastBlockHeight != UINT32_MAX && estimatedBlockHeight != 0 && lastBlockHeight + 16 >= estimatedBlockHeight; +} + +- (double)chainSyncProgress { + uint32_t chainSyncStartHeight = self.chainSyncStartHeight; + uint32_t lastSyncBlockHeight = self.lastSyncBlockHeight; + uint32_t estimatedBlockHeight = self.estimatedBlockHeight; + if (!self.hasDownloadPeer && chainSyncStartHeight == 0) + return 0.0; + else if (lastSyncBlockHeight >= estimatedBlockHeight) + return 1.0; + else if (estimatedBlockHeight == 0) + return 0.0; + else if (chainSyncStartHeight > lastSyncBlockHeight) + return MIN(1.0, MAX(0.0, 0.1 + 0.9 * lastSyncBlockHeight / estimatedBlockHeight)); + double deltaSyncHeight = estimatedBlockHeight - chainSyncStartHeight; + return deltaSyncHeight == 0 ? 0.0 : MIN(1.0, MAX(0.0, 0.1 + 0.9 * (lastSyncBlockHeight - chainSyncStartHeight) / deltaSyncHeight)); +} + +- (double)terminalHeaderSyncProgress { + uint32_t terminalSyncStartHeight = self.terminalSyncStartHeight; + uint32_t lastTerminalBlockHeight = self.lastTerminalBlockHeight; + uint32_t estimatedBlockHeight = self.estimatedBlockHeight; + if (!self.hasDownloadPeer && terminalSyncStartHeight == 0) + return 0.0; + else if (lastTerminalBlockHeight >= estimatedBlockHeight) + return 1.0; + else + return MIN(1.0, MAX(0.0, 0.1 + 0.9 * (terminalSyncStartHeight > lastTerminalBlockHeight ? lastTerminalBlockHeight / estimatedBlockHeight : (lastTerminalBlockHeight - terminalSyncStartHeight) / (estimatedBlockHeight - terminalSyncStartHeight)))); +} + +- (uint32_t)masternodeListsToSync { + uint32_t estimatedBlockHeight = self.estimatedBlockHeight; + uint32_t amountLeft = self.masternodeListSyncInfo.retrievalQueueCount; + uint32_t lastMasternodeListHeight = self.masternodeListSyncInfo.lastBlockHeight; + uint32_t maxAmount = self.masternodeListSyncInfo.retrievalQueueMaxAmount; + uint32_t storedCount = self.masternodeListSyncInfo.storedCount; + uint32_t masternodeListsToSync; + if (!([[DSOptionsManager sharedInstance] syncType] & DSSyncType_MasternodeList)) + masternodeListsToSync = 0; + else if (!maxAmount || storedCount <= 1) // 1 because there might be a default + masternodeListsToSync = (lastMasternodeListHeight == UINT32_MAX || estimatedBlockHeight < lastMasternodeListHeight) + ? 32 + : MIN(32, (uint32_t)ceil((estimatedBlockHeight - lastMasternodeListHeight) / 24.0f)); + else + masternodeListsToSync = amountLeft; + + return masternodeListsToSync; +} +/** + * A unit of weight is the time it would take to sync 1000 blocks; + * terminal headers are 4 times faster the blocks + * the first masternode list is worth 20000 blocks + * each masternode list after that is worth 2000 blocks + */ +- (double)combinedSyncProgress { + uint32_t estimatedBlockHeight = self.estimatedBlockHeight; + uint32_t lastTerminalBlockHeight = self.lastTerminalBlockHeight; + uint32_t lastSyncBlockHeight = self.lastSyncBlockHeight; + double chainWeight = lastSyncBlockHeight >= estimatedBlockHeight ? 0 : estimatedBlockHeight - lastSyncBlockHeight; + double terminalWeight = lastTerminalBlockHeight >= estimatedBlockHeight ? 0 : (estimatedBlockHeight - lastTerminalBlockHeight) / 4; + uint32_t listsToSync = [self masternodeListsToSync]; + double masternodeWeight = listsToSync ? (20000 + 2000 * (listsToSync - 1)) : 0; + double totalWeight = chainWeight + terminalWeight + masternodeWeight; + if (totalWeight == 0) { + return self.peerManagerConnected && self.hasDownloadPeer ? 1 : 0; + } else { + double terminalProgress = self.terminalHeaderSyncProgress * (terminalWeight / totalWeight); + double chainProgress = self.chainSyncProgress * (chainWeight / totalWeight); + double masternodesProgress = self.masternodeListProgress * (masternodeWeight / totalWeight); + double progress = terminalProgress + masternodesProgress + chainProgress; + if (progress < 0.99995) { + return progress; + } else { + return 1; + } + } +} + +- (DSSyncStateKind)kind { + if ([self atTheEndOfSyncBlocksAndSyncingMasternodeList] || [self atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList]) { + return DSSyncStateKind_Masternodes; + } else if (self.syncPhase == DSChainSyncPhase_InitialTerminalBlocks) { + return DSSyncStateKind_Headers; + } else { + return DSSyncStateKind_Chain; + } + +} + +- (BOOL)atTheEndOfSyncBlocksAndSyncingMasternodeList { + // We give a 6 block window, just in case a new block comes in + return self.lastSyncBlockHeight + 6 >= self.estimatedBlockHeight + && self.masternodeListSyncInfo.retrievalQueueCount > 0 + && self.syncPhase == DSChainSyncPhase_Synced; +} + +- (BOOL)atTheEndOfInitialTerminalBlocksAndSyncingMasternodeList { + // We give a 6 block window, just in case a new block comes in + return self.lastTerminalBlockHeight + 6 >= self.estimatedBlockHeight + && self.masternodeListSyncInfo.retrievalQueueCount > 0 + && self.syncPhase == DSChainSyncPhase_InitialTerminalBlocks; +} + +@end + diff --git a/DashSync/shared/Models/Wallet/DSWalletConstants.m b/DashSync/shared/Models/Wallet/DSWalletConstants.m index 96e679eed..8392923de 100644 --- a/DashSync/shared/Models/Wallet/DSWalletConstants.m +++ b/DashSync/shared/Models/Wallet/DSWalletConstants.m @@ -11,14 +11,13 @@ NSString *const DSChainManagerSyncWillStartNotification = @"DSChainManagerSyncWillStartNotification"; NSString *const DSChainManagerChainSyncDidStartNotification = @"DSChainManagerSyncDidStartNotification"; -NSString *const DSChainManagerSyncParametersUpdatedNotification = @"DSChainManagerSyncParametersUpdatedNotification"; NSString *const DSChainManagerSyncFinishedNotification = @"DSChainManagerSyncFinishedNotification"; NSString *const DSChainManagerSyncFailedNotification = @"DSChainManagerSyncFailedNotification"; +NSString *const DSChainManagerSyncStateDidChangeNotification = @"DSChainManagerSyncStateDidChangeNotification"; NSString *const DSTransactionManagerTransactionStatusDidChangeNotification = @"DSTransactionManagerTransactionStatusDidChangeNotification"; NSString *const DSTransactionManagerTransactionReceivedNotification = @"DSTransactionManagerTransactionReceivedNotification"; -NSString *const DSChainNewChainTipBlockNotification = @"DSChainNewChainTipBlockNotification"; NSString *const DSPeerManagerPeersDidChangeNotification = @"DSPeerManagerPeersDidChangeNotification"; NSString *const DSPeerManagerConnectedPeersDidChangeNotification = @"DSPeerManagerConnectedPeersDidChangeNotification"; NSString *const DSPeerManagerDownloadPeerDidChangeNotification = @"DSPeerManagerDownloadPeerDidChangeNotification"; @@ -27,8 +26,6 @@ NSString *const DSChainStandaloneDerivationPathsDidChangeNotification = @"DSChainStandaloneDerivationPathsDidChangeNotification"; NSString *const DSChainStandaloneAddressesDidChangeNotification = @"DSChainStandaloneAddressesDidChangeNotification"; -NSString *const DSChainChainSyncBlocksDidChangeNotification = @"DSChainChainSyncBlocksDidChangeNotification"; -NSString *const DSChainTerminalBlocksDidChangeNotification = @"DSChainTerminalBlocksDidChangeNotification"; NSString *const DSChainInitialHeadersDidFinishSyncingNotification = @"DSChainInitialHeadersDidFinishSyncingNotification"; NSString *const DSChainBlocksDidFinishSyncingNotification = @"DSChainBlocksDidFinishSyncingNotification"; NSString *const DSChainBlockWasLockedNotification = @"DSChainBlockWasLockedNotification"; @@ -66,6 +63,7 @@ NSString *const DSChainManagerNotificationWalletKey = @"DSChainManagerNotificationWalletKey"; NSString *const DSChainManagerNotificationAccountKey = @"DSChainManagerNotificationAccountKey"; +NSString *const DSChainManagerNotificationSyncStateKey = @"DSChainManagerNotificationSyncStateKey"; NSString *const DSPeerManagerNotificationPeerKey = @"DSPeerManagerNotificationPeerKey"; NSString *const DSTransactionManagerNotificationTransactionKey = @"DSTransactionManagerNotificationTransactionKey"; diff --git a/Example/DashSync.xcodeproj/project.pbxproj b/Example/DashSync.xcodeproj/project.pbxproj index fb909d947..c38885558 100644 --- a/Example/DashSync.xcodeproj/project.pbxproj +++ b/Example/DashSync.xcodeproj/project.pbxproj @@ -2401,6 +2401,7 @@ DevelopmentTeam = 44RJ69WHFF; }; 6003F5AD195388D20070C39A = { + DevelopmentTeam = 44RJ69WHFF; TestTargetID = 6003F589195388D20070C39A; }; FB9C9EF925B80DB40039880E = { @@ -2412,10 +2413,9 @@ }; buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "DashSync" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, fr, @@ -3229,6 +3229,48 @@ INFOPLIST_FILE = "DashSync/DashSync-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MODULE_NAME = ExampleApp; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"BoringSSL-GRPC-iOS\"", + "-l\"CocoaImageHashing-iOS\"", + "-l\"CocoaLumberjack-iOS\"", + "-l\"DAPI-GRPC-iOS\"", + "-l\"DSDynamicOptions-iOS\"", + "-l\"DWAlertController\"", + "-l\"DashSync-iOS\"", + "-l\"KVO-MVVM\"", + "-l\"Protobuf-iOS\"", + "-l\"SDWebImage-iOS\"", + "-l\"TinyCborObjc-iOS\"", + "-l\"abseil-iOS\"", + "-l\"bz2\"", + "-l\"c++\"", + "-l\"dash_shared_core_ios\"", + "-l\"gRPC-Core-iOS\"", + "-l\"gRPC-ProtoRPC-iOS\"", + "-l\"gRPC-RxLibrary-iOS\"", + "-l\"gRPC-iOS\"", + "-l\"resolv\"", + "-l\"sqlite3\"", + "-l\"tinycbor-iOS\"", + "-l\"z\"", + "-framework", + "\"BackgroundTasks\"", + "-framework", + "\"CoreData\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"ImageIO\"", + "-framework", + "\"Security\"", + "-framework", + "\"SystemConfiguration\"", + "-framework", + "\"UIKit\"", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; @@ -3247,6 +3289,48 @@ INFOPLIST_FILE = "DashSync/DashSync-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MODULE_NAME = ExampleApp; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"BoringSSL-GRPC-iOS\"", + "-l\"CocoaImageHashing-iOS\"", + "-l\"CocoaLumberjack-iOS\"", + "-l\"DAPI-GRPC-iOS\"", + "-l\"DSDynamicOptions-iOS\"", + "-l\"DWAlertController\"", + "-l\"DashSync-iOS\"", + "-l\"KVO-MVVM\"", + "-l\"Protobuf-iOS\"", + "-l\"SDWebImage-iOS\"", + "-l\"TinyCborObjc-iOS\"", + "-l\"abseil-iOS\"", + "-l\"bz2\"", + "-l\"c++\"", + "-l\"dash_shared_core_ios\"", + "-l\"gRPC-Core-iOS\"", + "-l\"gRPC-ProtoRPC-iOS\"", + "-l\"gRPC-RxLibrary-iOS\"", + "-l\"gRPC-iOS\"", + "-l\"resolv\"", + "-l\"sqlite3\"", + "-l\"tinycbor-iOS\"", + "-l\"z\"", + "-framework", + "\"BackgroundTasks\"", + "-framework", + "\"CoreData\"", + "-framework", + "\"Foundation\"", + "-framework", + "\"ImageIO\"", + "-framework", + "\"Security\"", + "-framework", + "\"SystemConfiguration\"", + "-framework", + "\"UIKit\"", + "-ld_classic", + ); PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; @@ -3258,6 +3342,7 @@ baseConfigurationReference = C90A9AD2C32828341F0E6915 /* Pods-DashSync_Tests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = 44RJ69WHFF; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; @@ -3279,6 +3364,7 @@ baseConfigurationReference = DFA886AB709D97089A70E82A /* Pods-DashSync_Tests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = 44RJ69WHFF; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; diff --git a/Example/DashSync/DSMiningViewController.m b/Example/DashSync/DSMiningViewController.m index 32eeaeaf1..cd8723ce2 100644 --- a/Example/DashSync/DSMiningViewController.m +++ b/Example/DashSync/DSMiningViewController.m @@ -19,6 +19,7 @@ #import "BigIntTypes.h" #import "DSChain+Protected.h" #import "DSChainManager.h" +#import "DSChainManager+Mining.h" #import "DSFullBlock.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" diff --git a/Example/DashSync/DSSyncViewController.m b/Example/DashSync/DSSyncViewController.m index 4377bb1ac..a560aa3c9 100644 --- a/Example/DashSync/DSSyncViewController.m +++ b/Example/DashSync/DSSyncViewController.m @@ -64,7 +64,7 @@ @interface DSSyncViewController () @property (strong, nonatomic) IBOutlet UILabel *masternodeListsCountLabel; @property (strong, nonatomic) IBOutlet UILabel *earliestMasternodeListLabel; @property (strong, nonatomic) IBOutlet UILabel *lastMasternodeListLabel; -@property (strong, nonatomic) id filterChangedObserver, syncFinishedObserver, syncFailedObserver, balanceObserver, blocksObserver, blocksResetObserver, headersResetObserver, sporkObserver, masternodeObserver, masternodeCountObserver, chainWalletObserver, chainStandaloneDerivationPathObserver, chainSingleAddressObserver, governanceObjectCountObserver, governanceObjectReceivedCountObserver, governanceVoteCountObserver, governanceVoteReceivedCountObserver, connectedPeerConnectionObserver, peerConnectionObserver, blockchainIdentitiesObserver, blockchainInvitationsObserver, quorumObserver; +@property (strong, nonatomic) id filterChangedObserver, syncFinishedObserver, syncFailedObserver, balanceObserver, syncStateObserver, sporkObserver, masternodeObserver, masternodeCountObserver, chainWalletObserver, chainStandaloneDerivationPathObserver, chainSingleAddressObserver, governanceObjectCountObserver, governanceObjectReceivedCountObserver, governanceVoteCountObserver, governanceVoteReceivedCountObserver, connectedPeerConnectionObserver, peerConnectionObserver, blockchainIdentitiesObserver, blockchainInvitationsObserver, quorumObserver; @property (strong, nonatomic) DSPasteboardAddressExtractor *pasteboardExtractor; - (IBAction)startSync:(id)sender; @@ -151,34 +151,18 @@ - (void)viewDidLoad { }]; - self.blocksObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:DSChainNewChainTipBlockNotification - object:nil - queue:nil - usingBlock:^(NSNotification *note) { - if ([note.userInfo[DSChainManagerNotificationChainKey] isEqual:[self chain]]) { - //DSLogPrivate(@"update blockheight"); - [self updateBlockHeight]; - [self updateHeaderHeight]; - } - }]; - self.blocksResetObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:DSChainChainSyncBlocksDidChangeNotification + self.syncStateObserver = + [[NSNotificationCenter defaultCenter] addObserverForName:DSChainManagerSyncStateDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note) { - [self updateBlockHeight]; - [self updateBalance]; - }]; - - self.headersResetObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:DSChainTerminalBlocksDidChangeNotification - object:nil - queue:nil - usingBlock:^(NSNotification *note) { - [self updateHeaderHeight]; - }]; + if ([note.userInfo[DSChainManagerNotificationChainKey] isEqual:[self chain]]) { + [self updateBlockHeight]; + [self updateHeaderHeight]; + [self updateBalance]; + } + }]; self.balanceObserver = [[NSNotificationCenter defaultCenter] addObserverForName:DSWalletBalanceDidChangeNotification diff --git a/Example/DashSync/DSTransactionFloodingViewController.m b/Example/DashSync/DSTransactionFloodingViewController.m index 8c63330d3..dc705b2ab 100644 --- a/Example/DashSync/DSTransactionFloodingViewController.m +++ b/Example/DashSync/DSTransactionFloodingViewController.m @@ -35,7 +35,7 @@ @interface DSTransactionFloodingViewController () @property (nonatomic, strong) IBOutlet UIBarButtonItem *startFloodingButton; -@property (strong, nonatomic) id blocksObserver, txStatusObserver; +@property (strong, nonatomic) id syncStateObserver, txStatusObserver; @property (strong, nonatomic) NSMutableDictionary *transactionSuccessDictionary; @@ -83,8 +83,8 @@ - (void)viewDidLoad { }]; } - self.blocksObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:DSChainNewChainTipBlockNotification + self.syncStateObserver = + [[NSNotificationCenter defaultCenter] addObserverForName:DSChainManagerSyncStateDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note) { @@ -100,7 +100,7 @@ - (void)viewDidLoad { } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self.blocksObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:self.syncStateObserver]; } - (void)updateISCounts { diff --git a/Example/Podfile.lock b/Example/Podfile.lock index ef3d798df..b0fb2e24d 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -657,7 +657,7 @@ PODS: - gRPC/Interface-Legacy (1.49.0): - gRPC-RxLibrary/Interface (= 1.49.0) - KVO-MVVM (0.5.1) - - Protobuf (3.26.1) + - Protobuf (3.27.2) - SDWebImage (5.14.3): - SDWebImage/Core (= 5.14.3) - SDWebImage/Core (5.14.3) @@ -721,7 +721,7 @@ SPEC CHECKSUMS: gRPC-ProtoRPC: 1c223e0f1732bb8d0b9e9e0ea60cc0fe995b8e2d gRPC-RxLibrary: 92327f150e11cf3b1c0f52e083944fd9f5cb5d1e KVO-MVVM: 4df3afd1f7ebcb69735458b85db59c4271ada7c6 - Protobuf: a53f5173a603075b3522a5c50be63a67a5f3353a + Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2 SDWebImage: 9c36e66c8ce4620b41a7407698dda44211a96764 tinycbor: d4d71dddda1f8392fbb4249f63faf8552f327590 TinyCborObjc: 5204540fb90ff0c40fb22d408fa51bab79d78a80 diff --git a/Example/Tests/DSDeterministicMasternodeListTests.m b/Example/Tests/DSDeterministicMasternodeListTests.m index f8e9b31e6..b874364f4 100644 --- a/Example/Tests/DSDeterministicMasternodeListTests.m +++ b/Example/Tests/DSDeterministicMasternodeListTests.m @@ -2875,7 +2875,7 @@ - (void)testMNLSavingAndRetrievingInIncorrectOrderFromDisk { XCTAssert(result1092940.validQuorums, @"validQuorums not valid at height %u", [chain heightForBlockHash:blockHash1092940]); DSQuorumEntry *quorum1092912 = [result1092940.addedQuorums firstObject]; // 1092912 and 1092916 are the same, 1092916 is older though and is original 1092912 is based off a reloaded 109 - UInt256 llmqHash1092912 = [chain.chainManager.masternodeManager buildLLMQHashFor:quorum1092912]; + UInt256 llmqHash1092912 = [DSKeyManager NSDataFrom:quorum_build_llmq_hash(quorum1092912.llmqType, quorum1092912.quorumHash.u8)].UInt256; NSArray *masternodeScores1092912 = [masternodeList1092912 scoresForQuorumModifier:llmqHash1092912 atBlockHeight:1092912]; NSArray *masternodeScores1092916 = [masternodeList1092916 scoresForQuorumModifier:llmqHash1092912 atBlockHeight:1092912]; // BOOL a = [quorum1092912 validateWithMasternodeList:masternodeList1092912]; diff --git a/Example/Tests/DSMiningTests.m b/Example/Tests/DSMiningTests.m index c2b6d559e..95799ae6e 100644 --- a/Example/Tests/DSMiningTests.m +++ b/Example/Tests/DSMiningTests.m @@ -21,6 +21,7 @@ #import "DSAccount.h" #import "DSChain+Protected.h" #import "DSChainManager.h" +#import "DSChainManager+Mining.h" #import "DSFullBlock.h" #import "DSWallet.h" #import "NSData+DSHash.h"