Skip to content

Commit

Permalink
optimized wallet sync for fewer network calls
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron Voisine committed Aug 18, 2013
1 parent 368c997 commit 09e47b9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 114 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,3 @@ DerivedData
.idea/
openssl
openssl-*
boost
boost_*
11 changes: 8 additions & 3 deletions ZincWallet/ZNPayViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#import "ZNAmountViewController.h"
#import "ZNReceiveViewController.h"
#import "ZNWallet.h"
#import "ZNWallet+WebSocket.h"
#import "ZNPaymentRequest.h"
#import "ZNKey.h"
#import "ZNTransaction.h"
Expand Down Expand Up @@ -127,13 +128,16 @@ - (void)viewDidLoad
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
if (w.timeSinceLastSync > DEFAULT_SYNC_INTERVAL) [self refresh:nil];
else [w openSocket];

[self layoutButtonsAnimated:YES]; // check the clipboard for changes
}];

self.reachabilityObserver =
[[NSNotificationCenter defaultCenter] addObserverForName:kReachabilityChangedNotification object:nil queue:nil
usingBlock:^(NSNotification *note) {
if (w.timeSinceLastSync > DEFAULT_SYNC_INTERVAL) [self refresh:nil];
else [w openSocket];
}];

self.balanceObserver =
Expand Down Expand Up @@ -161,6 +165,8 @@ - (void)viewDidLoad

self.navigationItem.title = [NSString stringWithFormat:@"%@ (%@)", [w stringForAmount:w.balance],
[w localCurrencyStringForAmount:w.balance]];

[w openSocket];
}];

self.syncFailedObserver =
Expand Down Expand Up @@ -272,6 +278,7 @@ - (void)viewDidAppear:(BOOL)animated
[super viewDidAppear:animated];

if ([[ZNWallet sharedInstance] timeSinceLastSync] > DEFAULT_SYNC_INTERVAL) [self refresh:nil];
else [[ZNWallet sharedInstance] openSocket];
}

- (void)viewWillDisappear:(BOOL)animated
Expand Down Expand Up @@ -504,9 +511,7 @@ - (IBAction)refresh:(id)sender
if (sender || [self.reachability currentReachabilityStatus] != NotReachable) {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];
[self.spinner startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[ZNWallet sharedInstance] synchronize];
});
[[ZNWallet sharedInstance] synchronize];
}
}

Expand Down
2 changes: 1 addition & 1 deletion ZincWallet/ZNWallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

#import <Foundation/Foundation.h>

#define WALLET_BIP32 1
//#define WALLET_BIP32 1
//XXXX switch mnemonic to bip39
//#define WALLET_BIP39 1

Expand Down
167 changes: 60 additions & 107 deletions ZincWallet/ZNWallet.m
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,10 @@ - (instancetype)init
self.localFormat.numberStyle = NSNumberFormatterCurrencyStyle;
self.localFormat.negativeFormat =
[self.localFormat.positiveFormat stringByReplacingOccurrencesOfString:@"¤" withString:@"¤-"];

[self openSocket];

return self;
}

- (void)dealloc
{
[self closeSocket];
}

- (instancetype)initWithSeedPhrase:(NSString *)phrase
{
if (! (self = [self init])) return nil;
Expand Down Expand Up @@ -349,95 +342,88 @@ - (void)synchronize
_synchronizing = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncStartedNotification object:self];
[self openSocket];
});

self.updatedTransactions = [NSMutableSet set];
NSMutableArray *newAddresses = [NSMutableArray arrayWithCapacity:GAP_LIMIT_EXTERNAL + GAP_LIMIT_INTERNAL +
self.fundedAddresses.count + self.spentAddresses.count];

//XXXX refactor this to optimize for fewest network reqeusts (should only make two)
[newAddresses addObjectsFromArray:[self addressesWithGapLimit:GAP_LIMIT_EXTERNAL internal:NO]];
[newAddresses addObjectsFromArray:[self addressesWithGapLimit:GAP_LIMIT_INTERNAL internal:YES]];
[newAddresses addObjectsFromArray:self.fundedAddresses];
[newAddresses addObjectsFromArray:self.spentAddresses];

[self synchronizeWithGapLimit:GAP_LIMIT_EXTERNAL internal:NO completion:^(NSError *error) {
// An ARC retain loop when using block recursion is avoided here by passing the block to itself as an argument
void (^completion)(NSError *, id) = ^(NSError *error, id completion) {
if (error) {
_synchronizing = NO;
[_defs synchronize];

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification object:self
userInfo:@{@"error":error}];
});
return;
}

[newAddresses removeObjectsAtIndexes:[newAddresses
indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self.spentAddresses containsObject:obj] || [self.fundedAddresses containsObject:obj];
}]];

if (newAddresses.count < GAP_LIMIT_EXTERNAL + GAP_LIMIT_INTERNAL) {
[newAddresses setArray:[self addressesWithGapLimit:GAP_LIMIT_EXTERNAL internal:NO]];
[newAddresses addObjectsFromArray:[self addressesWithGapLimit:GAP_LIMIT_INTERNAL internal:YES]];
if (newAddresses.count) {
[self queryAddresses:newAddresses completion:^(NSError *error) {
((void (^)(NSError *, id))completion)(error, completion);
}];
}
return;
}

[self synchronizeWithGapLimit:GAP_LIMIT_INTERNAL internal:YES completion:^(NSError *error) {
@synchronized(self) {
// remove unconfirmed transactions that no longer appear in query results
//XXX we should keep a seprate list of failed transactions to display along with the successful ones
[self.transactions
removeObjectsForKeys:[self.transactions keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return ! obj[@"block_height"] && ! [self.updatedTransactions containsObject:obj[@"hash"]];
}].allObjects];

[_defs setObject:self.transactions forKey:TRANSACTIONS_KEY];
}

[self queryUnspentOutputs:self.outdatedAddresses.allObjects completion:^(NSError *error) {
_synchronizing = NO;

if (error) {
_synchronizing = NO;
[_defs synchronize];

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification object:self
userInfo:@{@"error":error}];
});
return;
}

// check funded and spent addresses for new transactions
[self queryAddresses:[self.fundedAddresses arrayByAddingObjectsFromArray:self.spentAddresses]
completion:^(NSError *error) {
if (error) {
_synchronizing = NO;
[_defs synchronize];

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification
object:self userInfo:@{@"error":error}];
});
return;
}

[self cleanUnconfirmed];

[_defs setDouble:[NSDate timeIntervalSinceReferenceDate] forKey:LAST_SYNC_TIME_KEY];
[_defs synchronize];

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFinishedNotification object:self];

@synchronized(self) {
// remove unconfirmed transactions that no longer appear in query results
//XXX we should keep a seprate list of failed transactions to display along with the successful ones
[self.transactions removeObjectsForKeys:[self.transactions
keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return ! obj[@"block_height"] && ! [self.updatedTransactions containsObject:obj[@"hash"]];
}].allObjects];

[_defs setObject:self.transactions forKey:TRANSACTIONS_KEY];
}

[self queryUnspentOutputs:self.outdatedAddresses.allObjects completion:^(NSError *error) {
if (error) {
_synchronizing = NO;
[_defs synchronize];

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification
object:self userInfo:@{@"error":error}];
});
return;
}

_synchronizing = NO;

[self cleanUnconfirmed];

[_defs setDouble:[NSDate timeIntervalSinceReferenceDate] forKey:LAST_SYNC_TIME_KEY];
[_defs synchronize];

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFinishedNotification
object:self];

// need to send balance notification every time since exchnage rates might be different
//if (self.outdatedAddresses.count) {
[[NSNotificationCenter defaultCenter] postNotificationName:walletBalanceNotification
object:self];
//}
});
}];
}];
// send balance notification every time since exchnage rates might have changed
[[NSNotificationCenter defaultCenter] postNotificationName:walletBalanceNotification object:self];
});
}];
}];
};

self.updatedTransactions = [NSMutableSet set];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self queryAddresses:newAddresses completion:^(NSError *error) { completion(error, completion); }];
});
}

- (NSArray *)addressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal
Expand Down Expand Up @@ -482,36 +468,6 @@ - (NSArray *)addressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal
return addresses;
}

- (void)synchronizeWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal
completion:(void (^)(NSError *error))completion
{
NSMutableArray *newAddresses = [[self addressesWithGapLimit:gapLimit internal:internal] mutableCopy];

if (! newAddresses) {
if (completion) {
completion(nil);
//completion([NSError errorWithDomain:@"ZincWallet" code:500
// userInfo:@{NSLocalizedDescriptionKey:@"error generating keys"}]);
}
return;
}

[self queryAddresses:newAddresses completion:^(NSError *error) {
[newAddresses removeObjectsAtIndexes:[newAddresses
indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self.spentAddresses containsObject:obj] || [self.fundedAddresses containsObject:obj];
}]];

if (newAddresses.count < gapLimit) {
[self synchronizeWithGapLimit:gapLimit internal:internal completion:completion];
}
else if (self.outdatedAddresses.count) {
[self queryUnspentOutputs:self.outdatedAddresses.allObjects completion:completion];
}
else if (completion) completion(error);
}];
}

// query blockchain for the given addresses
- (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error))completion
{
Expand All @@ -537,7 +493,6 @@ - (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error)

NSURL *url = [NSURL URLWithString:[ADDRESS_URL stringByAppendingString:[[addresses componentsJoinedByString:@"|"]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];

__block dispatch_queue_t q = dispatch_get_current_queue();
__block AFJSONRequestOperation *requestOp =
[AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url]
Expand Down Expand Up @@ -601,17 +556,14 @@ - (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error)
if (price > DBL_EPSILON) [_defs setDouble:price forKey:LOCAL_CURRENCY_PRICE_KEY];
}

if (self.outdatedAddresses.count) {
[[NSNotificationCenter defaultCenter] postNotificationName:walletBalanceNotification object:self];
}

if (completion) dispatch_async(q, ^{ completion(nil); });
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(@"%@", error.localizedDescription);

if (completion) dispatch_async(q, ^{ completion(error); });
}];

NSLog(@"%@", url.absoluteString);
requestOp.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
requestOp.failureCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[requestOp start];
Expand Down Expand Up @@ -711,6 +663,7 @@ - (void)queryUnspentOutputs:(NSArray *)addresses completion:(void (^)(NSError *e
if (completion) dispatch_async(q, ^{ completion(nil); });
}];

NSLog(@"%@", url.absoluteString);
requestOp.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
requestOp.failureCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[requestOp start];
Expand Down
59 changes: 58 additions & 1 deletion ZincWalletTests/ZincWalletTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,64 @@ - (void)testTransactionHeightUntilFree

#pragma mark - testBIP39Mnemonic

//XXXX testBIP39Mnemonic
//XXXX test bip39
- (void)testBIP39MnemonicDecodePhrase
{
id<ZNMnemonic> mnemonic = [ZNBIP39Mnemonic sharedInstance];

NSData *d = [mnemonic decodePhrase:@"like just love know never want time out there make look eye"];

NSLog(@"like just love know never want time out there make look eye = 0x%@", [NSString hexWithData:d]);

//STAssertEqualObjects(d, @"00285dfe00285e0100285e0400285e07".hexToData, @"[ZNWallet decodePhrase:]");

d = [mnemonic decodePhrase:@"kick chair mask master passion quick raise smooth unless wander actually broke"];

NSLog(@"kick chair mask master passion quick raise smooth unless wander actually broke = 0x%@",
[NSString hexWithData:d]);

//STAssertEqualObjects(d, @"fea983ac0028608e0028609100286094".hexToData, @"[ZNWallet decodePhrase:]");

// test of phrase with trailing space
d = [mnemonic
decodePhrase:@"kick quiet student ignore cruel danger describe accident eager darkness embrace suppose "];

NSLog(@"kick quiet student ignore cruel danger describe accident eager darkness embrace suppose = 0x%@",
[NSString hexWithData:d]);

//STAssertEqualObjects(d, @"8d02be487e1953ce2dd6c186fcc97e65".hexToData, @"[ZNWallet decodePhrase:]");
}

- (void)testBIP39MnemonicEncodePhrase
{
id<ZNMnemonic> mnemonic = [ZNBIP39Mnemonic sharedInstance];

NSString *s = [mnemonic encodePhrase:@"00285dfe00285e0100285e0400285e07".hexToData];

NSLog(@"0x00285dfe00285e0100285e0400285e07 = %@", s);

//STAssertEqualObjects(s, @"like just love know never want time out there make look eye",
// @"[ZNWallet encodePhrase:]");

s = [mnemonic encodePhrase:@"00000000000000000000000000000000".hexToData];

NSLog(@"0x00285dfe00285e0100285e0400285e07 = %@", s);

s = [mnemonic encodePhrase:@"fea983ac0028608e0028609100286094".hexToData];

NSLog(@"0x00285dfe00285e0100285e0400285e07 = %@", s);

//STAssertEqualObjects(s, @"kick chair mask master passion quick raise smooth unless wander actually broke",
// @"[ZNWallet encodePhrase:]");

s = [mnemonic encodePhrase:@"8d02be487e1953ce2dd6c186fcc97e65".hexToData];

NSLog(@"0x8d02be487e1953ce2dd6c186fcc97e65 = %@", s);

//STAssertEqualObjects(s, @"kick quiet student ignore cruel danger describe accident eager darkness embrace suppose",
// @"[ZNWallet encodePhrase:]");
}


#pragma mark - testElectrumMnemonic

Expand Down

0 comments on commit 09e47b9

Please sign in to comment.