diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dac065ec1..e0ed49999 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,10 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: '15.4' + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim - name: Build dependencies @@ -59,4 +63,4 @@ jobs: -scheme "DashSync-Example" \ -workspace "DashSync.xcworkspace" \ -destination "platform=$platform,name=iPhone 13" \ - CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED=NO \ No newline at end of file + CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED=NO diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6504c8e63..71a1fd2be 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -43,6 +43,10 @@ jobs: ${{ runner.os }}-pods- - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Dependencies working-directory: ./dashsync/Example run: pod install --repo-update diff --git a/.github/workflows/e2eTestsTestnet.yml b/.github/workflows/e2eTestsTestnet.yml index debb99192..85d59ffd8 100644 --- a/.github/workflows/e2eTestsTestnet.yml +++ b/.github/workflows/e2eTestsTestnet.yml @@ -45,6 +45,10 @@ jobs: ${{ runner.os }}-pods- - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Dependencies working-directory: ./dashsync/Example run: pod install --repo-update diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2afc376d8..911ae0a85 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,6 +43,10 @@ jobs: key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} restore-keys: | ${{ runner.os }}-pods- + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim - name: Dependencies diff --git a/.github/workflows/network.yml b/.github/workflows/network.yml index 342edf1b3..18268c01a 100644 --- a/.github/workflows/network.yml +++ b/.github/workflows/network.yml @@ -44,6 +44,10 @@ jobs: run: cargo install cargo-lipo - name: Rustup add targets run: rustup target add x86_64-apple-darwin + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Build Dependencies working-directory: ./dashsync/Example run: pod install --repo-update diff --git a/.github/workflows/syncTestMainnet.yml b/.github/workflows/syncTestMainnet.yml index 54aa929e3..4f62f4c3f 100644 --- a/.github/workflows/syncTestMainnet.yml +++ b/.github/workflows/syncTestMainnet.yml @@ -44,6 +44,10 @@ jobs: ${{ runner.os }}-pods- - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Dependencies working-directory: ./dashsync/Example run: pod install --repo-update diff --git a/.github/workflows/syncTestTestnet.yml b/.github/workflows/syncTestTestnet.yml index d55703616..7deb6cab1 100644 --- a/.github/workflows/syncTestTestnet.yml +++ b/.github/workflows/syncTestTestnet.yml @@ -45,6 +45,10 @@ jobs: ${{ runner.os }}-pods- - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 - name: Dependencies working-directory: ./dashsync/Example run: pod install --repo-update diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 205e35954..d8a98852b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,11 @@ jobs: - name: Rustup add targets run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + - name: Setup CocoaPods + uses: maxim-lobanov/setup-cocoapods@v1 + with: + version: 1.15.2 + - name: Dependencies working-directory: ./dashsync/Example run: pod install --repo-update diff --git a/.gitmodules b/.gitmodules index a6a1c4c3c..ac3da934d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "dashsync/crypto/secp256k1"] path = dashsync/crypto/secp256k1 url = https://github.com/bitcoin-core/secp256k1.git -[submodule "platform"] - path = platform - url = https://github.com/dashevo/platform.git diff --git a/DAPI-GRPC.podspec b/DAPI-GRPC.podspec deleted file mode 100644 index ee6b62a53..000000000 --- a/DAPI-GRPC.podspec +++ /dev/null @@ -1,81 +0,0 @@ -Pod::Spec.new do |s| - s.name = "DAPI-GRPC" - s.version = "0.22.0-dev.8" - s.license = "MIT" - s.authors = { 'Dash Core Group, Inc.' => 'contact@dash.org' } - s.homepage = "https://github.com/dashevo/platform/tree/master/packages/dapi-grpc" - s.summary = "Dash's Decentralized API GRPC" - s.source = { :git => 'https://github.com/dashevo/dashsync-iOS.git', :tag => 'dapi-0.22.0-dev.8' } - - s.ios.deployment_target = "13.0" - s.osx.deployment_target = "10.15" - - # Base directory where the .proto files are. - src = "platform/packages/dapi-grpc/protos" - - # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients. - s.dependency "!ProtoCompiler-gRPCPlugin", "~> 1.0" - - # Pods directory corresponding to this app's Podfile, relative to the location of this podspec. - pods_root = 'dapi-grpc-pod-installer/Pods' - - # Path where Cocoapods downloads protoc and the gRPC plugin. - protoc_dir = "#{pods_root}/!ProtoCompiler" - protoc = "#{protoc_dir}/protoc" - plugin = "#{pods_root}/!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin" - - # Directory where the generated files will be placed. - dir = "#{pods_root}/#{s.name}" - - s.prepare_command = <<-CMD - pwd - git submodule update --init - - pushd dapi-grpc-pod-installer - pod install - popd - - mkdir -p #{dir} - #{protoc} \ - --plugin=protoc-gen-grpc=#{plugin} \ - --objc_out=#{dir} \ - --grpc_out=#{dir} \ - -I #{src}/core/v0 \ - -I #{protoc_dir} \ - #{src}/core/v0/core.proto - - #{protoc} \ - --plugin=protoc-gen-grpc=#{plugin} \ - --objc_out=#{dir} \ - --grpc_out=#{dir} \ - -I #{src}/platform/v0 \ - -I #{protoc_dir} \ - #{src}/platform/v0/platform.proto - CMD - - # Files generated by protoc - s.subspec "Messages" do |ms| - ms.source_files = "#{dir}/*.pbobjc.{h,m}", "#{dir}/**/*.pbobjc.{h,m}" - ms.header_mappings_dir = dir - ms.requires_arc = false - # The generated files depend on the protobuf runtime. - ms.dependency "Protobuf" - end - - # Files generated by the gRPC plugin - s.subspec "Services" do |ss| - ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}" - ss.header_mappings_dir = dir - ss.requires_arc = true - # The generated files depend on the gRPC runtime, and on the files generated by protoc. - ss.dependency "gRPC-ProtoRPC" - ss.dependency "#{s.name}/Messages" - end - - s.pod_target_xcconfig = { - # This is needed by all pods that depend on Protobuf: - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1', - # This is needed by all pods that depend on gRPC-RxLibrary: - 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES', - } -end diff --git a/DashSync.podspec b/DashSync.podspec index f052a94ad..10938bb80 100644 --- a/DashSync.podspec +++ b/DashSync.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.homepage = 'https://github.com/dashevo/dashsync-ios.git' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'quantumexplorer' => 'quantum@dash.org' } - s.source = { :git => 'https://github.com/dashevo/dashsync-iOS.git', :tag => s.version.to_s } + s.source = { :git => 'https://github.com/dashpay/dashsync-iOS.git', :tag => s.version.to_s } s.ios.deployment_target = '13.0' s.osx.deployment_target = '10.15' @@ -34,13 +34,10 @@ Pod::Spec.new do |s| s.ios.framework = 'UIKit' s.macos.framework = 'Cocoa' s.compiler_flags = '-Wno-comma' - s.dependency 'DashSharedCore', '0.4.19' + s.dependency 'DashSharedCore', '0.5.1' s.dependency 'CocoaLumberjack', '3.7.2' s.ios.dependency 'DWAlertController', '0.2.1' s.dependency 'DSDynamicOptions', '0.1.2' - s.dependency 'DAPI-GRPC', '0.22.0-dev.8' - s.dependency 'TinyCborObjc', '0.4.6' s.prefix_header_contents = '#import "DSEnvironment.h"' end - diff --git a/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/DSBasePinViewController.m b/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/DSBasePinViewController.m index 91cfad918..f9f66ec98 100644 --- a/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/DSBasePinViewController.m +++ b/DashSync/iOS/Models/Managers/Service Managers/Auth/Controllers/DSBasePinViewController.m @@ -29,7 +29,7 @@ @interface DSBasePinViewController () @implementation DSBasePinViewController + (NSString *)defaultTitle { - return [NSString stringWithFormat:DSLocalizedString(@"PIN for %@", nil), DISPLAY_NAME]; + return DSLocalizedFormat(@"PIN for %@", nil, DISPLAY_NAME); } - (void)viewDidLoad { diff --git a/DashSync/shared/Categories/BigIntTypes.h b/DashSync/shared/Categories/BigIntTypes.h index 602d86be9..e49e40943 100644 --- a/DashSync/shared/Categories/BigIntTypes.h +++ b/DashSync/shared/Categories/BigIntTypes.h @@ -205,6 +205,7 @@ typedef uint32_t (^_Nullable BlockHeightFinder)(UInt256 blockHash); #define uint768_hex(u) [NSData dataWithUInt768:u].hexString #define uint768_reverse_hex(u) [NSData dataWithUInt768:u].reverse.hexString #define ecpoint_hex(u) [NSData dataWithBytes:u.p length:sizeof(DSECPoint)].hexString +#define dsutxo_hex(u) [NSData dataWithBytes:u.p length:sizeof(DSUTXO)].hexString #define uint256_reverse(u) [NSData dataWithUInt256:u].reverse.UInt256 #define uint256_from_int(u) ((UInt256){.u32 = {u, 0, 0, 0, 0, 0, 0, 0}}) diff --git a/DashSync/shared/Categories/NSArray+Dash.h b/DashSync/shared/Categories/NSArray+Dash.h index c9028fe2c..79d95ebda 100644 --- a/DashSync/shared/Categories/NSArray+Dash.h +++ b/DashSync/shared/Categories/NSArray+Dash.h @@ -6,6 +6,8 @@ // #import "BigIntTypes.h" +#import "dash_spv_apple_bindings.h" +#import "DSKeyManager.h" #import "NSData+Dash.h" #import @@ -23,4 +25,35 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface NSArray (_) ++ (NSArray *)ffi_from_vec:(Vec_ *)ffi_ref; ++ (Vec_ *)ffi_to_vec:(NSArray *)obj; ++ (void)ffi_destroy_vec:(Vec_ *)ffi_ref; +@end + +@interface NSArray (Vec_String) ++ (NSArray *)ffi_from_vec_of_string:(Vec_String *)ffi_ref; ++ (Vec_String *)ffi_to_vec_of_string:(NSArray *)obj; ++ (void)ffi_destroy_vec_of_string:(Vec_String *)ffi_ref; +@end + +@interface NSArray (Vec_u8_32) ++ (NSArray *)ffi_from_vec_u256:(Vec_u8_32 *)ffi_ref; ++ (Vec_u8_32 *)ffi_to_vec_u256:(NSArray *)obj; ++ (void)ffi_destroy_vec_u256:(Vec_u8_32 *)ffi_ref; +@end + +@interface NSArray (Vec_Vec_u8) ++ (NSArray *)ffi_from_vec_vec_u8:(Vec_Vec_u8 *)ffi_ref; ++ (Vec_Vec_u8 *)ffi_to_vec_vec_u8:(NSArray *)obj; ++ (void)ffi_destroy_vec_vec_u8:(Vec_Vec_u8 *)ffi_ref; +@end + +@interface NSArray (std_collections_BTreeSet_dashcore_hash_types_BlockHash) ++ (NSArray *)ffi_from_block_hash_btree_set:(std_collections_BTreeSet_dashcore_hash_types_BlockHash *)ffi_ref; ++ (std_collections_BTreeSet_dashcore_hash_types_BlockHash *)ffi_to_block_hash_btree_set:(NSArray *)obj; ++ (void)ffi_destroy_block_hash_btree_set:(std_collections_BTreeSet_dashcore_hash_types_BlockHash *)ffi_ref; +@end + + NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Categories/NSArray+Dash.m b/DashSync/shared/Categories/NSArray+Dash.m index 989c098d4..406700422 100644 --- a/DashSync/shared/Categories/NSArray+Dash.m +++ b/DashSync/shared/Categories/NSArray+Dash.m @@ -68,3 +68,117 @@ - (NSArray *)map:(id (^)(id obj))block { } @end + +@implementation NSArray (_) + ++ (NSArray *)ffi_from_vec:(Vec_ *)ffi_ref { + NSMutableArray *arr = [NSMutableArray arrayWithCapacity:ffi_ref->count]; + for (int i = 0; i < ffi_ref->count; i++) { + [arr addObject:[NSString stringWithUTF8String:ffi_ref->values[i]]]; + } + return arr; +} ++ (Vec_ *)ffi_to_vec:(NSArray *)obj { + NSUInteger count = obj.count; + char **values = malloc(count * sizeof(char *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = strdup([obj[i] UTF8String]); + } + return Vec__ctor(count, values); +} ++ (void)ffi_destroy_vec:(Vec_ *)ffi_ref { + Vec__destroy(ffi_ref); +} +@end + +@implementation NSArray (Vec_String) + ++ (NSArray *)ffi_from_vec_of_string:(Vec_String *)ffi_ref { + NSMutableArray *arr = [NSMutableArray arrayWithCapacity:ffi_ref->count]; + for (int i = 0; i < ffi_ref->count; i++) { + [arr addObject:[NSString stringWithUTF8String:ffi_ref->values[i]]]; + } + return arr; +} ++ (Vec_String *)ffi_to_vec_of_string:(NSArray *)obj { + NSUInteger count = obj.count; + char **values = malloc(count * sizeof(char *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = strdup([obj[i] UTF8String]); + } + return Vec_String_ctor(count, values); +} ++ (void)ffi_destroy_vec_of_string:(Vec_String *)ffi_ref { + Vec_String_destroy(ffi_ref); +} +@end + +@implementation NSArray (Vec_u8_32) + ++ (NSArray *)ffi_from_vec_u256:(Vec_u8_32 *)ffi_ref { + NSMutableArray *arr = [NSMutableArray arrayWithCapacity:ffi_ref->count]; + for (int i = 0; i < ffi_ref->count; i++) { + [arr addObject:NSDataFromPtr(ffi_ref->values[i])]; + } + return arr; +} ++ (Vec_u8_32 *)ffi_to_vec_u256:(NSArray *)obj { + NSUInteger count = obj.count; + u256 **values = malloc(count * sizeof(u256 *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = u256_ctor(obj[i]); + } + return Vec_u8_32_ctor(count, values); +} ++ (void)ffi_destroy_vec_u256:(Vec_u8_32 *)ffi_ref { + Vec_u8_32_destroy(ffi_ref); +} +@end + +@implementation NSArray (Vec_Vec_u8) + ++ (NSArray *)ffi_from_vec_vec_u8:(Vec_Vec_u8 *)ffi_ref { + NSMutableArray *arr = [NSMutableArray arrayWithCapacity:ffi_ref->count]; + for (int i = 0; i < ffi_ref->count; i++) { + [arr addObject:NSDataFromPtr(ffi_ref->values[i])]; + } + return arr; +} ++ (Vec_Vec_u8 *)ffi_to_vec_vec_u8:(NSArray *)obj { + NSUInteger count = obj.count; + Vec_u8 **values = malloc(sizeof(Vec_u8 *) * count); + for (int i = 0; i < count; i++) { + values[i] = bytes_ctor(obj[i]); + } + return Vec_Vec_u8_ctor(count, values); +} ++ (void)ffi_destroy_vec_vec_u8:(Vec_Vec_u8 *)ffi_ref { + Vec_Vec_u8_destroy(ffi_ref); +} +@end + +@implementation NSArray (std_collections_BTreeSet_dashcore_hash_types_BlockHash) + ++ (NSArray *)ffi_from_block_hash_btree_set:(std_collections_BTreeSet_dashcore_hash_types_BlockHash *)ffi_ref { + NSMutableArray *arr = [NSMutableArray arrayWithCapacity:ffi_ref->count]; + for (int i = 0; i < ffi_ref->count; i++) { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->values[i]); + NSData *blockHashData = NSDataFromPtr(block_hash); + u256_dtor(block_hash); + [arr addObject:blockHashData]; + } + return arr; +} ++ (std_collections_BTreeSet_dashcore_hash_types_BlockHash *)ffi_to_block_hash_btree_set:(NSArray *)obj { + NSUInteger count = obj.count; + DBlockHash **values = malloc(sizeof(DBlockHash *) * count); + for (int i = 0; i < count; i++) { + values[i] = dashcore_hash_types_BlockHash_ctor(u256_ctor(obj[i])); + } + return std_collections_BTreeSet_dashcore_hash_types_BlockHash_ctor(count, values); +} ++ (void)ffi_destroy_block_hash_btree_set:(std_collections_BTreeSet_dashcore_hash_types_BlockHash *)ffi_ref { + std_collections_BTreeSet_dashcore_hash_types_BlockHash_destroy(ffi_ref); +} +@end + diff --git a/DashSync/shared/Categories/NSData/NSData+DSHash.m b/DashSync/shared/Categories/NSData/NSData+DSHash.m index a6f8e1294..07f48ffe2 100644 --- a/DashSync/shared/Categories/NSData/NSData+DSHash.m +++ b/DashSync/shared/Categories/NSData/NSData+DSHash.m @@ -23,14 +23,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSKeyManager.h" #import "NSData+DSHash.h" @implementation NSData (DSHash) - (NSData *)blake3Data { - return [DSKeyManager NSDataFrom:processor_blake3(self.bytes, self.length)]; + Slice_u8 *slice = slice_ctor(self); + u256 *result = dash_spv_crypto_blake3(slice); + NSData *data = NSDataFromPtr(result); + u256_dtor(result); + return data; } diff --git a/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.h b/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.h index 59516af76..0b0536136 100644 --- a/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.h +++ b/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.h @@ -15,19 +15,19 @@ // limitations under the License. // -#import "DSDAPIPlatformNetworkServiceProtocol.h" +//#import "DSDAPIPlatformNetworkServiceProtocol.h" #import NS_ASSUME_NONNULL_BEGIN -@class DSPlatformTreeQuery; +//@class DSPlatformTreeQuery; @interface NSData (DSMerkAVLTree) /* executeProofReturnElementDictionary returns items from the proof that match the specific query, if no query is set all items are returned. */ -- (NSData *_Nullable)executeProofReturnElementDictionary:(NSDictionary *_Nonnull *_Nullable)rElementDictionary query:(DSPlatformTreeQuery *_Nullable)query decode:(BOOL)decode usesVersion:(BOOL)usesVersion error:(NSError **)error; +//- (NSData *_Nullable)executeProofReturnElementDictionary:(NSDictionary *_Nonnull *_Nullable)rElementDictionary query:(DSPlatformTreeQuery *_Nullable)query decode:(BOOL)decode usesVersion:(BOOL)usesVersion error:(NSError **)error; @end diff --git a/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.m b/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.m index 5bdc85160..49fe34f8e 100644 --- a/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.m +++ b/DashSync/shared/Categories/NSData/NSData+DSMerkAVLTree.m @@ -15,62 +15,62 @@ // limitations under the License. // -#import "DSPlatformTreeQuery.h" -#import "NSData+DSCborDecoding.h" +//#import "DSPlatformTreeQuery.h" +//#import "NSData+DSCborDecoding.h" #import "NSData+DSMerkAVLTree.h" #import "NSData+Dash.h" #import "NSError+Dash.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" @implementation NSData (DSMerkAVLTree) -- (NSData *)executeProofReturnElementDictionary:(NSDictionary **)rElementDictionary query:(DSPlatformTreeQuery *)query decode:(BOOL)decode usesVersion:(BOOL)usesVersion error:(NSError **)error { - ExecuteProofResult *result; - if (query) { - result = execute_proof_query_keys_c(self.bytes, self.length, query.keys); - } else { - result = execute_proof_c(self.bytes, self.length); - } - if (result == nil) { - return nil; - } - if (result->valid == false) { - destroy_proof_c(result); - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned a non valid proof for our query"]; - return nil; // Even though we have the root hash, there is no reason to return it - } - NSData *rootHash = [NSData dataWithBytes:result->hash length:32]; - NSMutableDictionary *mElementDictionary = [[NSMutableDictionary alloc] initWithCapacity:result->element_count]; - // if there would be more than 255 elements we will get infinite loop here - for (uint8_t i = 0; i < result->element_count; i++) { - Element *element = result->elements[i]; - if (element->exists) { - NSData *value = [NSData dataWithBytes:element->value length:element->value_length]; - NSMutableDictionary *storedItemDictionary = [NSMutableDictionary dictionary]; - if (usesVersion) { - uint32_t version = [value UInt32AtOffset:0]; - value = [value subdataWithRange:NSMakeRange(4, value.length - 4)]; - [storedItemDictionary setObject:@(version) forKey:@(DSPlatformStoredMessage_Version)]; - } - if (decode) { - id documentValue = [value ds_decodeCborError:error]; - if (*error) { - return nil; - } - [storedItemDictionary setObject:documentValue - forKey:@(DSPlatformStoredMessage_Item)]; - [mElementDictionary setObject:[storedItemDictionary copy] forKey:[NSData dataWithBytes:element->key length:element->key_length]]; - } else { - [storedItemDictionary setObject:value forKey:@(DSPlatformStoredMessage_Data)]; - [mElementDictionary setObject:[storedItemDictionary copy] forKey:[NSData dataWithBytes:element->key length:element->key_length]]; - } - } else { - [mElementDictionary setObject:@(DSPlatformStoredMessage_NotPresent) forKey:[NSData dataWithBytes:element->key length:element->key_length]]; - } - } - *rElementDictionary = [mElementDictionary copy]; - destroy_proof_c(result); - return rootHash; -} +//- (NSData *)executeProofReturnElementDictionary:(NSDictionary **)rElementDictionary query:(DSPlatformTreeQuery *)query decode:(BOOL)decode usesVersion:(BOOL)usesVersion error:(NSError **)error { +// ExecuteProofResult *result; +// if (query) { +// result = execute_proof_query_keys_c(self.bytes, self.length, query.keys); +// } else { +// result = execute_proof_c(self.bytes, self.length); +// } +// if (result == nil) { +// return nil; +// } +// if (result->valid == false) { +// destroy_proof_c(result); +// *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned a non valid proof for our query"]; +// return nil; // Even though we have the root hash, there is no reason to return it +// } +// NSData *rootHash = [NSData dataWithBytes:result->hash length:32]; +// NSMutableDictionary *mElementDictionary = [[NSMutableDictionary alloc] initWithCapacity:result->element_count]; +// // if there would be more than 255 elements we will get infinite loop here +// for (uint8_t i = 0; i < result->element_count; i++) { +// Element *element = result->elements[i]; +// if (element->exists) { +// NSData *value = [NSData dataWithBytes:element->value length:element->value_length]; +// NSMutableDictionary *storedItemDictionary = [NSMutableDictionary dictionary]; +// if (usesVersion) { +// uint32_t version = [value UInt32AtOffset:0]; +// value = [value subdataWithRange:NSMakeRange(4, value.length - 4)]; +// [storedItemDictionary setObject:@(version) forKey:@(DSPlatformStoredMessage_Version)]; +// } +// if (decode) { +// id documentValue = [value ds_decodeCborError:error]; +// if (*error) { +// return nil; +// } +// [storedItemDictionary setObject:documentValue +// forKey:@(DSPlatformStoredMessage_Item)]; +// [mElementDictionary setObject:[storedItemDictionary copy] forKey:[NSData dataWithBytes:element->key length:element->key_length]]; +// } else { +// [storedItemDictionary setObject:value forKey:@(DSPlatformStoredMessage_Data)]; +// [mElementDictionary setObject:[storedItemDictionary copy] forKey:[NSData dataWithBytes:element->key length:element->key_length]]; +// } +// } else { +// [mElementDictionary setObject:@(DSPlatformStoredMessage_NotPresent) forKey:[NSData dataWithBytes:element->key length:element->key_length]]; +// } +// } +// *rElementDictionary = [mElementDictionary copy]; +// destroy_proof_c(result); +// return rootHash; +//} @end diff --git a/DashSync/shared/Categories/NSData/NSData+Dash.h b/DashSync/shared/Categories/NSData/NSData+Dash.h index bb9e37585..dc2c44442 100644 --- a/DashSync/shared/Categories/NSData/NSData+Dash.h +++ b/DashSync/shared/Categories/NSData/NSData+Dash.h @@ -246,6 +246,8 @@ size_t chacha20Poly1305AEADDecrypt(void *_Nullable out, size_t outLen, const voi - (BOOL)isSizedForAddress; + (NSData *)scriptPubKeyForAddress:(NSString *)address forChain:(DSChain *)chain; ++ (NSData *)creditBurnScriptPubKeyForAddress:(NSString *)address forChain:(DSChain *)chain; ++ (NSData *)assetLockOutputScript; - (uint64_t)trueBitsCount; diff --git a/DashSync/shared/Categories/NSData/NSData+Dash.m b/DashSync/shared/Categories/NSData/NSData+Dash.m index ae9ddb2a4..3e9294b67 100644 --- a/DashSync/shared/Categories/NSData/NSData+Dash.m +++ b/DashSync/shared/Categories/NSData/NSData+Dash.m @@ -33,27 +33,23 @@ #import "NSMutableData+Dash.h" #import "NSString+Bitcoin.h" #import "NSString+Dash.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" BOOL setKeychainData(NSData *data, NSString *key, BOOL authenticated) { NSCParameterAssert(key); if (!key) return NO; - id accessible = (authenticated) ? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly : (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: SEC_ATTR_SERVICE, (__bridge id)kSecAttrAccount: key}; - if (SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL) == errSecItemNotFound) { if (!data) return YES; - NSDictionary *item = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: SEC_ATTR_SERVICE, (__bridge id)kSecAttrAccount: key, (__bridge id)kSecAttrAccessible: accessible, (__bridge id)kSecValueData: data}; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)item, NULL); - if (status == noErr) return YES; DSLogPrivate(@"SecItemAdd error: %@", [NSError osStatusErrorWithCode:status].localizedDescription); return NO; @@ -61,16 +57,13 @@ BOOL setKeychainData(NSData *data, NSString *key, BOOL authenticated) { if (!data) { OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); - if (status == noErr) return YES; DSLogPrivate(@"SecItemDelete error: %@", [NSError osStatusErrorWithCode:status].localizedDescription); return NO; } - NSDictionary *update = @{(__bridge id)kSecAttrAccessible: accessible, (__bridge id)kSecValueData: data}; OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update); - if (status == noErr) return YES; DSLogPrivate(@"SecItemUpdate error: %@", [NSError osStatusErrorWithCode:status].localizedDescription); return NO; @@ -79,14 +72,12 @@ BOOL setKeychainData(NSData *data, NSString *key, BOOL authenticated) { BOOL hasKeychainData(NSString *key, NSError **error) { NSCParameterAssert(key); if (!key) return NO; - NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: SEC_ATTR_SERVICE, (__bridge id)kSecAttrAccount: key, (__bridge id)kSecReturnRef: @YES}; CFDataRef result = nil; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - if (status == errSecItemNotFound) return NO; if (status == noErr) return YES; DSLogPrivate(@"SecItemCopyMatching error: %@", [NSError osStatusErrorWithCode:status].localizedDescription); @@ -101,7 +92,6 @@ BOOL hasKeychainData(NSString *key, NSError **error) { (__bridge id)kSecReturnData: @YES}; CFDataRef result = nil; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - if (status == errSecItemNotFound) return nil; if (status == noErr) return CFBridgingRelease(result); DSLogPrivate(@"SecItemCopyMatching error: %@", [NSError osStatusErrorWithCode:status].localizedDescription); @@ -112,7 +102,6 @@ BOOL hasKeychainData(NSString *key, NSError **error) { BOOL setKeychainInt(int64_t i, NSString *key, BOOL authenticated) { @autoreleasepool { NSMutableData *d = [NSMutableData secureDataWithLength:sizeof(int64_t)]; - *(int64_t *)d.mutableBytes = i; return setKeychainData(d, key, authenticated); } @@ -121,7 +110,6 @@ BOOL setKeychainInt(int64_t i, NSString *key, BOOL authenticated) { int64_t getKeychainInt(NSString *key, NSError **error) { @autoreleasepool { NSData *d = getKeychainData(key, error); - return (d.length == sizeof(int64_t)) ? *(int64_t *)d.bytes : 0; } } @@ -131,7 +119,6 @@ BOOL setKeychainString(NSString *s, NSString *key, BOOL authenticated) { NSData *d = (s) ? CFBridgingRelease(CFStringCreateExternalRepresentation(SecureAllocator(), (CFStringRef)s, kCFStringEncodingUTF8, 0)) : nil; - return setKeychainData(d, key, authenticated); } } @@ -139,7 +126,6 @@ BOOL setKeychainString(NSString *s, NSString *key, BOOL authenticated) { NSString *getKeychainString(NSString *key, NSError **error) { @autoreleasepool { NSData *d = getKeychainData(key, error); - return (d) ? CFBridgingRelease(CFStringCreateFromExternalRepresentation(SecureAllocator(), (CFDataRef)d, kCFStringEncodingUTF8)) : nil; @@ -149,32 +135,27 @@ BOOL setKeychainString(NSString *s, NSString *key, BOOL authenticated) { BOOL setKeychainDict(NSDictionary *dict, NSString *key, BOOL authenticated) { @autoreleasepool { NSData *d = (dict) ? [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:NO error:nil] : nil; - return setKeychainData(d, key, authenticated); } } NSDictionary *getKeychainDict(NSString *key, NSArray *classes, NSError **error) { - //@autoreleasepool { - NSData *d = getKeychainData(key, error); - if (d == nil) return nil; - NSSet *set = [NSSet setWithArray:@[ - [NSDictionary class], - [NSMutableDictionary class] - ]]; - set = [set setByAddingObjectsFromArray:classes]; - NSDictionary *dictionary = [NSKeyedUnarchiver unarchivedObjectOfClasses:set fromData:d error:error]; - if (*error) { - DSLogPrivate(@"error retrieving dictionary from keychain %@", *error); + @autoreleasepool { + NSData *d = getKeychainData(key, error); + if (d == nil) return nil; + NSSet *set = [NSSet setWithArray:@[[NSDictionary class], [NSMutableDictionary class]]]; + set = [set setByAddingObjectsFromArray:classes]; + NSDictionary *dictionary = [NSKeyedUnarchiver unarchivedObjectOfClasses:set fromData:d error:error]; + if (*error) { + DSLogPrivate(@"error retrieving dictionary from keychain %@", *error); + } + return dictionary; } - return dictionary; - //} } BOOL setKeychainArray(NSArray *array, NSString *key, BOOL authenticated) { @autoreleasepool { NSData *d = (array) ? [NSKeyedArchiver archivedDataWithRootObject:array requiringSecureCoding:NO error:nil] : nil; - return setKeychainData(d, key, authenticated); } } @@ -199,7 +180,6 @@ BOOL setKeychainArray(NSArray *array, NSString *key, BOOL authenticated) { NSOrderedSet *getKeychainOrderedSet(NSString *key, NSError **error) { @autoreleasepool { NSData *d = getKeychainData(key, error); - return (d) ? [NSKeyedUnarchiver unarchivedObjectOfClass:[NSOrderedSet class] fromData:d error:nil] : nil; } } @@ -207,7 +187,6 @@ BOOL setKeychainArray(NSArray *array, NSString *key, BOOL authenticated) { BOOL setKeychainOrderedSet(NSOrderedSet *orderedSet, NSString *key, BOOL authenticated) { @autoreleasepool { NSData *d = (orderedSet) ? [NSKeyedArchiver archivedDataWithRootObject:orderedSet requiringSecureCoding:NO error:nil] : nil; - return setKeychainData(d, key, authenticated); } } @@ -1594,6 +1573,18 @@ + (NSData *)scriptPubKeyForAddress:(NSString *)address forChain:(DSChain *)chain [data appendScriptPubKeyForAddress:address forChain:chain]; return [data copy]; } ++ (NSData *)assetLockOutputScript { + NSMutableData *data = [NSMutableData data]; + [data appendUInt8:OP_RETURN]; + [data appendUInt8:0]; + return [data copy]; +} + ++ (NSData *)creditBurnScriptPubKeyForAddress:(NSString *)address forChain:(DSChain *)chain { + NSMutableData *script = [NSMutableData data]; + [script appendCreditBurnScriptPubKeyForAddress:address forChain:chain]; + return [script copy]; +} + (NSData *)merkleRootFromHashes:(NSArray *)hashes { NSMutableArray *higherLevel = [NSMutableArray array]; diff --git a/DashSync/shared/Categories/NSData/NSMutableData+Dash.m b/DashSync/shared/Categories/NSData/NSMutableData+Dash.m index 4a79f9667..9fb5a2bbe 100644 --- a/DashSync/shared/Categories/NSData/NSMutableData+Dash.m +++ b/DashSync/shared/Categories/NSData/NSMutableData+Dash.m @@ -26,7 +26,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "DSChain.h" +#import "DSChain+Params.h" #import "NSData+DSHash.h" #import "NSMutableData+Dash.h" #import "NSString+Dash.h" diff --git a/DashSync/shared/Categories/NSDate+Utils.h b/DashSync/shared/Categories/NSDate+Utils.h index 2d4eee56a..082812692 100644 --- a/DashSync/shared/Categories/NSDate+Utils.h +++ b/DashSync/shared/Categories/NSDate+Utils.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN @interface NSDate (Utils) + (NSTimeInterval)timeIntervalSince1970; ++ (NSTimeInterval)timeIntervalSince1970Minus:(NSTimeInterval)interval; ++ (NSTimeInterval)timeIntervalSince1970MinusHour; @end diff --git a/DashSync/shared/Categories/NSDate+Utils.m b/DashSync/shared/Categories/NSDate+Utils.m index 300e0f9b1..eed418e5d 100644 --- a/DashSync/shared/Categories/NSDate+Utils.m +++ b/DashSync/shared/Categories/NSDate+Utils.m @@ -12,5 +12,11 @@ @implementation NSDate (Utils) + (NSTimeInterval)timeIntervalSince1970 { return [[NSDate date] timeIntervalSince1970]; } ++ (NSTimeInterval)timeIntervalSince1970Minus:(NSTimeInterval)interval { + return [NSDate timeIntervalSince1970] - interval; +} ++ (NSTimeInterval)timeIntervalSince1970MinusHour { + return [NSDate timeIntervalSince1970] - 3600; +} @end diff --git a/DashSync/shared/Categories/NSIndexPath+Dash.m b/DashSync/shared/Categories/NSIndexPath+Dash.m index ffb93547c..b11e1cc82 100644 --- a/DashSync/shared/Categories/NSIndexPath+Dash.m +++ b/DashSync/shared/Categories/NSIndexPath+Dash.m @@ -7,7 +7,7 @@ #import "DSDerivationPath.h" #import "NSIndexPath+Dash.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" @implementation NSIndexPath (Dash) diff --git a/DashSync/shared/Categories/NSIndexPath+FFI.h b/DashSync/shared/Categories/NSIndexPath+FFI.h index a249373a6..e3069b04d 100644 --- a/DashSync/shared/Categories/NSIndexPath+FFI.h +++ b/DashSync/shared/Categories/NSIndexPath+FFI.h @@ -16,15 +16,15 @@ // #import -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" NS_ASSUME_NONNULL_BEGIN -@interface NSIndexPath (FFI) - -- (IndexPathData *)ffi_malloc; -+ (void)ffi_free:(IndexPathData *)entry; +@interface NSIndexPath (Vec_u32) ++ (NSIndexPath *)ffi_from:(Vec_u32 *)ffi_ref; ++ (Vec_u32 *)ffi_to:(NSIndexPath *)obj; ++ (void)ffi_destroy:(Vec_u32 *)ffi_ref; @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Categories/NSIndexPath+FFI.m b/DashSync/shared/Categories/NSIndexPath+FFI.m index 9580cc859..807a7130e 100644 --- a/DashSync/shared/Categories/NSIndexPath+FFI.m +++ b/DashSync/shared/Categories/NSIndexPath+FFI.m @@ -17,22 +17,22 @@ #import "NSIndexPath+FFI.h" -@implementation NSIndexPath (FFI) +@implementation NSIndexPath (Vec_u32) -- (IndexPathData *)ffi_malloc { - IndexPathData *obj = malloc(sizeof(IndexPathData)); - NSUInteger *indexes = calloc(self.length, sizeof(NSUInteger)); - [self getIndexes:indexes]; - obj->indexes = indexes; - obj->len = self.length; - return obj; ++ (NSIndexPath *)ffi_from:(Vec_u32 *)ffi_ref { + return [NSIndexPath indexPathWithIndexes:(NSUInteger *) ffi_ref->values length:ffi_ref->count]; } -+ (void)ffi_free:(IndexPathData *)entry { - if (entry->len > 0) { - free((void *) entry->indexes); ++ (Vec_u32 *)ffi_to:(NSIndexPath *)obj { + NSUInteger length = obj.length; + uint32_t *indexes = malloc(sizeof(uint32_t) * length); + for (NSUInteger i = 0; i < length; i++) { + indexes[i] = (uint32_t)[obj indexAtPosition:i]; } - free(entry); + return Vec_u32_ctor(length, indexes); + +} ++ (void)ffi_destroy:(Vec_u32 *)ffi_ref { + Vec_u32_destroy(ffi_ref); } - @end diff --git a/DashSync/shared/Categories/NSMutableArray+Dash.h b/DashSync/shared/Categories/NSMutableArray+Dash.h index 63fbd66df..818145b8c 100644 --- a/DashSync/shared/Categories/NSMutableArray+Dash.h +++ b/DashSync/shared/Categories/NSMutableArray+Dash.h @@ -15,6 +15,7 @@ // limitations under the License. // + #import NS_ASSUME_NONNULL_BEGIN diff --git a/DashSync/shared/Models/DAPI/DashPlatformProtocol+DashSync.m b/DashSync/shared/Categories/NSObject+Notification.h similarity index 69% rename from DashSync/shared/Models/DAPI/DashPlatformProtocol+DashSync.m rename to DashSync/shared/Categories/NSObject+Notification.h index d27390247..3d2b3e1e6 100644 --- a/DashSync/shared/Models/DAPI/DashPlatformProtocol+DashSync.m +++ b/DashSync/shared/Categories/NSObject+Notification.h @@ -1,6 +1,6 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 Dash Core Group. All rights reserved. +// +// Created by Vladimir Pirogov +// Copyright © 2025 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. @@ -15,11 +15,14 @@ // limitations under the License. // -#import "DSDashPlatform.h" +#import NS_ASSUME_NONNULL_BEGIN -@implementation DSDashPlatform (DashSync) +@interface NSObject (Notification) + +- (void)notify:(NSNotificationName)name + userInfo:(NSDictionary *_Nullable)userInfo; @end diff --git a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataProperties.m b/DashSync/shared/Categories/NSObject+Notification.m similarity index 54% rename from DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataProperties.m rename to DashSync/shared/Categories/NSObject+Notification.m index b8589a73d..a516072bb 100644 --- a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataProperties.m +++ b/DashSync/shared/Categories/NSObject+Notification.m @@ -1,6 +1,6 @@ // // Created by Vladimir Pirogov -// Copyright © 2022 Dash Core Group. All rights reserved. +// Copyright © 2025 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. @@ -15,17 +15,17 @@ // limitations under the License. // -#import "DSQuorumSnapshotEntity+CoreDataProperties.h" +#import "NSObject+Notification.h" -@implementation DSQuorumSnapshotEntity (CoreDataProperties) +@implementation NSObject (Notification) -+ (NSFetchRequest *)fetchRequest { - return [NSFetchRequest fetchRequestWithEntityName:@"DSQuorumSnapshotEntity"]; +- (void)notify:(NSNotificationName)name + userInfo:(NSDictionary *_Nullable)userInfo { + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:name + object:nil + userInfo:userInfo]; + }); } -@dynamic block; -@dynamic memberList; -@dynamic skipList; -@dynamic skipListMode; - @end diff --git a/DashSync/shared/Categories/NSSet+Dash.h b/DashSync/shared/Categories/NSSet+Dash.h index b494a2862..8b833a6a6 100644 --- a/DashSync/shared/Categories/NSSet+Dash.h +++ b/DashSync/shared/Categories/NSSet+Dash.h @@ -16,7 +16,7 @@ // #import - +#import "DSKeyManager.h" NS_ASSUME_NONNULL_BEGIN @interface NSSet (Dash) @@ -27,4 +27,13 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface NSSet (Vec_u8_32) + ++ (NSSet *)ffi_from_vec_u256:(Vec_u8_32 *)ffi_ref; ++ (Vec_u8_32 *)ffi_to_vec_u256:(NSSet *)obj; ++ (void)ffi_destroy_vec_u256:(Vec_u8_32 *)ffi_ref; +@end + + + NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Categories/NSSet+Dash.m b/DashSync/shared/Categories/NSSet+Dash.m index 8f4a85dd1..dc78a38c3 100644 --- a/DashSync/shared/Categories/NSSet+Dash.m +++ b/DashSync/shared/Categories/NSSet+Dash.m @@ -51,3 +51,27 @@ - (NSSet *)filter:(BOOL (^)(id obj))block { } @end + +@implementation NSSet (Vec_u8_32) + ++ (NSSet *)ffi_from_vec_u256:(Vec_u8_32 *)ffi_ref { + NSMutableSet *arr = [NSMutableSet set]; + for (int i = 0; i < ffi_ref->count; i++) { + [arr addObject:NSDataFromPtr(ffi_ref->values[i])]; + } + return arr; +} ++ (Vec_u8_32 *)ffi_to_vec_u256:(NSSet *)obj { + NSArray *arr = [obj allObjects]; + NSUInteger count = arr.count; + u256 **values = malloc(count * sizeof(u256 *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = u256_ctor(arr[i]); + } + return Vec_u8_32_ctor(count, values); +} ++ (void)ffi_destroy_vec_u256:(Vec_u8_32 *)ffi_ref { + Vec_u8_32_destroy(ffi_ref); +} +@end + diff --git a/DashSync/shared/Categories/NSString+Bitcoin.m b/DashSync/shared/Categories/NSString+Bitcoin.m index b48e2f8a2..bd178cc65 100644 --- a/DashSync/shared/Categories/NSString+Bitcoin.m +++ b/DashSync/shared/Categories/NSString+Bitcoin.m @@ -26,7 +26,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "DSChain.h" +#import "DSChain+Params.h" #import "NSData+Dash.h" #import "NSMutableData+Dash.h" #import "NSString+Bitcoin.h" diff --git a/DashSync/shared/Categories/NSString+Dash.m b/DashSync/shared/Categories/NSString+Dash.m index a9dd9e8cd..efb8af32b 100644 --- a/DashSync/shared/Categories/NSString+Dash.m +++ b/DashSync/shared/Categories/NSString+Dash.m @@ -26,7 +26,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "DSChain.h" +#import "DSChain+Params.h" #import "DSDerivationPath.h" #import "DSPriceManager.h" #import "NSData+DSHash.h" diff --git a/DashSync/shared/DSDashSharedCore.h b/DashSync/shared/DSDashSharedCore.h new file mode 100644 index 000000000..d420475f7 --- /dev/null +++ b/DashSync/shared/DSDashSharedCore.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 "dash_spv_apple_bindings.h" +#import "DSChain.h" + +NS_ASSUME_NONNULL_BEGIN + +#define DProcessor MasternodeProcessor +#define DArcProcessor std_sync_Arc_dash_spv_masternode_processor_processing_processor_MasternodeProcessor +#define DArcPlatformSDK std_sync_Arc_dash_spv_platform_PlatformSDK +#define DArcIdentitiesManager std_sync_Arc_dash_spv_platform_identity_manager_IdentitiesManager +#define DArcContractsManager std_sync_Arc_dash_spv_platform_contract_manager_ContractsManager +#define DArcDocumentsManager std_sync_Arc_dash_spv_platform_document_manager_DocumentsManager +#define DArcContactRequestManager std_sync_Arc_dash_spv_platform_document_contact_request_ContactRequestManager +#define DSaltedDomainHashesManager std_sync_Arc_dash_spv_platform_document_salted_domain_hashes_SaltedDomainHashesManager +#define DUsernamesManager std_sync_Arc_dash_spv_platform_document_usernames_UsernamesManager + +@class DSChain; + +@interface DSDashSharedCore : NSObject + +- (instancetype)initOnChain:(DSChain *)chain; + +- (DArcProcessor *)processor; +- (DArcPlatformSDK *)platform; +- (Runtime *)runtime; +- (DArcIdentitiesManager *)identitiesManager; +- (DArcContractsManager *)contractsManager; +- (DArcDocumentsManager *)documentsManager; +- (DArcContactRequestManager *)contactRequests; +- (DSaltedDomainHashesManager *)saltedDomainHashes; +- (DUsernamesManager *)usernames; + +@property (nonatomic, readonly) BOOL hasMasternodeListCurrentlyBeingSaved; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/DSDashSharedCore.m b/DashSync/shared/DSDashSharedCore.m new file mode 100644 index 000000000..a6601099d --- /dev/null +++ b/DashSync/shared/DSDashSharedCore.m @@ -0,0 +1,319 @@ +// +// 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 "DSBlock.h" +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChain+Identity.h" +#import "DSChain+Wallet.h" +#import "DSChainLock.h" +#import "DSChainManager+Protected.h" +#import "DSDashSharedCore.h" +#import "DSIdentity+Protected.h" +#import "DSKeyManager.h" +#import "DSMasternodeListService.h" +#import "DSMasternodeListDiffService.h" +#import "DSQuorumRotationService.h" +#import "DSMasternodeManager+Protected.h" +#import "NSArray+Dash.h" + +@class DSPeer; + +#define AS_OBJC(context) ((__bridge DSDashSharedCore *)(context)) +#define AS_RUST(context) ((__bridge void *)(context)) + +#define GetDataContract Fn_ARGS_std_os_raw_c_void_platform_value_types_identifier_Identifier_RTRN_Result_ok_Option_std_sync_Arc_dpp_data_contract_DataContract_err_drive_proof_verifier_error_ContextProviderError + +#define SignerCallback Fn_ARGS_std_os_raw_c_void_dpp_identity_identity_public_key_IdentityPublicKey_Vec_u8_RTRN_Result_ok_platform_value_types_binary_data_BinaryData_err_dpp_errors_protocol_error_ProtocolError +#define GetPlatformActivationHeight Fn_ARGS_std_os_raw_c_void_RTRN_Result_ok_dpp_prelude_CoreBlockHeight_err_drive_proof_verifier_error_ContextProviderError + +#define CanSign Fn_ARGS_std_os_raw_c_void_dpp_identity_identity_public_key_IdentityPublicKey_RTRN_bool + +#define GetBlockHeightByHash Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_u32 +#define GetBlockHashByHeight Fn_ARGS_std_os_raw_c_void_u32_RTRN_Option_u8_32 + +#define MerkleBlockByBlockHash Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_Result_ok_dash_spv_masternode_processor_common_block_MBlock_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define LastMerkleBlockByBlockHashForPeer Fn_ARGS_std_os_raw_c_void_Arr_u8_32_std_os_raw_c_void_RTRN_Result_ok_dash_spv_masternode_processor_common_block_MBlock_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError + + +#define AddInsight Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_ + +#define HasPersistInRetrieval Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_bool +#define GetBlockHeightOrLastTerminal Fn_ARGS_std_os_raw_c_void_u32_RTRN_Result_ok_dash_spv_masternode_processor_common_block_Block_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError + +#define FnMaybeCLSignature Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_Result_ok_dashcore_bls_sig_utils_BLSSignature_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define DMaybeCLSignature Result_ok_dashcore_bls_sig_utils_BLSSignature_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define DMaybeCLSignatureCtor(ok, err) Result_ok_dashcore_bls_sig_utils_BLSSignature_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError_ctor(ok, err) +#define LoadMasternodeList Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_Result_ok_dashcore_sml_masternode_list_MasternodeList_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define SaveMasternodeList Fn_ARGS_std_os_raw_c_void_Arr_u8_32_std_collections_Map_keys_u8_arr_32_values_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry_RTRN_Result_ok_bool_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define LoadLLMQSnapshot Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_Result_ok_dashcore_network_message_qrinfo_QuorumSnapshot_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define SaveLLMQSnapshot Fn_ARGS_std_os_raw_c_void_Arr_u8_32_dashcore_network_message_qrinfo_QuorumSnapshot_RTRN_Result_ok_bool_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError + +#define UpdateMasternodesAddressUsage Fn_ARGS_std_os_raw_c_void_Vec_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry_RTRN_ + +#define IssueWithMasternodeList Fn_ARGS_std_os_raw_c_void_bool_std_os_raw_c_void_RTRN_ +#define NotifySyncState Fn_ARGS_std_os_raw_c_void_dash_spv_masternode_processor_models_sync_state_CacheState_RTRN_ + +@interface DSDashSharedCore () + +@property (nonatomic) DSChain *chain; +@property (nonatomic, assign) DashSPVCore *core; +@property (nonatomic, strong) NSMutableDictionary *devnetSharedCoreDictionary; + +@end + +@implementation DSDashSharedCore + ++ (instancetype)sharedCore { + static DSDashSharedCore *_sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = [[self alloc] init]; + }); + return _sharedInstance; +} + +- (DArcProcessor *)processor { + return dash_spv_apple_bindings_DashSPVCore_processor(self.core); +} + +- (DArcPlatformSDK *)platform { + return dash_spv_apple_bindings_DashSPVCore_platform(self.core); +} +- (Runtime *)runtime { + return dash_spv_apple_bindings_DashSPVCore_runtime(self.core); +} + +- (DArcIdentitiesManager *)identitiesManager { + return dash_spv_platform_PlatformSDK_identity_manager(self.platform->obj); +} +- (DArcContractsManager *)contractsManager { + return dash_spv_platform_PlatformSDK_contract_manager(self.platform->obj); +} +- (DArcDocumentsManager *)documentsManager { + return dash_spv_platform_PlatformSDK_doc_manager(self.platform->obj); +} +- (DArcContactRequestManager *)contactRequests { + return dash_spv_platform_PlatformSDK_contact_requests(self.platform->obj); +} +- (DSaltedDomainHashesManager *)saltedDomainHashes { + return dash_spv_platform_PlatformSDK_salted_domain_hashes(self.platform->obj); +} + +- (DUsernamesManager *)usernames { + return dash_spv_platform_PlatformSDK_usernames(self.platform->obj); +} + +- (instancetype)initOnChain:(DSChain *)chain { + if (!(self = [super init])) return nil; + self.chain = chain; + const void *context = AS_RUST(self); + GetDataContract get_data_contract = { + .caller = &get_data_contract_caller, + .destructor = &get_data_contract_dtor + }; + SignerCallback callback_signer = { + .caller = &callback_signer_caller, + .destructor = &callback_signer_dtor + }; + GetPlatformActivationHeight get_platform_activation_height = { + .caller = &get_platform_activation_height_caller, + .destructor = &get_platform_activation_height_dtor + }; + CanSign callback_can_sign = { + .caller = &callback_can_sign_caller, + .destructor = &callback_can_sign_dtor + }; + GetBlockHeightByHash get_block_height_by_hash = { + .caller = &get_block_height_by_hash_caller, + .destructor = &get_block_height_by_hash_dtor + }; + GetBlockHashByHeight get_block_hash_by_height = { + .caller = &get_block_hash_by_height_caller, + .destructor = &get_block_hash_by_height_dtor + }; + UpdateMasternodesAddressUsage update_address_usage_of_masternodes = { + .caller = &update_address_usage_of_masternodes_caller + }; + IssueWithMasternodeList issue_with_masternode_list_from_peer = { + .caller = &issue_with_masternode_list_from_peer_caller + }; + FnMaybeCLSignature get_cl_signature_by_block_hash = { + .caller = &get_cl_signature_by_block_hash_caller, + .destructor = &get_cl_signature_by_block_hash_dtor + }; + NotifySyncState notify_sync_state = { + .caller = ¬ify_sync_state_caller, + }; + + NSArray *addresses = @[@"127.0.0.1"]; + switch (chain.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + addresses = @[ + @"149.28.241.190", @"216.238.75.46", @"134.255.182.186", @"66.245.196.52", @"178.157.91.186", @"157.66.81.162", @"213.199.34.250", @"157.90.238.161", @"5.182.33.231", @"185.198.234.68", @"37.60.236.212", @"207.244.247.40", @"45.32.70.131", @"158.220.122.76", @"52.33.9.172", @"185.158.107.124", @"185.198.234.17", @"93.190.140.101", @"194.163.153.225", @"194.146.13.7", @"93.190.140.112", @"75.119.132.2", @"65.108.74.95", @"44.240.99.214", @"5.75.133.148", @"192.248.178.237", @"95.179.159.65", @"139.84.232.129", @"37.60.243.119", @"194.195.87.34", @"46.254.241.7", @"45.77.77.195", @"65.108.246.145", @"64.176.10.71", @"158.247.247.241", @"37.60.244.220", @"2.58.82.231", @"139.180.143.115", @"185.198.234.54", @"213.199.44.112", @"37.27.67.154", @"134.255.182.185", @"86.107.168.28", @"139.84.137.143", @"173.212.239.124", @"157.10.199.77", @"5.189.186.78", @"139.84.170.10", @"173.249.53.139", @"37.60.236.151", @"37.27.67.159", @"104.200.24.196", @"37.60.236.225", @"172.104.90.249", @"57.128.212.163", @"37.60.236.249", @"158.220.122.74", @"185.198.234.25", @"148.113.201.221", @"134.255.183.250", @"185.192.96.70", @"134.255.183.248", @"52.36.102.91", @"134.255.183.247", @"49.13.28.255", @"168.119.102.10", @"86.107.168.44", @"49.13.237.193", @"37.27.83.17", @"134.255.182.187", @"142.132.165.149", @"193.203.15.209", @"38.242.198.100", @"192.175.127.198", @"37.27.67.163", @"79.137.71.84", @"198.7.115.43", @"70.34.206.123", @"163.172.20.205", @"65.108.74.78", @"108.61.165.170", @"157.10.199.79", @"31.220.88.116", @"185.166.217.154", @"37.27.67.164", @"31.220.85.180", @"161.97.170.251", @"157.10.199.82", @"91.107.226.241", @"167.88.169.16", @"216.238.99.9", @"62.169.17.112", @"52.10.213.198", @"149.28.201.164", @"198.7.115.38", @"37.60.236.161", @"49.13.193.251", @"46.254.241.9", @"65.108.74.75", @"192.99.44.64", @"95.179.241.182", @"95.216.146.18", @"185.194.216.84", @"31.220.84.93", @"185.197.250.227", @"149.28.247.165", @"86.107.168.29", @"213.199.34.251", @"108.160.135.149", @"185.198.234.12", @"87.228.24.64", @"45.32.52.10", @"91.107.204.136", @"64.176.35.235", @"167.179.90.255", @"157.66.81.130", @"157.10.199.125", @"46.254.241.8", @"49.12.102.105", @"134.255.182.189", @"81.17.101.141", @"65.108.74.79", @"64.23.134.67", @"54.69.95.118", @"158.220.122.13", @"49.13.154.121", @"75.119.149.9", @"93.190.140.111", @"93.190.140.114", @"195.201.238.55", @"135.181.110.216", @"45.76.141.74", @"65.21.145.147", @"50.116.28.103", @"188.245.90.255", @"130.162.233.186", @"65.109.65.126", @"188.208.196.183", @"178.157.91.184", @"37.60.236.201", @"95.179.139.125", @"213.199.34.248", @"178.157.91.178", @"213.199.35.18", @"213.199.35.6", @"37.60.243.59", @"37.27.67.156", @"37.60.236.247", @"159.69.204.162", @"46.254.241.11", @"173.199.71.83", @"185.215.166.126", @"91.234.35.132", @"157.66.81.218", @"213.199.35.15", @"114.132.172.215", @"93.190.140.162", @"65.108.74.109" + ]; + + break; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + addresses = @[@"35.165.50.126", @"52.42.202.128", @"52.12.176.90", @"44.233.44.95", @"35.167.145.149", @"52.34.144.50", @"44.240.98.102"]; + break; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: + break; + + default: + break; + } + Vec_ *address_list = [NSArray ffi_to_vec:addresses]; + + self.core = dash_spv_apple_bindings_DashSPVCore_with_callbacks(chain.chainType, address_list, get_data_contract, get_platform_activation_height, callback_signer, callback_can_sign, get_block_height_by_hash, get_block_hash_by_height, get_cl_signature_by_block_hash, update_address_usage_of_masternodes, issue_with_masternode_list_from_peer, notify_sync_state, context); + return self; +} + + + +- (void)dealloc { + if (_core != NULL) { + dash_spv_apple_bindings_DashSPVCore_destroy(_core); + _core = NULL; + } +} + +MaybeDataContract *get_data_contract_caller(const void *context, DIdentifier *identitifier) { + // TODO: implement it + return NULL; +} +void get_data_contract_dtor(MaybeDataContract *result) {} + +MaybeSignedData *callback_signer_caller(const void *context, DIdentityPublicKey *identity_public_key, Vec_u8 *data) { + DSDashSharedCore *core = AS_OBJC(context); + DBinaryData *ok = NULL; + dpp_errors_protocol_error_ProtocolError *error = NULL; + NSData *dataToSign = NSDataFromPtr(data); + DSLog(@"[SDK] callback_signer: identity_public_key: %p, data: %@", identity_public_key, dataToSign.hexString); + DMaybeOpaqueKey *maybe_key = [core.chain identityPrivateKeyForIdentityPublicKey:identity_public_key]; + if (!maybe_key || maybe_key->error) { + error = dpp_errors_protocol_error_ProtocolError_Generic_ctor(DSLocalizedChar(@"Can't find a signer for identity public key: %p", nil, identity_public_key)); + } else { + ok = DBinaryDataCtor(DOpaqueKeyHashAndSign(maybe_key->ok, data)); + } + DMaybeOpaqueKeyDtor(maybe_key); + dpp_identity_identity_public_key_IdentityPublicKey_destroy(identity_public_key); + bytes_dtor(data); + return Result_ok_platform_value_types_binary_data_BinaryData_err_dpp_errors_protocol_error_ProtocolError_ctor(ok, error); + +} +void callback_signer_dtor(MaybeSignedData *result) {} + +MaybePlatformActivationHeight *get_platform_activation_height_caller(const void *context) { + return NULL; +} +void get_platform_activation_height_dtor(MaybePlatformActivationHeight *result) {} + +bool callback_can_sign_caller(const void *context, DIdentityPublicKey *identity_public_key) { + // TODO: impl + return TRUE; +} +void callback_can_sign_dtor(bool result) {} + +uint32_t get_block_height_by_hash_caller(const void *context, u256 *block_hash) { + DSDashSharedCore *core = AS_OBJC(context); + UInt256 blockHash = u256_cast(block_hash); + uint32_t height = [core.chain heightForBlockHash:blockHash]; + + if (height == UINT32_MAX && core.chain.allowInsightBlocksForVerification) { + [core.chain blockUntilGetInsightForBlockHash:blockHash]; + height = [[core.chain insightVerifiedBlocksByHashDictionary] objectForKey:NSDataFromPtr(block_hash)].height; + } + u256_dtor(block_hash); + DSLog(@"[SDK] get_block_height_by_hash_caller: %@ = %u", uint256_hex(blockHash), height); + return height; +} +void get_block_height_by_hash_dtor(uint32_t result) {} + +u256 *get_block_hash_by_height_caller(const void *context, uint32_t block_height) { + DSDashSharedCore *core = AS_OBJC(context); + DSBlock *block = NULL; + @synchronized (context) { + block = (DSBlock *) [core.chain blockAtHeight:block_height]; + if (!block && core.chain.allowInsightBlocksForVerification) + block = [core.chain blockUntilGetInsightForBlockHeight:block_height]; + } + // DSLog(@"[SDK] get_block_hash_by_height_caller: %u = %@", block_height, uint256_hex(block.blockHash)); + UInt256 blockHash = block ? block.blockHash : UINT256_ZERO; + return u256_ctor_u(blockHash); +} + +void get_block_hash_by_height_dtor(u256 *result) {} + +DMaybeCLSignature *get_cl_signature_by_block_hash_caller(const void *context, u256 *block_hash) { + DSDashSharedCore *core = AS_OBJC(context); + UInt256 blockHash = u256_cast(block_hash); + UInt256 blockHashRev = uint256_reverse(blockHash); + u256_dtor(block_hash); + DSChainLock *chainLock = [core.chain.chainManager chainLockForBlockHash:blockHash]; + DSChainLock *chainLockRev = [core.chain.chainManager chainLockForBlockHash:blockHashRev]; + if (chainLock) { + DBLSSignature *bls_sig = DChainLockSignature(chainLock.lock); + DSLog(@"[SDK] get_cl_signature_by_block_hash_caller: %@ = %@", uint256_hex(blockHash), u768_hex(bls_sig->_0)); + return DMaybeCLSignatureCtor(bls_sig, NULL); + } else if (chainLockRev) { + DBLSSignature *bls_sig = DChainLockSignature(chainLockRev.lock); + DSLog(@"[SDK] get_cl_signature_by_block_hash_caller: %@ = %@", uint256_hex(blockHashRev), u768_hex(bls_sig->_0)); + return DMaybeCLSignatureCtor(bls_sig, NULL); + } else { + DSLog(@"[SDK] get_cl_signature_by_block_hash_caller: %@ = None", uint256_hex(blockHash)); + return DMaybeCLSignatureCtor(NULL, DCoreProviderErrorNullResultCtor(DSLocalizedChar(@"No clsig for block hash %@", nil, uint256_hex(blockHash)))); + } +} +void get_cl_signature_by_block_hash_dtor(DMaybeCLSignature *result) {} + + +void update_address_usage_of_masternodes_caller(const void *context, DMasternodeEntryList *masternodes) { + DSDashSharedCore *core = AS_OBJC(context); + [core.chain updateAddressUsageOfSimplifiedMasternodeEntries:masternodes]; + DMasternodeEntryListDtor(masternodes); +} + +void issue_with_masternode_list_from_peer_caller(const void *context, bool is_dip24, const void *peer_context) { + DSDashSharedCore *core = AS_OBJC(context); + DSPeer *peer = ((__bridge DSPeer *)(peer_context)); + [core.chain.masternodeManager issueWithMasternodeListFromPeer:peer]; +} + +void notify_sync_state_caller(const void *context, DMNSyncState *state) { + DSDashSharedCore *core = AS_OBJC(context); + DSMasternodeListSyncState *syncInfo = core.chain.chainManager.syncState.masternodeListSyncInfo; + @synchronized (syncInfo) { + [syncInfo updateWithSyncState:state]; + switch (state->tag) { + case DMNSyncStateQueueChanged: + DSLog(@"[%@] Masternode list queue updated: %lu/%lu", core.chain.name, state->queue_changed.count, state->queue_changed.max_amount); + break; + case DMNSyncStateStoreChanged: + DSLog(@"[%@] Masternode list store updated: %lu/%u", core.chain.name, state->store_changed.count, state->store_changed.last_block_height); + break; + case DMNSyncStateStubCount: + DSLog(@"[%@] Masternode list DB updated: %lu", core.chain.name, state->stub_count.count); + default: + break; + } + DMNSyncStateDtor(state); + [core.chain.chainManager notifySyncStateChanged]; + } +} + +@end diff --git a/DashSync/shared/DashSync.h b/DashSync/shared/DashSync.h index 7f28d2347..a2e362438 100644 --- a/DashSync/shared/DashSync.h +++ b/DashSync/shared/DashSync.h @@ -6,18 +6,23 @@ // Copyright © 2019 dashcore. All rights reserved. // -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSError.h" -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" #import "DSChain.h" +#import "DSChain+Identity.h" +#import "DSChain+Params.h" +#import "DSChain+Wallet.h" +#import "DSChain+Transaction.h" #import "DSCheckpoint.h" +#import "DSCoinJoinManager.h" #import "DSEnvironment.h" #import "DSPeerManager.h" #import "DSReachabilityManager.h" #import "DSAuthenticationKeysDerivationPath.h" -#import "DSCreditFundingDerivationPath.h" +#import "DSAssetLockDerivationPath.h" #import "DSDerivationPath.h" #import "DSDerivationPathFactory.h" #import "DSFundsDerivationPath.h" @@ -27,17 +32,18 @@ #import "DSSparseMerkleTree.h" -#import "DSBlockchainIdentity.h" -#import "DSBlockchainInvitation.h" -#import "DSCreditFundingTransaction.h" +#import "DSIdentity.h" +#import "DSIdentity+ContactRequest.h" +#import "DSIdentity+Friendship.h" +#import "DSIdentity+Profile.h" +#import "DSIdentity+Username.h" +#import "DSInvitation.h" #import "DSAccount.h" #import "DSAuthenticationManager.h" #import "DSBIP39Mnemonic.h" #import "DSChainManager.h" #import "DSChainsManager.h" -#import "DSDAPICoreNetworkService.h" -#import "DSDAPIPlatformNetworkService.h" #import "DSDerivationPath.h" #import "DSEventManager.h" #import "DSGovernanceObject.h" @@ -46,7 +52,6 @@ #import "DSIdentitiesManager.h" #import "DSInsightManager.h" #import "DSKeyManager.h" -#import "DSMasternodeListStore.h" #import "DSMasternodeListService.h" #import "DSMasternodeListDiffService.h" #import "DSQuorumRotationService.h" @@ -59,6 +64,9 @@ #import "DSTransactionManager.h" #import "DSVersionManager.h" #import "DSWallet.h" +#import "DSWallet+Identity.h" +#import "DSWallet+Invitation.h" + #import "NSMutableData+Dash.h" #import "NSString+Dash.h" @@ -77,11 +85,9 @@ #import "DSGovernanceVoteHashEntity+CoreDataProperties.h" #import "DSLocalMasternode.h" #import "DSLocalMasternodeEntity+CoreDataProperties.h" -#import "DSMasternodeList.h" #import "DSMerkleBlock.h" #import "DSMerkleBlockEntity+CoreDataProperties.h" #import "DSPeerEntity+CoreDataProperties.h" -#import "DSSimplifiedMasternodeEntry.h" #import "DSSpecialTransactionsWalletHolder.h" #import "DSSporkEntity+CoreDataProperties.h" #import "DSTransactionEntity+CoreDataProperties.h" @@ -99,32 +105,21 @@ #import "DSPaymentProtocol.h" #import "DSPaymentRequest.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h" #import "NSManagedObject+Sugar.h" -#import "NSPredicate+CBORData.h" #import "NSPredicate+DSUtils.h" #import "DSTransaction+Utils.h" #import "DSTransactionFactory.h" #import "DSTransactionInput.h" #import "DSTransactionOutput.h" - -#import "DSBlockchainIdentityCloseTransition.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSBlockchainIdentityTopupTransition.h" -#import "DSBlockchainIdentityUpdateTransition.h" - -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSBlockchainIdentityKeyPathEntity+CoreDataClass.h" #import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" #import "DSDashpayUserEntity+CoreDataClass.h" #import "DSFriendRequestEntity+CoreDataClass.h" -#import "DSMasternodeListEntity+CoreDataProperties.h" #import "DSPotentialContact.h" #import "DSPotentialOneWayFriendship.h" -#import "DSQuorumEntry.h" -#import "DSQuorumEntryEntity+CoreDataProperties.h" #import "DSNetworking.h" diff --git a/DashSync/shared/DashSync.m b/DashSync/shared/DashSync.m index 25d8f807b..2521d2a19 100644 --- a/DashSync/shared/DashSync.m +++ b/DashSync/shared/DashSync.m @@ -7,20 +7,19 @@ // #import "DashSync.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainManager+Protected.h" #import "DSDataController.h" #import "DSLocalMasternodeEntity+CoreDataClass.h" -#import "DSMasternodeListEntity+CoreDataClass.h" -#import "DSMasternodeListStore.h" #import "DSMasternodeManager+Protected.h" #import "DSMerkleBlockEntity+CoreDataClass.h" #import "DSPeerEntity+CoreDataClass.h" #import "DSPeerManager+Protected.h" #import "DSSyncState.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "DSQuorumSnapshotEntity+CoreDataClass.h" +#import "DSCoinJoinManager.h" #import "DSSporkManager+Protected.h" #import "DSTransactionEntity+CoreDataClass.h" #import "NSManagedObject+Sugar.h" @@ -122,13 +121,14 @@ - (void)startSyncForChain:(DSChain *)chain { - (void)stopSyncAllChains { NSArray *chains = [[DSChainsManager sharedInstance] chains]; for (DSChain *chain in chains) { + [[DSCoinJoinManager sharedInstanceForChain:chain] stop]; [[[DSChainsManager sharedInstance] chainManagerForChain:chain].peerManager disconnect: DSDisconnectReason_ChainWipe]; } } - (void)stopSyncForChain:(DSChain *)chain { NSParameterAssert(chain); - + [[DSCoinJoinManager sharedInstanceForChain:chain] stop]; [[[DSChainsManager sharedInstance] chainManagerForChain:chain] stopSync]; } @@ -212,10 +212,6 @@ - (void)wipeMasternodeDataForChain:(DSChain *)chain inContext:(NSManagedObjectCo [context performBlockAndWait:^{ DSChainEntity *chainEntity = [chain chainEntityInContext:context]; [DSLocalMasternodeEntity deleteAllOnChainEntity:chainEntity]; - [DSSimplifiedMasternodeEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSMasternodeListEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumSnapshotEntity deleteAllOnChainEntity:chainEntity]; DSChainManager *chainManager = [[DSChainsManager sharedInstance] chainManagerForChain:chain]; [chainManager wipeMasternodeInfo]; [context ds_save]; diff --git a/DashSync/shared/DashSync.xcdatamodeld/.xccurrentversion b/DashSync/shared/DashSync.xcdatamodeld/.xccurrentversion index 9697cdadc..904683a5e 100644 --- a/DashSync/shared/DashSync.xcdatamodeld/.xccurrentversion +++ b/DashSync/shared/DashSync.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - DashSync 21.xcdatamodel + DashSync 22.xcdatamodel diff --git a/DashSync/shared/DashSync.xcdatamodeld/DashSync 21.xcdatamodel/contents b/DashSync/shared/DashSync.xcdatamodeld/DashSync 21.xcdatamodel/contents index e8661f27e..1cde448c8 100644 --- a/DashSync/shared/DashSync.xcdatamodeld/DashSync 21.xcdatamodel/contents +++ b/DashSync/shared/DashSync.xcdatamodeld/DashSync 21.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/DashSync/shared/DashSync.xcdatamodeld/DashSync 22.xcdatamodel/contents b/DashSync/shared/DashSync.xcdatamodeld/DashSync 22.xcdatamodel/contents new file mode 100644 index 000000000..5c3cd50f6 --- /dev/null +++ b/DashSync/shared/DashSync.xcdatamodeld/DashSync 22.xcdatamodel/contents @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.h b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.h index 627210fca..ded5150f8 100644 --- a/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.h +++ b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.h @@ -19,6 +19,9 @@ NS_ASSUME_NONNULL_BEGIN +#define ERROR_500(msg) [NSError errorWithCode:500 localizedDescriptionKey:msg] +//#define DS_ERROR(domain, code, localizedDescriptionKey) [NSError errorWithDomain:domain code:code userInfo:@{ NSLocalizedDescriptionKey: localizedDescriptionKey }]; + @interface NSError (Dash) + (instancetype)errorWithCode:(NSInteger)code userInfo:(nullable NSDictionary *)dict; @@ -26,6 +29,7 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)errorWithCode:(NSInteger)code localizedDescriptionKey:(NSString *)localizedDescriptionKey; + (instancetype)osStatusErrorWithCode:(NSInteger)code; ++ (NSString *)errorsDescription:(NSArray *)errors; @end diff --git a/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.m b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.m index aa15b696a..01fe0779a 100644 --- a/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.m +++ b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Dash.m @@ -35,4 +35,12 @@ + (instancetype)osStatusErrorWithCode:(NSInteger)code { return [NSError errorWithDomain:NSOSStatusErrorDomain code:code userInfo:nil]; } ++ (NSString *)errorsDescription:(NSArray *)errors { + NSMutableString *description = [NSMutableString string]; + for (NSError *error in errors) { + [description appendFormat:@"%@\n", error.localizedDescription]; + } + return description; +} + @end diff --git a/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Platform.h b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Platform.h new file mode 100644 index 000000000..8ab4c7d91 --- /dev/null +++ b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Platform.h @@ -0,0 +1,45 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "dash_spv_apple_bindings.h" +#import "DSKeyManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface NSError (dash_spv_platform_error_Error) ++ (NSError *)ffi_from_platform_error:(dash_spv_platform_error_Error *)ffi_ref; +@end + + +@interface NSError (dash_spv_crypto_keys_KeyError) ++ (NSError *)ffi_from_key_error:(DKeyError *)ffi_ref; +@end + +@interface NSError (dash_spv_masternode_processor_processing_core_provider_CoreProviderError) ++ (NSError *)ffi_from_core_provider_error:(dash_spv_masternode_processor_processing_core_provider_CoreProviderError *)ffi_ref; +@end + +@interface NSError (DProcessingError) ++ (NSError *)ffi_from_processing_error:(DProcessingError *)ffi_ref; +@end + +@interface NSError (dashcore_sml_error_SmlError) ++ (NSError *)ffi_from_sml_error:(dashcore_sml_error_SmlError *)ffi_ref; +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Platform.m b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Platform.m new file mode 100644 index 000000000..d6b2c7729 --- /dev/null +++ b/DashSync/shared/Libraries/AdvancedOperations/Others/NSError+Platform.m @@ -0,0 +1,250 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "NSData+Dash.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" + +@implementation NSError (dash_spv_platform_error_Error) + ++ (nonnull NSError *)ffi_from_platform_error:(nonnull dash_spv_platform_error_Error *)ffi_ref { + switch (ffi_ref->tag) { + case dash_spv_platform_error_Error_KeyError: + return [NSError ffi_from_key_error:ffi_ref->key_error]; + case dash_spv_platform_error_Error_DashSDKError: + return [NSError errorWithCode:0 localizedDescriptionKey:NSStringFromPtr(ffi_ref->dash_sdk_error)]; + case dash_spv_platform_error_Error_Any: + return [NSError errorWithCode:ffi_ref->any._0 localizedDescriptionKey:NSStringFromPtr(ffi_ref->any._1)]; + case dash_spv_platform_error_Error_MaxRetryExceeded: + case dash_spv_platform_error_Error_InstantSendSignatureVerificationError: + return [NSError errorWithCode:0 localizedDescriptionKey:NSStringFromPtr(ffi_ref->instant_send_signature_verification_error)]; + } +} + +@end + + +@implementation NSError (dash_spv_crypto_keys_KeyError) + ++ (nonnull NSError *)ffi_from_key_error:(nonnull DKeyError *)ffi_ref { + switch (ffi_ref->tag) { + case dash_spv_crypto_keys_KeyError_WrongFormat: + return [NSError errorWithCode:0 localizedDescriptionKey:@"Wrong key format"]; + case dash_spv_crypto_keys_KeyError_WrongLength: + return [NSError errorWithCode:0 descriptionKey:DSLocalizedFormat(@"Wrong key length", nil, ffi_ref->wrong_length)]; + case dash_spv_crypto_keys_KeyError_Extended: + return [NSError errorWithCode:0 descriptionKey:DSLocalizedFormat(@"Key extended error", nil, ffi_ref->extended)]; + case dash_spv_crypto_keys_KeyError_UnableToDerive: + return [NSError errorWithCode:0 localizedDescriptionKey:@"Unable to derive key"]; + case dash_spv_crypto_keys_KeyError_DHKeyExchange: + return [NSError errorWithCode:0 localizedDescriptionKey:@"Unable to exchange key"]; + case dash_spv_crypto_keys_KeyError_CCCrypt: + return [NSError errorWithCode:0 descriptionKey:DSLocalizedFormat(@"CCrypt error", nil, ffi_ref->cc_crypt)]; + case dash_spv_crypto_keys_KeyError_EmptySecKey: + return [NSError errorWithCode:0 localizedDescriptionKey:@"Private key is empty"]; + case dash_spv_crypto_keys_KeyError_Product: + return [NSError errorWithCode:0 localizedDescriptionKey:@"Can't multiple keys"]; + case dash_spv_crypto_keys_KeyError_Any: + return [NSError errorWithCode:0 localizedDescriptionKey:NSStringFromPtr(ffi_ref->any)]; + } +} + +@end + +@implementation NSError (dashcore_sml_quorum_validation_error_ClientDataRetrievalError) + ++ (nonnull NSError *)ffi_from_client_data_retrieval_error:(nonnull dashcore_sml_quorum_validation_error_ClientDataRetrievalError *)ffi_ref { + switch (ffi_ref->tag) { + case dashcore_sml_quorum_validation_error_ClientDataRetrievalError_RequiredBlockNotPresent: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->required_block_not_present); + NSString *blockHashString = u256_hex(block_hash); + NSString *blockHashRevString = u256_reversed_hex(block_hash); + u256_dtor(block_hash); + return [NSError errorWithCode:0 descriptionKey:DSLocalizedFormat(@"Required Block Not Present %@ (%@)", nil, blockHashString, blockHashRevString)]; + } + case dashcore_sml_quorum_validation_error_ClientDataRetrievalError_CoinbaseNotFoundOnBlock: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->coinbase_not_found_on_block); + NSString *blockHashString = u256_hex(block_hash); + NSString *blockHashRevString = u256_reversed_hex(block_hash); + u256_dtor(block_hash); + return [NSError errorWithCode:0 descriptionKey:DSLocalizedFormat(@"Coinbase Not Found On Block %@ (%@)", nil, blockHashString, blockHashRevString)]; + } + } +} + +@end + +@implementation NSError (dashcore_sml_quorum_validation_error_QuorumValidationError) ++ (NSError *)ffi_from_quorum_validation_error:(dashcore_sml_quorum_validation_error_QuorumValidationError *)ffi_ref { + switch (ffi_ref->tag) { + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredBlockNotPresent: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->required_block_not_present); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required block %@ (%@) not present", nil, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredBlockHeightNotPresent: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required block height (%u) not present", nil, ffi_ref->required_block_height_not_present->_0)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_VerifyingMasternodeListNotPresent: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Verifying MasternodeList (%u) not present", nil, ffi_ref->verifying_masternode_list_not_present->_0)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredMasternodeListNotPresent: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required MasternodeList (%u) not present", nil, ffi_ref->required_masternode_list_not_present->_0)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredChainLockNotPresent: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->required_chain_lock_not_present._1); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required ChainLock %u: %@ (%@) not present", nil, ffi_ref->required_chain_lock_not_present._0->_0, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredRotatedChainLockSigNotPresent: { + uint8_t index = ffi_ref->required_rotated_chain_lock_sig_not_present._0; + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->required_rotated_chain_lock_sig_not_present._1); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required Rotated ChainLock Signature at index: %u: %@ (%@) not present", nil, index, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredRotatedChainLockSigsNotPresent: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->required_rotated_chain_lock_sigs_not_present); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required ChainLock Signatures %@ (%@) not present", nil, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_InsufficientSigners: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Insufficient Signers (%llu/%llu)", nil, ffi_ref->insufficient_signers.found, ffi_ref->insufficient_signers.required)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_InsufficientValidMembers: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Insufficient Valid Members (%llu/%llu)", nil, ffi_ref->insufficient_valid_members.found, ffi_ref->insufficient_valid_members.required)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_MismatchedBitsetLengths: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Mismatched Bitset Lengths (%lu/%lu)", nil, ffi_ref->mismatched_bitset_lengths.signers_len, ffi_ref->mismatched_bitset_lengths.valid_members_len)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_InvalidQuorumPublicKey: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Invalid Quorum PublicKey", nil)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_InvalidBLSPublicKey: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Invalid BLS PublicKey (%@)", nil, NSStringFromPtr(ffi_ref->invalid_bls_public_key))]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_InvalidBLSSignature: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Invalid BLS Signature (%@)", nil, NSStringFromPtr(ffi_ref->invalid_bls_signature))]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_InvalidQuorumSignature: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Invalid Quorum Signature", nil)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_InvalidFinalSignature: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Invalid Final Signature", nil)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_AllCommitmentAggregatedSignatureNotValid: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"All Commitment Aggregated Signature Not Valid (%@)", nil, NSStringFromPtr(ffi_ref->all_commitment_aggregated_signature_not_valid))]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_ThresholdSignatureNotValid: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Threshold Signature Not Valid (%@)", nil, NSStringFromPtr(ffi_ref->threshold_signature_not_valid))]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_CommitmentHashNotPresent: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Commitment Hash not present", nil)]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredSnapshotNotPresent: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->required_snapshot_not_present); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required Snapshot (%@) not present", nil, u256_hex(block_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_SMLError: + return [NSError ffi_from_sml_error:ffi_ref->sml_error]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredQuorumIndexNotPresent: { + u256 *quorum_hash = dashcore_hash_types_QuorumHash_inner(ffi_ref->required_quorum_index_not_present); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Required Quorum Index (%@) not present", nil, u256_hex(quorum_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_CorruptedCodeExecution: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Corruped Code Execution (%@)", nil, NSStringFromPtr(ffi_ref->all_commitment_aggregated_signature_not_valid))]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_ExpectedOnlyRotatedQuorums: { + u256 *quorum_hash = dashcore_hash_types_QuorumHash_inner(ffi_ref->expected_only_rotated_quorums._0); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Expected Only Rotated Quorums (%u: %@)", nil, dashcore_sml_llmq_type_LLMQType_index(ffi_ref->expected_only_rotated_quorums._1), u256_hex(quorum_hash))]; + } + case dashcore_sml_quorum_validation_error_QuorumValidationError_ClientDataRetrievalError: + return [NSError ffi_from_client_data_retrieval_error:ffi_ref->client_data_retrieval_error]; + case dashcore_sml_quorum_validation_error_QuorumValidationError_FeatureNotTurnedOn: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Feature Not Turned On (%@)", nil, NSStringFromPtr(ffi_ref->feature_not_turned_on))]; + } +} +@end + +@implementation NSError (dash_spv_masternode_processor_processing_core_provider_CoreProviderError) ++ (NSError *)ffi_from_core_provider_error:(dash_spv_masternode_processor_processing_core_provider_CoreProviderError *)ffi_ref { + switch (ffi_ref->tag) { + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_NullResult: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedString(@"Core Provider Null Result", nil)]; + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_BadBlockHash: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Bad Block Hash (%@)", nil, u256_hex(ffi_ref->bad_block_hash))]; + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_UnknownBlockHeightForHash: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Unknown height for Hash (%@)", nil, u256_hex(ffi_ref->unknown_block_height_for_hash))]; + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_BlockHashNotFoundAt: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Block hash for height %u not found", nil, ffi_ref->block_hash_not_found_at)]; + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_NoSnapshot: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedString(@"Quorum Snapshot not found", nil)]; + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_HexError: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedString(@"Parse Hex Error", nil)]; + case dash_spv_masternode_processor_processing_core_provider_CoreProviderError_MissedMasternodeListAt: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Missed Masternode List at (%@)", nil, u256_hex(ffi_ref->missed_masternode_list_at))]; + } +} +@end + +@implementation NSError (DProcessingError) ++ (NSError *)ffi_from_processing_error:(DProcessingError *)ffi_ref { + switch (ffi_ref->tag) { + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_PersistInRetrieval: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Unexpected Diff Processing (%@..%@)", nil, u256_hex(ffi_ref->persist_in_retrieval._0), u256_hex(ffi_ref->persist_in_retrieval._1))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_LocallyStored: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Masternode List already stored for %u: %@", nil, ffi_ref->locally_stored._0, u256_hex(ffi_ref->locally_stored._1))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_ParseError: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Message Parse Error", nil, NSStringFromPtr(ffi_ref->parse_error))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_HasNoBaseBlockHash: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Unknown base block hash", nil, u256_hex(ffi_ref->has_no_base_block_hash))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_UnknownBlockHash: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Unknown block hash %@", nil, u256_hex(ffi_ref->unknown_block_hash))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_InvalidResult: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Invalid Result", nil, NSStringFromPtr(ffi_ref->invalid_result))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_CoreProvider: + return [NSError ffi_from_core_provider_error:ffi_ref->core_provider]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_MissingLists: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Missing Masternode Lists: %@", nil, NSStringFromPtr(ffi_ref->missing_lists))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_EncodeError: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Encode Error: %@", nil, NSStringFromPtr(ffi_ref->encode_error))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_DecodeError: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"Decode Error: %@", nil, NSStringFromPtr(ffi_ref->decode_error))]; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_QuorumValidationError: + return [NSError ffi_from_quorum_validation_error:ffi_ref->quorum_validation_error]; + } +} +@end + +@implementation NSError (dashcore_sml_error_SmlError) ++ (NSError *)ffi_from_sml_error:(dashcore_sml_error_SmlError *)ffi_ref { + switch (ffi_ref->tag) { + case dashcore_sml_error_SmlError_BaseBlockNotGenesis: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->base_block_not_genesis); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::BaseBlockNotGenesis %@ (%@)", nil, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_error_SmlError_BlockHashLookupFailed: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->block_hash_lookup_failed); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::BlockHashLookupFailed %@ (%@)", nil, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_error_SmlError_IncompleteMnListDiff: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::IncompleteMnListDiff", nil)]; + case dashcore_sml_error_SmlError_MissingStartMasternodeList: { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(ffi_ref->missing_start_masternode_list); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::MissingStartMasternodeList %@ (%@)", nil, u256_hex(block_hash), u256_reversed_hex(block_hash))]; + } + case dashcore_sml_error_SmlError_BaseBlockHashMismatch: { + u256 *block_hash_expected = dashcore_hash_types_BlockHash_inner(ffi_ref->base_block_hash_mismatch.expected); + u256 *block_hash_found = dashcore_hash_types_BlockHash_inner(ffi_ref->base_block_hash_mismatch.found); + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::BaseBlockHashMismatch: expected: %@ (%@), found: %@ (%@)", nil, u256_hex(block_hash_expected), u256_hex(block_hash_expected), u256_hex(block_hash_found), u256_hex(block_hash_found))]; + } + case dashcore_sml_error_SmlError_UnknownError: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::UnknownError", nil)]; + case dashcore_sml_error_SmlError_CorruptedCodeExecution: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::CorruptedCodeExecution: %@", nil, NSStringFromPtr(ffi_ref->corrupted_code_execution))]; + case dashcore_sml_error_SmlError_FeatureNotTurnedOn: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::FeatureNotTurnedOn: %@", nil, NSStringFromPtr(ffi_ref->feature_not_turned_on))]; + case dashcore_sml_error_SmlError_InvalidIndexInSignatureSet: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::InvalidIndexInSignatureSet: %u", nil, ffi_ref->invalid_index_in_signature_set)]; + case dashcore_sml_error_SmlError_IncompleteSignatureSet: + return [NSError errorWithCode:0 localizedDescriptionKey:DSLocalizedFormat(@"SmlError::IncompleteSignatureSet", nil)]; + } +} +@end + + + diff --git a/DashSync/shared/Libraries/DSLogger.m b/DashSync/shared/Libraries/DSLogger.m index 5f59dc9c7..2d68e4ee0 100644 --- a/DashSync/shared/Libraries/DSLogger.m +++ b/DashSync/shared/Libraries/DSLogger.m @@ -16,6 +16,7 @@ // #import "DSLogger.h" +#import "CompressingLogFileManager.h" NS_ASSUME_NONNULL_BEGIN @@ -49,10 +50,13 @@ - (instancetype)init { if (self) { [DDLog addLogger:[DDOSLogger sharedInstance]]; // os_log - DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; - fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling - fileLogger.logFileManager.maximumNumberOfLogFiles = 3; // keep a 3 days worth of log files - //[fileLogger setLogFormatter:[[NoTimestampLogFormatter alloc] init]]; // Use the custom formatter + unsigned long long maxFileSize = 1024 * 1024 * 5; // 5 MB max. Then log files are ziped + CompressingLogFileManager *logFileManager = [[CompressingLogFileManager alloc] initWithFileSize:maxFileSize]; + DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:logFileManager]; + fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling + fileLogger.maximumFileSize = maxFileSize; + fileLogger.logFileManager.maximumNumberOfLogFiles = 10; + [DDLog addLogger:fileLogger]; _fileLogger = fileLogger; } @@ -60,22 +64,23 @@ - (instancetype)init { } - (NSArray *)logFiles { - NSArray *logFileInfos = [self.fileLogger.logFileManager unsortedLogFileInfos]; - NSMutableArray *logFiles = [NSMutableArray array]; - for (DDLogFileInfo *fileInfo in logFileInfos) { - NSURL *fileURL = [NSURL fileURLWithPath:fileInfo.filePath]; - if (fileURL) { - [logFiles addObject:fileURL]; - } - } - // add rust log file located at $CACHE/Logs/processor.log - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString *cacheDirectory = [paths objectAtIndex:0]; - NSString *rustLogPath = [cacheDirectory stringByAppendingPathComponent:@"Logs/processor.log"]; + NSString *logsDirectory = [self.fileLogger.logFileManager logsDirectory]; + NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil]; + NSMutableArray *logFiles = [NSMutableArray arrayWithCapacity:[fileNames count]]; - if ([[NSFileManager defaultManager] fileExistsAtPath:rustLogPath]) { - [logFiles addObject:[NSURL fileURLWithPath:rustLogPath]]; + for (NSString *fileName in fileNames) { + BOOL hasProperSuffix = [fileName hasSuffix:@".log"] || [fileName hasSuffix:@".gz"]; + + if (hasProperSuffix) { + NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName]; + NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + + if (fileURL) { + [logFiles addObject:fileURL]; + } + } } + return [logFiles copy]; } diff --git a/DashSync/shared/Libraries/DSUInt256IndexPath.h b/DashSync/shared/Libraries/DSUInt256IndexPath.h index 186201057..d7edcbf34 100644 --- a/DashSync/shared/Libraries/DSUInt256IndexPath.h +++ b/DashSync/shared/Libraries/DSUInt256IndexPath.h @@ -16,9 +16,7 @@ // #import - #import "BigIntTypes.h" -#import "dash_shared_core.h" NS_ASSUME_NONNULL_BEGIN diff --git a/DashSync/shared/Libraries/Logs/CompressingLogFileManager.h b/DashSync/shared/Libraries/Logs/CompressingLogFileManager.h new file mode 100644 index 000000000..8a34b52e2 --- /dev/null +++ b/DashSync/shared/Libraries/Logs/CompressingLogFileManager.h @@ -0,0 +1,20 @@ +// +// CompressingLogFileManager.h +// LogFileCompressor +// +// CocoaLumberjack Demos +// + +#import +#import + +@interface CompressingLogFileManager : DDLogFileManagerDefault +{ + BOOL upToDate; + BOOL isCompressing; +} + +@property (nonatomic, assign) unsigned long long maxFileSize; +- (instancetype)initWithFileSize:(unsigned long long)maxFileSize; + +@end diff --git a/DashSync/shared/Libraries/Logs/CompressingLogFileManager.m b/DashSync/shared/Libraries/Logs/CompressingLogFileManager.m new file mode 100644 index 000000000..2dce736d3 --- /dev/null +++ b/DashSync/shared/Libraries/Logs/CompressingLogFileManager.m @@ -0,0 +1,563 @@ +// +// CompressingLogFileManager.m +// LogFileCompressor +// +// CocoaLumberjack Demos +// + +#import "CompressingLogFileManager.h" +#import "dash_spv_apple_bindings.h" +#import + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#define LOG_LEVEL 4 + +#define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog(frmt, ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog(frmt, ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog(frmt, ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog(frmt, ##__VA_ARGS__); } while(0) + +@interface CompressingLogFileManager (/* Must be nameless for properties */) + +@property (readwrite) BOOL isCompressing; + +@end + +@interface DDLogFileInfo (Compressor) + +@property (nonatomic, readonly) BOOL isCompressed; + +- (NSString *)tempFilePathByAppendingPathExtension:(NSString *)newExt; +- (NSString *)fileNameByAppendingPathExtension:(NSString *)newExt; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation CompressingLogFileManager + +@synthesize isCompressing; + +- (instancetype)initWithFileSize:(unsigned long long)maxFileSize { + self = [self initWithLogsDirectory:nil]; + if (self) { + _maxFileSize = maxFileSize; + } + return self; +} + +- (id)init +{ + return [self initWithLogsDirectory:nil]; +} + +- (id)initWithLogsDirectory:(NSString *)aLogsDirectory +{ + if ((self = [super initWithLogsDirectory:aLogsDirectory])) + { + upToDate = NO; + + // Check for any files that need to be compressed. + // But don't start right away. + // Wait for the app startup process to finish. + + [self performSelector:@selector(compressNextLogFile) withObject:nil afterDelay:5.0]; + } + return self; +} + +- (void)dealloc +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(compressNextLogFile) object:nil]; +} + +- (void)compressLogFile:(DDLogFileInfo *)logFile +{ + self.isCompressing = YES; + + CompressingLogFileManager* __weak weakSelf = self; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [weakSelf backgroundThread_CompressLogFile:logFile]; + }); +} + +- (void)compressNextLogFile +{ + if (self.isCompressing) + { + // We're already compressing a file. + // Wait until it's done to move onto the next file. + return; + } + + NSLogVerbose(@"CompressingLogFileManager: compressNextLogFile"); + + upToDate = NO; + + NSMutableArray *sortedLogFileInfos = [[self sortedLogFileInfos] mutableCopy]; + [self handleProcessorLogRotationWithSortedLogs:sortedLogFileInfos]; + + NSUInteger count = [sortedLogFileInfos count]; + if (count == 0) + { + // Nothing to compress + upToDate = YES; + return; + } + + NSUInteger i = count; + while (i > 0) + { + DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:(i - 1)]; + + if ((logFileInfo.isArchived || [logFileInfo.fileName hasPrefix:@"processor"]) && !logFileInfo.isCompressed) + { + [self compressLogFile:logFileInfo]; + + break; + } + + i--; + } + + upToDate = YES; +} + +- (void)handleProcessorLogRotationWithSortedLogs:(NSMutableArray *)sortedLogFileInfos { + // Get all processor log files + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSArray *files = [fileManager contentsOfDirectoryAtPath:self.logsDirectory error:nil]; + NSMutableArray *processorLogs = [NSMutableArray array]; + NSMutableArray *compressedLogs = [NSMutableArray array]; + + // Separate processor logs into compressed and uncompressed + for (NSString *file in files) { + if ([file hasPrefix:@"processor."]) { + NSArray *components = [file componentsSeparatedByString:@"."]; + if (components.count >= 3) { + if ([file hasSuffix:@".gz"]) { + [compressedLogs addObject:file]; + } else { + [processorLogs addObject:file]; + } + } + } + } + + // Remove compressed logs older than 5 days + NSDate *cutoffDate = [NSDate dateWithTimeIntervalSinceNow:-5 * 24 * 60 * 60]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd"]; + + for (NSString *file in compressedLogs) { + // Extract date from filename (assuming format processor.[date].log.gz) + NSArray *components = [file componentsSeparatedByString:@"."]; + if (components.count >= 2) { + NSString *dateString = components[1]; + NSDate *fileDate = [dateFormatter dateFromString:dateString]; + + if (fileDate && [fileDate compare:cutoffDate] == NSOrderedAscending) { + NSString *fullPath = [self.logsDirectory stringByAppendingPathComponent:file]; + [fileManager removeItemAtPath:fullPath error:nil]; + } + } + } + + // Handle uncompressed processor logs + if (processorLogs.count > 1) { + // Sort by date (oldest first) + [processorLogs sortUsingComparator:^NSComparisonResult(NSString *file1, NSString *file2) { + return [file1 compare:file2]; + }]; + + // Add older logs to sortedLogFileInfos for compression + for (NSInteger i = 0; i < processorLogs.count - 1; i++) { + NSString *logPath = [self.logsDirectory stringByAppendingPathComponent:processorLogs[i]]; + [sortedLogFileInfos addObject:[DDLogFileInfo logFileWithPath:logPath]]; + } + } +} + +- (void)compressionDidSucceed:(DDLogFileInfo *)logFile +{ + NSLogVerbose(@"CompressingLogFileManager: compressionDidSucceed: %@", logFile.fileName); + + self.isCompressing = NO; + + [self compressNextLogFile]; +} + +- (void)compressionDidFail:(DDLogFileInfo *)logFile +{ + NSLogWarn(@"CompressingLogFileManager: compressionDidFail: %@", logFile.fileName); + + self.isCompressing = NO; + + // We should try the compression again, but after a short delay. + // + // If the compression failed there is probably some filesystem issue, + // so flooding it with compression attempts is only going to make things worse. + + NSTimeInterval delay = (60 * 15); // 15 minutes + + [self performSelector:@selector(compressNextLogFile) withObject:nil afterDelay:delay]; +} + +- (void)didArchiveLogFile:(NSString *)logFilePath wasRolled:(BOOL)wasRolled { + NSLogVerbose(@"CompressingLogFileManager: didArchiveLogFile: %@ wasRolled: %@", + [logFilePath lastPathComponent], (wasRolled ? @"YES" : @"NO")); + + // If all other log files have been compressed, then we can get started right away. + // Otherwise we should just wait for the current compression process to finish. + + if (upToDate) + { + [self compressLogFile:[DDLogFileInfo logFileWithPath:logFilePath]]; + } +} + +- (void)backgroundThread_CompressLogFile:(DDLogFileInfo *)logFile +{ + @autoreleasepool { + + NSLogInfo(@"CompressingLogFileManager: Compressing log file: %@", logFile.fileName); + + // Steps: + // 1. Create a new file with the same fileName, but added "gzip" extension + // 2. Open the new file for writing (output file) + // 3. Open the given file for reading (input file) + // 4. Setup zlib for gzip compression + // 5. Read a chunk of the given file + // 6. Compress the chunk + // 7. Write the compressed chunk to the output file + // 8. Repeat steps 5 - 7 until the input file is exhausted + // 9. Close input and output file + // 10. Teardown zlib + + + // STEP 1 + + NSString *inputFilePath = logFile.filePath; + + NSString *tempOutputFilePath = [logFile tempFilePathByAppendingPathExtension:@"gz"]; + +#if TARGET_OS_IPHONE + // We use the same protection as the original file. This means that it has the same security characteristics. + // Also, if the app can run in the background, this means that it gets + // NSFileProtectionCompleteUntilFirstUserAuthentication so that we can do this compression even with the + // device locked. c.f. DDFileLogger.doesAppRunInBackground. + NSString* protection = logFile.fileAttributes[NSFileProtectionKey]; + NSDictionary* attributes = protection == nil ? nil : @{NSFileProtectionKey: protection}; + [[NSFileManager defaultManager] createFileAtPath:tempOutputFilePath contents:nil attributes:attributes]; +#endif + + // STEP 2 & 3 + + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:inputFilePath]; + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:tempOutputFilePath append:NO]; + + [inputStream open]; + [outputStream open]; + + // STEP 4 + + z_stream strm; + + // Zero out the structure before (to be safe) before we start using it + bzero(&strm, sizeof(strm)); + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.total_out = 0; + + // Compresssion Levels: + // Z_NO_COMPRESSION + // Z_BEST_SPEED + // Z_BEST_COMPRESSION + // Z_DEFAULT_COMPRESSION + + deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, (15+16), 9, Z_DEFAULT_STRATEGY); + + // Prepare our variables for steps 5-7 + // + // inputDataLength : Total length of buffer that we will read file data into + // outputDataLength : Total length of buffer that zlib will output compressed bytes into + // + // Note: The output buffer can be smaller than the input buffer because the + // compressed/output data is smaller than the file/input data (obviously). + // + // inputDataSize : The number of bytes in the input buffer that have valid data to be compressed. + // + // Imagine compressing a tiny file that is actually smaller than our inputDataLength. + // In this case only a portion of the input buffer would have valid file data. + // The inputDataSize helps represent the portion of the buffer that is valid. + // + // Imagine compressing a huge file, but consider what happens when we get to the very end of the file. + // The last read will likely only fill a portion of the input buffer. + // The inputDataSize helps represent the portion of the buffer that is valid. + + NSUInteger inputDataLength = (1024 * 64); // 64 KB + NSUInteger outputDataLength = (1024 * 32); // 32 KB + + NSMutableData *inputData = [NSMutableData dataWithLength:inputDataLength]; + NSMutableData *outputData = [NSMutableData dataWithLength:outputDataLength]; + + NSUInteger inputDataSize = 0; + + BOOL done = YES; + NSError* error = nil; + do + { + @autoreleasepool { + + // STEP 5 + // Read data from the input stream into our input buffer. + // + // inputBuffer : pointer to where we want the input stream to copy bytes into + // inputBufferLength : max number of bytes the input stream should read + // + // Recall that inputDataSize is the number of valid bytes that already exist in the + // input buffer that still need to be compressed. + // This value is usually zero, but may be larger if a previous iteration of the loop + // was unable to compress all the bytes in the input buffer. + // + // For example, imagine that we ready 2K worth of data from the file in the last loop iteration, + // but when we asked zlib to compress it all, zlib was only able to compress 1.5K of it. + // We would still have 0.5K leftover that still needs to be compressed. + // We want to make sure not to skip this important data. + // + // The [inputData mutableBytes] gives us a pointer to the beginning of the underlying buffer. + // When we add inputDataSize we get to the proper offset within the buffer + // at which our input stream can start copying bytes into without overwriting anything it shouldn't. + + const void *inputBuffer = [inputData mutableBytes] + inputDataSize; + NSUInteger inputBufferLength = inputDataLength - inputDataSize; + + NSInteger readLength = [inputStream read:(uint8_t *)inputBuffer maxLength:inputBufferLength]; + if (readLength < 0) { + error = [inputStream streamError]; + break; + } + + NSLogVerbose(@"CompressingLogFileManager: Read %li bytes from file", (long)readLength); + + inputDataSize += readLength; + + // STEP 6 + // Ask zlib to compress our input buffer. + // Tell it to put the compressed bytes into our output buffer. + + strm.next_in = (Bytef *)[inputData mutableBytes]; // Read from input buffer + strm.avail_in = (uInt)inputDataSize; // as much as was read from file (plus leftovers). + + strm.next_out = (Bytef *)[outputData mutableBytes]; // Write data to output buffer + strm.avail_out = (uInt)outputDataLength; // as much space as is available in the buffer. + + // When we tell zlib to compress our data, + // it won't directly tell us how much data was processed. + // Instead it keeps a running total of the number of bytes it has processed. + // In other words, every iteration from the loop it increments its total values. + // So to figure out how much data was processed in this iteration, + // we fetch the totals before we ask it to compress data, + // and then afterwards we subtract from the new totals. + + NSUInteger prevTotalIn = strm.total_in; + NSUInteger prevTotalOut = strm.total_out; + + int flush = [inputStream hasBytesAvailable] ? Z_SYNC_FLUSH : Z_FINISH; + deflate(&strm, flush); + + NSUInteger inputProcessed = strm.total_in - prevTotalIn; + NSUInteger outputProcessed = strm.total_out - prevTotalOut; + + NSLogVerbose(@"CompressingLogFileManager: Total bytes uncompressed: %lu", (unsigned long)strm.total_in); + NSLogVerbose(@"CompressingLogFileManager: Total bytes compressed: %lu", (unsigned long)strm.total_out); + NSLogVerbose(@"CompressingLogFileManager: Compression ratio: %.1f%%", + (double)(1.0F - (float)(strm.total_out) / (float)(strm.total_in)) * 100); + + // STEP 7 + // Now write all compressed bytes to our output stream. + // + // It is theoretically possible that the write operation doesn't write everything we ask it to. + // Although this is highly unlikely, we take precautions. + // Also, we watch out for any errors (maybe the disk is full). + + NSUInteger totalWriteLength = 0; + NSInteger writeLength = 0; + + do + { + const void *outputBuffer = [outputData mutableBytes] + totalWriteLength; + NSUInteger outputBufferLength = outputProcessed - totalWriteLength; + + writeLength = [outputStream write:(const uint8_t *)outputBuffer maxLength:outputBufferLength]; + + if (writeLength < 0) + { + error = [outputStream streamError]; + } + else + { + totalWriteLength += writeLength; + } + + } while((totalWriteLength < outputProcessed) && !error); + + // STEP 7.5 + // + // We now have data in our input buffer that has already been compressed. + // We want to remove all the processed data from the input buffer, + // and we want to move any unprocessed data to the beginning of the buffer. + // + // If the amount processed is less than the valid buffer size, we have leftovers. + + NSUInteger inputRemaining = inputDataSize - inputProcessed; + if (inputRemaining > 0) + { + void *inputDst = [inputData mutableBytes]; + void *inputSrc = [inputData mutableBytes] + inputProcessed; + + memmove(inputDst, inputSrc, inputRemaining); + } + + inputDataSize = inputRemaining; + + // Are we done yet? + + done = ((flush == Z_FINISH) && (inputDataSize == 0)); + + // STEP 8 + // Loop repeats until end of data (or unlikely error) + + } // end @autoreleasepool + + } while (!done && error == nil); + + // STEP 9 + + [inputStream close]; + [outputStream close]; + + // STEP 10 + + deflateEnd(&strm); + + // We're done! + // Report success or failure back to the logging thread/queue. + + if (error) + { + // Remove output file. + // Our compression attempt failed. + + NSLogError(@"Compression of %@ failed: %@", inputFilePath, error); + error = nil; + BOOL ok = [[NSFileManager defaultManager] removeItemAtPath:tempOutputFilePath error:&error]; + if (!ok) + NSLogError(@"Failed to clean up %@ after failed compression: %@", tempOutputFilePath, error); + + // Report failure to class via logging thread/queue + + dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool { + [self compressionDidFail:logFile]; + }}); + } + else + { + // Remove original input file. + // It will be replaced with the new compressed version. + + error = nil; + BOOL ok = [[NSFileManager defaultManager] removeItemAtPath:inputFilePath error:&error]; + if (!ok) + NSLogWarn(@"Warning: failed to remove original file %@ after compression: %@", inputFilePath, error); + + // Mark the compressed file as archived, + // and then move it into its final destination. + // + // temp-log-ABC123.txt.gz -> log-ABC123.txt.gz + // + // The reason we were using the "temp-" prefix was so the file would not be + // considered a log file while it was only partially complete. + // Only files that begin with "log-" are considered log files. + + DDLogFileInfo *compressedLogFile = [DDLogFileInfo logFileWithPath:tempOutputFilePath]; + compressedLogFile.isArchived = YES; + + NSString *outputFileName = [logFile fileNameByAppendingPathExtension:@"gz"]; + [compressedLogFile renameFile:outputFileName]; + + // Report success to class via logging thread/queue + + dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool { + [self compressionDidSucceed:compressedLogFile]; + }}); + } + + } // end @autoreleasepool +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLogFileInfo (Compressor) + +@dynamic isCompressed; + +- (BOOL)isCompressed +{ + return [[[self fileName] pathExtension] isEqualToString:@"gz"]; +} + +- (NSString *)tempFilePathByAppendingPathExtension:(NSString *)newExt +{ + // Example: + // + // Current File Name: "/full/path/to/log-ABC123.txt" + // + // newExt: "gzip" + // result: "/full/path/to/temp-log-ABC123.txt.gzip" + + NSString *tempFileName = [NSString stringWithFormat:@"temp-%@", [self fileName]]; + + NSString *newFileName = [tempFileName stringByAppendingPathExtension:newExt]; + + NSString *fileDir = [[self filePath] stringByDeletingLastPathComponent]; + + NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName]; + + return newFilePath; +} + +- (NSString *)fileNameByAppendingPathExtension:(NSString *)newExt +{ + // Example: + // + // Current File Name: "log-ABC123.txt" + // + // newExt: "gzip" + // result: "log-ABC123.txt.gzip" + + NSString *fileNameExtension = [[self fileName] pathExtension]; + + if ([fileNameExtension isEqualToString:newExt]) + { + return [self fileName]; + } + + return [[self fileName] stringByAppendingPathExtension:newExt]; +} + +@end diff --git a/DashSync/shared/Models/Chain/DSBlock.m b/DashSync/shared/Models/Chain/DSBlock.m index f0400d26e..bd480480e 100644 --- a/DashSync/shared/Models/Chain/DSBlock.m +++ b/DashSync/shared/Models/Chain/DSBlock.m @@ -17,6 +17,7 @@ #import "DSBlock+Protected.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSChainLock.h" #import "DSCheckpoint.h" #import "NSData+DSHash.h" diff --git a/DashSync/shared/Models/Chain/DSChain+Checkpoint.h b/DashSync/shared/Models/Chain/DSChain+Checkpoint.h new file mode 100644 index 000000000..095fddb57 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Checkpoint.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" +#import "DSChainCheckpoints.h" +#import "DSCheckpoint.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSChain (Checkpoint) + +@property (nonatomic, strong) NSMutableDictionary *checkpointsByHashDictionary; +@property (nonatomic, strong) NSMutableDictionary *checkpointsByHeightDictionary; +///*! @brief An array of known hard coded checkpoints for the chain. */ +@property (nonatomic, strong) NSArray *checkpoints; +@property (nonatomic, readonly) DSCheckpoint *terminalHeadersOverrideUseCheckpoint; +@property (nonatomic, readonly) DSCheckpoint *syncHeadersOverrideUseCheckpoint; +@property (nonatomic, readonly) DSCheckpoint *lastCheckpoint; + +- (BOOL)blockHeightHasCheckpoint:(uint32_t)blockHeight; + + +// MARK: - Checkpoints + +- (DSCheckpoint *_Nullable)lastTerminalCheckpoint; + +/*! @brief Returns the last checkpoint that has a masternode list attached to it. */ +- (DSCheckpoint *_Nullable)lastCheckpointHavingMasternodeList; + +/*! @brief Returns the checkpoint matching the parameter block hash, if one exists. */ +- (DSCheckpoint *_Nullable)checkpointForBlockHash:(UInt256)blockHash; + +/*! @brief Returns the checkpoint at a given block height, if one exists at that block height. */ +- (DSCheckpoint *_Nullable)checkpointForBlockHeight:(uint32_t)blockHeight; + +/*! @brief Returns the last checkpoint on or before the given height. */ +- (DSCheckpoint *)lastCheckpointOnOrBeforeHeight:(uint32_t)height; + +/*! @brief Returns the last checkpoint on or before the given timestamp. */ +- (DSCheckpoint *)lastCheckpointOnOrBeforeTimestamp:(NSTimeInterval)timestamp; + +/*! @brief When used this will change the checkpoint used for initial headers sync. This value is not persisted. */ +- (void)useCheckpointBeforeOrOnHeightForTerminalBlocksSync:(uint32_t)blockHeight; + +/*! @brief When used this will change the checkpoint used for main chain syncing. This value is not persisted. */ +- (void)useCheckpointBeforeOrOnHeightForSyncingChainBlocks:(uint32_t)blockHeight; + + +// MARK: Protected ++ (NSMutableArray *)createCheckpointsArrayFromCheckpoints:(checkpoint *)checkpoints count:(NSUInteger)checkpointCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Chain/DSChain+Checkpoint.m b/DashSync/shared/Models/Chain/DSChain+Checkpoint.m new file mode 100644 index 000000000..0a6b8c1db --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Checkpoint.m @@ -0,0 +1,154 @@ +// +// 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+Checkpoint.h" +#import "DSChain+Params.h" +#import "NSData+Dash.h" +#import "NSString+Bitcoin.h" +#import + +NSString const *checkpointsKey = @"checkpointsKey"; +NSString const *lastCheckpointKey = @"lastCheckpointKey"; +NSString const *checkpointsByHashDictionaryKey = @"checkpointsByHashDictionaryKey"; +NSString const *checkpointsByHeightDictionaryKey = @"checkpointsByHeightDictionaryKey"; +NSString const *terminalHeadersOverrideUseCheckpointKey = @"terminalHeadersOverrideUseCheckpointKey"; +NSString const *syncHeadersOverrideUseCheckpointKey = @"syncHeadersOverrideUseCheckpointKey"; + +@implementation DSChain (Checkpoint) + +// MARK: - Checkpoints +- (NSArray *)checkpoints { + return objc_getAssociatedObject(self, &checkpointsKey); +} +- (void)setCheckpoints:(NSArray *)checkpoints { + objc_setAssociatedObject(self, &checkpointsKey, checkpoints, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSMutableDictionary *)checkpointsByHashDictionary { + return objc_getAssociatedObject(self, &checkpointsByHashDictionaryKey); +} +- (void)setCheckpointsByHashDictionary:(NSMutableDictionary *)checkpointsByHashDictionary { + objc_setAssociatedObject(self, &checkpointsByHashDictionaryKey, checkpointsByHashDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSMutableDictionary *)checkpointsByHeightDictionary { + return objc_getAssociatedObject(self, &checkpointsByHeightDictionaryKey); +} +- (void)setCheckpointsByHeightDictionary:(NSMutableDictionary *)checkpointsByHeightDictionary { + objc_setAssociatedObject(self, &checkpointsByHeightDictionaryKey, checkpointsByHeightDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL)blockHeightHasCheckpoint:(uint32_t)blockHeight { + DSCheckpoint *checkpoint = [self lastCheckpointOnOrBeforeHeight:blockHeight]; + return (checkpoint.height == blockHeight); +} + +- (DSCheckpoint *)lastCheckpoint { + DSCheckpoint *maybeLastCheckpoint = objc_getAssociatedObject(self, &lastCheckpointKey); + if (!maybeLastCheckpoint) { + maybeLastCheckpoint = [[self checkpoints] lastObject]; + objc_setAssociatedObject(self, &lastCheckpointKey, maybeLastCheckpoint, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return maybeLastCheckpoint; +} + +- (DSCheckpoint *)lastTerminalCheckpoint { + return self.terminalHeadersOverrideUseCheckpoint ? self.terminalHeadersOverrideUseCheckpoint : [self lastCheckpoint]; + +} + +- (DSCheckpoint *)lastCheckpointOnOrBeforeHeight:(uint32_t)height { + NSUInteger genesisHeight = [self isDevnetAny] ? 1 : 0; + // if we don't have any blocks yet, use the latest checkpoint that's at least a week older than earliestKeyTime + for (long i = self.checkpoints.count - 1; i >= genesisHeight; i--) { + if (i == genesisHeight || ![self syncsBlockchain] || (self.checkpoints[i].height <= height)) { + return self.checkpoints[i]; + } + } + return nil; +} + +- (DSCheckpoint *)lastCheckpointOnOrBeforeTimestamp:(NSTimeInterval)timestamp { + NSUInteger genesisHeight = [self isDevnetAny] ? 1 : 0; + // if we don't have any blocks yet, use the latest checkpoint that's at least a week older than earliestKeyTime + for (long i = self.checkpoints.count - 1; i >= genesisHeight; i--) { + if (i == genesisHeight || ![self syncsBlockchain] || (self.checkpoints[i].timestamp <= timestamp)) { + return self.checkpoints[i]; + } + } + return nil; +} + +- (DSCheckpoint *_Nullable)lastCheckpointHavingMasternodeList { + NSSet *set = [self.checkpointsByHeightDictionary keysOfEntriesPassingTest:^BOOL(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) { + DSCheckpoint *checkpoint = (DSCheckpoint *)obj; + return (checkpoint.masternodeListName && ![checkpoint.masternodeListName isEqualToString:@""]); + }]; + NSArray *numbers = [[set allObjects] sortedArrayUsingSelector:@selector(compare:)]; + if (!numbers.count) return nil; + return self.checkpointsByHeightDictionary[numbers.lastObject]; +} + +- (DSCheckpoint *)checkpointForBlockHash:(UInt256)blockHash { + return [self.checkpointsByHashDictionary objectForKey:uint256_data(blockHash)]; +} + +- (DSCheckpoint *)checkpointForBlockHeight:(uint32_t)blockHeight { + return [self.checkpointsByHeightDictionary objectForKey:@(blockHeight)]; +} + +- (DSCheckpoint *)terminalHeadersOverrideUseCheckpoint { + return objc_getAssociatedObject(self, &terminalHeadersOverrideUseCheckpointKey); +} + +- (void)useCheckpointBeforeOrOnHeightForTerminalBlocksSync:(uint32_t)blockHeight { + objc_setAssociatedObject(self, &terminalHeadersOverrideUseCheckpointKey, [self lastCheckpointOnOrBeforeHeight:blockHeight], OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (DSCheckpoint *)syncHeadersOverrideUseCheckpoint { + return objc_getAssociatedObject(self, &syncHeadersOverrideUseCheckpointKey); +} + +- (void)useCheckpointBeforeOrOnHeightForSyncingChainBlocks:(uint32_t)blockHeight { + objc_setAssociatedObject(self, &syncHeadersOverrideUseCheckpointKey, [self lastCheckpointOnOrBeforeHeight:blockHeight], OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + + ++ (NSMutableArray *)createCheckpointsArrayFromCheckpoints:(checkpoint *)checkpoints count:(NSUInteger)checkpointCount { + NSMutableArray *checkpointMutableArray = [NSMutableArray array]; + for (int i = 0; i < checkpointCount; i++) { + checkpoint cpt = checkpoints[i]; + NSString *merkleRootString = NSStringFromPtr(cpt.merkleRoot); + NSString *chainWorkString = NSStringFromPtr(cpt.chainWork); + uint32_t blockHeight = cpt.height; + NSString *blockHashHex = NSStringFromPtr(cpt.checkpointHash); + UInt256 blockHash = blockHashHex.hexToData.reverse.UInt256; + UInt256 chainWork = chainWorkString.hexToData.reverse.UInt256; + UInt256 merkleRoot = [merkleRootString isEqualToString:@""] ? UINT256_ZERO : merkleRootString.hexToData.reverse.UInt256; + DSCheckpoint *checkpoint = [DSCheckpoint checkpointForHeight:blockHeight + blockHash:blockHash + timestamp:cpt.timestamp + target:cpt.target + merkleRoot:merkleRoot + chainWork:chainWork + masternodeListName:NSStringFromPtr(cpt.masternodeListPath)]; + [checkpointMutableArray addObject:checkpoint]; + } + return [checkpointMutableArray copy]; +} + +@end diff --git a/DashSync/shared/Models/Chain/DSChain+Identity.h b/DashSync/shared/Models/Chain/DSChain+Identity.h new file mode 100644 index 000000000..e2db1d6f8 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Identity.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 "DSIdentity.h" +#import "DSChain.h" +#import "DSWallet+Identity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSChain (Identity) + +// MARK: - Identities + +/*! @brief Returns a count of local blockchain identities. */ +@property (nonatomic, readonly) uint32_t localIdentitiesCount; + +/*! @brief Returns a count of blockchain invitations that have been created locally. */ +@property (nonatomic, readonly) uint32_t localInvitationsCount; + +/*! @brief Returns an array of all local blockchain identities. */ +@property (nonatomic, readonly) NSArray *localIdentities; + +/*! @brief Returns a dictionary of all local blockchain identities keyed by uniqueId. */ +@property (nonatomic, readonly) NSDictionary *localIdentitiesByUniqueIdDictionary; + +/*! @brief Returns a blockchain identity by uniqueId, if it exists. */ +- (DSIdentity *_Nullable)identityForUniqueId:(UInt256)uniqueId; + +/*! @brief Returns a blockchain identity that could have created this contract. */ +- (DSIdentity *_Nullable)identityThatCreatedContract:(DDataContract *)contract + withContractId:(UInt256)contractId + foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet; + +- (DSIdentity *_Nullable)identityForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key + foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet; + +/*! @brief Returns a private key that is paired with this identity public key . */ +- (DMaybeOpaqueKey *_Nullable)identityPrivateKeyForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key; + +/*! @brief Returns a blockchain identity by uniqueId, if it exists. Also returns the wallet it was found in. */ +- (DSIdentity *_Nullable)identityForUniqueId:(UInt256)uniqueId + foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet; + +/*! @brief Returns a blockchain identity by uniqueId, if it exists. Also returns the wallet it was found in. Allows to search foreign blockchain identities too */ +- (DSIdentity *)identityForUniqueId:(UInt256)uniqueId + foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet + includeForeignIdentities:(BOOL)includeForeignIdentities; + +- (void)wipeIdentitiesPersistedDataInContext:(NSManagedObjectContext *)context; +- (void)wipeInvitationsPersistedDataInContext:(NSManagedObjectContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Chain/DSChain+Identity.m b/DashSync/shared/Models/Chain/DSChain+Identity.m new file mode 100644 index 000000000..24078e570 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Identity.m @@ -0,0 +1,196 @@ +// +// 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+Identity.h" +#import "DSChain+Wallet.h" +#import "DSWallet+Invitation.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSBlockchainInvitationEntity+CoreDataClass.h" +#import "DSChainManager.h" +#import "DSIdentitiesManager+CoreData.h" +#import "NSManagedObject+Sugar.h" + +@implementation DSChain (Identity) + +// MARK: - Identities + +- (uint32_t)localIdentitiesCount { + uint32_t identitiesCount = 0; + for (DSWallet *lWallet in self.wallets) { + identitiesCount += [lWallet identitiesCount]; + } + return identitiesCount; +} + +- (NSArray *)localIdentities { + NSMutableArray *rAllIdentities = [NSMutableArray array]; + for (DSWallet *wallet in self.wallets) { + [rAllIdentities addObjectsFromArray:[wallet.identities allValues]]; + } + return rAllIdentities; +} + +- (NSDictionary *)localIdentitiesByUniqueIdDictionary { + NSMutableDictionary *rAllIdentities = [NSMutableDictionary dictionary]; + for (DSWallet *wallet in self.wallets) { + for (DSIdentity *identity in [wallet.identities allValues]) { + rAllIdentities[identity.uniqueIDData] = identity; + } + } + return rAllIdentities; +} + + +- (DSIdentity *)identityForUniqueId:(UInt256)uniqueId { + NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); + return [self identityForUniqueId:uniqueId foundInWallet:nil includeForeignIdentities:NO]; +} + +- (DSIdentity *)identityForUniqueId:(UInt256)uniqueId + foundInWallet:(DSWallet **)foundInWallet { + NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); + return [self identityForUniqueId:uniqueId foundInWallet:foundInWallet includeForeignIdentities:NO]; +} + +- (DSIdentity *_Nullable)identityThatCreatedContract:(DDataContract *)contract + withContractId:(UInt256)contractId + foundInWallet:(DSWallet **)foundInWallet { + NSAssert(uint256_is_not_zero(contractId), @"contractId must not be null"); + for (DSWallet *wallet in self.wallets) { + DSIdentity *identity = [wallet identityThatCreatedContract:contract withContractId:contractId]; + if (identity) { + if (foundInWallet) + *foundInWallet = wallet; + return identity; + } + } + return nil; +} + +- (DSIdentity *)identityForUniqueId:(UInt256)uniqueId + foundInWallet:(DSWallet **)foundInWallet + includeForeignIdentities:(BOOL)includeForeignIdentities { + NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); + for (DSWallet *wallet in self.wallets) { + DSIdentity *identity = [wallet identityForUniqueId:uniqueId]; + if (identity) { + if (foundInWallet) + *foundInWallet = wallet; + return identity; + } + } + return includeForeignIdentities ? [self.chainManager.identitiesManager foreignIdentityWithUniqueId:uniqueId] : nil; +} + +- (DSIdentity *)identityForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key + foundInWallet:(DSWallet **)foundInWallet { + for (DSWallet *wallet in self.wallets) { + DSIdentity *identity = [wallet identityForIdentityPublicKey:identity_public_key]; + if (identity) { + if (foundInWallet) + *foundInWallet = wallet; + return identity; + } + } + return nil; +} + +- (DMaybeOpaqueKey *)identityPrivateKeyForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key { + for (DSWallet *wallet in self.wallets) { + DMaybeOpaqueKey *identity_private_key = [wallet identityPrivateKeyForIdentityPublicKey:identity_public_key]; + if (identity_private_key && identity_private_key->ok) + return identity_private_key; + } + return nil; +} + +- (void)wipeIdentitiesPersistedDataInContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + NSArray *objects = [DSBlockchainIdentityEntity objectsInContext:context matching:@"chain == %@", [self chainEntityInContext:context]]; + [DSBlockchainIdentityEntity deleteObjects:objects inContext:context]; + }]; +} + +// MARK: - Invitations + +- (uint32_t)localInvitationsCount { + uint32_t invitationsCount = 0; + for (DSWallet *lWallet in self.wallets) { + invitationsCount += [lWallet invitationsCount]; + } + return invitationsCount; +} + +- (void)wipeInvitationsPersistedDataInContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + NSArray *objects = [DSBlockchainInvitationEntity objectsInContext:context matching:@"chain == %@", [self chainEntityInContext:context]]; + [DSBlockchainInvitationEntity deleteObjects:objects inContext:context]; + }]; +} + +// TODO: should we revive this? +// +//-(BOOL)registerBlockchainIdentityRegistrationTransaction:(DSIdentityRegistrationTransition*)identityRegistrationTransaction { +// DSWallet * identityWallet = [self walletHavingIdentityAuthenticationHash:identityRegistrationTransaction.pubkeyHash foundAtIndex:nil]; +// BOOL registered = [odentityWallet.specialTransactionsHolder registerTransaction:identityRegistrationTransaction]; +// +// if (identityWallet) { +// DSAuthenticationKeysDerivationPath * identitiesDerivationPath = [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:identityWallet]; +// [identitiesDerivationPath registerTransactionAddress:identityRegistrationTransaction.pubkeyAddress]; +// } +// return registered; +//} +// +//-(BOOL)registerIdentityResetTransaction:(DSIdentityUpdateTransition*)identityResetTransaction { +// DSWallet * identityWallet = [self walletHavingIdentityAuthenticationHash:identityResetTransaction.replacementPublicKeyHash foundAtIndex:nil]; +// [identityWallet.specialTransactionsHolder registerTransaction:identityResetTransaction]; +// DSWallet * identityRegistrationWallet = nil; +// DSTransaction * identityRegistrationTransaction = [self transactionForHash:identityResetTransaction.registrationTransactionHash returnWallet:&identityRegistrationWallet]; +// BOOL registered = NO; +// if (identityRegistrationTransaction && identityRegistrationWallet && (identityWallet != identityRegistrationWallet)) { +// registered = [identityRegistrationWallet.specialTransactionsHolder registerTransaction:identityResetTransaction]; +// } +// +// if (identityWallet) { +// DSAuthenticationKeysDerivationPath * identitiesDerivationPath = [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:identityWallet]; +// [identitiesDerivationPath registerTransactionAddress:identityResetTransaction.replacementAddress]; +// } +// return registered; +//} +// +//-(BOOL)registerIdentityCloseTransaction:(DSIdentityCloseTransition*)identityCloseTransaction { +// DSWallet * identityRegistrationWallet = nil; +// DSTransaction * identityRegistrationTransaction = [self transactionForHash:identityCloseTransaction.registrationTransactionHash returnWallet:&identityRegistrationWallet]; +// if (identityRegistrationTransaction && identityRegistrationWallet) { +// return [identityRegistrationWallet.specialTransactionsHolder registerTransaction:identityCloseTransaction]; +// } else { +// return NO; +// } +//} +// +//-(BOOL)registerIdentityTopupTransaction:(DSIdentityTopupTransition*)identityTopupTransaction { +// DSWallet * identityRegistrationWallet = nil; +// DSTransaction * identityRegistrationTransaction = [self transactionForHash:identityTopupTransaction.registrationTransactionHash returnWallet:&identityRegistrationWallet]; +// if (identityRegistrationTransaction && identityRegistrationWallet) { +// return [identityRegistrationWallet.specialTransactionsHolder registerTransaction:identityTopupTransaction]; +// } else { +// return NO; +// } +//} +// + +@end diff --git a/DashSync/shared/Models/Chain/DSChain+Params.h b/DashSync/shared/Models/Chain/DSChain+Params.h new file mode 100644 index 000000000..c414979cf --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Params.h @@ -0,0 +1,147 @@ +// +// 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 DSChain (Params) + +/*! @brief The chain type (MainNet, TestNet or DevNet). */ +@property (nonatomic, assign) dash_spv_crypto_network_chain_type_ChainType *chainType; + +// MARK: Sync + +/*! @brief The genesis hash is the hash of the first block of the chain. For a devnet this is actually the second block as the first block is created in a special way for devnets. */ +@property (nonatomic, assign) UInt256 genesisHash; + +/*! @brief headersMaxAmount is the maximum amount of headers that is expected from peers. */ +@property (nonatomic, assign) uint64_t headersMaxAmount; + +/*! @brief This is the minimum amount that can be entered into an amount for a output for it not to be considered dust. */ +@property (nonatomic, readonly) uint64_t minOutputAmount; + +/*! @brief The magic number is used in message headers to indicate what network (or chain) a message is intended for. */ +@property (nonatomic, readonly) uint32_t magicNumber; + +/*! @brief The base reward is the intial mining reward at genesis for the chain. This goes down by 7% every year. A SPV client does not validate that the reward amount is correct as it would not make sense for miners to enter incorrect rewards as the blocks would be rejected by full nodes. */ +@property (nonatomic, readonly) uint64_t baseReward; +@property (nonatomic, readonly) uint32_t coinType; + +/*! @brief minProtocolVersion is the minimum protocol version that peers on this chain can communicate with. This should only be changed in the case of devnets. */ +@property (nonatomic, assign) uint32_t minProtocolVersion; + +/*! @brief protocolVersion is the protocol version that we currently use for this chain. This should only be changed in the case of devnets. */ +@property (nonatomic, assign) uint32_t protocolVersion; + +/*! @brief maxProofOfWork is the lowest amount of work effort required to mine a block on the chain. */ +@property (nonatomic, readonly) UInt256 maxProofOfWork; + +/*! @brief maxProofOfWorkTarget is the lowest amount of work effort required to mine a block on the chain. Here it is represented as the compact target. */ +@property (nonatomic, readonly) uint32_t maxProofOfWorkTarget; + +/*! @brief allowMinDifficultyBlocks is set to TRUE on networks where mining is low enough that it can be attacked by increasing difficulty with ASICs and then no longer running ASICs. This is set to NO for Mainnet, and generally should be YES on all other networks. */ +@property (nonatomic, readonly) BOOL allowMinDifficultyBlocks; + +/*! @brief The number of minimumDifficultyBlocks. */ +@property (nonatomic, assign) uint32_t minimumDifficultyBlocks; + +/*! @brief The default transaction version used when sending transactions. */ +@property (nonatomic, readonly) uint16_t transactionVersion; + +/*! @brief A threshold after which a peer will be banned. */ +@property (nonatomic, readonly) uintptr_t peerMisbehavingThreshold; + +/*! @brief The flag represents whether the quorum rotation is enabled in this chain. */ +@property (nonatomic, assign) BOOL isRotatedQuorumsPresented; + +// MARK: Ports + +/*! @brief The standard port for the chain for L1 communication. */ +@property (nonatomic, assign) uint32_t standardPort; + +/*! @brief The standard port for the chain for L2 communication through JRPC. */ +@property (nonatomic, assign) uint32_t standardDapiJRPCPort; + +/*! @brief The standard port for the chain for L2 communication through GRPC. */ +@property (nonatomic, assign) uint32_t standardDapiGRPCPort; + +// MARK: Names and Identifiers + +/*! @brief The unique identifier of the chain. This unique id follows the same chain accross devices because it is the short hex string of the genesis hash. */ +@property (nonatomic, readonly) NSString *uniqueID; + +/*! @brief The name of the chain (Mainnet-Testnet-Devnet). */ +@property (nonatomic, readonly) NSString *name; + +/*! @brief The localized name of the chain (Mainnet-Testnet-Devnet). */ +@property (nonatomic, readonly) NSString *localizedName; + +/*! @brief The network name. Currently main, test, dev or reg. */ +@property (nonatomic, readonly) NSString *networkName; + + +- (void)setDevnetNetworkName:(NSString *)networkName; + +// MARK: Sporks + +/*! @brief The spork public key as a hex string. */ +@property (nonatomic, strong, nullable) NSString *sporkPublicKeyHexString; + +/*! @brief The spork private key as a base 58 string. */ +@property (nonatomic, strong, nullable) NSString *sporkPrivateKeyBase58String; + +/*! @brief The spork address base 58 string (addresses are known to be base 58). */ +@property (nonatomic, strong, nullable) NSString *sporkAddress; + + + +// MARK: - L2 Network Chain Info + +/*! @brief platformProtocolVersion is the protocol version that we currently use for the platform chain. This should only be changed in the case of devnets. */ +@property (nonatomic, assign) uint32_t platformProtocolVersion; + +/*! @brief The dpns contract id. */ +@property (nonatomic, assign) UInt256 dpnsContractID; + +/*! @brief The dashpay contract id. */ +@property (nonatomic, assign) UInt256 dashpayContractID; + + +// MARK: Fees + +@property (nonatomic, assign) uint64_t feePerByte; + +/*! @brief The fee for transactions in L1 are now entirely dependent on their size. */ +- (uint64_t)feeForTxSize:(NSUInteger)size; + + +// MARK: - Chain Info methods + +- (BOOL)isMainnet; +- (BOOL)isTestnet; +- (BOOL)isDevnetAny; +- (BOOL)isEvolutionEnabled; +- (BOOL)isDevnetWithGenesisHash:(UInt256)genesisHash; +- (BOOL)isCore19Active; +- (BOOL)isCore20Active; +- (BOOL)isCore20ActiveAtHeight:(uint32_t)height; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Chain/DSChain+Params.m b/DashSync/shared/Models/Chain/DSChain+Params.m new file mode 100644 index 000000000..cb8dc5096 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Params.m @@ -0,0 +1,685 @@ +// +// 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+Params.h" +#import "DSChainConstants.h" +#import "DSWallet.h" +#import "NSData+Dash.h" +#import "NSString+Bitcoin.h" +#import + +#define PROTOCOL_VERSION_LOCATION @"PROTOCOL_VERSION_LOCATION" +#define DEFAULT_MIN_PROTOCOL_VERSION_LOCATION @"MIN_PROTOCOL_VERSION_LOCATION" + +#define PLATFORM_PROTOCOL_VERSION_LOCATION @"PLATFORM_PROTOCOL_VERSION_LOCATION" +#define PLATFORM_DEFAULT_MIN_PROTOCOL_VERSION_LOCATION @"PLATFORM_MIN_PROTOCOL_VERSION_LOCATION" + +#define QUORUM_ROTATION_PRESENCE_KEY @"QUORUM_ROTATION_PRESENCE_KEY" + +#define STANDARD_PORT_LOCATION @"STANDARD_PORT_LOCATION" +#define JRPC_PORT_LOCATION @"JRPC_PORT_LOCATION" +#define GRPC_PORT_LOCATION @"GRPC_PORT_LOCATION" + +#define DPNS_CONTRACT_ID @"DPNS_CONTRACT_ID" +#define DASHPAY_CONTRACT_ID @"DASHPAY_CONTRACT_ID" + +#define MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY @"MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY" + +#define SPORK_PUBLIC_KEY_LOCATION @"SPORK_PUBLIC_KEY_LOCATION" +#define SPORK_ADDRESS_LOCATION @"SPORK_ADDRESS_LOCATION" +#define SPORK_PRIVATE_KEY_LOCATION @"SPORK_PRIVATE_KEY_LOCATION" + +NSString const *chainTypeKey = @"chainTypeKey"; +NSString const *protocolVersionKey = @"protocolVersionKey"; +NSString const *minProtocolVersionKey = @"minProtocolVersionKey"; +NSString const *standardPortKey = @"standardPortKey"; +NSString const *standardDapiGRPCPortKey = @"standardDapiGRPCPortKey"; +NSString const *standardDapiJRPCPortKey = @"standardDapiJRPCPortKey"; +NSString const *minimumDifficultyBlocksKey = @"minimumDifficultyBlocksKey"; +NSString const *maxProofOfWorkKey = @"maxProofOfWorkKey"; +NSString const *dpnsContractIDKey = @"dpnsContractIDKey"; +NSString const *dashpayContractIDKey = @"dashpayContractIDKey"; +NSString const *uniqueIDKey = @"uniqueIDKey"; +NSString const *networkNameKey = @"networkNameKey"; +NSString const *headersMaxAmountKey = @"headersMaxAmountKey"; +NSString const *genesisHashKey = @"genesisHashKey"; +NSString const *minOutputAmountKey = @"minOutputAmountKey"; +NSString const *cachedIsQuorumRotationPresentedKey = @"cachedIsQuorumRotationPresentedKey"; +NSString const *feePerByteKey = @"feePerByteKey"; + +@implementation DSChain (Params) + +- (dash_spv_crypto_network_chain_type_ChainType *)chainType { + NSValue *value = objc_getAssociatedObject(self, &chainTypeKey); + return value.pointerValue; +} +- (void)setChainType:(dash_spv_crypto_network_chain_type_ChainType *)chainType { + objc_setAssociatedObject(self, &chainTypeKey, [NSValue valueWithPointer:chainType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +// MARK: Sync Parameters + +- (uint32_t)magicNumber { + return (uint32_t) dash_spv_crypto_network_chain_type_ChainType_magic_number(self.chainType); +} + +- (void)setProtocolVersion:(uint32_t)protocolVersion { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + setKeychainInt(protocolVersion, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PROTOCOL_VERSION_LOCATION], NO); + } +} + +- (uint32_t)protocolVersion { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + return PROTOCOL_VERSION_MAINNET; //(70216 + (self.headersMaxAmount / 2000)); + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + return PROTOCOL_VERSION_TESTNET; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + uint32_t protocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PROTOCOL_VERSION_LOCATION], &error); + if (!error && protocolVersion) + return protocolVersion; + else + return PROTOCOL_VERSION_DEVNET; + } + } +} + +- (BOOL)isRotatedQuorumsPresented { + BOOL cachedIsQuorumRotationPresented = objc_getAssociatedObject(self, &cachedIsQuorumRotationPresentedKey); + if (cachedIsQuorumRotationPresented) return cachedIsQuorumRotationPresented; + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: { + NSError *error = nil; + BOOL isPresented = (BOOL)getKeychainInt([NSString stringWithFormat:@"MAINNET_%@", QUORUM_ROTATION_PRESENCE_KEY], &error); + objc_setAssociatedObject(self, &cachedIsQuorumRotationPresentedKey, @(!error && isPresented), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + break; + } + case dash_spv_crypto_network_chain_type_ChainType_TestNet: { + NSError *error = nil; + BOOL isPresented = (BOOL)getKeychainInt([NSString stringWithFormat:@"TESTNET_%@", QUORUM_ROTATION_PRESENCE_KEY], &error); + objc_setAssociatedObject(self, &cachedIsQuorumRotationPresentedKey, @(!error && isPresented), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + break; + } + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + BOOL isPresented = (BOOL)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], QUORUM_ROTATION_PRESENCE_KEY], &error); + objc_setAssociatedObject(self, &cachedIsQuorumRotationPresentedKey, @(!error && isPresented), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + break; + } + } + return objc_getAssociatedObject(self, &cachedIsQuorumRotationPresentedKey); +} + + +- (void)setIsRotatedQuorumsPresented:(BOOL)isRotatedQuorumsPresented { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + setKeychainInt(isRotatedQuorumsPresented, [NSString stringWithFormat:@"MAINNET_%@", QUORUM_ROTATION_PRESENCE_KEY], NO); + break; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + setKeychainInt(isRotatedQuorumsPresented, [NSString stringWithFormat:@"TESTNET_%@", QUORUM_ROTATION_PRESENCE_KEY], NO); + break; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + setKeychainInt(isRotatedQuorumsPresented, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], QUORUM_ROTATION_PRESENCE_KEY], NO); + break; + } + } + objc_setAssociatedObject(self, &cachedIsQuorumRotationPresentedKey, @(isRotatedQuorumsPresented), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (uint32_t)minProtocolVersion { + @synchronized(self) { + NSNumber *cachedMinProtocolVersion = objc_getAssociatedObject(self, &minProtocolVersionKey); + if (cachedMinProtocolVersion) return [cachedMinProtocolVersion unsignedIntValue]; + NSError *error = nil; + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: { + uint32_t minProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"MAINNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], &error); + uint32_t version = !error && minProtocolVersion ? MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_MAINNET) : DEFAULT_MIN_PROTOCOL_VERSION_MAINNET; + cachedMinProtocolVersion = @(version); + break; + } + case dash_spv_crypto_network_chain_type_ChainType_TestNet: { + uint32_t minProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"TESTNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], &error); + uint32_t version = !error && minProtocolVersion ? MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_TESTNET) : DEFAULT_MIN_PROTOCOL_VERSION_TESTNET; + cachedMinProtocolVersion = @(version); + break; + } + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + uint32_t minProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], &error); + uint32_t version = !error && minProtocolVersion ? MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_DEVNET) : DEFAULT_MIN_PROTOCOL_VERSION_DEVNET; + cachedMinProtocolVersion = @(version); + break; + } + } + objc_setAssociatedObject(self, &minProtocolVersionKey, cachedMinProtocolVersion, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return [cachedMinProtocolVersion unsignedIntValue]; + } +} + + +- (void)setMinProtocolVersion:(uint32_t)minProtocolVersion { + @synchronized(self) { + if (minProtocolVersion < MIN_VALID_MIN_PROTOCOL_VERSION || minProtocolVersion > MAX_VALID_MIN_PROTOCOL_VERSION) return; + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + setKeychainInt(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_MAINNET), [NSString stringWithFormat:@"MAINNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], NO); + objc_setAssociatedObject(self, &minProtocolVersionKey, @(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_MAINNET)), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + break; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + setKeychainInt(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_TESTNET), [NSString stringWithFormat:@"TESTNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], NO); + objc_setAssociatedObject(self, &minProtocolVersionKey, @(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_TESTNET)), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + break; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + setKeychainInt(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_DEVNET), [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], NO); + objc_setAssociatedObject(self, &minProtocolVersionKey, @(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_DEVNET)), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + break; + } + } + } +} + +- (uint32_t)standardPort { + NSNumber *cachedStandardPort = objc_getAssociatedObject(self, &standardPortKey); + if (cachedStandardPort) return [cachedStandardPort unsignedIntValue]; + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + objc_setAssociatedObject(self, &standardPortKey, @(MAINNET_STANDARD_PORT), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return MAINNET_STANDARD_PORT; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + objc_setAssociatedObject(self, &standardPortKey, @(TESTNET_STANDARD_PORT), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return TESTNET_STANDARD_PORT; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + uint32_t port = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], STANDARD_PORT_LOCATION], &error); + if (!error && port) { + objc_setAssociatedObject(self, &standardPortKey, @(port), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return port; + } else { + return DEVNET_STANDARD_PORT; + } + } + } +} + +- (void)setStandardPort:(uint32_t)standardPort { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &standardPortKey, @(standardPort), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + setKeychainInt(standardPort, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], STANDARD_PORT_LOCATION], NO); + } +} + +- (uint32_t)standardDapiGRPCPort { + NSNumber *cachedStandardDapiGRPCPort = objc_getAssociatedObject(self, &standardDapiGRPCPortKey); + if (cachedStandardDapiGRPCPort) return [cachedStandardDapiGRPCPort unsignedIntValue]; + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + objc_setAssociatedObject(self, &standardDapiGRPCPortKey, @(MAINNET_DAPI_GRPC_STANDARD_PORT), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return MAINNET_DAPI_GRPC_STANDARD_PORT; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + objc_setAssociatedObject(self, &standardDapiGRPCPortKey, @(TESTNET_DAPI_GRPC_STANDARD_PORT), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return TESTNET_DAPI_GRPC_STANDARD_PORT; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + uint32_t cachedStandardDapiGRPCPort = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], GRPC_PORT_LOCATION], &error); + if (!error && cachedStandardDapiGRPCPort) { + objc_setAssociatedObject(self, &standardDapiGRPCPortKey, @(cachedStandardDapiGRPCPort), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cachedStandardDapiGRPCPort; + } else + return DEVNET_DAPI_GRPC_STANDARD_PORT; + } + } +} + +- (void)setStandardDapiGRPCPort:(uint32_t)standardDapiGRPCPort { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &standardDapiGRPCPortKey, @(standardDapiGRPCPort), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + setKeychainInt(standardDapiGRPCPort, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], GRPC_PORT_LOCATION], NO); + } +} +- (uint32_t)standardDapiJRPCPort { + NSNumber *cached = objc_getAssociatedObject(self, &standardDapiJRPCPortKey); + if (cached) return [cached unsignedIntValue]; + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + objc_setAssociatedObject(self, &standardDapiJRPCPortKey, @(MAINNET_DAPI_JRPC_STANDARD_PORT), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return MAINNET_DAPI_JRPC_STANDARD_PORT; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + objc_setAssociatedObject(self, &standardDapiJRPCPortKey, @(TESTNET_DAPI_JRPC_STANDARD_PORT), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return TESTNET_DAPI_JRPC_STANDARD_PORT; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + uint32_t cachedStandardDapiJRPCPort = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], JRPC_PORT_LOCATION], &error); + if (!error && cachedStandardDapiJRPCPort) { + objc_setAssociatedObject(self, &standardDapiJRPCPortKey, @(cachedStandardDapiJRPCPort), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cachedStandardDapiJRPCPort; + } else + return DEVNET_DAPI_JRPC_STANDARD_PORT; + } + } +} + +- (void)setStandardDapiJRPCPort:(uint32_t)standardDapiJRPCPort { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &standardDapiJRPCPortKey, @(standardDapiJRPCPort), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + setKeychainInt(standardDapiJRPCPort, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], JRPC_PORT_LOCATION], NO); + } +} + +// MARK: Mining and Dark Gravity Wave Parameters + +- (UInt256)maxProofOfWork { + NSData *cachedMaxProofOfWork = objc_getAssociatedObject(self, &maxProofOfWorkKey); + if (cachedMaxProofOfWork != nil) { + UInt256 work = cachedMaxProofOfWork.UInt256; + if (uint256_is_not_zero(work)) + return work; + } + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + cachedMaxProofOfWork = MAX_PROOF_OF_WORK_MAINNET_DATA; + break; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + cachedMaxProofOfWork = MAX_PROOF_OF_WORK_TESTNET_DATA; + break; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: + cachedMaxProofOfWork = MAX_PROOF_OF_WORK_DEVNET_DATA; + break; + } + objc_setAssociatedObject(self, &maxProofOfWorkKey, cachedMaxProofOfWork, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cachedMaxProofOfWork.UInt256; +} + +- (uint32_t)maxProofOfWorkTarget { + return dash_spv_crypto_network_chain_type_ChainType_max_proof_of_work_target(self.chainType); +} + +- (BOOL)allowMinDifficultyBlocks { + return dash_spv_crypto_network_chain_type_ChainType_allow_min_difficulty_blocks(self.chainType); +} + +- (uint64_t)baseReward { + if (dash_spv_crypto_network_chain_type_ChainType_is_mainnet(self.chainType)) return 5 * DUFFS; + return 50 * DUFFS; +} +- (uint32_t)coinType { + return dash_spv_crypto_network_chain_type_ChainType_coin_type(self.chainType); +} + +// MARK: Spork Parameters + +- (NSString *)sporkPublicKeyHexString { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + return SPORK_PUBLIC_KEY_MAINNET; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + return SPORK_PUBLIC_KEY_TESTNET; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + NSString *publicKey = getKeychainString([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PUBLIC_KEY_LOCATION], &error); + if (!error && publicKey) { + return publicKey; + } else { + return nil; + } + } + } + return nil; +} + +- (void)setSporkPublicKeyHexString:(NSString *)sporkPublicKey { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + setKeychainString(sporkPublicKey, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PUBLIC_KEY_LOCATION], NO); + } +} + +- (NSString *)sporkPrivateKeyBase58String { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + NSError *error = nil; + NSString *publicKey = getKeychainString([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PRIVATE_KEY_LOCATION], &error); + if (!error && publicKey) { + return publicKey; + } + } + return nil; +} + +- (void)setSporkPrivateKeyBase58String:(NSString *)sporkPrivateKey { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + setKeychainString(sporkPrivateKey, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PRIVATE_KEY_LOCATION], YES); + } +} + +- (NSString *)sporkAddress { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + return SPORK_ADDRESS_MAINNET; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + return SPORK_ADDRESS_TESTNET; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + NSString *publicKey = getKeychainString([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_ADDRESS_LOCATION], &error); + if (!error && publicKey) { + return publicKey; + } else { + return nil; + } + } + } + return nil; +} + +- (void)setSporkAddress:(NSString *)sporkAddress { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + setKeychainString(sporkAddress, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_ADDRESS_LOCATION], NO); + } +} + +// MARK: - L2 Chain Parameters + +- (uint32_t)platformProtocolVersion { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + return PLATFORM_PROTOCOL_VERSION_MAINNET; //(70216 + (self.headersMaxAmount / 2000)); + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + return PLATFORM_PROTOCOL_VERSION_TESTNET; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = nil; + uint32_t platformProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PLATFORM_PROTOCOL_VERSION_LOCATION], &error); + if (!error && platformProtocolVersion) + return platformProtocolVersion; + else + return PLATFORM_PROTOCOL_VERSION_DEVNET; + } + } +} + +- (void)setPlatformProtocolVersion:(uint32_t)platformProtocolVersion { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + setKeychainInt(platformProtocolVersion, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PLATFORM_PROTOCOL_VERSION_LOCATION], NO); + } +} + +- (UInt256)dpnsContractID { + if (!self.isEvolutionEnabled) return UINT256_ZERO; + NSData *cachedDpnsContractIDData = objc_getAssociatedObject(self, &dpnsContractIDKey); + if (cachedDpnsContractIDData != nil) { + UInt256 cachedDpnsContractID = cachedDpnsContractIDData.UInt256; + if (uint256_is_not_zero(cachedDpnsContractID)) + return cachedDpnsContractID; + } + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + cachedDpnsContractIDData = MAINNET_DPNS_CONTRACT_ID.base58ToData; + break; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + cachedDpnsContractIDData = TESTNET_DPNS_CONTRACT_ID.base58ToData; + break; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = NULL; + NSData *data = getKeychainData([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DPNS_CONTRACT_ID], &error); + if (!error && data) { + cachedDpnsContractIDData = data; + break; + } else { + return UINT256_ZERO; + } + } + } + objc_setAssociatedObject(self, &dpnsContractIDKey, cachedDpnsContractIDData, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cachedDpnsContractIDData.UInt256; + +} + +- (void)setDpnsContractID:(UInt256)dpnsContractID { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &dpnsContractIDKey, uint256_data(dpnsContractID), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + NSString *identifier = [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DPNS_CONTRACT_ID]; + if (uint256_is_zero(dpnsContractID)) { + NSError *error = nil; + BOOL hasDashpayContractID = (getKeychainData(identifier, &error) != nil); + if (hasDashpayContractID) { + setKeychainData(nil, identifier, NO); + } + } else { + setKeychainData(uint256_data(dpnsContractID), identifier, NO); + } + } +} + +- (UInt256)dashpayContractID { + if (!self.isEvolutionEnabled) return UINT256_ZERO; + NSData *cachedData = objc_getAssociatedObject(self, &dashpayContractIDKey); + if (cachedData != nil) { + UInt256 cachedDpnsContractID = cachedData.UInt256; + if (uint256_is_not_zero(cachedDpnsContractID)) + return cachedDpnsContractID; + } + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + cachedData = MAINNET_DASHPAY_CONTRACT_ID.base58ToData; + break; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + cachedData = TESTNET_DASHPAY_CONTRACT_ID.base58ToData; + break; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSError *error = NULL; + NSData *data = getKeychainData([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DASHPAY_CONTRACT_ID], &error); + + if (!error && data) { + cachedData = data; + break; + } else { + return UINT256_ZERO; + } + } + } + objc_setAssociatedObject(self, &dashpayContractIDKey, cachedData, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cachedData.UInt256; +} + +- (void)setDashpayContractID:(UInt256)dashpayContractID { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &dashpayContractIDKey, uint256_data(dashpayContractID), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (uint256_is_zero(dashpayContractID)) { + NSError *error = nil; + NSString *identifier = [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DASHPAY_CONTRACT_ID]; + BOOL hasDashpayContractID = (getKeychainData(identifier, &error) != nil); + if (hasDashpayContractID) { + setKeychainData(nil, identifier, NO); + } + } else { + setKeychainData(uint256_data(dashpayContractID), [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DASHPAY_CONTRACT_ID], NO); + } + } +} + +- (void)setMinimumDifficultyBlocks:(uint32_t)minimumDifficultyBlocks { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &minimumDifficultyBlocksKey, @(minimumDifficultyBlocks), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + setKeychainInt(minimumDifficultyBlocks, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY], NO); + } +} + +- (uint32_t)minimumDifficultyBlocks { + NSNumber *cached = objc_getAssociatedObject(self, &minimumDifficultyBlocksKey); + if (cached) return [cached unsignedIntValue]; + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + NSError *error = nil; + uint32_t cachedMinimumDifficultyBlocks = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY], &error); + + if (!error && cachedMinimumDifficultyBlocks) { + objc_setAssociatedObject(self, &minimumDifficultyBlocksKey, @(cachedMinimumDifficultyBlocks), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cachedMinimumDifficultyBlocks; + } else { + return 0; + } + } else { + objc_setAssociatedObject(self, &minimumDifficultyBlocksKey, @(0), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return 0; + } +} + +// MARK: - Names and Identifiers + +- (NSString *)uniqueID { + NSString *cached = objc_getAssociatedObject(self, &uniqueIDKey); + if (!cached) { + cached = [[NSData dataWithUInt256:[self genesisHash]] shortHexString]; + objc_setAssociatedObject(self, &uniqueIDKey, cached, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return cached; +} + + +- (NSString *)networkName { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + return @"main"; + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + return @"test"; + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSString *cached = objc_getAssociatedObject(self, &networkNameKey); + if (cached) return cached; + return @"dev"; + } + } +} + +- (NSString *)name { + return [DSKeyManager NSStringFrom:dash_spv_crypto_network_chain_type_ChainType_name(self.chainType)]; +} + +- (NSString *)localizedName { + switch (self.chainType->tag) { + case dash_spv_crypto_network_chain_type_ChainType_MainNet: + return DSLocalizedString(@"Mainnet", nil); + case dash_spv_crypto_network_chain_type_ChainType_TestNet: + return DSLocalizedString(@"Testnet", nil); + case dash_spv_crypto_network_chain_type_ChainType_DevNet: { + NSString *cached = objc_getAssociatedObject(self, &networkNameKey); + if (cached) return cached; + cached = [DSKeyManager NSStringFrom:dash_spv_crypto_network_chain_type_DevnetType_name(self.chainType->dev_net)]; + objc_setAssociatedObject(self, &networkNameKey, cached, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return cached; + } + } +} + +- (void)setDevnetNetworkName:(NSString *)networkName { + if (dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType)) { + objc_setAssociatedObject(self, &networkNameKey, @"Evonet", OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } +} + +- (uint16_t)transactionVersion { + return dash_spv_crypto_network_chain_type_ChainType_transaction_version(self.chainType); +} + +- (uintptr_t)peerMisbehavingThreshold { + return dash_spv_crypto_network_chain_type_ChainType_peer_misbehaving_threshold(self.chainType); +} + + +- (uint64_t)headersMaxAmount { + NSNumber *cached = objc_getAssociatedObject(self, &headersMaxAmountKey); + return [cached unsignedLongValue]; +} +- (void)setHeadersMaxAmount:(uint64_t)headersMaxAmount { + objc_setAssociatedObject(self, &headersMaxAmountKey, @(headersMaxAmount), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (UInt256)genesisHash { + NSData *data = objc_getAssociatedObject(self, &genesisHashKey); + return data.UInt256; +} + +- (void)setGenesisHash:(UInt256)genesisHash { + objc_setAssociatedObject(self, &genesisHashKey, uint256_data(genesisHash), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +// outputs below this amount are uneconomical due to fees +- (uint64_t)minOutputAmount { + uint64_t amount = (TX_MIN_OUTPUT_AMOUNT * self.feePerByte + MIN_FEE_PER_B - 1) / MIN_FEE_PER_B; + return (amount > TX_MIN_OUTPUT_AMOUNT) ? amount : TX_MIN_OUTPUT_AMOUNT; + +} + +// MARK: Fee Parameters + +// fee that will be added for a transaction of the given size in bytes +- (uint64_t)feeForTxSize:(NSUInteger)size { + uint64_t standardFee = size * TX_FEE_PER_B; //!OCLINT // standard fee based on tx size +#if (!!FEE_PER_KB_URL) + uint64_t fee = ((size * self.feePerByte + 99) / 100) * 100; // fee using feePerByte, rounded up to nearest 100 satoshi + return (fee > standardFee) ? fee : standardFee; +#else + return standardFee; +#endif +} + +- (uint64_t)feePerByte { + NSNumber *value = objc_getAssociatedObject(self, &feePerByteKey); + return [value unsignedLongValue]; +} +- (void)setFeePerByte:(uint64_t)feePerByte { + objc_setAssociatedObject(self, &feePerByteKey, @(feePerByte), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +// MARK: - Check Type + +- (BOOL)isMainnet { + return dash_spv_crypto_network_chain_type_ChainType_is_mainnet(self.chainType); +} + +- (BOOL)isTestnet { + return dash_spv_crypto_network_chain_type_ChainType_is_testnet(self.chainType); +} + +- (BOOL)isDevnetAny { + return dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType); +} + +- (BOOL)isEvolutionEnabled { + return YES; +} + +- (BOOL)isDevnetWithGenesisHash:(UInt256)genesisHash { + return [self isDevnetAny] && uint256_eq([self genesisHash], genesisHash); +} + +// MARK: - Helpers + +- (BOOL)isCore19Active { + return self.lastTerminalBlockHeight >= dash_spv_crypto_network_chain_type_ChainType_core19_activation_height(self.chainType); +} + +- (BOOL)isCore20Active { + return self.lastTerminalBlockHeight >= dash_spv_crypto_network_chain_type_ChainType_core20_activation_height(self.chainType); +} + +- (BOOL)isCore20ActiveAtHeight:(uint32_t)height { + return height >= dash_spv_crypto_network_chain_type_ChainType_core20_activation_height(self.chainType); +} + +//- (KeyKind)activeBLSType { +// return [self isCore19Active] ? KeyKind_BLSBasic : KeyKind_BLS; +//} + +@end diff --git a/DashSync/shared/Models/Chain/DSChain+Protected.h b/DashSync/shared/Models/Chain/DSChain+Protected.h index d69e39732..c607c810f 100644 --- a/DashSync/shared/Models/Chain/DSChain+Protected.h +++ b/DashSync/shared/Models/Chain/DSChain+Protected.h @@ -46,6 +46,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)setBlockHeight:(int32_t)height andTimestamp:(NSTimeInterval)timestamp forTransactionHashes:(NSArray *)txHashes; - (void)clearOrphans; - (void)blockUntilGetInsightForBlockHash:(UInt256)blockHash; +- (DSBlock *_Nullable)blockUntilGetInsightForBlockHeight:(uint32_t)blockHeight; + - (void)addInsightVerifiedBlock:(DSBlock *)block forBlockHash:(UInt256)blockHash; @property (nonatomic, readonly) BOOL allowInsightBlocksForVerification; @@ -72,25 +74,9 @@ NS_ASSUME_NONNULL_BEGIN // MARK: - Wallet, Accounts and Transactions -/*! @brief Add a wallet to the chain. It is only temporarily in the chain if externaly added this way. */ -- (BOOL)addWallet:(DSWallet *)wallet; - -- (BOOL)registerSpecialTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately; - -- (void)triggerUpdatesForLocalReferences:(DSTransaction *)transaction; -- (void)reloadDerivationPaths; -// MARK: Wallet Discovery -- (DSWallet *_Nullable)walletHavingBlockchainIdentityCreditFundingRegistrationHash:(UInt160)creditFundingRegistrationHash foundAtIndex:(uint32_t *_Nullable)rIndex; -- (DSWallet *_Nullable)walletHavingBlockchainIdentityCreditFundingTopupHash:(UInt160)creditFundingTopupHash foundAtIndex:(uint32_t *)rIndex; -- (DSWallet *_Nullable)walletHavingBlockchainIdentityCreditFundingInvitationHash:(UInt160)creditFundingInvitationHash foundAtIndex:(uint32_t *)rIndex; -- (DSWallet *_Nullable)walletHavingProviderVotingAuthenticationHash:(UInt160)votingAuthenticationHash foundAtIndex:(uint32_t *_Nullable)rIndex; -- (DSWallet *_Nullable)walletHavingProviderOwnerAuthenticationHash:(UInt160)owningAuthenticationHash foundAtIndex:(uint32_t *_Nullable)rIndex; -- (DSWallet *_Nullable)walletHavingProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey foundAtIndex:(uint32_t *_Nullable)rIndex; -- (DSWallet *_Nullable)walletHavingPlatformNodeAuthenticationHash:(UInt160)platformNodeAuthenticationHash foundAtIndex:(uint32_t *_Nullable)rIndex; -- (DSWallet *_Nullable)walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:(DSProviderRegistrationTransaction *_Nonnull)transaction foundAtIndex:(uint32_t *_Nullable)rIndex; // MARK: - Standalone Derivation Paths @@ -101,7 +87,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) UInt256 masternodeBaseBlockHash; -- (void)updateAddressUsageOfSimplifiedMasternodeEntries:(NSArray *)simplifiedMasternodeEntries; +- (void)updateAddressUsageOfSimplifiedMasternodeEntries:(DMasternodeEntryList *)simplifiedMasternodeEntries; /*! @brief The header locator array is an array of the 10 most recent block hashes in decending order followed by block hashes that double the step back each iteration in decending order and finishing with the previous known checkpoint after that last hash. Something like (top, -1, -2, -3, -4, -5, -6, -7, -8, -9, -11, -15, -23, -39, -71, -135, ..., 0). */ @property (nonatomic, readonly, nullable) NSArray *terminalBlocksLocatorArray; @@ -127,6 +113,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)saveBlockLocators; - (void)saveTerminalBlocks; +- (void)resetLastSyncBlock; @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Chain/DSChain+Transaction.h b/DashSync/shared/Models/Chain/DSChain+Transaction.h new file mode 100644 index 000000000..ca84169e5 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Transaction.h @@ -0,0 +1,51 @@ +// +// 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" +#import "DSTransaction.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSChain (Transaction) + +// MARK: - Transactions + +/*! @brief Returns all wallet transactions sorted by date, most recent first. */ +@property (nonatomic, readonly) NSArray *allTransactions; + +/*! @brief Returns the transaction with the given hash if it's been registered in any wallet on the chain (might also return non-registered) */ +- (DSTransaction *_Nullable)transactionForHash:(UInt256)txHash; + +/*! @brief Returns the amount received globally from the transaction (total outputs to change and/or receive addresses) */ +- (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction; + +/*! @brief Returns the amount sent globally by the trasaction (total wallet outputs consumed, change and fee included) */ +- (uint64_t)amountSentByTransaction:(DSTransaction *)transaction; + +/*! @brief Returns if this transaction has any local references. Local references are a pubkey hash contained in a wallet, pubkeys in wallets special derivation paths, or anything that would make the transaction relevant for this device. */ +- (BOOL)transactionHasLocalReferences:(DSTransaction *)transaction; + + +// MARK: Protected +- (BOOL)registerSpecialTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately; + +- (void)triggerUpdatesForLocalReferences:(DSTransaction *)transaction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Chain/DSChain+Transaction.m b/DashSync/shared/Models/Chain/DSChain+Transaction.m new file mode 100644 index 000000000..b48606d04 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Transaction.m @@ -0,0 +1,347 @@ +// +// 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 "DSAccount.h" +#import "DSAssetLockTransaction.h" +#import "DSAssetUnlockTransaction.h" +#import "DSAuthenticationKeysDerivationPath.h" +#import "DSIdentity+Protected.h" +#import "DSInvitation+Protected.h" +#import "DSChain+Transaction.h" +#import "DSChain+Params.h" +#import "DSChain+Wallet.h" +#import "DSChainManager.h" +#import "DSDerivationPathFactory.h" +#import "DSLocalMasternode+Protected.h" +#import "DSMasternodeHoldingsDerivationPath.h" +#import "DSMasternodeManager+LocalMasternode.h" +#import "DSProviderRegistrationTransaction.h" +#import "DSProviderUpdateRegistrarTransaction.h" +#import "DSProviderUpdateRevocationTransaction.h" +#import "DSProviderUpdateServiceTransaction.h" +#import "DSSpecialTransactionsWalletHolder.h" +#import "DSWallet.h" +#import "DSWallet+Identity.h" +#import "DSWallet+Invitation.h" +#import "DSWallet+Protected.h" + +@implementation DSChain (Transaction) + +// MARK: - Transactions + +- (DSTransaction *)transactionForHash:(UInt256)txHash { + return [self transactionForHash:txHash returnWallet:nil]; +} + +- (DSTransaction *)transactionForHash:(UInt256)txHash returnWallet:(DSWallet **)rWallet { + for (DSWallet *wallet in self.wallets) { + DSTransaction *transaction = [wallet transactionForHash:txHash]; + if (transaction) { + if (rWallet) *rWallet = wallet; + return transaction; + } + } + return nil; +} + +- (NSArray *)allTransactions { + NSMutableArray *mArray = [NSMutableArray array]; + for (DSWallet *wallet in self.wallets) { + [mArray addObjectsFromArray:wallet.allTransactions]; + } + return mArray; +} + +// retuns the amount sent globally by the trasaction (total wallet outputs consumed, change and fee included) +- (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction { + NSParameterAssert(transaction); + + uint64_t received = 0; + for (DSWallet *wallet in self.wallets) { + received += [wallet amountReceivedFromTransaction:transaction]; + } + return received; +} + +// retuns the amount sent globally by the trasaction (total wallet outputs consumed, change and fee included) +- (uint64_t)amountSentByTransaction:(DSTransaction *)transaction { + NSParameterAssert(transaction); + + uint64_t sent = 0; + for (DSWallet *wallet in self.wallets) { + sent += [wallet amountSentByTransaction:transaction]; + } + return sent; +} + +// MARK: - Special Transactions + +//Does the chain mat +- (BOOL)transactionHasLocalReferences:(DSTransaction *)transaction { + if ([self firstAccountThatCanContainTransaction:transaction]) return TRUE; + + //PROVIDERS + if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { + DSProviderRegistrationTransaction *tx = (DSProviderRegistrationTransaction *)transaction; + if ([self walletHavingProviderOwnerAuthenticationHash:tx.ownerKeyHash foundAtIndex:nil]) return TRUE; + if ([self walletHavingProviderVotingAuthenticationHash:tx.votingKeyHash foundAtIndex:nil]) return TRUE; + if ([self walletHavingProviderOperatorAuthenticationKey:tx.operatorKey foundAtIndex:nil]) return TRUE; + if ([self walletHavingPlatformNodeAuthenticationHash:tx.platformNodeID foundAtIndex:nil]) return TRUE; + if ([self walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:tx foundAtIndex:nil]) return TRUE; + if ([self accountContainingAddress:tx.payoutAddress]) return TRUE; + } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { + DSProviderUpdateServiceTransaction *tx = (DSProviderUpdateServiceTransaction *)transaction; + if ([self transactionForHash:tx.providerRegistrationTransactionHash]) return TRUE; + if ([self accountContainingAddress:tx.payoutAddress]) return TRUE; + } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { + DSProviderUpdateRegistrarTransaction *tx = (DSProviderUpdateRegistrarTransaction *)transaction; + if ([self walletHavingProviderVotingAuthenticationHash:tx.votingKeyHash foundAtIndex:nil]) return TRUE; + if ([self walletHavingProviderOperatorAuthenticationKey:tx.operatorKey foundAtIndex:nil]) return TRUE; + if ([self transactionForHash:tx.providerRegistrationTransactionHash]) return TRUE; + if ([self accountContainingAddress:tx.payoutAddress]) return TRUE; + } else if ([transaction isKindOfClass:[DSProviderUpdateRevocationTransaction class]]) { + DSProviderUpdateRevocationTransaction *tx = (DSProviderUpdateRevocationTransaction *)transaction; + if ([self transactionForHash:tx.providerRegistrationTransactionHash]) return TRUE; + + //BLOCKCHAIN USERS + } + // TODO: asset locks/unlocks/transitions? + + // else if ([transaction isKindOfClass:[DSIdentityRegistrationTransition class]]) { + // DSIdentityRegistrationTransition * identityRegistrationTransaction = (DSIdentityRegistrationTransition *)transaction; + // if ([self walletHavingIdentityAuthenticationHash:identityRegistrationTransaction.pubkeyHash foundAtIndex:nil]) return TRUE; + // } else if ([transaction isKindOfClass:[DSIdentityUpdateTransition class]]) { + // DSIdentityUpdateTransition * identityResetTransaction = (DSIdentityUpdateTransition *)transaction; + // if ([self walletHavingIdentityAuthenticationHash:identityResetTransaction.replacementPublicKeyHash foundAtIndex:nil]) return TRUE; + // if ([self transactionForHash:identityResetTransaction.registrationTransactionHash]) return TRUE; + // } else if ([transaction isKindOfClass:[DSIdentityCloseTransition class]]) { + // DSIdentityCloseTransition * identityCloseTransaction = (DSIdentityCloseTransition *)transaction; + // if ([self transactionForHash:identityCloseTransaction.registrationTransactionHash]) return TRUE; + // } else if ([transaction isKindOfClass:[DSIdentityTopupTransition class]]) { + // DSIdentityTopupTransition * identityTopupTransaction = (DSIdentityTopupTransition *)transaction; + // if ([self transactionForHash:identityTopupTransaction.registrationTransactionHash]) return TRUE; + // } + return FALSE; +} + +// MARK: - Registering special transactions + + +- (BOOL)registerProviderRegistrationTransaction:(DSProviderRegistrationTransaction *)transaction + saveImmediately:(BOOL)saveImmediately { + DSWallet *ownerWallet = [self walletHavingProviderOwnerAuthenticationHash:transaction.ownerKeyHash foundAtIndex:nil]; + DSWallet *votingWallet = [self walletHavingProviderVotingAuthenticationHash:transaction.votingKeyHash foundAtIndex:nil]; + DSWallet *operatorWallet = [self walletHavingProviderOperatorAuthenticationKey:transaction.operatorKey foundAtIndex:nil]; + DSWallet *holdingWallet = [self walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:transaction foundAtIndex:nil]; + DSWallet *platformNodeWallet = [self walletHavingPlatformNodeAuthenticationHash:transaction.platformNodeID foundAtIndex:nil]; + DSAccount *account = [self accountContainingAddress:transaction.payoutAddress]; + BOOL registered = NO; + registered |= [account registerTransaction:transaction saveImmediately:saveImmediately]; + registered |= [ownerWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + registered |= [votingWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + registered |= [operatorWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + registered |= [holdingWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + registered |= [platformNodeWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + + if (ownerWallet) { + DSAuthenticationKeysDerivationPath *ownerDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOwnerKeysDerivationPathForWallet:ownerWallet]; + [ownerDerivationPath registerTransactionAddress:transaction.ownerAddress]; + } + + if (votingWallet) { + DSAuthenticationKeysDerivationPath *votingDerivationPath = [[DSDerivationPathFactory sharedInstance] providerVotingKeysDerivationPathForWallet:votingWallet]; + [votingDerivationPath registerTransactionAddress:transaction.votingAddress]; + } + + if (operatorWallet) { + DSAuthenticationKeysDerivationPath *operatorDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOperatorKeysDerivationPathForWallet:operatorWallet]; + [operatorDerivationPath registerTransactionAddress:transaction.operatorAddress]; + } + + if (holdingWallet) { + DSMasternodeHoldingsDerivationPath *holdingDerivationPath = [[DSDerivationPathFactory sharedInstance] providerFundsDerivationPathForWallet:holdingWallet]; + [holdingDerivationPath registerTransactionAddress:transaction.holdingAddress]; + } + + if (platformNodeWallet) { + DSAuthenticationKeysDerivationPath *platformNodeDerivationPath = [[DSDerivationPathFactory sharedInstance] platformNodeKeysDerivationPathForWallet:platformNodeWallet]; + [platformNodeDerivationPath registerTransactionAddress:transaction.platformNodeAddress]; + } + + return registered; +} + +- (BOOL)registerProviderUpdateServiceTransaction:(DSProviderUpdateServiceTransaction *)transaction saveImmediately:(BOOL)saveImmediately { + DSWallet *wallet = nil; + DSTransaction *providerRegistrationTransaction = [self transactionForHash:transaction.providerRegistrationTransactionHash returnWallet:&wallet]; + DSAccount *account = [self accountContainingAddress:transaction.payoutAddress]; + BOOL registered = [account registerTransaction:transaction saveImmediately:saveImmediately]; + if (providerRegistrationTransaction && wallet) { + registered |= [wallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + } + return registered; +} + + +- (BOOL)registerProviderUpdateRegistrarTransaction:(DSProviderUpdateRegistrarTransaction *)transaction saveImmediately:(BOOL)saveImmediately { + DSWallet *votingWallet = [self walletHavingProviderVotingAuthenticationHash:transaction.votingKeyHash foundAtIndex:nil]; + DSWallet *operatorWallet = [self walletHavingProviderOperatorAuthenticationKey:transaction.operatorKey foundAtIndex:nil]; + [votingWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + [operatorWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + DSWallet *providerRegistrationWallet = nil; + DSTransaction *providerRegistrationTransaction = [self transactionForHash:transaction.providerRegistrationTransactionHash returnWallet:&providerRegistrationWallet]; + DSAccount *account = [self accountContainingAddress:transaction.payoutAddress]; + BOOL registered = [account registerTransaction:transaction saveImmediately:saveImmediately]; + if (providerRegistrationTransaction && providerRegistrationWallet) + registered |= [providerRegistrationWallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + + if (votingWallet) { + DSAuthenticationKeysDerivationPath *votingDerivationPath = [[DSDerivationPathFactory sharedInstance] providerVotingKeysDerivationPathForWallet:votingWallet]; + [votingDerivationPath registerTransactionAddress:transaction.votingAddress]; + } + + if (operatorWallet) { + DSAuthenticationKeysDerivationPath *operatorDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOperatorKeysDerivationPathForWallet:operatorWallet]; + [operatorDerivationPath registerTransactionAddress:transaction.operatorAddress]; + } + return registered; +} + +- (BOOL)registerProviderUpdateRevocationTransaction:(DSProviderUpdateRevocationTransaction *)transaction saveImmediately:(BOOL)saveImmediately { + DSWallet *wallet = nil; + DSTransaction *providerRegistrationTransaction = [self transactionForHash:transaction.providerRegistrationTransactionHash returnWallet:&wallet]; + if (providerRegistrationTransaction && wallet) { + return [wallet.specialTransactionsHolder registerTransaction:transaction saveImmediately:saveImmediately]; + } else { + return NO; + } +} +- (BOOL)registerAssetLockTransaction:(DSAssetLockTransaction *)transaction saveImmediately:(BOOL)saveImmediately { + DSAssetLockTransaction *assetLockTransaction = (DSAssetLockTransaction *)transaction; + UInt160 creditBurnPublicKeyHash = assetLockTransaction.creditBurnPublicKeyHash; + NSMutableString *debugString = [NSMutableString stringWithFormat:@"[%@] Registered AssetLockTX: creditBurnPublicKeyHash: %@, txHash: %@", self.name, uint160_hex(creditBurnPublicKeyHash), uint256_hex(assetLockTransaction.txHash)]; + BOOL isNewIdentity = FALSE; + DSIdentity *identity = nil; + uint32_t index; + DSWallet *wallet = [self walletHavingIdentityAssetLockRegistrationHash:creditBurnPublicKeyHash foundAtIndex:&index]; + if (!wallet) + wallet = [self walletHavingIdentityAssetLockTopupHash:creditBurnPublicKeyHash foundAtIndex:&index]; + + if (wallet) { + identity = [wallet identityForUniqueId:assetLockTransaction.creditBurnIdentityIdentifier]; + [debugString appendFormat:@" (Found wallet: %@, identity: %@)", wallet.uniqueIDString, identity]; + } + + if (!identity) { + [self triggerUpdatesForLocalReferences:assetLockTransaction]; + if (wallet) { + identity = [wallet identityForUniqueId:assetLockTransaction.creditBurnIdentityIdentifier]; +// [debugString appendFormat:@" (Found wallet after trigger updates: %@, identity: %@)", wallet.uniqueIDString, identity]; + if (identity) isNewIdentity = TRUE; + } + } else if (identity && !identity.registrationAssetLockTransaction) { +// [debugString appendFormat:@" (identity: %@ is known but has no asset lock tx)", identity]; + identity.registrationAssetLockTransactionHash = assetLockTransaction.txHash; + } else if (identity && ![identity containsTopupTransaction:transaction]) { + // TODO: what about topup transactions? how to distinguish them from registration + // TODO: For now use this solution + [identity.topupAssetLockTransactionHashes addObject:uint256_data(transaction.txHash)]; + } + DSLog(@"%@:", debugString); + if (!saveImmediately && identity && isNewIdentity) { + NSTimeInterval walletCreationTime = [identity.wallet walletCreationTime]; + if ((walletCreationTime == BIP39_WALLET_UNKNOWN_CREATION_TIME || walletCreationTime == BIP39_CREATION_TIME) && [identity isDefault]) { + [identity.wallet setGuessedWalletCreationTime:self.lastSyncBlockTimestamp - HOUR_TIME_INTERVAL - (DAY_TIME_INTERVAL / arc4random() % DAY_TIME_INTERVAL)]; + } + [self.chainManager.identitiesManager checkAssetLockTransactionForPossibleNewIdentity:assetLockTransaction]; + } + return isNewIdentity; +} + +- (BOOL)registerSpecialTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately { + if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { + return [self registerProviderRegistrationTransaction:(DSProviderRegistrationTransaction *)transaction saveImmediately:saveImmediately]; + } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { + return [self registerProviderUpdateServiceTransaction:(DSProviderUpdateServiceTransaction *)transaction saveImmediately:saveImmediately]; + } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { + return [self registerProviderUpdateRegistrarTransaction:(DSProviderUpdateRegistrarTransaction *)transaction saveImmediately:saveImmediately]; + } else if ([transaction isKindOfClass:[DSProviderUpdateRevocationTransaction class]]) { + return [self registerProviderUpdateRevocationTransaction:(DSProviderUpdateRevocationTransaction *)transaction saveImmediately:saveImmediately]; + } else if ([transaction isKindOfClass:[DSAssetLockTransaction class]]) { + return [self registerAssetLockTransaction:(DSAssetLockTransaction *)transaction saveImmediately:saveImmediately]; + } + return FALSE; +} + + +- (void)triggerUpdatesForLocalReferences:(DSTransaction *)transaction { + if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { + DSProviderRegistrationTransaction *providerRegistrationTransaction = (DSProviderRegistrationTransaction *)transaction; + if ([self walletHavingProviderOwnerAuthenticationHash:providerRegistrationTransaction.ownerKeyHash foundAtIndex:nil] || + [self walletHavingProviderVotingAuthenticationHash:providerRegistrationTransaction.votingKeyHash foundAtIndex:nil] || + [self walletHavingProviderOperatorAuthenticationKey:providerRegistrationTransaction.operatorKey foundAtIndex:nil] || + [self walletHavingPlatformNodeAuthenticationHash:providerRegistrationTransaction.platformNodeID foundAtIndex:nil]) { + [self.chainManager.masternodeManager localMasternodeFromProviderRegistrationTransaction:providerRegistrationTransaction save:TRUE]; + } + } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { + DSProviderUpdateServiceTransaction *tx = (DSProviderUpdateServiceTransaction *)transaction; + DSLocalMasternode *localMasternode = [self.chainManager.masternodeManager localMasternodeHavingProviderRegistrationTransactionHash:tx.providerRegistrationTransactionHash]; + [localMasternode updateWithUpdateServiceTransaction:tx save:TRUE]; + } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { + DSProviderUpdateRegistrarTransaction *tx = (DSProviderUpdateRegistrarTransaction *)transaction; + DSLocalMasternode *localMasternode = [self.chainManager.masternodeManager localMasternodeHavingProviderRegistrationTransactionHash:tx.providerRegistrationTransactionHash]; + [localMasternode updateWithUpdateRegistrarTransaction:tx save:TRUE]; + } else if ([transaction isKindOfClass:[DSProviderUpdateRevocationTransaction class]]) { + DSProviderUpdateRevocationTransaction *tx = (DSProviderUpdateRevocationTransaction *)transaction; + DSLocalMasternode *localMasternode = [self.chainManager.masternodeManager localMasternodeHavingProviderRegistrationTransactionHash:tx.providerRegistrationTransactionHash]; + [localMasternode updateWithUpdateRevocationTransaction:tx save:TRUE]; + } else if ([transaction isKindOfClass:[DSAssetLockTransaction class]]) { + DSAssetLockTransaction *tx = (DSAssetLockTransaction *)transaction; + UInt160 creditBurnPublicKeyHash = tx.creditBurnPublicKeyHash; + uint32_t index; + DSWallet *wallet = [self walletHavingIdentityAssetLockRegistrationHash:creditBurnPublicKeyHash foundAtIndex:&index]; + if (wallet) { + DSIdentity *identity = [wallet identityForUniqueId:tx.creditBurnIdentityIdentifier]; + if (!identity) { + identity = [[DSIdentity alloc] initAtIndex:index withAssetLockTransaction:tx inWallet:wallet]; + [identity registerInWalletForAssetLockTransaction:tx]; + } + } else { + wallet = [self walletHavingIdentityAssetLockTopupHash:creditBurnPublicKeyHash foundAtIndex:&index]; + if (wallet) { + DSIdentity *identity = [wallet identityForUniqueId:tx.creditBurnIdentityIdentifier]; +// [identity r] + + } else { + wallet = [self walletHavingIdentityAssetLockInvitationHash:creditBurnPublicKeyHash foundAtIndex:&index]; + if (wallet) { + DSInvitation *invitation = [wallet invitationForUniqueId:tx.creditBurnIdentityIdentifier]; + if (!invitation) { + invitation = [[DSInvitation alloc] initAtIndex:index withAssetLockTransaction:tx inWallet:wallet]; + [invitation registerInWalletForAssetLockTransaction:tx]; + } + } + } + } + + } +// else if ([transaction isKindOfClass:[DSAssetUnlockTransaction class]]) { +// DSAssetUnlockTransaction *tx = (DSAssetUnlockTransaction *)transaction; +// } +} + +@end diff --git a/DashSync/shared/Models/Chain/DSChain+Wallet.h b/DashSync/shared/Models/Chain/DSChain+Wallet.h new file mode 100644 index 000000000..9982b4e7b --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Wallet.h @@ -0,0 +1,105 @@ +// +// 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 "DSWallet.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSChain (Wallet) + +// MARK: - Wallets + +/*! @brief The wallets in the chain. */ +@property (nonatomic, readonly) NSArray *wallets; +@property (nonatomic, strong) NSMutableArray *mWallets; + +/*! @brief Conveniance method. Does this walleet have a chain? */ +@property (nonatomic, readonly) BOOL hasAWallet; + +/*! @brief Conveniance method. The earliest known creation time for any wallet in this chain. */ +@property (nonatomic, readonly) NSTimeInterval earliestWalletCreationTime; + +/*! @brief Add a wallet to the chain. It is only temporarily in the chain if externaly added this way. */ +- (BOOL)addWallet:(DSWallet *)wallet; + +/*! @brief Unregister a wallet from the chain, it will no longer be loaded or used. */ +- (void)unregisterWallet:(DSWallet *)wallet; + +/*! @brief Register a wallet to the chain. */ +- (void)registerWallet:(DSWallet *)wallet; + +/*! @brief Unregister all wallets from the chain, they will no longer be loaded or used. */ +- (void)unregisterAllWallets; + +/*! @brief Unregister all wallets from the chain that don't have an extended public key in one of their derivation paths, they will no longer be loaded or used. */ +- (void)unregisterAllWalletsMissingExtendedPublicKeys; + +// MARK: Wallet Discovery + +- (DSWallet *_Nullable)walletHavingIdentityAssetLockRegistrationHash:(UInt160)hash + foundAtIndex:(uint32_t *_Nullable)rIndex; +- (DSWallet *_Nullable)walletHavingIdentityAssetLockTopupHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex; +- (DSWallet *_Nullable)walletHavingIdentityAssetLockInvitationHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex; +- (DSWallet *_Nullable)walletHavingProviderVotingAuthenticationHash:(UInt160)hash + foundAtIndex:(uint32_t *_Nullable)rIndex; +- (DSWallet *_Nullable)walletHavingProviderOwnerAuthenticationHash:(UInt160)hash + foundAtIndex:(uint32_t *_Nullable)rIndex; +- (DSWallet *_Nullable)walletHavingProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey + foundAtIndex:(uint32_t *_Nullable)rIndex; +- (DSWallet *_Nullable)walletHavingPlatformNodeAuthenticationHash:(UInt160)hash + foundAtIndex:(uint32_t *_Nullable)rIndex; +- (DSWallet *_Nullable)walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:(DSProviderRegistrationTransaction *_Nonnull)transaction + foundAtIndex:(uint32_t *_Nullable)rIndex; + +// MARK: - Accounts and Balances + +/*! @brief The current wallet balance excluding transactions known to be invalid. */ +@property (nonatomic, readonly) uint64_t balance; + +/*! @brief All accounts that contain the specified transaction hash. The transaction is also returned if it is found. */ +- (NSArray *)accountsForTransactionHash:(UInt256)txHash + transaction:(DSTransaction *_Nullable *_Nullable)transaction; + +/*! @brief Returns the first account with a balance. */ +- (DSAccount *_Nullable)firstAccountWithBalance; + +/*! @brief Returns an account to which the given transaction is or can be associated with (even if it hasn't been registered), no account if the transaction is not associated with the wallet. */ +- (DSAccount *_Nullable)firstAccountThatCanContainTransaction:(DSTransaction *)transaction; + +/*! @brief Returns all accounts to which the given transaction is or can be associated with (even if it hasn't been registered) */ +- (NSArray *)accountsThatCanContainTransaction:(DSTransaction *_Nonnull)transaction; + +/*! @brief Returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet. */ +- (DSAccount *_Nullable)firstAccountForTransactionHash:(UInt256)txHash + transaction:(DSTransaction *_Nullable *_Nullable)transaction + wallet:(DSWallet *_Nullable *_Nullable)wallet; + +/*! @brief Returns an account to which the given address is contained in a derivation path. */ +- (DSAccount *_Nullable)accountContainingAddress:(NSString *)address; + +/*! @brief Returns an account to which the given address is known by a dashpay outgoing derivation path. */ +- (DSAccount *_Nullable)accountContainingDashpayExternalDerivationPathAddress:(NSString *)address; + +// MARK: Protected +- (void)reloadDerivationPaths; +- (void)retrieveWallets; +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Chain/DSChain+Wallet.m b/DashSync/shared/Models/Chain/DSChain+Wallet.m new file mode 100644 index 000000000..c69666d99 --- /dev/null +++ b/DashSync/shared/Models/Chain/DSChain+Wallet.m @@ -0,0 +1,353 @@ +// +// 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 "DSAccount.h" +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChain+Wallet.h" +#import "DSChainManager.h" +#import "DSProviderRegistrationTransaction.h" +#import "DSTransactionOutput.h" +#import "DSWallet+Identity.h" +#import "DSWallet+Protected.h" +#import "NSObject+Notification.h" + +#define CHAIN_WALLETS_KEY @"CHAIN_WALLETS_KEY" + +NSString const *mWalletsKey = @"mWalletsKey"; + +@implementation DSChain (Wallet) + +- (NSString *)chainWalletsKey { + return [NSString stringWithFormat:@"%@_%@", CHAIN_WALLETS_KEY, [self uniqueID]]; +} + +// This is a time interval since 1970 +- (NSTimeInterval)earliestWalletCreationTime { + if (![self.wallets count]) return BIP39_CREATION_TIME; + NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]; + for (DSWallet *wallet in self.wallets) { + if (timeInterval > wallet.walletCreationTime) { + timeInterval = wallet.walletCreationTime; + } + } + return timeInterval; +} + +- (void)reloadDerivationPaths { + for (DSWallet *wallet in self.mWallets) { + if (!wallet.isTransient) { //no need to reload transient wallets (those are for testing purposes) + [wallet reloadDerivationPaths]; + } + } +} + +// MARK: - Wallet + +- (NSMutableArray *)mWallets { + return objc_getAssociatedObject(self, &mWalletsKey); +} +- (void)setMWallets:(NSMutableArray *)mWallets { + objc_setAssociatedObject(self, &mWalletsKey, mWallets, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + + +- (BOOL)hasAWallet { + return [self.mWallets count] > 0; +} + +- (NSArray *)wallets { + return [self.mWallets copy]; +} + +- (void)unregisterAllWallets { + for (DSWallet *wallet in [self.mWallets copy]) { + [self unregisterWallet:wallet]; + } +} + +- (void)unregisterAllWalletsMissingExtendedPublicKeys { + for (DSWallet *wallet in [self.mWallets copy]) { + if ([wallet hasAnExtendedPublicKeyMissing]) { + [self unregisterWallet:wallet]; + } + } +} + +- (void)unregisterWallet:(DSWallet *)wallet { + NSAssert(wallet.chain == self, @"the wallet you are trying to remove is not on this chain"); + [wallet wipeBlockchainInfoInContext:self.chainManagedObjectContext]; + [wallet wipeWalletInfo]; + [self.mWallets removeObject:wallet]; + NSError *error = nil; + NSMutableArray *keyChainArray = [getKeychainArray(self.chainWalletsKey, @[[NSString class]], &error) mutableCopy]; + if (!keyChainArray) keyChainArray = [NSMutableArray array]; + [keyChainArray removeObject:wallet.uniqueIDString]; + setKeychainArray(keyChainArray, self.chainWalletsKey, NO); + [self notify:DSChainWalletsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; +} + +- (BOOL)addWallet:(DSWallet *)walletToAdd { + BOOL alreadyPresent = FALSE; + for (DSWallet *cWallet in self.mWallets) { + if ([cWallet.uniqueIDString isEqual:walletToAdd.uniqueIDString]) + alreadyPresent = TRUE; + } + if (!alreadyPresent) { + [self.mWallets addObject:walletToAdd]; + return TRUE; + } + return FALSE; +} + +- (void)registerWallet:(DSWallet *)wallet { + BOOL firstWallet = !self.mWallets.count; + if ([self.mWallets indexOfObject:wallet] == NSNotFound) { + [self addWallet:wallet]; + } + + if (firstWallet) { + //this is the first wallet, we should reset the last block height to the most recent checkpoint. + //it will lazy load later + [self resetLastSyncBlock]; + } + + NSError *error = nil; + NSMutableArray *keyChainArray = [getKeychainArray(self.chainWalletsKey, @[[NSString class]], &error) mutableCopy]; + if (!keyChainArray) keyChainArray = [NSMutableArray array]; + if (![keyChainArray containsObject:wallet.uniqueIDString]) { + [keyChainArray addObject:wallet.uniqueIDString]; + setKeychainArray(keyChainArray, self.chainWalletsKey, NO); + [self notify:DSChainWalletsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; + } +} + +- (void)retrieveWallets { + NSError *error = nil; + NSArray *walletIdentifiers = getKeychainArray(self.chainWalletsKey, @[[NSString class]], &error); + if (!error && walletIdentifiers) { + for (NSString *uniqueID in walletIdentifiers) { + DSWallet *wallet = [[DSWallet alloc] initWithUniqueID:uniqueID forChain:self]; + [self addWallet:wallet]; + } + //we should load blockchain identies after all wallets are in the chain, as blockchain identities might be on different wallets and have interactions between each other + for (DSWallet *wallet in self.wallets) { + [wallet loadIdentities]; + } + } +} + +// MARK: - Merging Wallets + +- (DSWallet *)walletHavingIdentityAssetLockRegistrationHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfIdentityAssetLockRegistrationHash:hash]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *)walletHavingIdentityAssetLockTopupHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfIdentityAssetLockTopupHash:hash]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *)walletHavingIdentityAssetLockInvitationHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfIdentityAssetLockInvitationHash:hash]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *)walletHavingProviderVotingAuthenticationHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfProviderVotingAuthenticationHash:hash]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *_Nullable)walletHavingProviderOwnerAuthenticationHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfProviderOwningAuthenticationHash:hash]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *_Nullable)walletHavingProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfProviderOperatorAuthenticationKey:providerOperatorAuthenticationKey]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *_Nullable)walletHavingPlatformNodeAuthenticationHash:(UInt160)hash + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + NSUInteger index = [wallet indexOfPlatformNodeAuthenticationHash:hash]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +- (DSWallet *_Nullable)walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:(DSProviderRegistrationTransaction *_Nonnull)transaction + foundAtIndex:(uint32_t *)rIndex { + for (DSWallet *wallet in self.wallets) { + for (DSTransactionOutput *output in transaction.outputs) { + NSString *address = output.address; + if (!address || address == (id)[NSNull null]) continue; + NSUInteger index = [wallet indexOfHoldingAddress:address]; + if (index != NSNotFound) { + if (rIndex) *rIndex = (uint32_t)index; + return wallet; + } + } + } + if (rIndex) *rIndex = UINT32_MAX; + return nil; +} + +// MARK: - Accounts + +- (uint64_t)balance { + uint64_t rBalance = 0; + for (DSWallet *wallet in self.wallets) { + rBalance += wallet.balance; + } + for (DSDerivationPath *standaloneDerivationPath in self.standaloneDerivationPaths) { + rBalance += standaloneDerivationPath.balance; + } + return rBalance; +} + +- (DSAccount *_Nullable)firstAccountWithBalance { + for (DSWallet *wallet in self.wallets) { + DSAccount *account = [wallet firstAccountWithBalance]; + if (account) return account; + } + return nil; +} + +- (DSAccount *_Nullable)firstAccountThatCanContainTransaction:(DSTransaction *)transaction { + if (!transaction) return nil; + for (DSWallet *wallet in self.wallets) { + DSAccount *account = [wallet firstAccountThatCanContainTransaction:transaction]; + if (account) return account; + } + return nil; +} + +- (NSArray *)accountsThatCanContainTransaction:(DSTransaction *)transaction { + NSMutableArray *mArray = [NSMutableArray array]; + if (!transaction) return @[]; + for (DSWallet *wallet in self.wallets) { + [mArray addObjectsFromArray:[wallet accountsThatCanContainTransaction:transaction]]; + } + return [mArray copy]; +} + +- (DSAccount *_Nullable)accountContainingAddress:(NSString *)address { + if (!address) return nil; + for (DSWallet *wallet in self.wallets) { + DSAccount *account = [wallet accountForAddress:address]; + if (account) return account; + } + return nil; +} + +- (DSAccount *_Nullable)accountContainingDashpayExternalDerivationPathAddress:(NSString *)address { + if (!address) return nil; + for (DSWallet *wallet in self.wallets) { + DSAccount *account = [wallet accountForDashpayExternalDerivationPathAddress:address]; + if (account) return account; + } + return nil; +} + +// returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet +- (DSAccount *_Nullable)firstAccountForTransactionHash:(UInt256)txHash + transaction:(DSTransaction **)transaction + wallet:(DSWallet **)wallet { + for (DSWallet *lWallet in self.wallets) { + for (DSAccount *account in lWallet.accounts) { + DSTransaction *lTransaction = [account transactionForHash:txHash]; + if (lTransaction) { + if (transaction) *transaction = lTransaction; + if (wallet) *wallet = lWallet; + return account; + } + } + } + return nil; +} + +// returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet +- (NSArray *)accountsForTransactionHash:(UInt256)txHash + transaction:(DSTransaction **)transaction { + NSMutableArray *accounts = [NSMutableArray array]; + for (DSWallet *lWallet in self.wallets) { + for (DSAccount *account in lWallet.accounts) { + DSTransaction *lTransaction = [account transactionForHash:txHash]; + if (lTransaction) { + if (transaction) *transaction = lTransaction; + [accounts addObject:account]; + } + } + } + return [accounts copy]; +} +@end diff --git a/DashSync/shared/Models/Chain/DSChain.h b/DashSync/shared/Models/Chain/DSChain.h index 4537474e8..6463e631d 100644 --- a/DashSync/shared/Models/Chain/DSChain.h +++ b/DashSync/shared/Models/Chain/DSChain.h @@ -24,8 +24,10 @@ // THE SOFTWARE. #import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSChainConstants.h" +#import "DSDashSharedCore.h" +#import "DSTransaction.h" #import #import @@ -41,14 +43,6 @@ FOUNDATION_EXPORT NSString *const DSChainNotificationBlockKey; FOUNDATION_EXPORT NSString *const DSChainInitialHeadersDidFinishSyncingNotification; FOUNDATION_EXPORT NSString *const DSChainBlocksDidFinishSyncingNotification; -typedef NS_ENUM(NSUInteger, DSTransactionDirection) -{ - DSTransactionDirection_Sent, - DSTransactionDirection_Received, - DSTransactionDirection_Moved, - DSTransactionDirection_NotAccountFunds, -}; - typedef NS_ENUM(uint16_t, DSChainSyncPhase) { DSChainSyncPhase_Offline = 0, @@ -57,7 +51,7 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) DSChainSyncPhase_Synced, }; -@class DSChain, DSChainEntity, DSChainManager, DSWallet, DSMerkleBlock, DSBlock, DSFullBlock, DSPeer, DSDerivationPath, DSTransaction, DSAccount, DSSimplifiedMasternodeEntry, DSBlockchainIdentity, DSBloomFilter, DSProviderRegistrationTransaction, DSMasternodeList, DPContract, DSCheckpoint, DSChainLock; +@class DSChain, DSChainEntity, DSChainManager, DSWallet, DSMerkleBlock, DSBlock, DSFullBlock, DSPeer, DSDerivationPath, DSTransaction, DSAccount, DSIdentity, DSBloomFilter, DSProviderRegistrationTransaction, DPContract, DSCheckpoint, DSChainLock, DSDashSharedCore, DSMasternodeManager; @protocol DSChainDelegate; @@ -67,100 +61,37 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) /*! @brief The chain manager is a container for all managers (peer, identity, governance, masternode, spork and transition). It also is used to control the sync process. */ @property (nonatomic, weak, nullable) DSChainManager *chainManager; +@property (nonatomic, weak, nullable) DSMasternodeManager *masternodeManager; +// MARK: - Shortcuts -/*! @brief The chain entity associated in Core Data in the required context. */ -- (DSChainEntity *)chainEntityInContext:(NSManagedObjectContext *)context; - -/*! @brief The managed object context of the chain. */ -@property (nonatomic, readonly) NSManagedObjectContext *chainManagedObjectContext; - -// MARK: - L1 Network Chain Info - -/*! @brief The network name. Currently main, test, dev or reg. */ -@property (nonatomic, readonly) NSString *networkName; - -/*! @brief An array of known hard coded checkpoints for the chain. */ -@property (nonatomic, readonly) NSArray *checkpoints; - -// MARK: Sync - -/*! @brief The genesis hash is the hash of the first block of the chain. For a devnet this is actually the second block as the first block is created in a special way for devnets. */ -@property (nonatomic, readonly) UInt256 genesisHash; - -/*! @brief The magic number is used in message headers to indicate what network (or chain) a message is intended for. */ -@property (nonatomic, readonly) uint32_t magicNumber; - -/*! @brief The base reward is the intial mining reward at genesis for the chain. This goes down by 7% every year. A SPV client does not validate that the reward amount is correct as it would not make sense for miners to enter incorrect rewards as the blocks would be rejected by full nodes. */ -@property (nonatomic, readonly) uint64_t baseReward; - -/*! @brief minProtocolVersion is the minimum protocol version that peers on this chain can communicate with. This should only be changed in the case of devnets. */ -@property (nonatomic, assign) uint32_t minProtocolVersion; - -/*! @brief protocolVersion is the protocol version that we currently use for this chain. This should only be changed in the case of devnets. */ -@property (nonatomic, assign) uint32_t protocolVersion; - -/*! @brief headersMaxAmount is the maximum amount of headers that is expected from peers. */ -@property (nonatomic, assign) uint64_t headersMaxAmount; - -/*! @brief maxProofOfWork is the lowest amount of work effort required to mine a block on the chain. */ -@property (nonatomic, readonly) UInt256 maxProofOfWork; - -/*! @brief maxProofOfWorkTarget is the lowest amount of work effort required to mine a block on the chain. Here it is represented as the compact target. */ -@property (nonatomic, readonly) uint32_t maxProofOfWorkTarget; - -/*! @brief allowMinDifficultyBlocks is set to TRUE on networks where mining is low enough that it can be attacked by increasing difficulty with ASICs and then no longer running ASICs. This is set to NO for Mainnet, and generally should be YES on all other networks. */ -@property (nonatomic, readonly) BOOL allowMinDifficultyBlocks; - -/*! @brief This is the minimum amount that can be entered into an amount for a output for it not to be considered dust. */ -@property (nonatomic, readonly) uint64_t minOutputAmount; - -// MARK: Fees - -@property (nonatomic, assign) uint64_t feePerByte; - -/*! @brief The fee for transactions in L1 are now entirely dependent on their size. */ -- (uint64_t)feeForTxSize:(NSUInteger)size; - -// MARK: Ports - -/*! @brief The standard port for the chain for L1 communication. */ -@property (nonatomic, assign) uint32_t standardPort; - -/*! @brief The standard port for the chain for L2 communication through JRPC. */ -@property (nonatomic, assign) uint32_t standardDapiJRPCPort; - -/*! @brief The standard port for the chain for L2 communication through GRPC. */ -@property (nonatomic, assign) uint32_t standardDapiGRPCPort; - -// MARK: Sporks - -/*! @brief The spork public key as a hex string. */ -@property (nonatomic, strong, nullable) NSString *sporkPublicKeyHexString; +/*! @brief The shared core is a container for all stuff related to rust dash-shared-core. */ +@property (nonatomic, nullable) DSDashSharedCore *shareCore; -/*! @brief The spork private key as a base 58 string. */ -@property (nonatomic, strong, nullable) NSString *sporkPrivateKeyBase58String; +/*! @brief Tokio Runtime Reference */ +@property (nonatomic, nullable) Runtime *sharedRuntime; -/*! @brief The spork address base 58 string (addresses are known to be base 58). */ -@property (nonatomic, strong, nullable) NSString *sporkAddress; +/*! @brief Masternode Processor Reference */ +@property (nonatomic, nullable) DArcProcessor *sharedProcessor; +@property (nonatomic, nullable) DProcessor *sharedProcessorObj; +/*! @brief Masternode Processor Cache Reference */ +@property (nonatomic, nullable) DArcPlatformSDK *sharedPlatform; +@property (nonatomic, nullable) PlatformSDK *sharedPlatformObj; +@property (nonatomic, nullable) ContactRequestManager *sharedContactsObj; +@property (nonatomic, nullable) IdentitiesManager *sharedIdentitiesObj; +@property (nonatomic, nullable) DocumentsManager *sharedDocumentsObj; +@property (nonatomic, nullable) ContractsManager *sharedContractsObj; +@property (nonatomic, nullable) SaltedDomainHashesManager *sharedSaltedDomainHashesObj; -// MARK: - L2 Network Chain Info -/*! @brief platformProtocolVersion is the protocol version that we currently use for the platform chain. This should only be changed in the case of devnets. */ -@property (nonatomic, assign) uint32_t platformProtocolVersion; +/*! @brief The chain entity associated in Core Data in the required context. */ +- (DSChainEntity *)chainEntityInContext:(NSManagedObjectContext *)context; -/*! @brief The dpns contract id. */ -@property (nonatomic, assign) UInt256 dpnsContractID; +/*! @brief The managed object context of the chain. */ +@property (nonatomic, readonly) NSManagedObjectContext *chainManagedObjectContext; -/*! @brief The dashpay contract id. */ -@property (nonatomic, assign) UInt256 dashpayContractID; // MARK: - DashSync Chain Info -/*! @brief The chain type (MainNet, TestNet or DevNet). */ -@property (nonatomic, readonly) ChainType chainType; - -/*! @brief A threshold after which a peer will be banned. */ -@property (nonatomic, readonly) uintptr_t peerMisbehavingThreshold; /*! @brief True if this chain syncs the blockchain. All Chains currently sync the blockchain. */ @property (nonatomic, readonly) BOOL syncsBlockchain; @@ -168,53 +99,10 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) /*! @brief True if this chain should sync headers first for masternode list verification. */ @property (nonatomic, readonly) BOOL needsInitialTerminalHeadersSync; -/*! @brief The default transaction version used when sending transactions. */ -@property (nonatomic, readonly) uint16_t transactionVersion; - -/*! @brief The number of minimumDifficultyBlocks. */ -@property (nonatomic, assign) uint32_t minimumDifficultyBlocks; - -/*! @brief The flag represents whether the quorum rotation is enabled in this chain. */ -@property (nonatomic, assign) BOOL isRotatedQuorumsPresented; /*! @brief Returns all standard derivaton paths used for the chain based on the account number. */ - (NSArray *)standardDerivationPathsForAccountNumber:(uint32_t)accountNumber; -// MARK: Names and Identifiers - -/*! @brief The unique identifier of the chain. This unique id follows the same chain accross devices because it is the short hex string of the genesis hash. */ -@property (nonatomic, readonly) NSString *uniqueID; - -/*! @brief The name of the chain (Mainnet-Testnet-Devnet). */ -@property (nonatomic, readonly) NSString *name; - -/*! @brief The localized name of the chain (Mainnet-Testnet-Devnet). */ -@property (nonatomic, readonly) NSString *localizedName; - -- (void)setDevnetNetworkName:(NSString *)networkName; - -// MARK: - Wallets - -/*! @brief The wallets in the chain. */ -@property (nonatomic, readonly) NSArray *wallets; - -/*! @brief Conveniance method. Does this walleet have a chain? */ -@property (nonatomic, readonly) BOOL hasAWallet; - -/*! @brief Conveniance method. The earliest known creation time for any wallet in this chain. */ -@property (nonatomic, readonly) NSTimeInterval earliestWalletCreationTime; - -/*! @brief Unregister a wallet from the chain, it will no longer be loaded or used. */ -- (void)unregisterWallet:(DSWallet *)wallet; - -/*! @brief Register a wallet to the chain. */ -- (void)registerWallet:(DSWallet *)wallet; - -/*! @brief Unregister all wallets from the chain, they will no longer be loaded or used. */ -- (void)unregisterAllWallets; - -/*! @brief Unregister all wallets from the chain that don't have an extended public key in one of their derivation paths, they will no longer be loaded or used. */ -- (void)unregisterAllWalletsMissingExtendedPublicKeys; // MARK: - Standalone Derivation Paths @@ -230,28 +118,6 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) /*! @brief Register a standalone derivation path to the chain. Standalone derivation paths are currently an experimental feature */ - (void)registerStandaloneDerivationPath:(DSDerivationPath *)derivationPath; -// MARK: - Checkpoints - -/*! @brief Returns the last checkpoint that has a masternode list attached to it. */ -- (DSCheckpoint *_Nullable)lastCheckpointHavingMasternodeList; - -/*! @brief Returns the checkpoint matching the parameter block hash, if one exists. */ -- (DSCheckpoint *_Nullable)checkpointForBlockHash:(UInt256)blockHash; - -/*! @brief Returns the checkpoint at a given block height, if one exists at that block height. */ -- (DSCheckpoint *_Nullable)checkpointForBlockHeight:(uint32_t)blockHeight; - -/*! @brief Returns the last checkpoint on or before the given height. */ -- (DSCheckpoint *)lastCheckpointOnOrBeforeHeight:(uint32_t)height; - -/*! @brief Returns the last checkpoint on or before the given timestamp. */ -- (DSCheckpoint *)lastCheckpointOnOrBeforeTimestamp:(NSTimeInterval)timestamp; - -/*! @brief When used this will change the checkpoint used for initial headers sync. This value is not persisted. */ -- (void)useCheckpointBeforeOrOnHeightForTerminalBlocksSync:(uint32_t)blockHeight; - -/*! @brief When used this will change the checkpoint used for main chain syncing. This value is not persisted. */ -- (void)useCheckpointBeforeOrOnHeightForSyncingChainBlocks:(uint32_t)blockHeight; // MARK: - Blocks and Headers @@ -303,6 +169,8 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) /*! @brief Returns a known block with a given distance from the chain tip. A null result would mean that the given distance exceeded the number of blocks kept locally. */ - (DSMerkleBlock *_Nullable)blockFromChainTip:(NSUInteger)blocksAgo; +- (uint32_t)chainTipHeight; + // MARK: Chain Sync /*! @brief Returns the hash of the last persisted sync block. The sync block itself most likely is not persisted. */ @@ -354,25 +222,6 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) /*! @brief Returns if there is a block at the following height that is confirmed. */ - (BOOL)blockHeightChainLocked:(uint32_t)height; -// MARK: - Transactions - -/*! @brief Returns all wallet transactions sorted by date, most recent first. */ -@property (nonatomic, readonly) NSArray *allTransactions; - -/*! @brief Returns the transaction with the given hash if it's been registered in any wallet on the chain (might also return non-registered) */ -- (DSTransaction *_Nullable)transactionForHash:(UInt256)txHash; - -/*! @brief Returns the direction of a transaction for the chain (Sent - Received - Moved - Not Account Funds) */ -- (DSTransactionDirection)directionOfTransaction:(DSTransaction *)transaction; - -/*! @brief Returns the amount received globally from the transaction (total outputs to change and/or receive addresses) */ -- (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction; - -/*! @brief Returns the amount sent globally by the trasaction (total wallet outputs consumed, change and fee included) */ -- (uint64_t)amountSentByTransaction:(DSTransaction *)transaction; - -/*! @brief Returns if this transaction has any local references. Local references are a pubkey hash contained in a wallet, pubkeys in wallets special derivation paths, or anything that would make the transaction relevant for this device. */ -- (BOOL)transactionHasLocalReferences:(DSTransaction *)transaction; // MARK: - Bloom Filter @@ -382,62 +231,15 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) /*! @brief Returns a bloom filter with the given false positive rate tweaked with the value tweak. The value tweak is generally peer specific. */ - (DSBloomFilter *)bloomFilterWithFalsePositiveRate:(double)falsePositiveRate withTweak:(uint32_t)tweak; -// MARK: - Accounts and Balances - -/*! @brief The current wallet balance excluding transactions known to be invalid. */ -@property (nonatomic, readonly) uint64_t balance; - -/*! @brief All accounts that contain the specified transaction hash. The transaction is also returned if it is found. */ -- (NSArray *)accountsForTransactionHash:(UInt256)txHash transaction:(DSTransaction *_Nullable *_Nullable)transaction; - -/*! @brief Returns the first account with a balance. */ -- (DSAccount *_Nullable)firstAccountWithBalance; - -/*! @brief Returns an account to which the given transaction is or can be associated with (even if it hasn't been registered), no account if the transaction is not associated with the wallet. */ -- (DSAccount *_Nullable)firstAccountThatCanContainTransaction:(DSTransaction *)transaction; - -/*! @brief Returns all accounts to which the given transaction is or can be associated with (even if it hasn't been registered) */ -- (NSArray *)accountsThatCanContainTransaction:(DSTransaction *_Nonnull)transaction; - -/*! @brief Returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet. */ -- (DSAccount *_Nullable)firstAccountForTransactionHash:(UInt256)txHash transaction:(DSTransaction *_Nullable *_Nullable)transaction wallet:(DSWallet *_Nullable *_Nullable)wallet; +/*! @brief Returns possibly new addresses for bloom filter, checks that at least the next . */ +- (NSArray *)newAddressesForBloomFilter; -/*! @brief Returns an account to which the given address is contained in a derivation path. */ -- (DSAccount *_Nullable)accountContainingAddress:(NSString *)address; - -/*! @brief Returns an account to which the given address is known by a dashpay outgoing derivation path. */ -- (DSAccount *_Nullable)accountContainingDashpayExternalDerivationPathAddress:(NSString *)address; // MARK: - Governance /*! @brief Returns a count of all governance objects. */ @property (nonatomic, assign) uint32_t totalGovernanceObjectsCount; -// MARK: - Identities - -/*! @brief Returns a count of local blockchain identities. */ -@property (nonatomic, readonly) uint32_t localBlockchainIdentitiesCount; - -/*! @brief Returns a count of blockchain invitations that have been created locally. */ -@property (nonatomic, readonly) uint32_t localBlockchainInvitationsCount; - -/*! @brief Returns an array of all local blockchain identities. */ -@property (nonatomic, readonly) NSArray *localBlockchainIdentities; - -/*! @brief Returns a dictionary of all local blockchain identities keyed by uniqueId. */ -@property (nonatomic, readonly) NSDictionary *localBlockchainIdentitiesByUniqueIdDictionary; - -/*! @brief Returns a blockchain identity by uniqueId, if it exists. */ -- (DSBlockchainIdentity *_Nullable)blockchainIdentityForUniqueId:(UInt256)uniqueId; - -/*! @brief Returns a blockchain identity that could have created this contract. */ -- (DSBlockchainIdentity *_Nullable)blockchainIdentityThatCreatedContract:(DPContract *)contract withContractId:(UInt256)contractId foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet; - -/*! @brief Returns a blockchain identity by uniqueId, if it exists. Also returns the wallet it was found in. */ -- (DSBlockchainIdentity *_Nullable)blockchainIdentityForUniqueId:(UInt256)uniqueId foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet; - -/*! @brief Returns a blockchain identity by uniqueId, if it exists. Also returns the wallet it was found in. Allows to search foreign blockchain identities too */ -- (DSBlockchainIdentity *)blockchainIdentityForUniqueId:(UInt256)uniqueId foundInWallet:(DSWallet *_Nullable *_Nullable)foundInWallet includeForeignBlockchainIdentities:(BOOL)includeForeignBlockchainIdentities; // MARK: - Chain Retrieval methods @@ -451,7 +253,7 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) + (DSChain *_Nullable)devnetWithIdentifier:(NSString *)identifier; /*! @brief Set up a given devnet with an identifier, checkpoints, default L1, JRPC and GRPC ports, a dpns contractId and a dashpay contract id. minimumDifficultyBlocks are used to speed up the initial chain creation. This devnet will be registered on the keychain. The additional isTransient property allows for test usage where you do not wish to persist the devnet. */ -+ (DSChain *)setUpDevnetWithIdentifier:(DevnetType)devnetType ++ (DSChain *)setUpDevnetWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType protocolVersion:(uint32_t)protocolVersion minProtocolVersion:(uint32_t)minProtocolVersion withCheckpoints:(NSArray *_Nullable)checkpointArray @@ -464,22 +266,11 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) isTransient:(BOOL)isTransient; /*! @brief Retrieve from the keychain a devnet with an identifier and add given checkpoints. */ -+ (DSChain *)recoverKnownDevnetWithIdentifier:(DevnetType)devnetType withCheckpoints:(NSArray *)checkpointArray performSetup:(BOOL)performSetup; ++ (DSChain *)recoverKnownDevnetWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType withCheckpoints:(NSArray *)checkpointArray performSetup:(BOOL)performSetup; /*! @brief Retrieve a chain having the specified network name. */ + (DSChain *_Nullable)chainForNetworkName:(NSString *_Nullable)networkName; -// MARK: - Chain Info methods - -- (BOOL)isMainnet; -- (BOOL)isTestnet; -- (BOOL)isDevnetAny; -- (BOOL)isEvolutionEnabled; -- (BOOL)isDevnetWithGenesisHash:(UInt256)genesisHash; -- (BOOL)isCore19Active; -- (BOOL)isCore20Active; -- (BOOL)isCore20ActiveAtHeight:(uint32_t)height; -- (KeyKind)activeBLSType; @end @@ -493,7 +284,7 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) @protocol DSChainIdentitiesDelegate @required -- (void)chain:(DSChain *)chain didFinishInChainSyncPhaseFetchingBlockchainIdentityDAPInformation:(DSBlockchainIdentity *)blockchainIdentity; +- (void)chain:(DSChain *)chain didFinishInChainSyncPhaseFetchingIdentityDAPInformation:(DSIdentity *)identity; @end @@ -510,6 +301,7 @@ typedef NS_ENUM(uint16_t, DSChainSyncPhase) - (void)chain:(DSChain *)chain receivedOrphanBlock:(DSBlock *)merkleBlock fromPeer:(DSPeer *)peer; - (void)chain:(DSChain *)chain wasExtendedWithBlock:(DSBlock *)merkleBlock fromPeer:(DSPeer *)peer; - (void)chain:(DSChain *)chain badBlockReceivedFromPeer:(DSPeer *)peer; +- (void)chain:(DSChain *)chain badMasternodeListReceivedFromPeer:(DSPeer *)peer; @end diff --git a/DashSync/shared/Models/Chain/DSChain.m b/DashSync/shared/Models/Chain/DSChain.m index 834465ffc..128c67c77 100644 --- a/DashSync/shared/Models/Chain/DSChain.m +++ b/DashSync/shared/Models/Chain/DSChain.m @@ -22,104 +22,57 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "BigIntTypes.h" #import "DSAccount.h" #import "DSAuthenticationKeysDerivationPath.h" -#import "DSBIP39Mnemonic.h" #import "DSBlock+Protected.h" -#import "DSBlockchainIdentity+Protected.h" -#import "DSBlockchainIdentityCloseTransition.h" -#import "DSBlockchainIdentityEntity+CoreDataClass.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSBlockchainIdentityTopupTransition.h" -#import "DSBlockchainIdentityUpdateTransition.h" -#import "DSBlockchainInvitation+Protected.h" #import "DSBloomFilter.h" +#import "DSChain.h" +#import "DSChain+Checkpoint.h" +#import "DSChain+Identity.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Transaction.h" +#import "DSChain+Wallet.h" #import "DSChainCheckpoints.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainLock.h" #import "DSChainManager+Protected.h" #import "DSChainsManager.h" #import "DSCheckpoint.h" -#import "DSCreditFundingTransaction.h" -#import "DSDerivationPath.h" #import "DSDerivationPathEntity+CoreDataProperties.h" #import "DSDerivationPathFactory.h" -#import "DSEventManager.h" #import "DSFullBlock.h" #import "DSFundsDerivationPath.h" +#import "DSGapLimit.h" #import "DSIdentitiesManager+Protected.h" #import "DSInsightManager.h" -#import "DSKeyManager.h" #import "DSLocalMasternode+Protected.h" #import "DSLocalMasternodeEntity+CoreDataProperties.h" -#import "DSMasternodeHoldingsDerivationPath.h" -#import "DSMasternodeListEntity+CoreDataProperties.h" -#import "DSMasternodeManager+LocalMasternode.h" #import "DSMasternodeManager+Protected.h" #import "DSMerkleBlock.h" #import "DSMerkleBlockEntity+CoreDataClass.h" #import "DSOptionsManager.h" -#import "DSPeer.h" #import "DSPeerManager.h" -#import "DSPriceManager.h" -#import "DSProviderRegistrationTransaction.h" -#import "DSProviderUpdateRegistrarTransaction.h" -#import "DSProviderUpdateRevocationTransaction.h" -#import "DSProviderUpdateServiceTransaction.h" -#import "DSQuorumEntryEntity+CoreDataProperties.h" -#import "DSQuorumSnapshotEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h" -#import "DSSpecialTransactionsWalletHolder.h" -#import "DSSporkManager.h" -#import "DSTransaction.h" -#import "DSTransactionEntity+CoreDataClass.h" #import "DSTransactionHashEntity+CoreDataProperties.h" #import "DSTransactionInput.h" #import "DSTransactionOutput.h" -#import "DSTransition.h" -#import "DSWallet+Protected.h" -#import "NSCoder+Dash.h" -#import "NSData+DSHash.h" -#import "NSData+Dash.h" +#import "NSManagedObjectContext+DSSugar.h" #import "NSManagedObject+Sugar.h" #import "NSMutableData+Dash.h" +#import "NSObject+Notification.h" #import "NSString+Bitcoin.h" #define FEE_PER_BYTE_KEY @"FEE_PER_BYTE" -#define CHAIN_WALLETS_KEY @"CHAIN_WALLETS_KEY" #define CHAIN_STANDALONE_DERIVATIONS_KEY @"CHAIN_STANDALONE_DERIVATIONS_KEY" #define REGISTERED_PEERS_KEY @"REGISTERED_PEERS_KEY" -#define PROTOCOL_VERSION_LOCATION @"PROTOCOL_VERSION_LOCATION" -#define DEFAULT_MIN_PROTOCOL_VERSION_LOCATION @"MIN_PROTOCOL_VERSION_LOCATION" - -#define PLATFORM_PROTOCOL_VERSION_LOCATION @"PLATFORM_PROTOCOL_VERSION_LOCATION" -#define PLATFORM_DEFAULT_MIN_PROTOCOL_VERSION_LOCATION @"PLATFORM_MIN_PROTOCOL_VERSION_LOCATION" - #define ISLOCK_QUORUM_TYPE @"ISLOCK_QUORUM_TYPE" #define ISDLOCK_QUORUM_TYPE @"ISDLOCK_QUORUM_TYPE" #define CHAINLOCK_QUORUM_TYPE @"CHAINLOCK_QUORUM_TYPE" #define PLATFORM_QUORUM_TYPE @"PLATFORM_QUORUM_TYPE" -#define STANDARD_PORT_LOCATION @"STANDARD_PORT_LOCATION" -#define JRPC_PORT_LOCATION @"JRPC_PORT_LOCATION" -#define GRPC_PORT_LOCATION @"GRPC_PORT_LOCATION" - -#define DPNS_CONTRACT_ID @"DPNS_CONTRACT_ID" -#define DASHPAY_CONTRACT_ID @"DASHPAY_CONTRACT_ID" - -#define MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY @"MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY" - -#define SPORK_PUBLIC_KEY_LOCATION @"SPORK_PUBLIC_KEY_LOCATION" -#define SPORK_ADDRESS_LOCATION @"SPORK_ADDRESS_LOCATION" -#define SPORK_PRIVATE_KEY_LOCATION @"SPORK_PRIVATE_KEY_LOCATION" - #define CHAIN_VOTING_KEYS_KEY @"CHAIN_VOTING_KEYS_KEY" -#define QUORUM_ROTATION_PRESENCE_KEY @"QUORUM_ROTATION_PRESENCE_KEY" #define LOG_PREV_BLOCKS_ON_ORPHAN 0 @@ -137,34 +90,13 @@ @interface DSChain () @property (nonatomic, strong) DSBlock *lastSyncBlock, *lastTerminalBlock, *lastOrphan; @property (nonatomic, strong) NSMutableDictionary *mSyncBlocks, *mTerminalBlocks, *mOrphans; -@property (nonatomic, strong) NSMutableDictionary *checkpointsByHashDictionary; -@property (nonatomic, strong) NSMutableDictionary *checkpointsByHeightDictionary; -@property (nonatomic, strong) NSArray *checkpoints; -@property (nonatomic, copy) NSString *uniqueID; -@property (nonatomic, copy) NSString *networkName; -@property (nonatomic, strong) NSMutableArray *mWallets; @property (nonatomic, strong) DSAccount *viewingAccount; @property (nonatomic, strong) NSMutableDictionary *> *estimatedBlockHeights; -@property (nonatomic, assign) uint32_t cachedMinimumDifficultyBlocks; @property (nonatomic, assign) uint32_t bestEstimatedBlockHeight; -@property (nonatomic, assign) uint32_t cachedMinProtocolVersion; -@property (nonatomic, assign) uint32_t cachedProtocolVersion; -@property (nonatomic, assign) UInt256 cachedMaxProofOfWork; -@property (nonatomic, assign) uint32_t cachedStandardPort; -@property (nonatomic, assign) uint32_t cachedStandardDapiJRPCPort; -@property (nonatomic, assign) uint32_t cachedStandardDapiGRPCPort; -@property (nonatomic, assign) UInt256 genesisHash; -@property (nonatomic, assign) UInt256 cachedDpnsContractID; -@property (nonatomic, assign) UInt256 cachedDashpayContractID; @property (nonatomic, strong) NSMutableDictionary *transactionHashHeights; @property (nonatomic, strong) NSMutableDictionary *transactionHashTimestamps; @property (nonatomic, strong) NSManagedObjectContext *chainManagedObjectContext; -@property (nonatomic, strong) DSCheckpoint *terminalHeadersOverrideUseCheckpoint; -@property (nonatomic, strong) DSCheckpoint *syncHeadersOverrideUseCheckpoint; -@property (nonatomic, strong) DSCheckpoint *lastCheckpoint; @property (nonatomic, assign, getter=isTransient) BOOL transient; -@property (nonatomic, assign) BOOL cachedIsQuorumRotationPresented; -@property (nonatomic, readonly) NSString *chainWalletsKey; @end @@ -194,34 +126,36 @@ - (instancetype)init { self.feePerByte = DEFAULT_FEE_PER_B; uint64_t feePerByte = [[NSUserDefaults standardUserDefaults] doubleForKey:FEE_PER_BYTE_KEY]; if (feePerByte >= MIN_FEE_PER_B && feePerByte <= MAX_FEE_PER_B) self.feePerByte = feePerByte; - + return self; } -- (instancetype)initWithType:(ChainType)type checkpoints:(NSArray *)checkpoints { +- (instancetype)initWithType:(DChainType *)type checkpoints:(NSArray *)checkpoints { if (!(self = [self init])) return nil; - NSAssert(!chain_type_is_devnet_any(type), @"DevNet should be configured with initAsDevnetWithIdentifier:version:checkpoints:port:dapiPort:dapiGRPCPort:dpnsContractID:dashpayContractID:"); - _chainType = type; - self.standardPort = chain_standard_port(type); - self.standardDapiJRPCPort = chain_standard_dapi_jrpc_port(type); - self.headersMaxAmount = chain_headers_max_amount(type); + + NSAssert(!dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(type), @"DevNet should be configured with initAsDevnetWithIdentifier:version:checkpoints:port:dapiPort:dapiGRPCPort:dpnsContractID:dashpayContractID:"); + self.chainType = type; + self.standardPort = dash_spv_crypto_network_chain_type_ChainType_standard_port(type); + self.standardDapiJRPCPort = dash_spv_crypto_network_chain_type_ChainType_standard_dapi_jrpc_port(type); + self.headersMaxAmount = dash_spv_crypto_network_chain_type_ChainType_header_max_amount(type); self.checkpoints = checkpoints; self.genesisHash = self.checkpoints[0].blockHash; - _checkpointsByHashDictionary = [NSMutableDictionary dictionary]; - _checkpointsByHeightDictionary = [NSMutableDictionary dictionary]; + self.checkpointsByHashDictionary = [NSMutableDictionary dictionary]; + self.checkpointsByHeightDictionary = [NSMutableDictionary dictionary]; dispatch_sync(self.networkingQueue, ^{ self.chainManagedObjectContext = [NSManagedObjectContext chainContext]; }); - + self.shareCore = [[DSDashSharedCore alloc] initOnChain:self]; + return self; } -- (instancetype)initAsDevnetWithIdentifier:(DevnetType)devnetType +- (instancetype)initAsDevnetWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType onProtocolVersion:(uint32_t)protocolVersion checkpoints:(NSArray *)checkpoints { //for devnet the genesis checkpoint is really the second block if (!(self = [self init])) return nil; - _chainType = chain_type_for_devnet_type(devnetType); + self.chainType = dash_spv_crypto_network_chain_type_ChainType_DevNet_ctor(devnetType); if (!checkpoints || ![checkpoints count]) { DSCheckpoint *genesisCheckpoint = [DSCheckpoint genesisDevnetCheckpoint]; DSCheckpoint *secondCheckpoint = [self createDevNetGenesisBlockCheckpointForParentCheckpoint:genesisCheckpoint withIdentifier:devnetType onProtocolVersion:protocolVersion]; @@ -234,11 +168,13 @@ - (instancetype)initAsDevnetWithIdentifier:(DevnetType)devnetType dispatch_sync(self.networkingQueue, ^{ self.chainManagedObjectContext = [NSManagedObjectContext chainContext]; }); - self.headersMaxAmount = chain_headers_max_amount(_chainType); + self.headersMaxAmount = dash_spv_crypto_network_chain_type_ChainType_header_max_amount(self.chainType); + self.shareCore = [[DSDashSharedCore alloc] initOnChain:self]; + return self; } -- (instancetype)initAsDevnetWithIdentifier:(DevnetType)devnetType +- (instancetype)initAsDevnetWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType protocolVersion:(uint32_t)protocolVersion minProtocolVersion:(uint32_t)minProtocolVersion checkpoints:(NSArray *)checkpoints @@ -261,12 +197,56 @@ - (instancetype)initAsDevnetWithIdentifier:(DevnetType)devnetType return self; } +- (Runtime *)sharedRuntime { + return self.shareCore.runtime; +} + +- (DArcProcessor *)sharedProcessor { + return self.shareCore.processor; +} +- (DProcessor *)sharedProcessorObj { + return self.sharedProcessor->obj; +} +//- (DArcCache *)sharedCache { +// return self.shareCore.cache; +//} +//- (DCache *)sharedCacheObj { +// return self.sharedCache->obj; +//} +- (DArcPlatformSDK *)sharedPlatform { + return self.shareCore.platform; +} +- (PlatformSDK *)sharedPlatformObj { + return self.sharedPlatform->obj; +} + +- (ContactRequestManager *)sharedContactsObj { + return self.shareCore.contactRequests->obj; +} + +- (IdentitiesManager *)sharedIdentitiesObj { + return self.shareCore.identitiesManager->obj; +} + +- (DocumentsManager *)sharedDocumentsObj { + return self.shareCore.documentsManager->obj; +} + +- (ContractsManager *)sharedContractsObj { + return self.shareCore.contractsManager->obj; +} + +- (SaltedDomainHashesManager *)sharedSaltedDomainHashesObj { + return self.shareCore.saltedDomainHashes->obj; +} + + + (DSChain *)mainnet { static DSChain *_mainnet = nil; static dispatch_once_t mainnetToken = 0; __block BOOL inSetUp = FALSE; dispatch_once(&mainnetToken, ^{ - _mainnet = [[DSChain alloc] initWithType:chain_type_from_index(ChainType_MainNet) checkpoints:[DSChain createCheckpointsArrayFromCheckpoints:mainnet_checkpoint_array count:(sizeof(mainnet_checkpoint_array) / sizeof(*mainnet_checkpoint_array))]]; + _mainnet = [[DSChain alloc] initWithType:dash_spv_crypto_network_chain_type_ChainType_MainNet_ctor() checkpoints:[DSChain createCheckpointsArrayFromCheckpoints:mainnet_checkpoint_array count:(sizeof(mainnet_checkpoint_array) / sizeof(*mainnet_checkpoint_array))]]; inSetUp = TRUE; }); if (inSetUp) { @@ -290,7 +270,7 @@ + (DSChain *)testnet { static dispatch_once_t testnetToken = 0; __block BOOL inSetUp = FALSE; dispatch_once(&testnetToken, ^{ - _testnet = [[DSChain alloc] initWithType:chain_type_from_index(ChainType_TestNet) checkpoints:[DSChain createCheckpointsArrayFromCheckpoints:testnet_checkpoint_array count:(sizeof(testnet_checkpoint_array) / sizeof(*testnet_checkpoint_array))]]; + _testnet = [[DSChain alloc] initWithType:dash_spv_crypto_network_chain_type_ChainType_TestNet_ctor() checkpoints:[DSChain createCheckpointsArrayFromCheckpoints:testnet_checkpoint_array count:(sizeof(testnet_checkpoint_array) / sizeof(*testnet_checkpoint_array))]]; inSetUp = TRUE; }); if (inSetUp) { @@ -320,14 +300,17 @@ + (DSChain *)devnetWithIdentifier:(NSString *)identifier { return devnetChain; } -+ (DSChain *)recoverKnownDevnetWithIdentifier:(DevnetType)devnetType withCheckpoints:(NSArray *)checkpointArray performSetup:(BOOL)performSetup { ++ (DSChain *)recoverKnownDevnetWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType + withCheckpoints:(NSArray *)checkpointArray + performSetup:(BOOL)performSetup { dispatch_once(&devnetToken, ^{ _devnetDictionary = [NSMutableDictionary dictionary]; }); DSChain *devnetChain = nil; __block BOOL inSetUp = FALSE; +// char *identifier = dash_spv_crypto_network_chain_type_DevnetType_identifier(devnetType); @synchronized(self) { - NSString *devnetIdentifier = [DSKeyManager NSStringFrom:chain_devnet_identifier(devnetType)]; + NSString *devnetIdentifier = [DSKeyManager NSStringFrom:dash_spv_crypto_network_chain_type_DevnetType_identifier(devnetType)]; if (![_devnetDictionary objectForKey:devnetIdentifier]) { devnetChain = [[DSChain alloc] initAsDevnetWithIdentifier:devnetType onProtocolVersion:PROTOCOL_VERSION_DEVNET checkpoints:checkpointArray]; _devnetDictionary[devnetIdentifier] = devnetChain; @@ -345,7 +328,7 @@ + (DSChain *)recoverKnownDevnetWithIdentifier:(DevnetType)devnetType withCheckpo return devnetChain; } -+ (DSChain *)setUpDevnetWithIdentifier:(DevnetType)devnetType ++ (DSChain *)setUpDevnetWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType protocolVersion:(uint32_t)protocolVersion minProtocolVersion:(uint32_t)minProtocolVersion withCheckpoints:(NSArray *_Nullable)checkpointArray @@ -362,7 +345,7 @@ + (DSChain *)setUpDevnetWithIdentifier:(DevnetType)devnetType DSChain *devnetChain = nil; __block BOOL inSetUp = FALSE; @synchronized(self) { - NSString *devnetIdentifier = [DSKeyManager NSStringFrom:chain_devnet_identifier(devnetType)]; + NSString *devnetIdentifier = [DSKeyManager NSStringFrom:dash_spv_crypto_network_chain_type_DevnetType_identifier(devnetType)]; if (![_devnetDictionary objectForKey:devnetIdentifier]) { devnetChain = [[DSChain alloc] initAsDevnetWithIdentifier:devnetType protocolVersion:protocolVersion minProtocolVersion:minProtocolVersion checkpoints:checkpointArray minimumDifficultyBlocks:minimumDifficultyBlocks port:port dapiJRPCPort:dapiJRPCPort dapiGRPCPort:dapiGRPCPort dpnsContractID:dpnsContractID dashpayContractID:dashpayContractID isTransient:isTransient]; _devnetDictionary[devnetIdentifier] = devnetChain; @@ -403,23 +386,6 @@ - (void)setUp { [self retrieveStandaloneDerivationPaths]; } -// MARK: - Helpers - -- (BOOL)isCore19Active { - return self.lastTerminalBlockHeight >= chain_core19_activation_height(self.chainType); -} - -- (BOOL)isCore20Active { - return self.lastTerminalBlockHeight >= chain_core20_activation_height(self.chainType); -} - -- (BOOL)isCore20ActiveAtHeight:(uint32_t)height { - return height >= chain_core20_activation_height(self.chainType); -} - -- (KeyKind)activeBLSType { - return [self isCore19Active] ? KeyKind_BLSBasic : KeyKind_BLS; -} - (NSDictionary *)syncBlocks { return [self.mSyncBlocks copy]; @@ -477,25 +443,13 @@ - (DSChainManager *)chainManager { - (DSKeyManager *)keyManager { return [[self chainManager] keyManager]; } - -+ (NSMutableArray *)createCheckpointsArrayFromCheckpoints:(checkpoint *)checkpoints count:(NSUInteger)checkpointCount { - NSMutableArray *checkpointMutableArray = [NSMutableArray array]; - for (int i = 0; i < checkpointCount; i++) { - checkpoint cpt = checkpoints[i]; - NSString *merkleRootString = [NSString stringWithCString:cpt.merkleRoot encoding:NSUTF8StringEncoding]; - NSString *chainWorkString = [NSString stringWithCString:cpt.chainWork encoding:NSUTF8StringEncoding]; - uint32_t blockHeight = cpt.height; - UInt256 blockHash = [NSString stringWithCString:cpt.checkpointHash encoding:NSUTF8StringEncoding].hexToData.reverse.UInt256; - UInt256 chainWork = chainWorkString.hexToData.reverse.UInt256; - UInt256 merkleRoot = [merkleRootString isEqualToString:@""] ? UINT256_ZERO : merkleRootString.hexToData.reverse.UInt256; - DSCheckpoint *checkpoint = [DSCheckpoint checkpointForHeight:blockHeight blockHash:blockHash timestamp:cpt.timestamp target:cpt.target merkleRoot:merkleRoot chainWork:chainWork masternodeListName:[NSString stringWithCString:cpt.masternodeListPath encoding:NSUTF8StringEncoding]]; - [checkpointMutableArray addObject:checkpoint]; - } - return [checkpointMutableArray copy]; +- (DSMasternodeManager *)masternodeManager { + return [[self chainManager] masternodeManager]; } + - (BOOL)isEqual:(id)obj { - return self == obj || ([obj isKindOfClass:[DSChain class]] && uint256_eq([obj genesisHash], _genesisHash)); + return self == obj || ([obj isKindOfClass:[DSChain class]] && uint256_eq([obj genesisHash], self.genesisHash)); } - (NSUInteger)hash { @@ -515,7 +469,9 @@ - (UInt256)blockHashForDevNetGenesisBlockWithVersion:(uint32_t)version prevHash: return [DSKeyManager x11:d]; } -- (DSCheckpoint *)createDevNetGenesisBlockCheckpointForParentCheckpoint:(DSCheckpoint *)checkpoint withIdentifier:(DevnetType)identifier onProtocolVersion:(uint32_t)protocolVersion { +- (DSCheckpoint *)createDevNetGenesisBlockCheckpointForParentCheckpoint:(DSCheckpoint *)checkpoint + withIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)identifier + onProtocolVersion:(uint32_t)protocolVersion { uint32_t nTime = checkpoint.timestamp + 1; uint32_t nBits = checkpoint.target; UInt256 fullTarget = setCompactLE(nBits); @@ -549,34 +505,9 @@ - (dispatch_queue_t)dapiMetadataQueue { return _dapiMetadataQueue; } -// MARK: - Check Type - -- (BOOL)isMainnet { - return self.chainType.tag == ChainType_MainNet; -} - -- (BOOL)isTestnet { - return self.chainType.tag == ChainType_TestNet; -} - -- (BOOL)isDevnetAny { - return chain_type_is_devnet_any(self.chainType); -} - -- (BOOL)isEvolutionEnabled { - return NO; - // return [self isDevnetAny] || [self isTestnet]; -} - -- (BOOL)isDevnetWithGenesisHash:(UInt256)genesisHash { - return chain_type_is_devnet_any(self.chainType) && uint256_eq([self genesisHash], genesisHash); -} // MARK: - Keychain Strings -- (NSString *)chainWalletsKey { - return [NSString stringWithFormat:@"%@_%@", CHAIN_WALLETS_KEY, [self uniqueID]]; -} - (NSString *)chainStandaloneDerivationPathsKey { return [NSString stringWithFormat:@"%@_%@", CHAIN_STANDALONE_DERIVATIONS_KEY, [self uniqueID]]; @@ -586,65 +517,6 @@ - (NSString *)registeredPeersKey { return [NSString stringWithFormat:@"%@_%@", REGISTERED_PEERS_KEY, [self uniqueID]]; } -- (NSString *)votingKeysKey { - return [NSString stringWithFormat:@"%@_%@", CHAIN_VOTING_KEYS_KEY, [self uniqueID]]; -} - - -// MARK: - Names and Identifiers - -- (NSString *)uniqueID { - if (!_uniqueID) { - _uniqueID = [[NSData dataWithUInt256:[self genesisHash]] shortHexString]; - } - return _uniqueID; -} - - -- (NSString *)networkName { - switch (self.chainType.tag) { - case ChainType_MainNet: - return @"main"; - case ChainType_TestNet: - return @"test"; - case ChainType_DevNet: - if (_networkName) return _networkName; - return @"dev"; - } - if (_networkName) return _networkName; -} - -- (NSString *)name { - switch (self.chainType.tag) { - case ChainType_MainNet: - return @"Mainnet"; - case ChainType_TestNet: - return @"Testnet"; - case ChainType_DevNet: - if (_networkName) return _networkName; - return [NSString stringWithFormat:@"Devnet - %@.%u", [DSKeyManager devnetIdentifierFor:self.chainType], devnet_version_for_chain_type(self.chainType)]; - } - if (_networkName) return _networkName; -} - -- (NSString *)localizedName { - switch (self.chainType.tag) { - case ChainType_MainNet: - return DSLocalizedString(@"Mainnet", nil); - case ChainType_TestNet: - return DSLocalizedString(@"Testnet", nil); - case ChainType_DevNet: - if (_networkName) return _networkName; - return [NSString stringWithFormat:@"%@ - %@.%u", DSLocalizedString(@"Devnet", nil), [DSKeyManager devnetIdentifierFor:self.chainType], devnet_version_for_chain_type(self.chainType)]; - } - if (_networkName) return _networkName; -} - -- (void)setDevnetNetworkName:(NSString *)networkName { - if (chain_type_is_devnet_any(self.chainType)) { - _networkName = @"Evonet"; - } -} // MARK: - L1 Chain Parameters @@ -652,20 +524,18 @@ - (void)setDevnetNetworkName:(NSString *)networkName { - (NSArray *)standardDerivationPathsForAccountNumber:(uint32_t)accountNumber { if (accountNumber == 0) { - return @[[DSFundsDerivationPath bip32DerivationPathForAccountNumber:accountNumber onChain:self], [DSFundsDerivationPath bip44DerivationPathForAccountNumber:accountNumber onChain:self], [DSDerivationPath masterBlockchainIdentityContactsDerivationPathForAccountNumber:accountNumber onChain:self]]; + return @[[DSFundsDerivationPath bip32DerivationPathForAccountNumber:accountNumber onChain:self], + [DSFundsDerivationPath bip44DerivationPathForAccountNumber:accountNumber onChain:self], + [DSDerivationPath masterIdentityContactsDerivationPathForAccountNumber:accountNumber onChain:self], + [DSFundsDerivationPath coinJoinDerivationPathForAccountNumber:accountNumber onChain:self]]; } else { //don't include BIP32 derivation path on higher accounts - return @[[DSFundsDerivationPath bip44DerivationPathForAccountNumber:accountNumber onChain:self], [DSDerivationPath masterBlockchainIdentityContactsDerivationPathForAccountNumber:accountNumber onChain:self]]; + return @[[DSFundsDerivationPath bip44DerivationPathForAccountNumber:accountNumber onChain:self], + [DSDerivationPath masterIdentityContactsDerivationPathForAccountNumber:accountNumber onChain:self], + [DSFundsDerivationPath coinJoinDerivationPathForAccountNumber:accountNumber onChain:self]]; } } -- (uint16_t)transactionVersion { - return chain_transaction_version(self.chainType); -} - -- (uintptr_t)peerMisbehavingThreshold { - return chain_peer_misbehaving_threshold(self.chainType); -} - (BOOL)syncsBlockchain { //required for SPV wallets return ([[DSOptionsManager sharedInstance] syncType] & DSSyncType_NeedsWalletSyncType) != 0; @@ -675,17 +545,6 @@ - (BOOL)needsInitialTerminalHeadersSync { return !(self.estimatedBlockHeight == self.lastTerminalBlockHeight); } -// This is a time interval since 1970 -- (NSTimeInterval)earliestWalletCreationTime { - if (![self.wallets count]) return BIP39_CREATION_TIME; - NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]; - for (DSWallet *wallet in self.wallets) { - if (timeInterval > wallet.walletCreationTime) { - timeInterval = wallet.walletCreationTime; - } - } - return timeInterval; -} - (NSTimeInterval)startSyncFromTime { if ([self syncsBlockchain]) { @@ -698,467 +557,11 @@ - (NSTimeInterval)startSyncFromTime { - (NSString *)chainTip { return [NSData dataWithUInt256:self.lastTerminalBlock.blockHash].shortHexString; } - -// MARK: Sync Parameters - -- (uint32_t)magicNumber { - return chain_magic_number(_chainType); -} - -- (uint32_t)protocolVersion { - switch (self.chainType.tag) { - case ChainType_MainNet: - return PROTOCOL_VERSION_MAINNET; //(70216 + (self.headersMaxAmount / 2000)); - case ChainType_TestNet: - return PROTOCOL_VERSION_TESTNET; - case ChainType_DevNet: { - NSError *error = nil; - uint32_t protocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PROTOCOL_VERSION_LOCATION], &error); - if (!error && protocolVersion) - return protocolVersion; - else - return PROTOCOL_VERSION_DEVNET; - } - } -} - -- (void)setProtocolVersion:(uint32_t)protocolVersion { - if (chain_type_is_devnet_any(self.chainType)) { - setKeychainInt(protocolVersion, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PROTOCOL_VERSION_LOCATION], NO); - } -} - -- (BOOL)isRotatedQuorumsPresented { - if (_cachedIsQuorumRotationPresented) return _cachedIsQuorumRotationPresented; - switch (self.chainType.tag) { - case ChainType_MainNet: { - NSError *error = nil; - BOOL isPresented = (BOOL)getKeychainInt([NSString stringWithFormat:@"MAINNET_%@", QUORUM_ROTATION_PRESENCE_KEY], &error); - _cachedIsQuorumRotationPresented = !error && isPresented; - break; - } - case ChainType_TestNet: { - NSError *error = nil; - BOOL isPresented = (BOOL)getKeychainInt([NSString stringWithFormat:@"TESTNET_%@", QUORUM_ROTATION_PRESENCE_KEY], &error); - _cachedIsQuorumRotationPresented = !error && isPresented; - break; - } - case ChainType_DevNet: { - NSError *error = nil; - BOOL isPresented = (BOOL)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], QUORUM_ROTATION_PRESENCE_KEY], &error); - _cachedIsQuorumRotationPresented = !error && isPresented; - break; - } - } - return _cachedIsQuorumRotationPresented; -} - - -- (void)setIsRotatedQuorumsPresented:(BOOL)isRotatedQuorumsPresented { - switch (self.chainType.tag) { - case ChainType_MainNet: - setKeychainInt(isRotatedQuorumsPresented, [NSString stringWithFormat:@"MAINNET_%@", QUORUM_ROTATION_PRESENCE_KEY], NO); - break; - case ChainType_TestNet: - setKeychainInt(isRotatedQuorumsPresented, [NSString stringWithFormat:@"TESTNET_%@", QUORUM_ROTATION_PRESENCE_KEY], NO); - break; - case ChainType_DevNet: { - setKeychainInt(isRotatedQuorumsPresented, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], QUORUM_ROTATION_PRESENCE_KEY], NO); - break; - } - } - _cachedIsQuorumRotationPresented = isRotatedQuorumsPresented; -} - -- (uint32_t)minProtocolVersion { - @synchronized(self) { - if (_cachedMinProtocolVersion) return _cachedMinProtocolVersion; - switch (self.chainType.tag) { - case ChainType_MainNet: { - NSError *error = nil; - uint32_t minProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"MAINNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], &error); - if (!error && minProtocolVersion) - _cachedMinProtocolVersion = MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_MAINNET); - else - _cachedMinProtocolVersion = DEFAULT_MIN_PROTOCOL_VERSION_MAINNET; - break; - } - case ChainType_TestNet: { - NSError *error = nil; - uint32_t minProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"TESTNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], &error); - if (!error && minProtocolVersion) - _cachedMinProtocolVersion = MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_TESTNET); - else - _cachedMinProtocolVersion = DEFAULT_MIN_PROTOCOL_VERSION_TESTNET; - break; - } - case ChainType_DevNet: { - NSError *error = nil; - uint32_t minProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], &error); - if (!error && minProtocolVersion) - _cachedMinProtocolVersion = MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_DEVNET); - else - _cachedMinProtocolVersion = DEFAULT_MIN_PROTOCOL_VERSION_DEVNET; - break; - } - } - return _cachedMinProtocolVersion; - } -} - - -- (void)setMinProtocolVersion:(uint32_t)minProtocolVersion { - @synchronized(self) { - if (minProtocolVersion < MIN_VALID_MIN_PROTOCOL_VERSION || minProtocolVersion > MAX_VALID_MIN_PROTOCOL_VERSION) return; - switch (self.chainType.tag) { - case ChainType_MainNet: - setKeychainInt(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_MAINNET), [NSString stringWithFormat:@"MAINNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], NO); - _cachedMinProtocolVersion = MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_MAINNET); - break; - case ChainType_TestNet: - setKeychainInt(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_TESTNET), [NSString stringWithFormat:@"TESTNET_%@", DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], NO); - _cachedMinProtocolVersion = MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_TESTNET); - break; - case ChainType_DevNet: { - setKeychainInt(MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_DEVNET), [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DEFAULT_MIN_PROTOCOL_VERSION_LOCATION], NO); - _cachedMinProtocolVersion = MAX(minProtocolVersion, DEFAULT_MIN_PROTOCOL_VERSION_DEVNET); - break; - } - } - } -} - -- (uint32_t)standardPort { - if (_cachedStandardPort) return _cachedStandardPort; - switch (self.chainType.tag) { - case ChainType_MainNet: - _cachedStandardPort = MAINNET_STANDARD_PORT; - return MAINNET_STANDARD_PORT; - case ChainType_TestNet: - _cachedStandardPort = TESTNET_STANDARD_PORT; - return TESTNET_STANDARD_PORT; - case ChainType_DevNet: { - NSError *error = nil; - uint32_t cachedStandardPort = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], STANDARD_PORT_LOCATION], &error); - if (!error && cachedStandardPort) { - _cachedStandardPort = cachedStandardPort; - return _cachedStandardPort; - } - return DEVNET_STANDARD_PORT; - } - } -} - -- (void)setStandardPort:(uint32_t)standardPort { - if (chain_type_is_devnet_any(self.chainType)) { - _cachedStandardPort = standardPort; - setKeychainInt(standardPort, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], STANDARD_PORT_LOCATION], NO); - } -} - -- (uint32_t)standardDapiGRPCPort { - if (_cachedStandardDapiGRPCPort) return _cachedStandardDapiGRPCPort; - switch (self.chainType.tag) { - case ChainType_MainNet: - _cachedStandardDapiGRPCPort = MAINNET_DAPI_GRPC_STANDARD_PORT; - return MAINNET_DAPI_GRPC_STANDARD_PORT; - case ChainType_TestNet: - _cachedStandardDapiGRPCPort = TESTNET_DAPI_GRPC_STANDARD_PORT; - return TESTNET_DAPI_GRPC_STANDARD_PORT; - case ChainType_DevNet: { - NSError *error = nil; - uint32_t cachedStandardDapiGRPCPort = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], GRPC_PORT_LOCATION], &error); - if (!error && cachedStandardDapiGRPCPort) { - _cachedStandardDapiGRPCPort = cachedStandardDapiGRPCPort; - return _cachedStandardDapiGRPCPort; - } else - return DEVNET_DAPI_GRPC_STANDARD_PORT; - } - } -} - -- (void)setStandardDapiGRPCPort:(uint32_t)standardDapiGRPCPort { - if (chain_type_is_devnet_any(self.chainType)) { - _cachedStandardDapiGRPCPort = standardDapiGRPCPort; - setKeychainInt(standardDapiGRPCPort, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], GRPC_PORT_LOCATION], NO); - } -} - -// MARK: Mining and Dark Gravity Wave Parameters - -- (UInt256)maxProofOfWork { - if (uint256_is_not_zero(_cachedMaxProofOfWork)) return _cachedMaxProofOfWork; - switch (self.chainType.tag) { - case ChainType_MainNet: - _cachedMaxProofOfWork = MAX_PROOF_OF_WORK_MAINNET; - break; - case ChainType_TestNet: - _cachedMaxProofOfWork = MAX_PROOF_OF_WORK_TESTNET; - break; - case ChainType_DevNet: - _cachedMaxProofOfWork = MAX_PROOF_OF_WORK_DEVNET; - break; - } - return _cachedMaxProofOfWork; -} - -- (uint32_t)maxProofOfWorkTarget { - return chain_max_proof_of_work_target(self.chainType); -} - -- (BOOL)allowMinDifficultyBlocks { - return chain_allow_min_difficulty_blocks(self.chainType); -} - -- (uint64_t)baseReward { - if (self.chainType.tag == ChainType_MainNet) return 5 * DUFFS; - return 50 * DUFFS; -} - -// MARK: Spork Parameters - -- (NSString *)sporkPublicKeyHexString { - switch (self.chainType.tag) { - case ChainType_MainNet: - return SPORK_PUBLIC_KEY_MAINNET; - case ChainType_TestNet: - return SPORK_PUBLIC_KEY_TESTNET; - case ChainType_DevNet: { - NSError *error = nil; - NSString *publicKey = getKeychainString([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PUBLIC_KEY_LOCATION], &error); - if (!error && publicKey) { - return publicKey; - } else { - return nil; - } - } - } - return nil; -} - -- (void)setSporkPublicKeyHexString:(NSString *)sporkPublicKey { - if (chain_type_is_devnet_any(self.chainType)) { - setKeychainString(sporkPublicKey, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PUBLIC_KEY_LOCATION], NO); - } -} - -- (NSString *)sporkPrivateKeyBase58String { - if (chain_type_is_devnet_any(self.chainType)) { - NSError *error = nil; - NSString *publicKey = getKeychainString([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PRIVATE_KEY_LOCATION], &error); - if (!error && publicKey) { - return publicKey; - } - } - return nil; -} - -- (void)setSporkPrivateKeyBase58String:(NSString *)sporkPrivateKey { - if (chain_type_is_devnet_any(self.chainType)) { - setKeychainString(sporkPrivateKey, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_PRIVATE_KEY_LOCATION], YES); - } -} - -- (NSString *)sporkAddress { - switch (self.chainType.tag) { - case ChainType_MainNet: - return SPORK_ADDRESS_MAINNET; - case ChainType_TestNet: - return SPORK_ADDRESS_TESTNET; - case ChainType_DevNet: { - NSError *error = nil; - NSString *publicKey = getKeychainString([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_ADDRESS_LOCATION], &error); - if (!error && publicKey) { - return publicKey; - } else { - return nil; - } - } - } - return nil; -} - -- (void)setSporkAddress:(NSString *)sporkAddress { - if (chain_type_is_devnet_any(self.chainType)) { - setKeychainString(sporkAddress, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], SPORK_ADDRESS_LOCATION], NO); - } -} - -// MARK: Fee Parameters - -// fee that will be added for a transaction of the given size in bytes -- (uint64_t)feeForTxSize:(NSUInteger)size { - uint64_t standardFee = size * TX_FEE_PER_B; //!OCLINT // standard fee based on tx size -#if (!!FEE_PER_KB_URL) - uint64_t fee = ((size * self.feePerByte + 99) / 100) * 100; // fee using feePerByte, rounded up to nearest 100 satoshi - return (fee > standardFee) ? fee : standardFee; -#else - return standardFee; -#endif -} - -// outputs below this amount are uneconomical due to fees -- (uint64_t)minOutputAmount { - uint64_t amount = (TX_MIN_OUTPUT_AMOUNT * self.feePerByte + MIN_FEE_PER_B - 1) / MIN_FEE_PER_B; - return (amount > TX_MIN_OUTPUT_AMOUNT) ? amount : TX_MIN_OUTPUT_AMOUNT; -} - -// MARK: - L2 Chain Parameters - -- (uint32_t)platformProtocolVersion { - switch (self.chainType.tag) { - case ChainType_MainNet: - return PLATFORM_PROTOCOL_VERSION_MAINNET; //(70216 + (self.headersMaxAmount / 2000)); - case ChainType_TestNet: - return PLATFORM_PROTOCOL_VERSION_TESTNET; - case ChainType_DevNet: { - NSError *error = nil; - uint32_t platformProtocolVersion = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PLATFORM_PROTOCOL_VERSION_LOCATION], &error); - if (!error && platformProtocolVersion) - return platformProtocolVersion; - else - return PLATFORM_PROTOCOL_VERSION_DEVNET; - } - } -} - -- (void)setPlatformProtocolVersion:(uint32_t)platformProtocolVersion { - if (chain_type_is_devnet_any(self.chainType)) { - setKeychainInt(platformProtocolVersion, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], PLATFORM_PROTOCOL_VERSION_LOCATION], NO); - } -} - -- (UInt256)dpnsContractID { - if (uint256_is_not_zero(_cachedDpnsContractID)) return _cachedDpnsContractID; - switch (self.chainType.tag) { - case ChainType_MainNet: - if (!self.isEvolutionEnabled) return UINT256_ZERO; - _cachedDpnsContractID = MAINNET_DPNS_CONTRACT_ID.base58ToData.UInt256; - return _cachedDpnsContractID; - case ChainType_TestNet: - if (!self.isEvolutionEnabled) return UINT256_ZERO; - _cachedDpnsContractID = TESTNET_DPNS_CONTRACT_ID.base58ToData.UInt256; - return _cachedDpnsContractID; - case ChainType_DevNet: { - NSError *error = nil; - NSData *cachedDpnsContractIDData = getKeychainData([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DPNS_CONTRACT_ID], &error); - if (!error && cachedDpnsContractIDData) { - _cachedDpnsContractID = cachedDpnsContractIDData.UInt256; - return _cachedDpnsContractID; - } - return UINT256_ZERO; - } - } -} - -- (void)setDpnsContractID:(UInt256)dpnsContractID { - if (chain_type_is_devnet_any(self.chainType)) { - _cachedDpnsContractID = dpnsContractID; - if (uint256_is_zero(dpnsContractID)) { - NSError *error = nil; - NSString *identifier = [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DPNS_CONTRACT_ID]; - BOOL hasDashpayContractID = (getKeychainData(identifier, &error) != nil); - if (hasDashpayContractID) { - setKeychainData(nil, identifier, NO); - } - } else { - setKeychainData(uint256_data(dpnsContractID), [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DPNS_CONTRACT_ID], NO); - } - } -} - -- (UInt256)dashpayContractID { - if (uint256_is_not_zero(_cachedDashpayContractID)) return _cachedDashpayContractID; - switch (self.chainType.tag) { - case ChainType_MainNet: - if (!self.isEvolutionEnabled) return UINT256_ZERO; - _cachedDashpayContractID = MAINNET_DASHPAY_CONTRACT_ID.base58ToData.UInt256; - return _cachedDashpayContractID; - case ChainType_TestNet: - if (!self.isEvolutionEnabled) return UINT256_ZERO; - _cachedDashpayContractID = TESTNET_DASHPAY_CONTRACT_ID.base58ToData.UInt256; - return _cachedDashpayContractID; - case ChainType_DevNet: { - NSError *error = nil; - NSData *cachedDashpayContractIDData = getKeychainData([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DASHPAY_CONTRACT_ID], &error); - if (!error && cachedDashpayContractIDData) { - _cachedDashpayContractID = cachedDashpayContractIDData.UInt256; - return _cachedDashpayContractID; - } - return UINT256_ZERO; - } - } -} - -- (void)setDashpayContractID:(UInt256)dashpayContractID { - if (chain_type_is_devnet_any(self.chainType)) { - _cachedDashpayContractID = dashpayContractID; - if (uint256_is_zero(dashpayContractID)) { - NSError *error = nil; - NSString *identifier = [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DASHPAY_CONTRACT_ID]; - BOOL hasDashpayContractID = (getKeychainData(identifier, &error) != nil); - if (hasDashpayContractID) { - setKeychainData(nil, identifier, NO); - } - } else { - setKeychainData(uint256_data(dashpayContractID), [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], DASHPAY_CONTRACT_ID], NO); - } - } -} - -- (void)setMinimumDifficultyBlocks:(uint32_t)minimumDifficultyBlocks { - if (chain_type_is_devnet_any(self.chainType)) { - _cachedMinimumDifficultyBlocks = minimumDifficultyBlocks; - setKeychainInt(minimumDifficultyBlocks, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY], NO); - } -} - -- (uint32_t)minimumDifficultyBlocks { - if (_cachedMinimumDifficultyBlocks) return _cachedMinimumDifficultyBlocks; - if (chain_type_is_devnet_any(self.chainType)) { - NSError *error = nil; - uint32_t cachedMinimumDifficultyBlocks = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], MINIMUM_DIFFICULTY_BLOCKS_COUNT_KEY], &error); - if (!error && cachedMinimumDifficultyBlocks) { - _cachedMinimumDifficultyBlocks = cachedMinimumDifficultyBlocks; - return _cachedMinimumDifficultyBlocks; - } else { - return 0; - } - } else { - _cachedMinimumDifficultyBlocks = 0; - return 0; - } +- (uint32_t)chainTipHeight { + return self.lastTerminalBlock.height; } -- (uint32_t)standardDapiJRPCPort { - if (_cachedStandardDapiJRPCPort) return _cachedStandardDapiJRPCPort; - switch (self.chainType.tag) { - case ChainType_MainNet: - _cachedStandardDapiJRPCPort = MAINNET_DAPI_JRPC_STANDARD_PORT; - return MAINNET_DAPI_JRPC_STANDARD_PORT; - case ChainType_TestNet: - _cachedStandardDapiJRPCPort = TESTNET_DAPI_JRPC_STANDARD_PORT; - return TESTNET_DAPI_JRPC_STANDARD_PORT; - case ChainType_DevNet: { - NSError *error = nil; - uint32_t cachedStandardDapiJRPCPort = (uint32_t)getKeychainInt([NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], JRPC_PORT_LOCATION], &error); - if (!error && cachedStandardDapiJRPCPort) { - _cachedStandardDapiJRPCPort = cachedStandardDapiJRPCPort; - return _cachedStandardDapiJRPCPort; - } else - return DEVNET_DAPI_JRPC_STANDARD_PORT; - } - } -} - -- (void)setStandardDapiJRPCPort:(uint32_t)standardDapiJRPCPort { - if (chain_type_is_devnet_any(self.chainType)) { - _cachedStandardDapiJRPCPort = standardDapiJRPCPort; - setKeychainInt(standardDapiJRPCPort, [NSString stringWithFormat:@"%@%@", [DSKeyManager devnetIdentifierFor:self.chainType], JRPC_PORT_LOCATION], NO); - } -} - // MARK: - Standalone Derivation Paths - (BOOL)hasAStandaloneDerivationPath { @@ -1221,6 +624,26 @@ - (NSArray *)standaloneDerivationPaths { // MARK: - Probabilistic Filters + +- (NSArray *)newAddressesForBloomFilter { + NSMutableArray *allAddressesArray = [NSMutableArray array]; + for (DSWallet *wallet in self.wallets) { + // every time a new wallet address is added, the bloom filter has to be rebuilt, and each address is only used for + // one transaction, so here we generate some spare addresses to avoid rebuilding the filter each time a wallet + // transaction is encountered during the blockchain download + [wallet registerAddressesWithProlongGapLimit]; + [allAddressesArray addObjectsFromArray:[wallet allAddresses]]; + } + + for (DSFundsDerivationPath *derivationPath in self.standaloneDerivationPaths) { + [derivationPath registerAddressesWithSettings:[DSGapLimitFunds external:SEQUENCE_GAP_LIMIT_EXTERNAL]]; + [derivationPath registerAddressesWithSettings:[DSGapLimitFunds internal:SEQUENCE_GAP_LIMIT_INTERNAL]]; + NSArray *addresses = [derivationPath.allReceiveAddresses arrayByAddingObjectsFromArray:derivationPath.allChangeAddresses]; + [allAddressesArray addObjectsFromArray:addresses]; + } + return allAddressesArray; +} + - (DSBloomFilter *)bloomFilterWithFalsePositiveRate:(double)falsePositiveRate withTweak:(uint32_t)tweak { NSMutableSet *allAddresses = [NSMutableSet set]; NSMutableSet *allUTXOs = [NSMutableSet set]; @@ -1228,23 +651,14 @@ - (DSBloomFilter *)bloomFilterWithFalsePositiveRate:(double)falsePositiveRate wi // every time a new wallet address is added, the bloom filter has to be rebuilt, and each address is only used for // one transaction, so here we generate some spare addresses to avoid rebuilding the filter each time a wallet // transaction is encountered during the blockchain download - [wallet registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INITIAL unusedAccountGapLimit:SEQUENCE_UNUSED_GAP_LIMIT_INITIAL dashpayGapLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL internal:NO error:nil]; - [wallet registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INITIAL unusedAccountGapLimit:SEQUENCE_UNUSED_GAP_LIMIT_INITIAL dashpayGapLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL internal:YES error:nil]; - NSSet *addresses = [wallet.allReceiveAddresses setByAddingObjectsFromSet:wallet.allChangeAddresses]; - [allAddresses addObjectsFromArray:[addresses allObjects]]; + [wallet registerAddressesWithInitialGapLimit]; [allUTXOs addObjectsFromArray:wallet.unspentOutputs]; - - //we should also add the blockchain user public keys to the filter - //[allAddresses addObjectsFromArray:[wallet blockchainIdentityAddresses]]; - [allAddresses addObjectsFromArray:[wallet providerOwnerAddresses]]; - [allAddresses addObjectsFromArray:[wallet providerVotingAddresses]]; - [allAddresses addObjectsFromArray:[wallet providerOperatorAddresses]]; - [allAddresses addObjectsFromArray:[wallet platformNodeAddresses]]; + [allAddresses addObjectsFromArray:[wallet allAddresses]]; } for (DSFundsDerivationPath *derivationPath in self.standaloneDerivationPaths) { - [derivationPath registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INITIAL internal:NO error:nil]; - [derivationPath registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INITIAL internal:YES error:nil]; + [derivationPath registerAddressesWithSettings:[DSGapLimitFunds external:SEQUENCE_GAP_LIMIT_INITIAL]]; + [derivationPath registerAddressesWithSettings:[DSGapLimitFunds internal:SEQUENCE_GAP_LIMIT_INITIAL]]; NSArray *addresses = [derivationPath.allReceiveAddresses arrayByAddingObjectsFromArray:derivationPath.allChangeAddresses]; [allAddresses addObjectsFromArray:addresses]; } @@ -1297,168 +711,19 @@ - (DSBloomFilter *)bloomFilterWithFalsePositiveRate:(double)falsePositiveRate wi if (![filter containsData:d]) [filter insertData:d]; } - for (d in inputs) { // also add TXOs spent within the last 100 blocks - if (![filter containsData:d]) [filter insertData:d]; - } - return filter; -} - -- (BOOL)canConstructAFilter { - return [self hasAStandaloneDerivationPath] || [self hasAWallet]; -} - -// MARK: - Checkpoints - -- (BOOL)blockHeightHasCheckpoint:(uint32_t)blockHeight { - DSCheckpoint *checkpoint = [self lastCheckpointOnOrBeforeHeight:blockHeight]; - return (checkpoint.height == blockHeight); -} - -- (DSCheckpoint *)lastCheckpoint { - if (!_lastCheckpoint) { - _lastCheckpoint = [[self checkpoints] lastObject]; - } - return _lastCheckpoint; -} - -- (DSCheckpoint *)lastCheckpointOnOrBeforeHeight:(uint32_t)height { - NSUInteger genesisHeight = [self isDevnetAny] ? 1 : 0; - // if we don't have any blocks yet, use the latest checkpoint that's at least a week older than earliestKeyTime - for (long i = self.checkpoints.count - 1; i >= genesisHeight; i--) { - if (i == genesisHeight || ![self syncsBlockchain] || (self.checkpoints[i].height <= height)) { - return self.checkpoints[i]; - } - } - return nil; -} - -- (DSCheckpoint *)lastCheckpointOnOrBeforeTimestamp:(NSTimeInterval)timestamp { - NSUInteger genesisHeight = [self isDevnetAny] ? 1 : 0; - // if we don't have any blocks yet, use the latest checkpoint that's at least a week older than earliestKeyTime - for (long i = self.checkpoints.count - 1; i >= genesisHeight; i--) { - if (i == genesisHeight || ![self syncsBlockchain] || (self.checkpoints[i].timestamp <= timestamp)) { - return self.checkpoints[i]; - } - } - return nil; -} - -- (DSCheckpoint *_Nullable)lastCheckpointHavingMasternodeList { - NSSet *set = [self.checkpointsByHeightDictionary keysOfEntriesPassingTest:^BOOL(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) { - DSCheckpoint *checkpoint = (DSCheckpoint *)obj; - return (checkpoint.masternodeListName && ![checkpoint.masternodeListName isEqualToString:@""]); - }]; - NSArray *numbers = [[set allObjects] sortedArrayUsingSelector:@selector(compare:)]; - if (!numbers.count) return nil; - return self.checkpointsByHeightDictionary[numbers.lastObject]; -} - -- (DSCheckpoint *)checkpointForBlockHash:(UInt256)blockHash { - return [self.checkpointsByHashDictionary objectForKey:uint256_data(blockHash)]; -} - -- (DSCheckpoint *)checkpointForBlockHeight:(uint32_t)blockHeight { - return [self.checkpointsByHeightDictionary objectForKey:@(blockHeight)]; -} - -- (void)useCheckpointBeforeOrOnHeightForTerminalBlocksSync:(uint32_t)blockHeight { - DSCheckpoint *checkpoint = [self lastCheckpointOnOrBeforeHeight:blockHeight]; - self.terminalHeadersOverrideUseCheckpoint = checkpoint; -} - -- (void)useCheckpointBeforeOrOnHeightForSyncingChainBlocks:(uint32_t)blockHeight { - DSCheckpoint *checkpoint = [self lastCheckpointOnOrBeforeHeight:blockHeight]; - self.syncHeadersOverrideUseCheckpoint = checkpoint; -} - - -// MARK: - Wallet - -- (BOOL)hasAWallet { - return [self.mWallets count] > 0; -} - -- (NSArray *)wallets { - return [self.mWallets copy]; -} - -- (void)unregisterAllWallets { - for (DSWallet *wallet in [self.mWallets copy]) { - [self unregisterWallet:wallet]; - } -} - -- (void)unregisterAllWalletsMissingExtendedPublicKeys { - for (DSWallet *wallet in [self.mWallets copy]) { - if ([wallet hasAnExtendedPublicKeyMissing]) { - [self unregisterWallet:wallet]; - } - } -} - -- (void)unregisterWallet:(DSWallet *)wallet { - NSAssert(wallet.chain == self, @"the wallet you are trying to remove is not on this chain"); - [wallet wipeBlockchainInfoInContext:self.chainManagedObjectContext]; - [wallet wipeWalletInfo]; - [self.mWallets removeObject:wallet]; - NSError *error = nil; - NSMutableArray *keyChainArray = [getKeychainArray(self.chainWalletsKey, @[[NSString class]], &error) mutableCopy]; - if (!keyChainArray) keyChainArray = [NSMutableArray array]; - [keyChainArray removeObject:wallet.uniqueIDString]; - setKeychainArray(keyChainArray, self.chainWalletsKey, NO); - [self notify:DSChainWalletsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; -} - -- (BOOL)addWallet:(DSWallet *)walletToAdd { - BOOL alreadyPresent = FALSE; - for (DSWallet *cWallet in self.mWallets) { - if ([cWallet.uniqueIDString isEqual:walletToAdd.uniqueIDString]) { - alreadyPresent = TRUE; - } - } - if (!alreadyPresent) { - [self.mWallets addObject:walletToAdd]; - return TRUE; - } - return FALSE; -} - -- (void)registerWallet:(DSWallet *)wallet { - BOOL firstWallet = !self.mWallets.count; - if ([self.mWallets indexOfObject:wallet] == NSNotFound) { - [self addWallet:wallet]; - } - - if (firstWallet) { - //this is the first wallet, we should reset the last block height to the most recent checkpoint. - _lastSyncBlock = nil; //it will lazy load later - } - - NSError *error = nil; - NSMutableArray *keyChainArray = [getKeychainArray(self.chainWalletsKey, @[[NSString class]], &error) mutableCopy]; - if (!keyChainArray) keyChainArray = [NSMutableArray array]; - if (![keyChainArray containsObject:wallet.uniqueIDString]) { - [keyChainArray addObject:wallet.uniqueIDString]; - setKeychainArray(keyChainArray, self.chainWalletsKey, NO); - [self notify:DSChainWalletsDidChangeNotification userInfo:@{DSChainManagerNotificationChainKey: self}]; + for (d in inputs) { // also add TXOs spent within the last 100 blocks + if (![filter containsData:d]) [filter insertData:d]; } + return filter; } -- (void)retrieveWallets { - NSError *error = nil; - NSArray *walletIdentifiers = getKeychainArray(self.chainWalletsKey, @[[NSString class]], &error); - if (!error && walletIdentifiers) { - for (NSString *uniqueID in walletIdentifiers) { - DSWallet *wallet = [[DSWallet alloc] initWithUniqueID:uniqueID forChain:self]; - [self addWallet:wallet]; - } - //we should load blockchain identies after all wallets are in the chain, as blockchain identities might be on different wallets and have interactions between each other - for (DSWallet *wallet in self.wallets) { - [wallet loadBlockchainIdentities]; - } - } +- (BOOL)canConstructAFilter { + return [self hasAStandaloneDerivationPath] || [self hasAWallet]; } + + + // MARK: - Blocks - (NSDictionary *)recentBlocks { @@ -1493,7 +758,7 @@ - (DSBlock *)lastBlockOnOrBeforeTimestamp:(NSTimeInterval)timestamp { } - (void)setLastTerminalBlockFromCheckpoints { - DSCheckpoint *checkpoint = self.terminalHeadersOverrideUseCheckpoint ? self.terminalHeadersOverrideUseCheckpoint : [self lastCheckpoint]; + DSCheckpoint *checkpoint = [self lastTerminalCheckpoint]; if (checkpoint) { if (self.mTerminalBlocks[uint256_obj(checkpoint.blockHash)]) { _lastTerminalBlock = self.mSyncBlocks[uint256_obj(checkpoint.blockHash)]; @@ -1544,6 +809,11 @@ - (DSBlock *)lastSyncBlock { return [self lastSyncBlockWithUseCheckpoints:YES]; } +- (void)resetLastSyncBlock { + _lastSyncBlock = nil; +} + + - (DSBlock *)lastSyncBlockWithUseCheckpoints:(BOOL)useCheckpoints { if (_lastSyncBlock) return _lastSyncBlock; @@ -1575,8 +845,8 @@ - (NSMutableDictionary *)mSyncBlocks { UInt256 checkpointHash = checkpoint.blockHash; self->_mSyncBlocks[uint256_obj(checkpointHash)] = [[DSBlock alloc] initWithCheckpoint:checkpoint onChain:self]; - self->_checkpointsByHeightDictionary[@(checkpoint.height)] = checkpoint; - self->_checkpointsByHashDictionary[uint256_data(checkpointHash)] = checkpoint; + self.checkpointsByHeightDictionary[@(checkpoint.height)] = checkpoint; + self.checkpointsByHashDictionary[uint256_data(checkpointHash)] = checkpoint; } }]; @@ -1585,7 +855,8 @@ - (NSMutableDictionary *)mSyncBlocks { } - (NSArray *)chainSyncBlockLocatorArray { - if (_lastSyncBlock && !(_lastSyncBlock.height == 1 && self.chainType.tag == ChainType_DevNet)) { + + if (_lastSyncBlock && !(_lastSyncBlock.height == 1 && dash_spv_crypto_network_chain_type_ChainType_is_devnet_any(self.chainType))) { return [self blockLocatorArrayForBlock:_lastSyncBlock]; } else if (!_lastPersistedChainSyncLocators) { _lastPersistedChainSyncLocators = [self blockLocatorArrayOnOrBeforeTimestamp:BIP39_CREATION_TIME includeInitialTerminalBlocks:NO]; @@ -1716,6 +987,7 @@ - (DSBlock *)blockFromChainTip:(NSUInteger)blocksAgo { return b; } + // MARK: From Insight on Testnet - (void)blockUntilGetInsightForBlockHash:(UInt256)blockHash { dispatch_semaphore_t sem = dispatch_semaphore_create(0); @@ -1729,6 +1001,19 @@ - (void)blockUntilGetInsightForBlockHash:(UInt256)blockHash { }]; dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } +- (DSBlock *_Nullable)blockUntilGetInsightForBlockHeight:(uint32_t)blockHeight { + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block DSBlock *b = NULL; + [[DSInsightManager sharedInstance] blockForBlockHeight:blockHeight onChain:self completion:^(DSBlock *_Nullable block, NSError *_Nullable error) { + if (!error && block) { + [self addInsightVerifiedBlock:block forBlockHash:block.blockHash]; + b = block; + } + dispatch_semaphore_signal(sem); + }]; + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + return b; +} - (void)addInsightVerifiedBlock:(DSBlock *)block forBlockHash:(UInt256)blockHash { if ([self allowInsightBlocksForVerification]) { @@ -1782,13 +1067,13 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( DSLog(@"%@ Block was received from peer after reset, ignoring it", prefix); return FALSE; } - //DSLog(@"a block %@",uint256_hex(block.blockHash)); //All blocks will be added from same delegateQueue NSArray *txHashes = block.transactionHashes; NSValue *blockHash = uint256_obj(block.blockHash), *prevBlock = uint256_obj(block.prevBlock); DSBlock *prev = nil; - + DSLog(@"[%@] + block (asHeader: %u) %@ prev: %@", self.name, isHeaderOnly, uint256_hex(block.blockHash), uint256_hex(block.prevBlock)); + DSBlockPosition blockPosition = DSBlockPosition_Orphan; DSChainSyncPhase phase = self.chainManager.syncPhase; if (phase == DSChainSyncPhase_InitialTerminalBlocks) { @@ -1956,7 +1241,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( onMainChain = TRUE; if ([self blockHeightHasCheckpoint:h] || - ((h % 1000 == 0) && (h + BLOCK_NO_FORK_DEPTH < self.lastTerminalBlockHeight) && !self.chainManager.masternodeManager.hasMasternodeListCurrentlyBeingSaved)) { + ((h % 1000 == 0) && (h + BLOCK_NO_FORK_DEPTH < self.lastTerminalBlockHeight) && !self.shareCore.hasMasternodeListCurrentlyBeingSaved)) { [self saveBlockLocators]; } @@ -2031,7 +1316,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( return TRUE; } - if (h <= self.lastChainLock.height) { + if (h <= DChainLockBlockHeight(self.lastChainLock.lock)) { DSLog(@"%@ ignoring block on fork when main chain is chainlocked: %d, blockHash: %@", prefix, h, blockHash); return TRUE; } @@ -2138,7 +1423,7 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( } if ((blockPosition & DSBlockPosition_Terminal) && checkpoint && checkpoint == [self lastCheckpointHavingMasternodeList]) { - [self.chainManager.masternodeManager loadFileDistributedMasternodeLists]; + [self.chainManager.masternodeManager restoreState]; } BOOL savedBlockLocators = NO; @@ -2189,13 +1474,6 @@ - (BOOL)addBlock:(DSBlock *)block receivedAsHeader:(BOOL)isHeaderOnly fromPeer:( return TRUE; } -- (void)notify:(NSNotificationName)name userInfo:(NSDictionary *_Nullable)userInfo { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:userInfo]; - }); -} - - // MARK: Terminal Blocks - (NSMutableDictionary *)mTerminalBlocks { @@ -2209,8 +1487,8 @@ - (NSMutableDictionary *)mTerminalBlocks { UInt256 checkpointHash = checkpoint.blockHash; self->_mTerminalBlocks[uint256_obj(checkpointHash)] = [[DSBlock alloc] initWithCheckpoint:checkpoint onChain:self]; - self->_checkpointsByHeightDictionary[@(checkpoint.height)] = checkpoint; - self->_checkpointsByHashDictionary[uint256_data(checkpointHash)] = checkpoint; + self.checkpointsByHeightDictionary[@(checkpoint.height)] = checkpoint; + self.checkpointsByHashDictionary[uint256_data(checkpointHash)] = checkpoint; } for (DSMerkleBlockEntity *e in [DSMerkleBlockEntity lastTerminalBlocks:KEEP_RECENT_TERMINAL_BLOCKS onChainEntity:[self chainEntityInContext:self.chainManagedObjectContext]]) { @autoreleasepool { @@ -2242,7 +1520,7 @@ - (DSBlock *)lastTerminalBlock { @synchronized (self) { if (!_lastTerminalBlock) { // if we don't have any headers yet, use the latest checkpoint - DSCheckpoint *lastCheckpoint = self.terminalHeadersOverrideUseCheckpoint ? self.terminalHeadersOverrideUseCheckpoint : self.lastCheckpoint; + DSCheckpoint *lastCheckpoint = [self lastTerminalCheckpoint]; uint32_t lastSyncBlockHeight = self.lastSyncBlockHeight; if (lastCheckpoint.height >= lastSyncBlockHeight) { @@ -2299,11 +1577,11 @@ - (void)clearOrphans { // MARK: Chain Locks - (BOOL)addChainLock:(DSChainLock *)chainLock { - DSBlock *terminalBlock = self.mTerminalBlocks[uint256_obj(chainLock.blockHash)]; + DSBlock *terminalBlock = self.mTerminalBlocks[uint256_obj(chainLock.blockHashData.UInt256)]; [terminalBlock setChainLockedWithChainLock:chainLock]; if ((terminalBlock.chainLocked) && (![self recentTerminalBlockForBlockHash:terminalBlock.blockHash])) { //the newly chain locked block is not in the main chain, we will need to reorg to it - DSLog(@"[%@] Added a chain lock for block %@ that was not on the main terminal chain ending in %@, reorginizing", self.name, terminalBlock, self.lastSyncBlock); + DSLog(@"[%@] Added a chain lock for block %@ that was not on the main terminal chain ending in %@, reorginizing", self.name, terminalBlock, self.lastSyncBlock); //clb chain locked block //tbmc terminal block DSBlock *clb = terminalBlock, *tbmc = self.lastTerminalBlock; @@ -2353,7 +1631,7 @@ - (BOOL)addChainLock:(DSChainLock *)chainLock { } } } - DSBlock *syncBlock = self.mSyncBlocks[uint256_obj(chainLock.blockHash)]; + DSBlock *syncBlock = self.mSyncBlocks[uint256_obj(chainLock.blockHashData.UInt256)]; [syncBlock setChainLockedWithChainLock:chainLock]; DSBlock *sbmc = self.lastSyncBlockDontUseCheckpoints; if (sbmc && (syncBlock.chainLocked) && ![self recentSyncBlockForBlockHash:syncBlock.blockHash]) { //!OCLINT @@ -2474,7 +1752,8 @@ - (uint32_t)lastTerminalBlockHeight { } - (BOOL)allowInsightBlocksForVerification { - return !self.isMainnet; + return NO; +// return !self.isMainnet; } - (uint32_t)quickHeightForBlockHash:(UInt256)blockhash { @@ -2618,13 +1897,6 @@ - (void)setBlockHeight:(int32_t)height andTimestamp:(NSTimeInterval)timestamp fo updatedTransactions:updatedTransactions]; } -- (void)reloadDerivationPaths { - for (DSWallet *wallet in self.mWallets) { - if (!wallet.isTransient) { //no need to reload transient wallets (those are for testing purposes) - [wallet reloadDerivationPaths]; - } - } -} - (uint32_t)estimatedBlockHeight { @synchronized (self) { @@ -2710,159 +1982,9 @@ - (void)removeEstimatedBlockHeightOfPeer:(DSPeer *)peer { } } -// MARK: - Accounts - -- (uint64_t)balance { - uint64_t rBalance = 0; - for (DSWallet *wallet in self.wallets) { - rBalance += wallet.balance; - } - for (DSDerivationPath *standaloneDerivationPath in self.standaloneDerivationPaths) { - rBalance += standaloneDerivationPath.balance; - } - return rBalance; -} - -- (DSAccount *_Nullable)firstAccountWithBalance { - for (DSWallet *wallet in self.wallets) { - DSAccount *account = [wallet firstAccountWithBalance]; - if (account) return account; - } - return nil; -} - -- (DSAccount *_Nullable)firstAccountThatCanContainTransaction:(DSTransaction *)transaction { - if (!transaction) return nil; - for (DSWallet *wallet in self.wallets) { - DSAccount *account = [wallet firstAccountThatCanContainTransaction:transaction]; - if (account) return account; - } - return nil; -} - -- (NSArray *)accountsThatCanContainTransaction:(DSTransaction *)transaction { - NSMutableArray *mArray = [NSMutableArray array]; - if (!transaction) return @[]; - for (DSWallet *wallet in self.wallets) { - [mArray addObjectsFromArray:[wallet accountsThatCanContainTransaction:transaction]]; - } - return [mArray copy]; -} - -- (DSAccount *_Nullable)accountContainingAddress:(NSString *)address { - if (!address) return nil; - for (DSWallet *wallet in self.wallets) { - DSAccount *account = [wallet accountForAddress:address]; - if (account) return account; - } - return nil; -} - -- (DSAccount *_Nullable)accountContainingDashpayExternalDerivationPathAddress:(NSString *)address { - if (!address) return nil; - for (DSWallet *wallet in self.wallets) { - DSAccount *account = [wallet accountForDashpayExternalDerivationPathAddress:address]; - if (account) return account; - } - return nil; -} - -// returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet -- (DSAccount *_Nullable)firstAccountForTransactionHash:(UInt256)txHash transaction:(DSTransaction **)transaction wallet:(DSWallet **)wallet { - for (DSWallet *lWallet in self.wallets) { - for (DSAccount *account in lWallet.accounts) { - DSTransaction *lTransaction = [account transactionForHash:txHash]; - if (lTransaction) { - if (transaction) *transaction = lTransaction; - if (wallet) *wallet = lWallet; - return account; - } - } - } - return nil; -} - -// returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet -- (NSArray *)accountsForTransactionHash:(UInt256)txHash transaction:(DSTransaction **)transaction { - NSMutableArray *accounts = [NSMutableArray array]; - for (DSWallet *lWallet in self.wallets) { - for (DSAccount *account in lWallet.accounts) { - DSTransaction *lTransaction = [account transactionForHash:txHash]; - if (lTransaction) { - if (transaction) *transaction = lTransaction; - [accounts addObject:account]; - } - } - } - return [accounts copy]; -} - -// MARK: - Transactions - -- (DSTransaction *)transactionForHash:(UInt256)txHash { - return [self transactionForHash:txHash returnWallet:nil]; -} - -- (DSTransaction *)transactionForHash:(UInt256)txHash returnWallet:(DSWallet **)rWallet { - for (DSWallet *wallet in self.wallets) { - DSTransaction *transaction = [wallet transactionForHash:txHash]; - if (transaction) { - if (rWallet) *rWallet = wallet; - return transaction; - } - } - return nil; -} - -- (NSArray *)allTransactions { - NSMutableArray *mArray = [NSMutableArray array]; - for (DSWallet *wallet in self.wallets) { - [mArray addObjectsFromArray:wallet.allTransactions]; - } - return mArray; -} -// retuns the amount sent globally by the trasaction (total wallet outputs consumed, change and fee included) -- (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction { - NSParameterAssert(transaction); - - uint64_t received = 0; - for (DSWallet *wallet in self.wallets) { - received += [wallet amountReceivedFromTransaction:transaction]; - } - return received; -} -// retuns the amount sent globally by the trasaction (total wallet outputs consumed, change and fee included) -- (uint64_t)amountSentByTransaction:(DSTransaction *)transaction { - NSParameterAssert(transaction); - - uint64_t sent = 0; - for (DSWallet *wallet in self.wallets) { - sent += [wallet amountSentByTransaction:transaction]; - } - return sent; -} -- (DSTransactionDirection)directionOfTransaction:(DSTransaction *)transaction { - const uint64_t sent = [self amountSentByTransaction:transaction]; - const uint64_t received = [self amountReceivedFromTransaction:transaction]; - const uint64_t fee = transaction.feeUsed; - - if (sent > 0 && (received + fee) == sent) { - // moved - return DSTransactionDirection_Moved; - } else if (sent > 0) { - // sent - return DSTransactionDirection_Sent; - } else if (received > 0) { - // received - return DSTransactionDirection_Received; - } else { - // no funds moved on this account - return DSTransactionDirection_NotAccountFunds; - } -} // MARK: - Wiping @@ -2871,10 +1993,10 @@ - (void)wipeBlockchainInfoInContext:(NSManagedObjectContext *)context { for (DSWallet *wallet in self.wallets) { [wallet wipeBlockchainInfoInContext:context]; } - [self wipeBlockchainIdentitiesPersistedDataInContext:context]; - [self wipeBlockchainInvitationsPersistedDataInContext:context]; + [self wipeIdentitiesPersistedDataInContext:context]; + [self wipeInvitationsPersistedDataInContext:context]; [self.viewingAccount wipeBlockchainInfo]; - [self.chainManager.identitiesManager clearExternalBlockchainIdentities]; + [self.chainManager.identitiesManager clearExternalIdentities]; _bestBlockHeight = 0; @synchronized (_mSyncBlocks) { _mSyncBlocks = [NSMutableDictionary dictionary]; @@ -2899,10 +2021,10 @@ - (void)wipeBlockchainNonTerminalInfoInContext:(NSManagedObjectContext *)context for (DSWallet *wallet in self.wallets) { [wallet wipeBlockchainInfoInContext:context]; } - [self wipeBlockchainIdentitiesPersistedDataInContext:context]; - [self wipeBlockchainInvitationsPersistedDataInContext:context]; + [self wipeIdentitiesPersistedDataInContext:context]; + [self wipeInvitationsPersistedDataInContext:context]; [self.viewingAccount wipeBlockchainInfo]; - [self.chainManager.identitiesManager clearExternalBlockchainIdentities]; + [self.chainManager.identitiesManager clearExternalIdentities]; _bestBlockHeight = 0; @synchronized (_mSyncBlocks) { _mSyncBlocks = [NSMutableDictionary dictionary]; @@ -2921,10 +2043,6 @@ - (void)wipeMasternodesInContext:(NSManagedObjectContext *)context { DSLog(@"[%@] Wiping Masternode Info", self.name); DSChainEntity *chainEntity = [self chainEntityInContext:context]; [DSLocalMasternodeEntity deleteAllOnChainEntity:chainEntity]; - [DSSimplifiedMasternodeEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSMasternodeListEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumSnapshotEntity deleteAllOnChainEntity:chainEntity]; [self.chainManager wipeMasternodeInfo]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:[NSString stringWithFormat:@"%@_%@", self.uniqueID, LAST_SYNCED_MASTERNODE_LIST]]; } @@ -2937,368 +2055,23 @@ - (void)wipeWalletsAndDerivatives { self.viewingAccount = nil; } -// MARK: - Identities - -- (uint32_t)localBlockchainIdentitiesCount { - uint32_t blockchainIdentitiesCount = 0; - for (DSWallet *lWallet in self.wallets) { - blockchainIdentitiesCount += [lWallet blockchainIdentitiesCount]; - } - return blockchainIdentitiesCount; -} - -- (NSArray *)localBlockchainIdentities { - NSMutableArray *rAllBlockchainIdentities = [NSMutableArray array]; - for (DSWallet *wallet in self.wallets) { - [rAllBlockchainIdentities addObjectsFromArray:[wallet.blockchainIdentities allValues]]; - } - return rAllBlockchainIdentities; -} - -- (NSDictionary *)localBlockchainIdentitiesByUniqueIdDictionary { - NSMutableDictionary *rAllBlockchainIdentities = [NSMutableDictionary dictionary]; - for (DSWallet *wallet in self.wallets) { - for (DSBlockchainIdentity *blockchainIdentity in [wallet.blockchainIdentities allValues]) { - rAllBlockchainIdentities[blockchainIdentity.uniqueIDData] = blockchainIdentity; - } - } - return rAllBlockchainIdentities; -} - - -- (DSBlockchainIdentity *)blockchainIdentityForUniqueId:(UInt256)uniqueId { - NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); - return [self blockchainIdentityForUniqueId:uniqueId foundInWallet:nil includeForeignBlockchainIdentities:NO]; -} - -- (DSBlockchainIdentity *)blockchainIdentityForUniqueId:(UInt256)uniqueId foundInWallet:(DSWallet **)foundInWallet { - NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); - return [self blockchainIdentityForUniqueId:uniqueId foundInWallet:foundInWallet includeForeignBlockchainIdentities:NO]; -} - -- (DSBlockchainIdentity *_Nullable)blockchainIdentityThatCreatedContract:(DPContract *)contract withContractId:(UInt256)contractId foundInWallet:(DSWallet **)foundInWallet { - NSAssert(uint256_is_not_zero(contractId), @"contractId must not be null"); - for (DSWallet *wallet in self.wallets) { - DSBlockchainIdentity *blockchainIdentity = [wallet blockchainIdentityThatCreatedContract:contract withContractId:contractId]; - if (blockchainIdentity) { - if (foundInWallet) { - *foundInWallet = wallet; - } - return blockchainIdentity; - } - } - return nil; -} - -- (DSBlockchainIdentity *)blockchainIdentityForUniqueId:(UInt256)uniqueId foundInWallet:(DSWallet **)foundInWallet includeForeignBlockchainIdentities:(BOOL)includeForeignBlockchainIdentities { - NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); - for (DSWallet *wallet in self.wallets) { - DSBlockchainIdentity *blockchainIdentity = [wallet blockchainIdentityForUniqueId:uniqueId]; - if (blockchainIdentity) { - if (foundInWallet) { - *foundInWallet = wallet; - } - return blockchainIdentity; - } - } - if (includeForeignBlockchainIdentities) { - return [self.chainManager.identitiesManager foreignBlockchainIdentityWithUniqueId:uniqueId]; - } else { - return nil; - } -} - -- (void)wipeBlockchainIdentitiesPersistedDataInContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - NSArray *objects = [DSBlockchainIdentityEntity objectsInContext:context matching:@"chain == %@", [self chainEntityInContext:context]]; - [DSBlockchainIdentityEntity deleteObjects:objects inContext:context]; - }]; -} - -// MARK: - Invitations - -- (uint32_t)localBlockchainInvitationsCount { - uint32_t blockchainInvitationsCount = 0; - for (DSWallet *lWallet in self.wallets) { - blockchainInvitationsCount += [lWallet blockchainInvitationsCount]; - } - return blockchainInvitationsCount; -} - -- (void)wipeBlockchainInvitationsPersistedDataInContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - NSArray *objects = [DSBlockchainInvitationEntity objectsInContext:context matching:@"chain == %@", [self chainEntityInContext:context]]; - [DSBlockchainInvitationEntity deleteObjects:objects inContext:context]; - }]; -} - - -// MARK: - Registering special transactions - - -- (BOOL)registerProviderRegistrationTransaction:(DSProviderRegistrationTransaction *)providerRegistrationTransaction saveImmediately:(BOOL)saveImmediately { - DSWallet *ownerWallet = [self walletHavingProviderOwnerAuthenticationHash:providerRegistrationTransaction.ownerKeyHash foundAtIndex:nil]; - DSWallet *votingWallet = [self walletHavingProviderVotingAuthenticationHash:providerRegistrationTransaction.votingKeyHash foundAtIndex:nil]; - DSWallet *operatorWallet = [self walletHavingProviderOperatorAuthenticationKey:providerRegistrationTransaction.operatorKey foundAtIndex:nil]; - DSWallet *holdingWallet = [self walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:providerRegistrationTransaction foundAtIndex:nil]; - DSWallet *platformNodeWallet = [self walletHavingPlatformNodeAuthenticationHash:providerRegistrationTransaction.platformNodeID foundAtIndex:nil]; - DSAccount *account = [self accountContainingAddress:providerRegistrationTransaction.payoutAddress]; - BOOL registered = NO; - registered |= [account registerTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - registered |= [ownerWallet.specialTransactionsHolder registerTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - registered |= [votingWallet.specialTransactionsHolder registerTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - registered |= [operatorWallet.specialTransactionsHolder registerTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - registered |= [holdingWallet.specialTransactionsHolder registerTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - registered |= [platformNodeWallet.specialTransactionsHolder registerTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - - if (ownerWallet) { - DSAuthenticationKeysDerivationPath *ownerDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOwnerKeysDerivationPathForWallet:ownerWallet]; - [ownerDerivationPath registerTransactionAddress:providerRegistrationTransaction.ownerAddress]; - } - - if (votingWallet) { - DSAuthenticationKeysDerivationPath *votingDerivationPath = [[DSDerivationPathFactory sharedInstance] providerVotingKeysDerivationPathForWallet:votingWallet]; - [votingDerivationPath registerTransactionAddress:providerRegistrationTransaction.votingAddress]; - } - - if (operatorWallet) { - DSAuthenticationKeysDerivationPath *operatorDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOperatorKeysDerivationPathForWallet:operatorWallet]; - [operatorDerivationPath registerTransactionAddress:providerRegistrationTransaction.operatorAddress]; - } - - if (holdingWallet) { - DSMasternodeHoldingsDerivationPath *holdingDerivationPath = [[DSDerivationPathFactory sharedInstance] providerFundsDerivationPathForWallet:holdingWallet]; - [holdingDerivationPath registerTransactionAddress:providerRegistrationTransaction.holdingAddress]; - } - - if (platformNodeWallet) { - DSAuthenticationKeysDerivationPath *platformNodeDerivationPath = [[DSDerivationPathFactory sharedInstance] platformNodeKeysDerivationPathForWallet:platformNodeWallet]; - [platformNodeDerivationPath registerTransactionAddress:providerRegistrationTransaction.platformNodeAddress]; - } - - return registered; -} - -- (BOOL)registerProviderUpdateServiceTransaction:(DSProviderUpdateServiceTransaction *)providerUpdateServiceTransaction saveImmediately:(BOOL)saveImmediately { - DSWallet *providerRegistrationWallet = nil; - DSTransaction *providerRegistrationTransaction = [self transactionForHash:providerUpdateServiceTransaction.providerRegistrationTransactionHash returnWallet:&providerRegistrationWallet]; - DSAccount *account = [self accountContainingAddress:providerUpdateServiceTransaction.payoutAddress]; - BOOL registered = [account registerTransaction:providerUpdateServiceTransaction saveImmediately:saveImmediately]; - if (providerRegistrationTransaction && providerRegistrationWallet) { - registered |= [providerRegistrationWallet.specialTransactionsHolder registerTransaction:providerUpdateServiceTransaction saveImmediately:saveImmediately]; - } - return registered; -} - - -- (BOOL)registerProviderUpdateRegistrarTransaction:(DSProviderUpdateRegistrarTransaction *)providerUpdateRegistrarTransaction saveImmediately:(BOOL)saveImmediately { - DSWallet *votingWallet = [self walletHavingProviderVotingAuthenticationHash:providerUpdateRegistrarTransaction.votingKeyHash foundAtIndex:nil]; - DSWallet *operatorWallet = [self walletHavingProviderOperatorAuthenticationKey:providerUpdateRegistrarTransaction.operatorKey foundAtIndex:nil]; - [votingWallet.specialTransactionsHolder registerTransaction:providerUpdateRegistrarTransaction saveImmediately:saveImmediately]; - [operatorWallet.specialTransactionsHolder registerTransaction:providerUpdateRegistrarTransaction saveImmediately:saveImmediately]; - DSWallet *providerRegistrationWallet = nil; - DSTransaction *providerRegistrationTransaction = [self transactionForHash:providerUpdateRegistrarTransaction.providerRegistrationTransactionHash returnWallet:&providerRegistrationWallet]; - DSAccount *account = [self accountContainingAddress:providerUpdateRegistrarTransaction.payoutAddress]; - BOOL registered = [account registerTransaction:providerUpdateRegistrarTransaction saveImmediately:saveImmediately]; - if (providerRegistrationTransaction && providerRegistrationWallet) { - registered |= [providerRegistrationWallet.specialTransactionsHolder registerTransaction:providerUpdateRegistrarTransaction saveImmediately:saveImmediately]; - } - - if (votingWallet) { - DSAuthenticationKeysDerivationPath *votingDerivationPath = [[DSDerivationPathFactory sharedInstance] providerVotingKeysDerivationPathForWallet:votingWallet]; - [votingDerivationPath registerTransactionAddress:providerUpdateRegistrarTransaction.votingAddress]; - } - - if (operatorWallet) { - DSAuthenticationKeysDerivationPath *operatorDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOperatorKeysDerivationPathForWallet:operatorWallet]; - [operatorDerivationPath registerTransactionAddress:providerUpdateRegistrarTransaction.operatorAddress]; - } - return registered; -} - -- (BOOL)registerProviderUpdateRevocationTransaction:(DSProviderUpdateRevocationTransaction *)providerUpdateRevocationTransaction saveImmediately:(BOOL)saveImmediately { - DSWallet *providerRegistrationWallet = nil; - DSTransaction *providerRegistrationTransaction = [self transactionForHash:providerUpdateRevocationTransaction.providerRegistrationTransactionHash returnWallet:&providerRegistrationWallet]; - if (providerRegistrationTransaction && providerRegistrationWallet) { - return [providerRegistrationWallet.specialTransactionsHolder registerTransaction:providerUpdateRevocationTransaction saveImmediately:saveImmediately]; - } else { - return NO; - } -} -// -//-(BOOL)registerBlockchainIdentityRegistrationTransaction:(DSBlockchainIdentityRegistrationTransition*)blockchainIdentityRegistrationTransaction { -// DSWallet * blockchainIdentityWallet = [self walletHavingBlockchainIdentityAuthenticationHash:blockchainIdentityRegistrationTransaction.pubkeyHash foundAtIndex:nil]; -// BOOL registered = [blockchainIdentityWallet.specialTransactionsHolder registerTransaction:blockchainIdentityRegistrationTransaction]; -// -// if (blockchainIdentityWallet) { -// DSAuthenticationKeysDerivationPath * blockchainIdentitiesDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:blockchainIdentityWallet]; -// [blockchainIdentitiesDerivationPath registerTransactionAddress:blockchainIdentityRegistrationTransaction.pubkeyAddress]; -// } -// return registered; -//} -// -//-(BOOL)registerBlockchainIdentityResetTransaction:(DSBlockchainIdentityUpdateTransition*)blockchainIdentityResetTransaction { -// DSWallet * blockchainIdentityWallet = [self walletHavingBlockchainIdentityAuthenticationHash:blockchainIdentityResetTransaction.replacementPublicKeyHash foundAtIndex:nil]; -// [blockchainIdentityWallet.specialTransactionsHolder registerTransaction:blockchainIdentityResetTransaction]; -// DSWallet * blockchainIdentityRegistrationWallet = nil; -// DSTransaction * blockchainIdentityRegistrationTransaction = [self transactionForHash:blockchainIdentityResetTransaction.registrationTransactionHash returnWallet:&blockchainIdentityRegistrationWallet]; -// BOOL registered = NO; -// if (blockchainIdentityRegistrationTransaction && blockchainIdentityRegistrationWallet && (blockchainIdentityWallet != blockchainIdentityRegistrationWallet)) { -// registered = [blockchainIdentityRegistrationWallet.specialTransactionsHolder registerTransaction:blockchainIdentityResetTransaction]; -// } -// -// if (blockchainIdentityWallet) { -// DSAuthenticationKeysDerivationPath * blockchainIdentitiesDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:blockchainIdentityWallet]; -// [blockchainIdentitiesDerivationPath registerTransactionAddress:blockchainIdentityResetTransaction.replacementAddress]; -// } -// return registered; -//} -// -//-(BOOL)registerBlockchainIdentityCloseTransaction:(DSBlockchainIdentityCloseTransition*)blockchainIdentityCloseTransaction { -// DSWallet * blockchainIdentityRegistrationWallet = nil; -// DSTransaction * blockchainIdentityRegistrationTransaction = [self transactionForHash:blockchainIdentityCloseTransaction.registrationTransactionHash returnWallet:&blockchainIdentityRegistrationWallet]; -// if (blockchainIdentityRegistrationTransaction && blockchainIdentityRegistrationWallet) { -// return [blockchainIdentityRegistrationWallet.specialTransactionsHolder registerTransaction:blockchainIdentityCloseTransaction]; -// } else { -// return NO; -// } -//} -// -//-(BOOL)registerBlockchainIdentityTopupTransaction:(DSBlockchainIdentityTopupTransition*)blockchainIdentityTopupTransaction { -// DSWallet * blockchainIdentityRegistrationWallet = nil; -// DSTransaction * blockchainIdentityRegistrationTransaction = [self transactionForHash:blockchainIdentityTopupTransaction.registrationTransactionHash returnWallet:&blockchainIdentityRegistrationWallet]; -// if (blockchainIdentityRegistrationTransaction && blockchainIdentityRegistrationWallet) { -// return [blockchainIdentityRegistrationWallet.specialTransactionsHolder registerTransaction:blockchainIdentityTopupTransaction]; -// } else { -// return NO; -// } -//} -// -//-(BOOL)registerTransition:(DSTransition*)transition { -// DSWallet * blockchainIdentityRegistrationWallet = nil; -// DSTransaction * blockchainIdentityRegistrationTransaction = [self transactionForHash:transition.registrationTransactionHash returnWallet:&blockchainIdentityRegistrationWallet]; -// if (blockchainIdentityRegistrationTransaction && blockchainIdentityRegistrationWallet) { -// return [blockchainIdentityRegistrationWallet.specialTransactionsHolder registerTransaction:transition]; -// } else { -// return NO; -// } -//} -- (BOOL)registerSpecialTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately { - if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { - DSProviderRegistrationTransaction *providerRegistrationTransaction = (DSProviderRegistrationTransaction *)transaction; - return [self registerProviderRegistrationTransaction:providerRegistrationTransaction saveImmediately:saveImmediately]; - } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { - DSProviderUpdateServiceTransaction *providerUpdateServiceTransaction = (DSProviderUpdateServiceTransaction *)transaction; - return [self registerProviderUpdateServiceTransaction:providerUpdateServiceTransaction saveImmediately:saveImmediately]; - } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { - DSProviderUpdateRegistrarTransaction *providerUpdateRegistrarTransaction = (DSProviderUpdateRegistrarTransaction *)transaction; - return [self registerProviderUpdateRegistrarTransaction:providerUpdateRegistrarTransaction saveImmediately:saveImmediately]; - } else if ([transaction isKindOfClass:[DSProviderUpdateRevocationTransaction class]]) { - DSProviderUpdateRevocationTransaction *providerUpdateRevocationTransaction = (DSProviderUpdateRevocationTransaction *)transaction; - return [self registerProviderUpdateRevocationTransaction:providerUpdateRevocationTransaction saveImmediately:saveImmediately]; - } - return FALSE; -} -// MARK: - Special Transactions -//Does the chain mat -- (BOOL)transactionHasLocalReferences:(DSTransaction *)transaction { - if ([self firstAccountThatCanContainTransaction:transaction]) return TRUE; - - //PROVIDERS - if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { - DSProviderRegistrationTransaction *providerRegistrationTransaction = (DSProviderRegistrationTransaction *)transaction; - if ([self walletHavingProviderOwnerAuthenticationHash:providerRegistrationTransaction.ownerKeyHash foundAtIndex:nil]) return TRUE; - if ([self walletHavingProviderVotingAuthenticationHash:providerRegistrationTransaction.votingKeyHash foundAtIndex:nil]) return TRUE; - if ([self walletHavingProviderOperatorAuthenticationKey:providerRegistrationTransaction.operatorKey foundAtIndex:nil]) return TRUE; - if ([self walletHavingPlatformNodeAuthenticationHash:providerRegistrationTransaction.platformNodeID foundAtIndex:nil]) return TRUE; - if ([self walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:providerRegistrationTransaction foundAtIndex:nil]) return TRUE; - if ([self accountContainingAddress:providerRegistrationTransaction.payoutAddress]) return TRUE; - } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { - DSProviderUpdateServiceTransaction *providerUpdateServiceTransaction = (DSProviderUpdateServiceTransaction *)transaction; - if ([self transactionForHash:providerUpdateServiceTransaction.providerRegistrationTransactionHash]) return TRUE; - if ([self accountContainingAddress:providerUpdateServiceTransaction.payoutAddress]) return TRUE; - } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { - DSProviderUpdateRegistrarTransaction *providerUpdateRegistrarTransaction = (DSProviderUpdateRegistrarTransaction *)transaction; - if ([self walletHavingProviderVotingAuthenticationHash:providerUpdateRegistrarTransaction.votingKeyHash foundAtIndex:nil]) return TRUE; - if ([self walletHavingProviderOperatorAuthenticationKey:providerUpdateRegistrarTransaction.operatorKey foundAtIndex:nil]) return TRUE; - if ([self transactionForHash:providerUpdateRegistrarTransaction.providerRegistrationTransactionHash]) return TRUE; - if ([self accountContainingAddress:providerUpdateRegistrarTransaction.payoutAddress]) return TRUE; - } else if ([transaction isKindOfClass:[DSProviderUpdateRevocationTransaction class]]) { - DSProviderUpdateRevocationTransaction *providerUpdateRevocationTransaction = (DSProviderUpdateRevocationTransaction *)transaction; - if ([self transactionForHash:providerUpdateRevocationTransaction.providerRegistrationTransactionHash]) return TRUE; - - //BLOCKCHAIN USERS - } - // else if ([transaction isKindOfClass:[DSBlockchainIdentityRegistrationTransition class]]) { - // DSBlockchainIdentityRegistrationTransition * blockchainIdentityRegistrationTransaction = (DSBlockchainIdentityRegistrationTransition *)transaction; - // if ([self walletHavingBlockchainIdentityAuthenticationHash:blockchainIdentityRegistrationTransaction.pubkeyHash foundAtIndex:nil]) return TRUE; - // } else if ([transaction isKindOfClass:[DSBlockchainIdentityUpdateTransition class]]) { - // DSBlockchainIdentityUpdateTransition * blockchainIdentityResetTransaction = (DSBlockchainIdentityUpdateTransition *)transaction; - // if ([self walletHavingBlockchainIdentityAuthenticationHash:blockchainIdentityResetTransaction.replacementPublicKeyHash foundAtIndex:nil]) return TRUE; - // if ([self transactionForHash:blockchainIdentityResetTransaction.registrationTransactionHash]) return TRUE; - // } else if ([transaction isKindOfClass:[DSBlockchainIdentityCloseTransition class]]) { - // DSBlockchainIdentityCloseTransition * blockchainIdentityCloseTransaction = (DSBlockchainIdentityCloseTransition *)transaction; - // if ([self transactionForHash:blockchainIdentityCloseTransaction.registrationTransactionHash]) return TRUE; - // } else if ([transaction isKindOfClass:[DSBlockchainIdentityTopupTransition class]]) { - // DSBlockchainIdentityTopupTransition * blockchainIdentityTopupTransaction = (DSBlockchainIdentityTopupTransition *)transaction; - // if ([self transactionForHash:blockchainIdentityTopupTransaction.registrationTransactionHash]) return TRUE; - // } - return FALSE; -} - -- (void)triggerUpdatesForLocalReferences:(DSTransaction *)transaction { - if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { - DSProviderRegistrationTransaction *providerRegistrationTransaction = (DSProviderRegistrationTransaction *)transaction; - if ([self walletHavingProviderOwnerAuthenticationHash:providerRegistrationTransaction.ownerKeyHash foundAtIndex:nil] || - [self walletHavingProviderVotingAuthenticationHash:providerRegistrationTransaction.votingKeyHash foundAtIndex:nil] || - [self walletHavingProviderOperatorAuthenticationKey:providerRegistrationTransaction.operatorKey foundAtIndex:nil] || - [self walletHavingPlatformNodeAuthenticationHash:providerRegistrationTransaction.platformNodeID foundAtIndex:nil]) { - [self.chainManager.masternodeManager localMasternodeFromProviderRegistrationTransaction:providerRegistrationTransaction save:TRUE]; - } - } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { - DSProviderUpdateServiceTransaction *providerUpdateServiceTransaction = (DSProviderUpdateServiceTransaction *)transaction; - DSLocalMasternode *localMasternode = [self.chainManager.masternodeManager localMasternodeHavingProviderRegistrationTransactionHash:providerUpdateServiceTransaction.providerRegistrationTransactionHash]; - [localMasternode updateWithUpdateServiceTransaction:providerUpdateServiceTransaction save:TRUE]; - } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { - DSProviderUpdateRegistrarTransaction *providerUpdateRegistrarTransaction = (DSProviderUpdateRegistrarTransaction *)transaction; - DSLocalMasternode *localMasternode = [self.chainManager.masternodeManager localMasternodeHavingProviderRegistrationTransactionHash:providerUpdateRegistrarTransaction.providerRegistrationTransactionHash]; - [localMasternode updateWithUpdateRegistrarTransaction:providerUpdateRegistrarTransaction save:TRUE]; - } else if ([transaction isKindOfClass:[DSProviderUpdateRevocationTransaction class]]) { - DSProviderUpdateRevocationTransaction *providerUpdateRevocationTransaction = (DSProviderUpdateRevocationTransaction *)transaction; - DSLocalMasternode *localMasternode = [self.chainManager.masternodeManager localMasternodeHavingProviderRegistrationTransactionHash:providerUpdateRevocationTransaction.providerRegistrationTransactionHash]; - [localMasternode updateWithUpdateRevocationTransaction:providerUpdateRevocationTransaction save:TRUE]; - } else if ([transaction isKindOfClass:[DSCreditFundingTransaction class]]) { - DSCreditFundingTransaction *creditFundingTransaction = (DSCreditFundingTransaction *)transaction; - uint32_t index; - DSWallet *wallet = [self walletHavingBlockchainIdentityCreditFundingRegistrationHash:creditFundingTransaction.creditBurnPublicKeyHash foundAtIndex:&index]; - if (wallet) { - DSBlockchainIdentity *blockchainIdentity = [wallet blockchainIdentityForUniqueId:creditFundingTransaction.creditBurnIdentityIdentifier]; - if (!blockchainIdentity) { - blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index withFundingTransaction:creditFundingTransaction withUsernameDictionary:nil inWallet:wallet]; - [blockchainIdentity registerInWalletForRegistrationFundingTransaction:creditFundingTransaction]; - } - } else { - wallet = [self walletHavingBlockchainIdentityCreditFundingInvitationHash:creditFundingTransaction.creditBurnPublicKeyHash foundAtIndex:&index]; - if (wallet) { - DSBlockchainInvitation *blockchainInvitation = [wallet blockchainInvitationForUniqueId:creditFundingTransaction.creditBurnIdentityIdentifier]; - if (!blockchainInvitation) { - blockchainInvitation = [[DSBlockchainInvitation alloc] initAtIndex:index withFundingTransaction:creditFundingTransaction inWallet:wallet]; - [blockchainInvitation registerInWalletForRegistrationFundingTransaction:creditFundingTransaction]; - } - } +- (void)updateAddressUsageOfSimplifiedMasternodeEntries:(DMasternodeEntryList *)simplifiedMasternodeEntries { + for (int i = 0; i < simplifiedMasternodeEntries->count; i++) { + DMasternodeEntry *entry = simplifiedMasternodeEntries->values[i]; + NSString *votingAddress = [DSKeyManager NSStringFrom:DMasternodeEntryVotingAddress(entry->masternode_list_entry->key_id_voting, self.chainType)]; + NSString *operatorAddress = [DSKeyManager NSStringFrom:DMasternodeEntryOperatorPublicKeyAddress(entry->masternode_list_entry->operator_public_key, self.chainType)]; + NSString *platformNodeAddress = nil; + switch (entry->masternode_list_entry->mn_type->tag) { + case dashcore_sml_masternode_list_entry_EntryMasternodeType_Regular: + break; + case dashcore_sml_masternode_list_entry_EntryMasternodeType_HighPerformance: + platformNodeAddress = [DSKeyManager NSStringFrom:DMasternodeEntryEvoNodeAddress(entry->masternode_list_entry->mn_type->high_performance.platform_node_id, self.chainType)]; + break; } - } -} - -- (void)updateAddressUsageOfSimplifiedMasternodeEntries:(NSArray *)simplifiedMasternodeEntries { - for (DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry in simplifiedMasternodeEntries) { - NSString *votingAddress = simplifiedMasternodeEntry.votingAddress; - NSString *operatorAddress = simplifiedMasternodeEntry.operatorAddress; - NSString *platformNodeAddress = simplifiedMasternodeEntry.platformNodeAddress; for (DSWallet *wallet in self.wallets) { DSAuthenticationKeysDerivationPath *providerOperatorKeysDerivationPath = [[DSDerivationPathFactory sharedInstance] providerOperatorKeysDerivationPathForWallet:wallet]; if ([providerOperatorKeysDerivationPath containsAddress:operatorAddress]) { @@ -3316,107 +2089,6 @@ - (void)updateAddressUsageOfSimplifiedMasternodeEntries:(NSArray *)simplifiedMas } } -// MARK: - Merging Wallets - -- (DSWallet *)walletHavingBlockchainIdentityCreditFundingRegistrationHash:(UInt160)creditFundingRegistrationHash foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfBlockchainIdentityCreditFundingRegistrationHash:creditFundingRegistrationHash]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *)walletHavingBlockchainIdentityCreditFundingTopupHash:(UInt160)creditFundingTopupHash foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfBlockchainIdentityCreditFundingTopupHash:creditFundingTopupHash]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *)walletHavingBlockchainIdentityCreditFundingInvitationHash:(UInt160)creditFundingInvitationHash foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfBlockchainIdentityCreditFundingInvitationHash:creditFundingInvitationHash]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *)walletHavingProviderVotingAuthenticationHash:(UInt160)votingAuthenticationHash foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfProviderVotingAuthenticationHash:votingAuthenticationHash]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *_Nullable)walletHavingProviderOwnerAuthenticationHash:(UInt160)owningAuthenticationHash foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfProviderOwningAuthenticationHash:owningAuthenticationHash]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *_Nullable)walletHavingProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfProviderOperatorAuthenticationKey:providerOperatorAuthenticationKey]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *_Nullable)walletHavingPlatformNodeAuthenticationHash:(UInt160)platformNodeAuthenticationHash foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - NSUInteger index = [wallet indexOfPlatformNodeAuthenticationHash:platformNodeAuthenticationHash]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} - -- (DSWallet *_Nullable)walletContainingMasternodeHoldingAddressForProviderRegistrationTransaction:(DSProviderRegistrationTransaction *_Nonnull)transaction foundAtIndex:(uint32_t *)rIndex { - for (DSWallet *wallet in self.wallets) { - for (DSTransactionOutput *output in transaction.outputs) { - NSString *address = output.address; - if (!address || address == (id)[NSNull null]) continue; - NSUInteger index = [wallet indexOfHoldingAddress:address]; - if (index != NSNotFound) { - if (rIndex) *rIndex = (uint32_t)index; - return wallet; - } - } - } - if (rIndex) *rIndex = UINT32_MAX; - return nil; -} // MARK: - Persistence @@ -3501,14 +2173,21 @@ - (void)saveTerminalBlocks { if ([[DSOptionsManager sharedInstance] keepHeaders]) { //only remove orphan chains NSArray *recentOrphans = [DSMerkleBlockEntity objectsInContext:self.chainManagedObjectContext matching:@"(chain == %@) && (height > %u) && !(blockHash in %@)", [self chainEntityInContext:self.chainManagedObjectContext], startHeight, blocks.allKeys]; - if ([recentOrphans count]) DSLog(@"%lu recent orphans will be removed from disk", (unsigned long)[recentOrphans count]); + if ([recentOrphans count]) DSLog(@"[%@] %lu recent orphans will be removed from disk", self.name, (unsigned long)[recentOrphans count]); + for (DSMerkleBlockEntity *e in recentOrphans) { + DSLog(@"[%@] remove orphan MerkleBlockEntity: %u: %@", self.name, e.height, e.blockHash.hexString); + } [DSMerkleBlockEntity deleteObjects:recentOrphans inContext:self.chainManagedObjectContext]; } else { + //remember to not delete blocks needed for quorums - NSArray *oldBlockHeaders = [DSMerkleBlockEntity objectsInContext:self.chainManagedObjectContext matching:@"(chain == %@) && masternodeList == NIL && (usedByQuorums.@count == 0) && !(blockHash in %@)", [self chainEntityInContext:self.chainManagedObjectContext], blocks.allKeys]; - /*for (DSMerkleBlockEntity *e in oldBlockHeaders) { - DSLog(@"• remove Merkle block: %u: %@", e.height, e.blockHash.hexString); - }*/ + // TODO: check how this change really affects in runtime + NSSet *blockSet = [[self.masternodeManager blockHashesUsedByMasternodeLists] setByAddingObjectsFromArray:blocks.allKeys]; + NSArray *oldBlockHeaders = [DSMerkleBlockEntity objectsInContext:self.chainManagedObjectContext matching:@"(chain == %@) && !(blockHash in %@)", [self chainEntityInContext:self.chainManagedObjectContext], blockSet]; +// NSArray *oldBlockHeaders = [DSMerkleBlockEntity objectsInContext:self.chainManagedObjectContext matching:@"(chain == %@) && masternodeList == NIL && (usedByQuorums.@count == 0) && !(blockHash in %@)", [self chainEntityInContext:self.chainManagedObjectContext], blocks.allKeys]; +// for (DSMerkleBlockEntity *e in oldBlockHeaders) { +// DSLog(@"[%@] remove MerkleBlockEntity: %u: %@", self.name, e.height, e.blockHash.hexString); +// } [DSMerkleBlockEntity deleteObjects:oldBlockHeaders inContext:self.chainManagedObjectContext]; } DSChainEntity *chainEntity = [self chainEntityInContext:self.chainManagedObjectContext]; diff --git a/DashSync/shared/Models/Chain/DSChainConstants.h b/DashSync/shared/Models/Chain/DSChainConstants.h index 1a8b5f9b5..3c0d2f6d1 100644 --- a/DashSync/shared/Models/Chain/DSChainConstants.h +++ b/DashSync/shared/Models/Chain/DSChainConstants.h @@ -58,9 +58,12 @@ #define MAX_TARGET_PROOF_OF_WORK_TESTNET 0x1e0fffffu #define MAX_TARGET_PROOF_OF_WORK_DEVNET 0x207fffffu -#define MAX_PROOF_OF_WORK_MAINNET @"00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".hexToData.reverse.UInt256 // highest value for difficulty target (higher values are less difficult) -#define MAX_PROOF_OF_WORK_TESTNET @"00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".hexToData.reverse.UInt256 -#define MAX_PROOF_OF_WORK_DEVNET @"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".hexToData.reverse.UInt256 +#define MAX_PROOF_OF_WORK_MAINNET_DATA @"00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".hexToData.reverse // highest value for difficulty target (higher values are less difficult) +#define MAX_PROOF_OF_WORK_MAINNET MAX_PROOF_OF_WORK_MAINNET_DATA.UInt256 // highest value for difficulty target (higher values are less difficult) +#define MAX_PROOF_OF_WORK_TESTNET_DATA @"00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".hexToData.reverse +#define MAX_PROOF_OF_WORK_TESTNET MAX_PROOF_OF_WORK_TESTNET_DATA.UInt256 +#define MAX_PROOF_OF_WORK_DEVNET_DATA @"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".hexToData.reverse +#define MAX_PROOF_OF_WORK_DEVNET MAX_PROOF_OF_WORK_DEVNET_DATA.UInt256 #define SPORK_PUBLIC_KEY_MAINNET @"04549ac134f694c0243f503e8c8a9a986f5de6610049c40b07816809b0d1d06a21b07be27b9bb555931773f62ba6cf35a25fd52f694d4e1106ccd237a7bb899fdd" @@ -70,8 +73,8 @@ #define SPORK_ADDRESS_MAINNET @"Xgtyuk76vhuFW2iT7UAiHgNdWXCf3J34wh" #define SPORK_ADDRESS_TESTNET @"yjPtiKh2uwk3bDutTEA2q9mCtXyiZRWn55" -#define MAINNET_DASHPAY_CONTRACT_ID @"" -#define MAINNET_DPNS_CONTRACT_ID @"" +#define MAINNET_DASHPAY_CONTRACT_ID @"Bwr4WHCPz5rFVAD87RqTs3izo4zpzwsEdKPWUT1NS1C7" +#define MAINNET_DPNS_CONTRACT_ID @"GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec" #define TESTNET_DASHPAY_CONTRACT_ID @"Bwr4WHCPz5rFVAD87RqTs3izo4zpzwsEdKPWUT1NS1C7" #define TESTNET_DPNS_CONTRACT_ID @"GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec" @@ -82,3 +85,5 @@ #define MAX_FEE_PER_B 1000 // slightly higher than a 1000bit fee on a 191byte tx #define HEADER_WINDOW_BUFFER_TIME (WEEK_TIME_INTERVAL / 2) //This is about the time if we consider a block every 10 mins (for 500 blocks) + +#define COINBASE_MATURITY 100 // Coinbase transaction outputs can only be spent after this number of new blocks (network rule) diff --git a/DashSync/shared/Models/Chain/DSChainLock.h b/DashSync/shared/Models/Chain/DSChainLock.h index 4feee31ef..7625855da 100644 --- a/DashSync/shared/Models/Chain/DSChainLock.h +++ b/DashSync/shared/Models/Chain/DSChainLock.h @@ -23,37 +23,37 @@ // THE SOFTWARE. #import "BigIntTypes.h" +#import "DSKeyManager.h" #import NS_ASSUME_NONNULL_BEGIN -@class DSChain, DSQuorumEntry, DSMasternodeList; +@class DSChain; @interface DSChainLock : NSObject -@property (nonatomic, readonly) uint32_t height; +@property (nonatomic, readonly) DChainLock *lock; +@property (nonatomic, readonly) DSChain *chain; @property (nonatomic, readonly) UInt256 blockHash; -@property (nonatomic, readonly) UInt256 requestID; +@property (nonatomic, readonly) NSData *blockHashData; @property (nonatomic, readonly) UInt768 signature; +@property (nonatomic, readonly) NSData *signatureData; @property (nonatomic, readonly) BOOL signatureVerified; @property (nonatomic, readonly) BOOL saved; -@property (nonatomic, readonly) DSQuorumEntry *intendedQuorum; // message can be either a merkleblock or header message + (instancetype)chainLockWithMessage:(NSData *)message onChain:(DSChain *)chain; - - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain; - -- (instancetype)initWithBlockHash:(UInt256)blockHash signature:(UInt768)signature signatureVerified:(BOOL)signatureVerified quorumVerified:(BOOL)quorumVerified onChain:(DSChain *)chain; +- (instancetype)initWithBlockHash:(NSData *)blockHash + height:(uint32_t)height + signature:(NSData *)signature + signatureVerified:(BOOL)signatureVerified + quorumVerified:(BOOL)quorumVerified + onChain:(DSChain *)chain; - (instancetype)init NS_UNAVAILABLE; - -- (DSQuorumEntry *)findSigningQuorumReturnMasternodeList:(DSMasternodeList *_Nullable *_Nullable)returnMasternodeList; - - (BOOL)verifySignature; - - (void)saveInitial; - - (void)saveSignatureValid; @end diff --git a/DashSync/shared/Models/Chain/DSChainLock.m b/DashSync/shared/Models/Chain/DSChainLock.m index 3d9af3dea..9daea4656 100644 --- a/DashSync/shared/Models/Chain/DSChainLock.m +++ b/DashSync/shared/Models/Chain/DSChainLock.m @@ -23,15 +23,13 @@ // THE SOFTWARE. #import "DSChainLock.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainLockEntity+CoreDataClass.h" #import "DSChainManager.h" -#import "DSMasternodeList.h" #import "DSMasternodeManager.h" #import "DSMerkleBlockEntity+CoreDataClass.h" -#import "DSQuorumEntry.h" -#import "DSSimplifiedMasternodeEntry.h" #import "DSSporkManager.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" @@ -41,20 +39,21 @@ @interface DSChainLock () -@property (nonatomic, assign) uint32_t height; -@property (nonatomic, assign) UInt256 blockHash; -@property (nonatomic, assign) UInt768 signature; +@property (nonatomic, assign) DChainLock *lock; @property (nonatomic, strong) DSChain *chain; -@property (nonatomic, assign) UInt256 requestID; -@property (nonatomic, strong) NSArray *inputOutpoints; @property (nonatomic, assign) BOOL signatureVerified; @property (nonatomic, assign) BOOL quorumVerified; -@property (nonatomic, strong) DSQuorumEntry *intendedQuorum; @property (nonatomic, assign) BOOL saved; @end @implementation DSChainLock +- (void)dealloc { + if (_lock != NULL) { + DChainLockDtor(_lock); + _lock = NULL; + } +} // message can be either a merkleblock or header message + (instancetype)chainLockWithMessage:(NSData *)message onChain:(DSChain *)chain { @@ -63,19 +62,9 @@ + (instancetype)chainLockWithMessage:(NSData *)message onChain:(DSChain *)chain - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { if (!(self = [self init])) return nil; - if (message.length < 132) return nil; - NSUInteger off = 0; - - _height = [message UInt32AtOffset:off]; - off += sizeof(uint32_t); - _blockHash = [message UInt256AtOffset:off]; - off += sizeof(UInt256); - _signature = [message UInt768AtOffset:off]; - off += sizeof(UInt768); + self.lock = dash_spv_masternode_processor_processing_chain_lock_from_message(slice_ctor(message)); self.chain = chain; - - DSLog(@"[%@] the chain lock signature received for height %d (sig %@) (blockhash %@)", chain.name, self.height, uint768_hex(_signature), uint256_hex(_blockHash)); - + //DSLog(@"[%@] the chain lock signature received for height %d (sig %@) (blockhash %@)", chain.name, self.height, uint768_hex(self.signature), uint256_hex(_blockHash)); return self; } @@ -87,98 +76,67 @@ - (instancetype)initOnChain:(DSChain *)chain { return self; } -- (instancetype)initWithBlockHash:(UInt256)blockHash signature:(UInt768)signature signatureVerified:(BOOL)signatureVerified quorumVerified:(BOOL)quorumVerified onChain:(DSChain *)chain { +- (instancetype)initWithBlockHash:(NSData *)blockHash + height:(uint32_t)height + signature:(NSData *)signature + signatureVerified:(BOOL)signatureVerified + quorumVerified:(BOOL)quorumVerified + onChain:(DSChain *)chain { if (!(self = [self initOnChain:chain])) return nil; - self.blockHash = blockHash; + u256 *hash = blockHash ? u256_ctor(blockHash) : u256_ctor_u(UINT256_ZERO); + u768 *sig = signature ? u768_ctor(signature) : u768_ctor_u(UINT768_ZERO); + DBlockHash *block_hash = dashcore_hash_types_BlockHash_ctor(hash); + DBLSSignature *bls_signature = DBLSSignatureCtor(sig); + self.lock = DChainLockCtor(height, block_hash, bls_signature); self.signatureVerified = signatureVerified; self.quorumVerified = quorumVerified; self.saved = YES; //this is coming already from the persistant store and not from the network return self; } -- (UInt256)requestID { - if (uint256_is_not_zero(_requestID)) return _requestID; - NSMutableData *data = [NSMutableData data]; - [data appendString:@"clsig"]; - [data appendUInt32:self.height]; - _requestID = [data SHA256_2]; - DSLog(@"[%@] the chain lock request ID is %@ for height %d", self.chain.name, uint256_hex(_requestID), self.height); - return _requestID; +- (UInt256)blockHash { + u256 *block_hash = dash_spv_masternode_processor_processing_chain_lock_block_hash(self.lock); + UInt256 data = u256_cast(block_hash); + u256_dtor(block_hash); + return uint256_reverse(data); } - -- (UInt256)signIDForQuorumEntry:(DSQuorumEntry *)quorumEntry { - NSMutableData *data = [NSMutableData data]; - [data appendVarInt:quorum_type_for_chain_locks(self.chain.chainType)]; - [data appendUInt256:quorumEntry.quorumHash]; - [data appendUInt256:self.requestID]; - [data appendUInt256:self.blockHash]; - return [data SHA256_2]; +- (NSData *)blockHashData { + u256 *block_hash = dash_spv_masternode_processor_processing_chain_lock_block_hash(self.lock); + NSData *data = NSDataFromPtr(block_hash); + u256_dtor(block_hash); + + return [data reverse]; } -- (BOOL)verifySignatureAgainstQuorum:(DSQuorumEntry *)quorumEntry { - UInt256 signId = [self signIDForQuorumEntry:quorumEntry]; - BOOL verified = key_bls_verify(quorumEntry.quorumPublicKey.u8, quorumEntry.useLegacyBLSScheme, signId.u8, self.signature.u8); -#if DEBUG - DSLog(@"[%@] verifySignatureAgainstQuorum (%u): %u: %u: %@: %@: %@: %u", self.chain.name, verified, quorumEntry.llmqType, quorumEntry.verified, @"", uint384_hex(quorumEntry.quorumPublicKey), @"", quorumEntry.useLegacyBLSScheme); -#else - DSLogPrivate(@"[%@] verifySignatureAgainstQuorum (%u): %u: %u: %@: %@: %@: %u", self.chain.name, verified, quorumEntry.llmqType, quorumEntry.verified, uint256_hex(signId), uint384_hex(quorumEntry.quorumPublicKey), uint768_hex(self.signature), quorumEntry.useLegacyBLSScheme); -#endif - return verified; +- (UInt768)signature { + u768 *sig = dash_spv_masternode_processor_processing_chain_lock_signature(self.lock); + UInt768 data = u768_cast(sig); + u768_dtor(sig); + return data; } -- (DSQuorumEntry *)findSigningQuorumReturnMasternodeList:(DSMasternodeList **)returnMasternodeList { - DSQuorumEntry *foundQuorum = nil; - for (DSMasternodeList *masternodeList in self.chain.chainManager.masternodeManager.recentMasternodeLists) { - for (DSQuorumEntry *quorumEntry in [[masternodeList quorumsOfType:quorum_type_for_chain_locks(self.chain.chainType)] allValues]) { - BOOL signatureVerified = [self verifySignatureAgainstQuorum:quorumEntry]; - if (signatureVerified) { - foundQuorum = quorumEntry; - if (returnMasternodeList) *returnMasternodeList = masternodeList; - break; - } - } - if (foundQuorum) break; - } - return foundQuorum; +- (NSData *)signatureData { + u768 *sig = dash_spv_masternode_processor_processing_chain_lock_signature(self.lock); + NSData *data = NSDataFromPtr(sig); + u768_dtor(sig); + return data; } -- (BOOL)verifySignatureWithQuorumOffset:(uint32_t)offset { - DSQuorumEntry *quorumEntry = [self.chain.chainManager.masternodeManager quorumEntryForChainLockRequestID:[self requestID] forBlockHeight:self.height - offset]; - if (quorumEntry && quorumEntry.verified) { - self.signatureVerified = [self verifySignatureAgainstQuorum:quorumEntry]; - if (!self.signatureVerified) { - DSLog(@"[%@] unable to verify signature with offset %d", self.chain.name, offset); - } else { - DSLog(@"[%@] signature verified with offset %d", self.chain.name, offset); - } - } else if (quorumEntry) { - DSLog(@"[%@] quorum entry %@ found but is not yet verified", self.chain.name, uint256_hex(quorumEntry.quorumHash)); - } else { - DSLog(@"[%@] no quorum entry found", self.chain.name); - } - if (self.signatureVerified) { - self.intendedQuorum = quorumEntry; - self.quorumVerified = self.intendedQuorum.verified; - //We should also set the chain's last chain lock - if (!self.chain.lastChainLock || self.chain.lastChainLock.height < self.height) { - self.chain.lastChainLock = self; - } - } else if (quorumEntry.verified && offset == 8) { - //try again a few blocks more in the past - DSLog(@"[%@] trying with offset 0", self.chain.name); - return [self verifySignatureWithQuorumOffset:0]; - } else if (quorumEntry.verified && offset == 0) { - //try again a few blocks more in the future - DSLog(@"[%@] trying with offset 16", self.chain.name); - return [self verifySignatureWithQuorumOffset:16]; - } - DSLog(@"[%@] returning chain lock signature verified %d with offset %d", self.chain.name, self.signatureVerified, offset); - return self.signatureVerified; -} - (BOOL)verifySignature { - return [self verifySignatureWithQuorumOffset:8]; + if (self.lock) { +#if defined(DASHCORE_MESSAGE_VERIFICATION) + DMessageVerificationResult *result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_verify_is_lock(self.chain.sharedProcessorObj, self.lock); + BOOL verified = result->ok; + DMessageVerificationResultDtor(result); + return verified; +#else + return YES; +#endif + } else { + return NO; + } } - (void)saveInitial { @@ -186,7 +144,7 @@ - (void)saveInitial { //saving here will only create, not update. NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; [context performBlockAndWait:^{ // add the transaction to core data - if ([DSChainLockEntity countObjectsInContext:context matching:@"merkleBlock.blockHash == %@", uint256_data(self.blockHash)] == 0) { + if ([DSChainLockEntity countObjectsInContext:context matching:@"merkleBlock.blockHash == %@", self.blockHashData] == 0) { DSChainLockEntity *chainLockEntity = [DSChainLockEntity chainLockEntityForChainLock:self inContext:context]; if (chainLockEntity) { [context ds_save]; @@ -203,7 +161,7 @@ - (void)saveSignatureValid { }; NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; [context performBlockAndWait:^{ // add the transaction to core data - NSArray *chainLocks = [DSChainLockEntity objectsInContext:context matching:@"merkleBlock.blockHash == %@", uint256_data(self.blockHash)]; + NSArray *chainLocks = [DSChainLockEntity objectsInContext:context matching:@"merkleBlock.blockHash == %@", self.blockHashData]; DSChainLockEntity *chainLockEntity = [chainLocks firstObject]; if (chainLockEntity) { @@ -214,7 +172,7 @@ - (void)saveSignatureValid { } - (NSString *)description { - return [NSString stringWithFormat:@"", self.chain.name, self.height, self.signatureVerified ? @"Verified" : @"Not Verified"]; + return [NSString stringWithFormat:@"", self.chain.name, DChainLockBlockHeight(self.lock), self.signatureVerified ? @"Verified" : @"Not Verified"]; } diff --git a/DashSync/shared/Models/Chain/DSCheckpoint.h b/DashSync/shared/Models/Chain/DSCheckpoint.h index 491bea9dd..bf4a91455 100644 --- a/DashSync/shared/Models/Chain/DSCheckpoint.h +++ b/DashSync/shared/Models/Chain/DSCheckpoint.h @@ -48,7 +48,13 @@ typedef NS_ENUM(uint8_t, DSCheckpointOptions) @property (nonatomic, readonly) UInt256 merkleRoot; @property (nonatomic, readonly) UInt256 chainWork; -+ (instancetype)checkpointForHeight:(uint32_t)height blockHash:(UInt256)blockHash timestamp:(uint32_t)timestamp target:(uint32_t)target merkleRoot:(UInt256)merkleRoot chainWork:(UInt256)chainWork masternodeListName:(NSString *_Nullable)masternodeListName; ++ (instancetype)checkpointForHeight:(uint32_t)height + blockHash:(UInt256)blockHash + timestamp:(uint32_t)timestamp + target:(uint32_t)target + merkleRoot:(UInt256)merkleRoot + chainWork:(UInt256)chainWork + masternodeListName:(NSString *_Nullable)masternodeListName; + (instancetype)checkpointFromBlock:(DSBlock *)block options:(uint8_t)options; diff --git a/DashSync/shared/Models/Chain/DSCheckpoint.m b/DashSync/shared/Models/Chain/DSCheckpoint.m index 1e0443076..53347269b 100644 --- a/DashSync/shared/Models/Chain/DSCheckpoint.m +++ b/DashSync/shared/Models/Chain/DSCheckpoint.m @@ -46,7 +46,7 @@ @implementation DSCheckpoint + (DSCheckpoint *)genesisDevnetCheckpoint { DSCheckpoint *checkpoint = [DSCheckpoint new]; - checkpoint.blockHash = [NSString stringWithCString:"000008ca1832a4baf228eb1553c03d3a2c8e02399550dd6ea8d65cec3ef23d2e" encoding:NSUTF8StringEncoding].hexToData.reverse.UInt256; + checkpoint.blockHash = @"000008ca1832a4baf228eb1553c03d3a2c8e02399550dd6ea8d65cec3ef23d2e".hexToData.reverse.UInt256; checkpoint.height = 0; checkpoint.timestamp = 1417713337; checkpoint.target = 0x207fffffu; @@ -108,13 +108,31 @@ - (instancetype)initWithData:(NSData *)data atOffset:(uint32_t)offset finalOffse return self; } -+ (instancetype)checkpointForHeight:(uint32_t)height blockHash:(UInt256)blockHash timestamp:(uint32_t)timestamp target:(uint32_t)target merkleRoot:(UInt256)merkleRoot chainWork:(UInt256)chainWork masternodeListName:(NSString *_Nullable)masternodeListName { - return [[self alloc] initWithHeight:height blockHash:blockHash timestamp:timestamp target:target merkleRoot:merkleRoot chainWork:chainWork masternodeListName:masternodeListName]; ++ (instancetype)checkpointForHeight:(uint32_t)height + blockHash:(UInt256)blockHash + timestamp:(uint32_t)timestamp + target:(uint32_t)target + merkleRoot:(UInt256)merkleRoot + chainWork:(UInt256)chainWork + masternodeListName:(NSString *_Nullable)masternodeListName { + return [[self alloc] initWithHeight:height + blockHash:blockHash + timestamp:timestamp + target:target + merkleRoot:merkleRoot + chainWork:chainWork + masternodeListName:masternodeListName]; } + (instancetype)checkpointFromBlock:(DSBlock *)block options:(uint8_t)options { NSAssert(block.height != BLOCK_UNKNOWN_HEIGHT, @"Block height must be known"); - return [[self alloc] initWithHeight:block.height blockHash:block.blockHash timestamp:block.timestamp target:block.target merkleRoot:(options & DSCheckpointOptions_SaveMerkleRoot) ? block.merkleRoot : UINT256_ZERO chainWork:block.chainWork masternodeListName:nil]; + return [[self alloc] initWithHeight:block.height + blockHash:block.blockHash + timestamp:block.timestamp + target:block.target + merkleRoot:(options & DSCheckpointOptions_SaveMerkleRoot) ? block.merkleRoot : UINT256_ZERO + chainWork:block.chainWork + masternodeListName:nil]; } - (uint8_t)chainWorkSize { diff --git a/DashSync/shared/Models/Chain/DSFullBlock.m b/DashSync/shared/Models/Chain/DSFullBlock.m index 585f6f9f4..65d501f12 100644 --- a/DashSync/shared/Models/Chain/DSFullBlock.m +++ b/DashSync/shared/Models/Chain/DSFullBlock.m @@ -18,7 +18,9 @@ #import "DSFullBlock.h" #import "DSBlock+Protected.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSChainLock.h" +#import "DSCoinbaseTransaction.h" #import "DSKeyManager.h" #import "DSTransactionFactory.h" #import "NSData+DSHash.h" @@ -97,7 +99,6 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { return self; } - - (instancetype)initWithCoinbaseTransaction:(DSCoinbaseTransaction *)coinbaseTransaction transactions:(NSSet *)transactions previousBlockHash:(UInt256)previousBlockHash previousBlocks:(NSDictionary *)previousBlocks timestamp:(uint32_t)timestamp height:(uint32_t)height onChain:(DSChain *)chain { if (!(self = [super initWithVersion:COINBASE_TX_CORE_19 timestamp:timestamp height:height onChain:chain])) return nil; NSMutableSet *totalTransactionsSet = [transactions mutableCopy]; diff --git a/DashSync/shared/Models/Chain/DSMerkleBlock.h b/DashSync/shared/Models/Chain/DSMerkleBlock.h index 2a5a53ddf..45ec7c9d0 100644 --- a/DashSync/shared/Models/Chain/DSMerkleBlock.h +++ b/DashSync/shared/Models/Chain/DSMerkleBlock.h @@ -52,7 +52,11 @@ typedef union _UInt256 UInt256; + (instancetype)merkleBlockWithMessage:(NSData *)message onChain:(DSChain *)chain; // this init is used to check that the coinbase transaction is properly in the merkle tree of a block -- (instancetype)initWithBlockHash:(UInt256)blockHash merkleRoot:(UInt256)merkleRoot totalTransactions:(uint32_t)totalTransactions hashes:(NSData *)hashes flags:(NSData *)flags; +- (instancetype)initWithBlockHash:(UInt256)blockHash + merkleRoot:(UInt256)merkleRoot + totalTransactions:(uint32_t)totalTransactions + hashes:(NSData *)hashes + flags:(NSData *)flags; - (instancetype)initWithVersion:(uint32_t)version blockHash:(UInt256)blockHash prevBlock:(UInt256)prevBlock merkleRoot:(UInt256)merkleRoot diff --git a/DashSync/shared/Models/Chain/DSMerkleBlock.m b/DashSync/shared/Models/Chain/DSMerkleBlock.m index ebdf7a2da..8b43cb01d 100644 --- a/DashSync/shared/Models/Chain/DSMerkleBlock.m +++ b/DashSync/shared/Models/Chain/DSMerkleBlock.m @@ -115,7 +115,9 @@ - (instancetype)initWithBlockHash:(UInt256)blockHash merkleRoot:(UInt256)merkleR return self; } -- (instancetype)initWithVersion:(uint32_t)version blockHash:(UInt256)blockHash prevBlock:(UInt256)prevBlock +- (instancetype)initWithVersion:(uint32_t)version + blockHash:(UInt256)blockHash + prevBlock:(UInt256)prevBlock merkleRoot:(UInt256)merkleRoot timestamp:(uint32_t)timestamp target:(uint32_t)target diff --git a/DashSync/shared/Models/CoinJoin/DSCoinControl.h b/DashSync/shared/Models/CoinJoin/DSCoinControl.h new file mode 100644 index 000000000..63b05d4d2 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinControl.h @@ -0,0 +1,54 @@ +// +// Created by Andrei Ashikhmin +// 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 "BigIntTypes.h" +#import "DSKeyManager.h" +#import "DSCoinJoinWrapper.h" + +NS_ASSUME_NONNULL_BEGIN + +// CoinControl comes from Dash Core. Not all functions fields and functions are supported within the Wallet class +@interface DSCoinControl : NSObject + +@property (nonatomic, assign) BOOL allowOtherInputs; +@property (nonatomic, assign) BOOL requireAllInputs; +@property (nonatomic, assign) BOOL allowWatchOnly; +@property (nonatomic, assign) BOOL overrideFeeRate; +@property (nonatomic, assign) BOOL avoidPartialSpends; +@property (nonatomic, assign) BOOL avoidAddressReuse; +@property (nonatomic, assign) int32_t minDepth; +@property (nonatomic, assign) int32_t maxDepth; +@property (nonatomic, assign) uint64_t feeRate; +@property (nonatomic, assign) uint64_t discardFeeRate; +@property (nonatomic, strong) NSNumber *confirmTarget; +@property (nonatomic, assign) DCoinType coinType; +@property (nonatomic, strong) NSMutableOrderedSet *setSelected; +@property (nonatomic, strong) NSString *destChange; + +- (instancetype)initWithFFICoinControl:(DCoinControl *)coinControl + chainType:(DChainType *)chainType; + +- (BOOL)hasSelected; +- (BOOL)isSelected:(DSUTXO)utxo; +- (void)useCoinJoin:(BOOL)useCoinJoin; +- (BOOL)isUsingCoinJoin; +- (void)select:(DSUTXO)utxo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/DSCoinControl.m b/DashSync/shared/Models/CoinJoin/DSCoinControl.m new file mode 100644 index 000000000..a128c6fe8 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinControl.m @@ -0,0 +1,90 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCoinControl.h" + +@implementation DSCoinControl + +- (instancetype)initWithFFICoinControl:(DCoinControl *)coinControl + chainType:(DChainType *)chainType { + if (!(self = [super init])) return nil; + self.coinType = DCoinTypeIndex(coinControl->coin_type); + self.minDepth = coinControl->min_depth; + self.maxDepth = coinControl->max_depth; + self.avoidAddressReuse = coinControl->avoid_address_reuse; + self.allowOtherInputs = coinControl->allow_other_inputs; + + if (coinControl->dest_change) + self.destChange = [DSKeyManager NSStringFrom:DAddressWithScriptPubKeyData(coinControl->dest_change, chainType)]; + NSMutableOrderedSet *setSelected = [NSMutableOrderedSet orderedSetWithCapacity:coinControl->set_selected->count]; + for (int i = 0; i < coinControl->set_selected->count; i++) { + DOutPoint *outpoint = coinControl->set_selected->values[i]; + [setSelected addObject:dsutxo_obj(((DSUTXO){u256_cast(dashcore_hash_types_Txid_inner(outpoint->txid)), outpoint->vout}))]; + } + return self; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _setSelected = [[NSMutableOrderedSet alloc] init]; + _coinType = dash_spv_coinjoin_models_coin_control_CoinType_AllCoins; + _allowOtherInputs = NO; + _requireAllInputs = NO; + _allowWatchOnly = NO; + _overrideFeeRate = NO; + _avoidPartialSpends = NO; + _avoidAddressReuse = NO; + _minDepth = 0; + _destChange = NULL; + } + return self; +} + +- (BOOL)hasSelected { + return self.setSelected.count > 0; +} + +- (BOOL)isSelected:(DSUTXO)utxo { + for (NSValue *selectedValue in self.setSelected) { + DSUTXO selectedUTXO; + [selectedValue getValue:&selectedUTXO]; + + if (dsutxo_eq(utxo, selectedUTXO)) { + return YES; + } + } + + return NO; +} + +- (void)useCoinJoin:(BOOL)useCoinJoin { + self.coinType = useCoinJoin ? dash_spv_coinjoin_models_coin_control_CoinType_OnlyFullyMixed : dash_spv_coinjoin_models_coin_control_CoinType_AllCoins; +} + +- (BOOL)isUsingCoinJoin { + return self.coinType == dash_spv_coinjoin_models_coin_control_CoinType_OnlyFullyMixed; +} + +- (void)select:(DSUTXO)utxo { + NSValue *utxoValue = [NSValue valueWithBytes:&utxo objCType:@encode(DSUTXO)]; + if (![self.setSelected containsObject:utxoValue]) { + [self.setSelected addObject:utxoValue]; + } +} + +@end diff --git a/DashSync/shared/Models/CoinJoin/DSCoinJoinBalance.h b/DashSync/shared/Models/CoinJoin/DSCoinJoinBalance.h new file mode 100644 index 000000000..7edca0108 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinJoinBalance.h @@ -0,0 +1,53 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSKeyManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSCoinJoinBalance : NSObject + +@property (nonatomic, assign) uint64_t myTrusted; +@property (nonatomic, assign) uint64_t denominatedTrusted; +@property (nonatomic, assign) uint64_t anonymized; +@property (nonatomic, assign) uint64_t myImmature; +@property (nonatomic, assign) uint64_t myUntrustedPending; +@property (nonatomic, assign) uint64_t denominatedUntrustedPending; +@property (nonatomic, assign) uint64_t watchOnlyTrusted; +@property (nonatomic, assign) uint64_t watchOnlyUntrustedPending; +@property (nonatomic, assign) uint64_t watchOnlyImmature; + ++ (DSCoinJoinBalance *)balanceWithMyTrusted:(uint64_t)myTrusted + denominatedTrusted:(uint64_t)denominatedTrusted + anonymized:(uint64_t)anonymized + myImmature:(uint64_t)myImmature + myUntrustedPending:(uint64_t)myUntrustedPending + denominatedUntrustedPending:(uint64_t)denominatedUntrustedPending + watchOnlyTrusted:(uint64_t)watchOnlyTrusted + watchOnlyUntrustedPending:(uint64_t)watchOnlyUntrustedPending + watchOnlyImmature:(uint64_t)watchOnlyImmature; +@end + +@interface DSCoinJoinBalance (FFI) + ++ (DBalance *)ffi_to:(DSCoinJoinBalance *)obj; ++ (void)ffi_destroy:(DBalance *)ffi_ref; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/DSCoinJoinBalance.m b/DashSync/shared/Models/CoinJoin/DSCoinJoinBalance.m new file mode 100644 index 000000000..1a29a7b54 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinJoinBalance.m @@ -0,0 +1,59 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCoinJoinBalance.h" + +@implementation DSCoinJoinBalance + ++ (DSCoinJoinBalance *)balanceWithMyTrusted:(uint64_t)myTrusted + denominatedTrusted:(uint64_t)denominatedTrusted + anonymized:(uint64_t)anonymized + myImmature:(uint64_t)myImmature + myUntrustedPending:(uint64_t)myUntrustedPending + denominatedUntrustedPending:(uint64_t)denominatedUntrustedPending + watchOnlyTrusted:(uint64_t)watchOnlyTrusted + watchOnlyUntrustedPending:(uint64_t)watchOnlyUntrustedPending + watchOnlyImmature:(uint64_t)watchOnlyImmature { + + DSCoinJoinBalance *balance = [[DSCoinJoinBalance alloc] init]; + balance.myTrusted = myTrusted; + balance.denominatedTrusted = denominatedTrusted; + balance.anonymized = anonymized; + balance.myImmature = myImmature; + balance.myUntrustedPending = myUntrustedPending; + balance.denominatedUntrustedPending = denominatedUntrustedPending; + balance.watchOnlyTrusted = watchOnlyTrusted; + balance.watchOnlyUntrustedPending = watchOnlyUntrustedPending; + balance.watchOnlyImmature = watchOnlyImmature; + + return balance; +} + +@end + +@implementation DSCoinJoinBalance (FFI) + ++ (DBalance *)ffi_to:(DSCoinJoinBalance *)obj { + return DBalanceCtor(obj.myTrusted, obj.myUntrustedPending, obj.myImmature, obj.watchOnlyTrusted, obj.watchOnlyUntrustedPending, obj.watchOnlyImmature, obj.anonymized, obj.denominatedTrusted, obj.denominatedUntrustedPending); + +} ++ (void)ffi_destroy:(DBalance *)ffi_ref { + if (ffi_ref) + dash_spv_coinjoin_models_balance_Balance_destroy(ffi_ref); +} + +@end diff --git a/DashSync/shared/Models/CoinJoin/DSCoinJoinManager.h b/DashSync/shared/Models/CoinJoin/DSCoinJoinManager.h new file mode 100644 index 000000000..84363324c --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinJoinManager.h @@ -0,0 +1,138 @@ +// +// Created by Andrei Ashikhmin +// 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 "dash_spv_apple_bindings.h" +#import "DSChain.h" +#import "DSTransactionOutput.h" +#import "DSCoinControl.h" +#import "DSCompactTallyItem.h" +#import "DSCoinJoinManager.h" +#import "DSCoinJoinWrapper.h" +#import "DSMasternodeGroup.h" +#import "DSCoinJoinBalance.h" + +NS_ASSUME_NONNULL_BEGIN + + +@protocol DSCoinJoinManagerDelegate + +- (void)sessionStartedWithId:(int32_t)baseId + clientSessionId:(UInt256)clientId + denomination:(uint32_t)denom + poolState:(DPoolState)state + poolMessage:(DPoolMessage)message + poolStatus:(DPoolStatus)status + ipAddress:(UInt128)address + isJoined:(BOOL)joined; +- (void)sessionCompleteWithId:(int32_t)baseId + clientSessionId:(UInt256)clientId + denomination:(uint32_t)denom + poolState:(DPoolState)state + poolMessage:(DPoolMessage)message + poolStatus:(DPoolStatus)status + ipAddress:(UInt128)address + isJoined:(BOOL)joined; +- (void)mixingStarted; +- (void)mixingComplete:(BOOL)withError + errorStatus:(DPoolStatus *)errorStatus + isInterrupted:(BOOL)isInterrupted; +- (void)transactionProcessedWithId:(UInt256)txId + type:(DCoinJoinTransactionType *)type; + +@end + +@interface DSCoinJoinManager : NSObject + +@property (nonatomic, assign, nullable) DSChain *chain; +@property (nonatomic, strong, nullable) DSMasternodeGroup *masternodeGroup; +@property (nonatomic, assign, nullable) DCoinJoinClientOptions *options; +@property (nonatomic, nullable, weak) id managerDelegate; +@property (nonatomic, assign) BOOL anonymizableTallyCachedNonDenom; +@property (nonatomic, assign) BOOL anonymizableTallyCached; +@property (nonatomic, strong, nullable) DSCoinJoinWrapper *wrapper; +@property (nonatomic, readonly) BOOL isWaitingForNewBlock; +@property (atomic) BOOL isMixing; +@property (atomic) BOOL isShuttingDown; +@property (readonly) BOOL isChainSynced; + ++ (instancetype)sharedInstanceForChain:(DSChain *)chain; +- (instancetype)initWithChain:(DSChain *)chain; + +- (void)initMasternodeGroup; +- (BOOL)isMineInput:(UInt256)txHash index:(uint32_t)index; +- (NSArray *) availableCoins:(WalletEx *)walletEx onlySafe:(BOOL)onlySafe coinControl:(DSCoinControl *_Nullable)coinControl minimumAmount:(uint64_t)minimumAmount maximumAmount:(uint64_t)maximumAmount minimumSumAmount:(uint64_t)minimumSumAmount maximumCount:(uint64_t)maximumCount; +- (NSArray *)selectCoinsGroupedByAddresses:(WalletEx *)walletEx skipDenominated:(BOOL)skipDenominated anonymizable:(BOOL)anonymizable skipUnconfirmed:(BOOL)skipUnconfirmed maxOupointsPerAddress:(int32_t)maxOupointsPerAddress; +- (uint32_t)countInputsWithAmount:(uint64_t)inputAmount; +- (NSString *)freshAddress:(BOOL)internal; +- (NSArray *)getIssuedReceiveAddresses; +- (NSArray *)getUsedReceiveAddresses; +- (BOOL)commitTransactionForAmounts:(NSArray *)amounts outputs:(NSArray *)outputs coinControl:(DSCoinControl *)coinControl onPublished:(void (^)(UInt256 txId, NSError * _Nullable error))onPublished; +- (DMasternodeEntry *)masternodeEntryByHash:(UInt256)hash; +- (uintptr_t)validMNCount; +- (DMasternodeList *)mnList; +- (BOOL)isMasternodeOrDisconnectRequested:(UInt128)ip port:(uint16_t)port; +- (BOOL)disconnectMasternode:(UInt128)ip port:(uint16_t)port; +- (BOOL)sendMessageOfType:(NSString *)messageType message:(NSData *)message withPeerIP:(UInt128)address port:(uint16_t)port warn:(BOOL)warn; +- (DSCoinJoinBalance *)getBalance; +- (void)configureMixingWithAmount:(uint64_t)amount rounds:(int32_t)rounds sessions:(int32_t)sessions withMultisession:(BOOL)multisession denominationGoal:(int32_t)denomGoal denominationHardCap:(int32_t)denomHardCap; +- (void)startAsync; +- (void)stopAsync; +- (void)start; +- (void)stop; +- (BOOL)addPendingMasternode:(UInt256)proTxHash clientSessionId:(UInt256)sessionId; +- (void)processMessageFrom:(DSPeer *)peer message:(NSData *)message type:(NSString *)type; +- (void)setStopOnNothingToDo:(BOOL)stop; +- (BOOL)startMixing; +- (void)doAutomaticDenominatingWithDryRun:(BOOL)dryRun completion:(void (^)(BOOL success))completion; +- (void)updateSuccessBlock; +- (void)refreshUnusedKeys; +- (DCoinJoinTransactionType *)coinJoinTxTypeForTransaction:(DSTransaction *)transaction; +- (double)getMixingProgress; +- (DSCoinControl *)selectCoinJoinUTXOs; +- (uint64_t)getSmallestDenomination; +//- (void)hasCollateralInputsWithOnlyConfirmed:(BOOL)onlyConfirmed completion:(void (^)(BOOL balance))completion; +- (void)calculateAnonymizableBalanceWithSkipDenominated:(BOOL)skipDenominated skipUnconfirmed:(BOOL)skipUnconfirmed completion:(void (^)(uint64_t balance))completion; +- (void)minimumAnonymizableBalanceWithCompletion:(void (^)(uint64_t balance))completion; +- (void)updateOptionsWithAmount:(uint64_t)amount; +- (void)updateOptionsWithEnabled:(BOOL)isEnabled; +- (void)initiateShutdown; + +// Events +- (void)onSessionComplete:(int32_t)baseId + clientSessionId:(UInt256)clientId + denomination:(uint32_t)denom + poolState:(DPoolState)state + poolMessage:(DPoolMessage)message + poolStatus:(DPoolStatus)status + ipAddress:(UInt128)address + isJoined:(BOOL)joined; +- (void)onSessionStarted:(int32_t)baseId + clientSessionId:(UInt256)clientId + denomination:(uint32_t)denom + poolState:(DPoolState)state + poolMessage:(DPoolMessage)message + poolStatus:(DPoolStatus)status + ipAddress:(UInt128)address + isJoined:(BOOL)joined; +- (void)onMixingStarted:(nonnull NSArray *)statuses; +- (void)onMixingComplete:(nonnull NSArray *)statuses isInterrupted:(BOOL)isInterrupted; +- (void)onTransactionProcessed:(UInt256)txId type:(DCoinJoinTransactionType *)type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/DSCoinJoinManager.m b/DashSync/shared/Models/CoinJoin/DSCoinJoinManager.m new file mode 100644 index 000000000..de3f78448 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinJoinManager.m @@ -0,0 +1,1075 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCoinJoinManager.h" +#import "DSTransaction.h" +#import "DSTransactionOutput.h" +#import "DSAccount.h" +#import "DSCoinControl.h" +#import "DSWallet.h" +#import "BigIntTypes.h" +#import "NSString+Bitcoin.h" +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChain+Transaction.h" +#import "DSChain+Wallet.h" +#import "DSChainManager.h" +#import "DSTransactionManager.h" +#import "DSMasternodeManager.h" +#import "DSCoinJoinAcceptMessage.h" +#import "DSCoinJoinEntryMessage.h" +#import "DSCoinJoinSignedInputs.h" +#import "DSPeerManager.h" +#import "DSBlock.h" +#import "DSKeyManager.h" +#import "DSDerivationPath+Protected.h" + +int32_t const DEFAULT_MIN_DEPTH = 0; +int32_t const DEFAULT_MAX_DEPTH = 9999999; +int32_t const MIN_BLOCKS_TO_WAIT = 1; + +@interface DSCoinJoinManager () + +@property (nonatomic, strong) dispatch_queue_t processingQueue; +@property (nonatomic, strong) dispatch_source_t coinjoinTimer; +@property (atomic) int32_t cachedLastSuccessBlock; +@property (atomic) int32_t cachedBlockHeight; // Keep track of current block height +@property (atomic) double lastReportedProgress; +@property (atomic) BOOL hasReportedSuccess; +@property (atomic) BOOL hasReportedFailure; + +@end + +@implementation DSCoinJoinManager + +static NSMutableDictionary *_managerChainDictionary = nil; +static dispatch_once_t managerChainToken = 0; + ++ (instancetype)sharedInstanceForChain:(DSChain *)chain { + NSParameterAssert(chain); + + dispatch_once(&managerChainToken, ^{ + _managerChainDictionary = [NSMutableDictionary dictionary]; + }); + DSCoinJoinManager *managerForChain = nil; + @synchronized(_managerChainDictionary) { + if (![_managerChainDictionary objectForKey:chain.uniqueID]) { + managerForChain = [[DSCoinJoinManager alloc] initWithChain:chain]; + _managerChainDictionary[chain.uniqueID] = managerForChain; + } else { + managerForChain = [_managerChainDictionary objectForKey:chain.uniqueID]; + } + } + return managerForChain; +} + +- (instancetype)initWithChain:(DSChain *)chain { + self = [super init]; + if (self) { + _chain = chain; + _wrapper = [[DSCoinJoinWrapper alloc] initWithManagers:self chainManager:chain.chainManager]; + _processingQueue = dispatch_queue_create([[NSString stringWithFormat:@"org.dashcore.dashsync.coinjoin.%@", self.chain.uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL); + _cachedBlockHeight = 0; + _cachedLastSuccessBlock = 0; + _lastReportedProgress = 0; + _options = [self createOptions]; + [self printUsedKeys]; + } + return self; +} + +- (void)initMasternodeGroup { + _masternodeGroup = [[DSMasternodeGroup alloc] initWithManager:self]; +} + +- (DCoinJoinClientOptions *)createOptions { + DCoinJoinClientOptions *options = dash_spv_coinjoin_models_coinjoin_client_options_CoinJoinClientOptions_ctor(true, DUFFS / 8, 6, 1, COINJOIN_RANDOM_ROUNDS, DEFAULT_COINJOIN_DENOMS_GOAL, DEFAULT_COINJOIN_DENOMS_HARDCAP, false, self.chain.chainType, false); + return options; +} + +- (void)updateOptionsWithAmount:(uint64_t)amount { + if (self.options->coinjoin_amount != amount) { + self.options->coinjoin_amount = amount; + + if (self.wrapper.isRegistered) { + [self.wrapper updateOptions:self.options]; + } + } +} + +- (void)updateOptionsWithEnabled:(BOOL)isEnabled { + if (self.options->enable_coinjoin != isEnabled) { + self.options->enable_coinjoin = isEnabled; + + if (self.wrapper.isRegistered) { + [self.wrapper updateOptions:self.options]; + } + } +} + +- (void)updateOptionsWithSessions:(int32_t)sessions { + if (self.options->coinjoin_sessions != sessions) { + self.options->coinjoin_sessions = sessions; + + if (self.wrapper.isRegistered) { + [self.wrapper updateOptions:self.options]; + } + } +} + +- (void)configureMixingWithAmount:(uint64_t)amount rounds:(int32_t)rounds sessions:(int32_t)sessions withMultisession:(BOOL)multisession denominationGoal:(int32_t)denomGoal denominationHardCap:(int32_t)denomHardCap { + DSLog(@"[%@] CoinJoin: mixing configuration: { rounds: %d, sessions: %d, amount: %llu, multisession: %s, denomGoal: %d, denomHardCap: %d }", self.chain.name, rounds, sessions, amount, multisession ? "YES" : "NO", denomGoal, denomHardCap); + self.options->enable_coinjoin = true; + self.options->coinjoin_amount = amount; + self.options->coinjoin_rounds = rounds; + self.options->coinjoin_sessions = sessions; + self.options->coinjoin_multi_session = multisession; + self.options->coinjoin_denoms_goal = denomGoal; + self.options->coinjoin_denoms_hardcap = denomHardCap; + + if (self.wrapper.isRegistered) { + [self.wrapper updateOptions:self.options]; + } +} + +- (BOOL)isChainSynced { + return self.chain.chainManager.syncPhase == DSChainSyncPhase_Synced; +} + +- (void)startAsync { + if (!self.masternodeGroup.isRunning) { + [self.chain.chainManager.peerManager shouldSendDsq:true]; + [self.masternodeGroup startAsync]; + } +} + +- (void)start { + DSLog(@"[%@] CoinJoinManager starting", self.chain.name); + [self cancelCoinjoinTimer]; + uint32_t interval = 1; + uint32_t delay = 1; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleTransactionReceivedNotification) + name:DSTransactionManagerTransactionReceivedNotification + object:nil]; + @synchronized (self) { + self.cachedBlockHeight = self.chain.lastSyncBlock.height; + self.options->enable_coinjoin = YES; + + if ([self.wrapper isRegistered]) { + [self.wrapper updateOptions:self.options]; + } else { + [self.wrapper registerCoinJoin:self.options]; + } + + self.coinjoinTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.processingQueue); + if (self.coinjoinTimer) { + dispatch_source_set_timer(self.coinjoinTimer, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), interval * NSEC_PER_SEC, 1ull * NSEC_PER_SEC); + dispatch_source_set_event_handler(self.coinjoinTimer, ^{ + [self doMaintenance]; + }); + dispatch_resume(self.coinjoinTimer); + } + } +} + +- (void)doMaintenance { + if ([self validMNCount] == 0) { + return; + } + + [self.wrapper doMaintenance]; +} + +- (BOOL)startMixing { + self.isMixing = true; + self.isShuttingDown = false; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleSyncStateDidChangeNotification:) + name:DSChainManagerSyncStateDidChangeNotification + object:nil]; + return [self.wrapper startMixing]; +} + +- (void)initiateShutdown { + if (self.isMixing && !self.isShuttingDown) { + DSLog(@"[%@] CoinJoinManager initiated shutdown", self.chain.name); + self.isShuttingDown = true; + [self updateOptionsWithSessions:0]; + [self.wrapper initiateShutdown]; + + if (self.masternodeGroup != nil && self.masternodeGroup.isRunning) { + [self.chain.chainManager.peerManager shouldSendDsq:false]; + [self.masternodeGroup stopAsync]; + } + } +} + +- (void)stop { + if (self.isMixing) { + DSLog(@"[%@] CoinJoinManager stopping", self.chain.name); + self.isMixing = false; + [self cancelCoinjoinTimer]; + self.cachedLastSuccessBlock = 0; + [self updateOptionsWithEnabled:NO]; + [self.wrapper stopAndResetClientManager]; + [self stopAsync]; + self.isShuttingDown = false; + } +} + +- (void)stopAsync { + if (self.masternodeGroup != nil && self.masternodeGroup.isRunning) { + [self.chain.chainManager.peerManager shouldSendDsq:false]; + [self.masternodeGroup stopAsync]; + self.masternodeGroup = nil; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)dealloc { + if (_options != NULL) { + free(_options); + } +} + +- (void)handleSyncStateDidChangeNotification:(NSNotification *)note { + if ([note.userInfo[DSChainManagerNotificationChainKey] isEqual:[self chain]] && self.chain.lastSyncBlock.height > self.cachedBlockHeight) { + self.cachedBlockHeight = self.chain.lastSyncBlock.height; + dispatch_async(self.processingQueue, ^{ + [self.wrapper notifyNewBestBlock:self.chain.lastSyncBlock]; + }); + } +} + +- (void)handleTransactionReceivedNotification { + DSWallet *wallet = self.chain.wallets.firstObject; + DSTransaction *lastTransaction = wallet.accounts.firstObject.recentTransactions.firstObject; + + if ([self.wrapper isMixingFeeTx:lastTransaction.txHash]) { +#if DEBUG + DSLogPrivate(@"[%@] CoinJoin tx: Mixing Fee: %@", self.chain.name, uint256_reverse_hex(lastTransaction.txHash)); +#else + DSLog(@"[%@] CoinJoin tx: Mixing Fee: %@", self.chain.name, @""); +#endif + [self onTransactionProcessed:lastTransaction.txHash type:dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_MixingFee_ctor()]; + } else if (DCoinJoinTransactionTypeIndex([self coinJoinTxTypeForTransaction:lastTransaction]) == dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_Mixing) { +#if DEBUG + DSLogPrivate(@"[%@] CoinJoin tx: Mixing Transaction: %@", self.chain.name, uint256_reverse_hex(lastTransaction.txHash)); +#else + DSLog(@"[%@] CoinJoin tx: Mixing Transaction: %@", self.chain.name, @""); +#endif + [self.wrapper unlockOutputs:lastTransaction]; + [self onTransactionProcessed:lastTransaction.txHash type:dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_Mixing_ctor()]; + } +} + +- (void)doAutomaticDenominatingWithDryRun:(BOOL)dryRun completion:(void (^)(BOOL success))completion { + if (![self.wrapper isRegistered]) { + [self.wrapper registerCoinJoin:self.options]; + } + + if (!dryRun && [self validMNCount] == 0) { + completion(NO); + return; + } + + DSLog(@"[%@] CoinJoin: doAutomaticDenominatingWithDryRun: %@", self.chain.name, dryRun ? @"YES" : @"NO"); + + dispatch_async(self.processingQueue, ^{ + BOOL result = [self.wrapper doAutomaticDenominatingWithDryRun:dryRun]; + + if (!dryRun) { + if (result) { + if (!self.hasReportedSuccess) { + DSLog(@"[%@] CoinJoin: Mixing started successfully", self.chain.name); + self.hasReportedSuccess = YES; + self.hasReportedFailure = NO; + } + } else { + if (!self.hasReportedFailure) { + DSLog(@"[%@] CoinJoin: Mixing start failed, will retry", self.chain.name); + self.hasReportedFailure = YES; + self.hasReportedSuccess = NO; + } + } + } + + completion(result); + }); +} + +- (void)cancelCoinjoinTimer { + @synchronized (self) { + if (self.coinjoinTimer) { + dispatch_source_cancel(self.coinjoinTimer); + self.coinjoinTimer = nil; + } + } +} + +- (void)setStopOnNothingToDo:(BOOL)stop { + [self.wrapper setStopOnNothingToDo:stop]; +} + +- (void)refreshUnusedKeys { + [self.wrapper refreshUnusedKeys]; +} + +- (void)processMessageFrom:(DSPeer *)peer message:(NSData *)message type:(NSString *)type { + if (!self.isMixing) { + return; + } + + dispatch_async(self.processingQueue, ^{ + if ([type isEqualToString:MSG_COINJOIN_QUEUE]) { + if (!self.isShuttingDown) { + [self.wrapper processDSQueueFrom:peer message:message]; + } + } else { + [self.wrapper processMessageFrom:peer message:message type:type]; + } + }); +} + +- (BOOL)isMineInput:(UInt256)txHash index:(uint32_t)index { + DSTransaction *tx = [self.chain transactionForHash:txHash]; + DSAccount *account = [self.chain firstAccountThatCanContainTransaction:tx]; + + if (index < tx.outputs.count) { + DSTransactionOutput *output = tx.outputs[index]; + + if ([account containsAddress:output.address]) { + return YES; + } + } + + return NO; +} + +- (NSArray *)selectCoinsGroupedByAddresses:(WalletEx *)walletEx + skipDenominated:(BOOL)skipDenominated + anonymizable:(BOOL)anonymizable + skipUnconfirmed:(BOOL)skipUnconfirmed + maxOupointsPerAddress:(int32_t)maxOupointsPerAddress { + @synchronized(self) { + // Note: cache is checked in dash-shared-core. + uint64_t smallestDenom = dash_spv_coinjoin_coinjoin_CoinJoin_get_smallest_denomination(); + NSMutableDictionary *mapTally = [[NSMutableDictionary alloc] init]; + NSMutableSet *setWalletTxesCounted = [[NSMutableSet alloc] init]; + + DSUTXO outpoint; + NSArray *utxos = self.chain.wallets.firstObject.unspentOutputs; + for (NSValue *value in utxos) { + [value getValue:&outpoint]; + + if ([setWalletTxesCounted containsObject:uint256_data(outpoint.hash)]) { + continue; + } + + [setWalletTxesCounted addObject:uint256_data(outpoint.hash)]; + DSTransaction *wtx = [self.chain transactionForHash:outpoint.hash]; + + if (wtx == nil) { + continue; + } + + if (wtx.isCoinbaseClassicTransaction && [wtx getBlocksToMaturity] > 0) { + continue; + } + + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + if (!account.coinJoinDerivationPath.addressesLoaded) { + DSLog(@"[%@] CoinJoin selectCoinsGroupedByAddresses: CJDerivationPath addresses NOT loaded", self.chain.name); + } + + BOOL isTrusted = wtx.instantSendReceived || [account transactionIsVerified:wtx]; + + if (skipUnconfirmed && !isTrusted) { + continue; + } + + for (int32_t i = 0; i < wtx.outputs.count; i++) { + DSTransactionOutput *output = wtx.outputs[i]; + NSData *txDest = output.outScript; + NSString *address = [NSString bitcoinAddressWithScriptPubKey:txDest forChain:self.chain]; + + if (address == nil) { + continue; + } + + if (![account containsAddress:output.address]) { + continue; + } + + DSCompactTallyItem *tallyItem = mapTally[txDest]; + + if (maxOupointsPerAddress != -1 && tallyItem != nil && tallyItem.inputCoins.count >= maxOupointsPerAddress) { + continue; + } + + if ([account isSpent:dsutxo_obj(((DSUTXO){outpoint.hash, i}))]) { + continue; + } + + if (dash_spv_coinjoin_wallet_ex_WalletEx_is_locked_coin(walletEx, DOutPointCtorU(outpoint.hash, i))) { + continue; + } + + if (skipDenominated && dash_spv_coinjoin_coinjoin_CoinJoin_is_denominated_amount(output.amount)) { + continue; + } + + if (anonymizable) { + // ignore collaterals + if (dash_spv_coinjoin_coinjoin_CoinJoin_is_collateral_amount(output.amount)) { + continue; + } + + // ignore outputs that are 10 times smaller then the smallest denomination + // otherwise they will just lead to higher fee / lower priority + if (output.amount <= smallestDenom/10) { + continue; + } + + // ignore mixed + + if (dash_spv_coinjoin_wallet_ex_WalletEx_check_if_is_fully_mixed(walletEx, DOutPointCtorU(outpoint.hash, i))) { + continue; + } + } + + if (tallyItem == nil) { + tallyItem = [[DSCompactTallyItem alloc] init]; + tallyItem.txDestination = txDest; + mapTally[txDest] = tallyItem; + } + + tallyItem.amount += output.amount; + DSInputCoin *coin = [[DSInputCoin alloc] initWithTx:wtx index:i]; + [tallyItem.inputCoins addObject:coin]; + } + } + + // construct resulting vector + // NOTE: vecTallyRet is "sorted" by txdest (i.e. address), just like mapTally + NSMutableArray *vecTallyRet = [NSMutableArray array]; + + for (DSCompactTallyItem *item in mapTally.allValues) { + // TODO: (dashj) ignore this to get this dust back in + if (anonymizable && item.amount < smallestDenom) { + continue; + } + + [vecTallyRet addObject:item]; + } + + // Note: cache is assigned in dash-shared-core + + return vecTallyRet; + } +} + +- (uint32_t)countInputsWithAmount:(uint64_t)inputAmount { + uint32_t total = 0; + + @synchronized(self) { + NSArray *unspent = self.chain.wallets.firstObject.unspentOutputs; + + DSUTXO outpoint; + for (uint32_t i = 0; i < unspent.count; i++) { + [unspent[i] getValue:&outpoint]; + DSTransaction *tx = [self.chain transactionForHash:outpoint.hash]; + + if (tx == NULL) { + continue; + } + + if (tx.outputs[outpoint.n].amount != inputAmount) { + continue; + } + + if (tx.confirmations < 0) { + continue; + } + + total++; + } + } + + return total; +} + +- (NSArray *)availableCoins:(WalletEx *)walletEx + onlySafe:(BOOL)onlySafe + coinControl:(DSCoinControl *_Nullable)coinControl + minimumAmount:(uint64_t)minimumAmount + maximumAmount:(uint64_t)maximumAmount + minimumSumAmount:(uint64_t)minimumSumAmount + maximumCount:(uint64_t)maximumCount { + NSMutableArray *vCoins = [NSMutableArray array]; + + @synchronized(self) { + DCoinType coinType = coinControl != nil ? coinControl.coinType : dash_spv_coinjoin_models_coin_control_CoinType_AllCoins; + + uint64_t total = 0; + // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses + BOOL allowUsedAddresses = /* !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || */ (coinControl != nil && !coinControl.avoidAddressReuse); + int32_t minDepth = coinControl != nil ? coinControl.minDepth : DEFAULT_MIN_DEPTH; + int32_t maxDepth = coinControl != nil ? coinControl.maxDepth : DEFAULT_MAX_DEPTH; + NSSet *spendables = [self getSpendableTXs]; + + for (DSTransaction *coin in spendables) { + UInt256 wtxid = coin.txHash; + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + if (!account.coinJoinDerivationPath.addressesLoaded) { + DSLog(@"[%@] CoinJoin availableCoins: CJDerivationPath addresses NOT loaded", self.chain.name); + } + + if ([account transactionIsPending:coin]) { + continue; + } + + if (coin.isImmatureCoinBase) { + continue; + } + + BOOL safeTx = coin.instantSendReceived || [account transactionIsVerified:coin]; + + if (onlySafe && !safeTx) { + continue; + } + + uint32_t depth = coin.confirmations; + + if (depth < minDepth || depth > maxDepth) { + continue; + } + + for (uint32_t i = 0; i < coin.outputs.count; i++) { + DSTransactionOutput *output = coin.outputs[i]; + uint64_t value = output.amount; + BOOL found = NO; + + if (coinType == dash_spv_coinjoin_models_coin_control_CoinType_OnlyFullyMixed) { + + if (!dash_spv_coinjoin_coinjoin_CoinJoin_is_denominated_amount(value)) { + continue; + } + + DOutPoint *outpoint = DOutPointCtorU(wtxid, i); + found = dash_spv_coinjoin_wallet_ex_WalletEx_check_if_is_fully_mixed(walletEx, outpoint); + } else if (coinType == dash_spv_coinjoin_models_coin_control_CoinType_OnlyReadyToMix) { + if (!dash_spv_coinjoin_coinjoin_CoinJoin_is_denominated_amount(value)) { + continue; + } + DOutPoint *outpoint = DOutPointCtorU(wtxid, i); + found = !dash_spv_coinjoin_wallet_ex_WalletEx_check_if_is_fully_mixed(walletEx, outpoint); + } else if (coinType == dash_spv_coinjoin_models_coin_control_CoinType_OnlyNonDenominated) { + if (dash_spv_coinjoin_coinjoin_CoinJoin_is_collateral_amount(value)) { + continue; // do not use collateral amounts + } + found = !dash_spv_coinjoin_coinjoin_CoinJoin_is_denominated_amount(value); + } else if (coinType == dash_spv_coinjoin_models_coin_control_CoinType_OnlyMasternodeCollateral) { + found = value == 1000 * DUFFS; + } else if (coinType == dash_spv_coinjoin_models_coin_control_CoinType_OnlyCoinJoinCollateral) { + found = dash_spv_coinjoin_coinjoin_CoinJoin_is_collateral_amount(value); + } else { + found = YES; + } + + if (!found) { + continue; + } + + if (value < minimumAmount || value > maximumAmount) { + continue; + } + + DSUTXO utxo = ((DSUTXO){wtxid, i}); + + if (coinControl != nil && coinControl.hasSelected && !coinControl.allowOtherInputs && ![coinControl isSelected:utxo]) { + continue; + } + if (dash_spv_coinjoin_wallet_ex_WalletEx_is_locked_coin(walletEx, DOutPointCtorU(wtxid, i)) && + coinType != dash_spv_coinjoin_models_coin_control_CoinType_OnlyMasternodeCollateral) { + continue; + } + + if ([account isSpent:dsutxo_obj(utxo)]) { + continue; + } + + if (output.address == nil || ![account containsAddress:output.address]) { + continue; + } + + if (!allowUsedAddresses && [account transactionAddressAlreadySeenInOutputs:output.address]) { + continue; + } + + [vCoins addObject:[[DSInputCoin alloc] initWithTx:coin index:i]]; + + // Checks the sum amount of all UTXO's. + if (minimumSumAmount != MAX_MONEY) { + total += value; + + if (total >= minimumSumAmount) { + return vCoins; + } + } + + // Checks the maximum number of UTXO's. + if (maximumCount > 0 && vCoins.count >= maximumCount) { + return vCoins; + } + } + } + } + + return vCoins; +} + +- (double)getMixingProgress { + if (![self.wrapper isRegistered]) { + [self.wrapper registerCoinJoin:self.options]; + } + + double requiredRounds = self.options->coinjoin_rounds + 0.875; // 1 x 50% + 1 x 50%^2 + 1 x 50%^3 + __block int totalInputs = 0; + __block int totalRounds = 0; + + NSDictionary *> *outputs = [self getOutputs]; + uint64_t collateralAmount = [self.wrapper getCollateralAmount]; + NSArray *denominations = [self.wrapper getStandardDenominations]; + + [outputs enumerateKeysAndObjectsUsingBlock:^(NSNumber *denom, NSArray *outputs, BOOL *stop) { + [outputs enumerateObjectsUsingBlock:^(NSValue *output, NSUInteger idx, BOOL *stop) { + DSUTXO outpoint; + [output getValue:&outpoint]; + + if (denom.intValue >= 0) { + int rounds = [self.wrapper getRealOutpointCoinJoinRounds:outpoint]; + + if (rounds >= 0) { + totalInputs += 1; + totalRounds += rounds; + } + } else if (denom.intValue == -2) { + DSTransaction *tx = [self.chain transactionForHash:outpoint.hash]; + DSTransactionOutput *output = tx.outputs[outpoint.n]; + + __block int unmixedInputs = 0; + __block int64_t outputValue = output.amount - collateralAmount; + + [denominations enumerateObjectsUsingBlock:^(NSNumber *coin, NSUInteger idx, BOOL *stop) { + while (outputValue - coin.longLongValue > 0) { + unmixedInputs++; + outputValue -= coin.longLongValue; + } + }]; + + totalInputs += unmixedInputs; + } + }]; + }]; + + double progress = totalInputs != 0 ? (double)totalRounds / (requiredRounds * totalInputs) : 0.0; + + if (self.lastReportedProgress != progress) { + _lastReportedProgress = progress; + DSLog(@"[%@] CoinJoin: getMixingProgress: %f = %d / (%f * %d)", self.chain.name, progress, totalRounds, requiredRounds, totalInputs); + } + + return fmax(0.0, fmin(progress, 1.0)); +} + +- (NSDictionary *> *)getOutputs { + NSMutableDictionary *> *outputs = [NSMutableDictionary dictionary]; + + for (NSNumber *amount in [self.wrapper getStandardDenominations]) { + outputs[@([self.wrapper amountToDenomination:amount.unsignedLongLongValue])] = [NSMutableArray array]; + } + + outputs[@(-2)] = [NSMutableArray array]; + outputs[@(0)] = [NSMutableArray array]; + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + NSArray *utxos = account.unspentOutputs; + DSUTXO outpoint; + + for (NSValue *value in utxos) { + [value getValue:&outpoint]; + + DSTransaction *tx = [self.chain transactionForHash:outpoint.hash]; + DSTransactionOutput *output = tx.outputs[outpoint.n]; + NSString *address = [DSKeyManager addressWithScriptPubKey:output.outScript forChain:self.chain]; + + if ([account containsCoinJoinAddress:address]) { + int denom = [self.wrapper amountToDenomination:output.amount]; + NSMutableArray *listDenoms = outputs[@(denom)]; + [listDenoms addObject:value]; + } else { + // non-denominated and non-collateral coins + [outputs[@(-2)] addObject:value]; + } + } + + return outputs; +} + +- (BOOL)isCoinJoinOutput:(DSTransactionOutput *)output utxo:(DSUTXO)utxo { + if (![self.wrapper isDenominatedAmount:output.amount]) { + return false; + } + + if (![self.wrapper isFullyMixed:utxo]) { + return false; + } + + return [self.chain.wallets.firstObject.accounts.firstObject.coinJoinDerivationPath containsAddress:output.address]; +} + +- (DSCoinJoinBalance *)getBalance { + if (![self.wrapper isRegistered]) { + [self.wrapper registerCoinJoin:self.options]; + } + + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + uint64_t anonymizedBalance = 0; + uint64_t denominatedBalance = 0; + DSUTXO outpoint; + NSArray *utxos = account.unspentOutputs; + + for (NSValue *value in utxos) { + [value getValue:&outpoint]; + DSTransaction *tx = [account transactionForHash:outpoint.hash]; + DSTransactionOutput *output = tx.outputs[outpoint.n]; + + if ([self isCoinJoinOutput:output utxo:outpoint]) { + anonymizedBalance += output.amount; + } + + if ([self.wrapper isDenominatedAmount:output.amount]) { + denominatedBalance += output.amount; + } + } + + // TODO(DashJ): support more balance types? + DSCoinJoinBalance *balance = + [DSCoinJoinBalance balanceWithMyTrusted:self.chain.balance + denominatedTrusted:denominatedBalance + anonymized:anonymizedBalance + myImmature:0 + myUntrustedPending:0 + denominatedUntrustedPending:0 + watchOnlyTrusted:0 + watchOnlyUntrustedPending:0 + watchOnlyImmature:0]; + + return balance; +} + +- (NSSet *)getSpendableTXs { + NSMutableSet *ret = [[NSMutableSet alloc] init]; + NSArray *unspent = self.chain.wallets.firstObject.unspentOutputs; + + DSUTXO outpoint; + for (uint32_t i = 0; i < unspent.count; i++) { + [unspent[i] getValue:&outpoint]; + DSTransaction *tx = [self.chain transactionForHash:outpoint.hash]; + + if (tx) { + [ret addObject:tx]; + + // Skip entries until we encounter a new TX + DSUTXO nextOutpoint; + while (i + 1 < unspent.count) { + [unspent[i + 1] getValue:&nextOutpoint]; + + if (!uint256_eq(nextOutpoint.hash, outpoint.hash)) { + break; + } + i++; + } + } + } + + return ret; +} + +- (NSString *)freshAddress:(BOOL)internal { + NSString *address; + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + + if (internal) { + address = account.coinJoinChangeAddress; + } else { + address = account.coinJoinReceiveAddress; + } + + return address; +} + +- (NSArray *)getIssuedReceiveAddresses { + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + return account.allCoinJoinReceiveAddresses; +} + +- (NSArray *)getUsedReceiveAddresses { + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + return account.usedCoinJoinReceiveAddresses; +} + +- (BOOL)commitTransactionForAmounts:(NSArray *)amounts outputs:(NSArray *)outputs coinControl:(DSCoinControl *)coinControl onPublished:(void (^)(UInt256 txId, NSError * _Nullable error))onPublished { + DSAccount *account = self.chain.wallets.firstObject.accounts.firstObject; + DSTransaction *transaction = [account transactionForAmounts:amounts toOutputScripts:outputs withFee:YES coinControl:coinControl]; + + if (!transaction) { + return NO; + } + + BOOL signedTransaction = [account signTransaction:transaction]; + + if (!signedTransaction || !transaction.isSigned) { + DSLog(@"[%@] CoinJoin error: not signed", self.chain.name); + return NO; + } else { + [self.chain.chainManager.transactionManager publishTransaction:transaction completion:^(NSError *error) { + NSString *txDescription = @""; + #if DEBUG + txDescription = transaction.description; + #endif + + if (error) { + DSLog(@"[%@] CoinJoin publish error: %@ for tx: %@", self.chain.name, error.description, txDescription); + } else { + DSLog(@"[%@] CoinJoin publish success: %@", self.chain.name, txDescription); + } + + dispatch_async(self.processingQueue, ^{ + onPublished(transaction.txHash, error); + }); + }]; + } + + return YES; +} + +- (DMasternodeEntry *)masternodeEntryByHash:(UInt256)hash { + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_current_masternode_list_masternode_with_pro_reg_tx_hash(self.chain.sharedProcessorObj, dashcore_hash_types_ProTxHash_ctor(u256_ctor_u(uint256_reverse(hash)))); +} + +- (uintptr_t)validMNCount { + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_valid_masternodes_count(self.chain.sharedProcessorObj); +} + +- (DMasternodeList *)mnList { + return self.chain.chainManager.masternodeManager.currentMasternodeList; +} + +- (BOOL)isMasternodeOrDisconnectRequested:(UInt128)ip port:(uint16_t)port { + return [self.masternodeGroup isMasternodeOrDisconnectRequested:ip port:port]; +} + +- (BOOL)disconnectMasternode:(UInt128)ip port:(uint16_t)port { + return [self.masternodeGroup disconnectMasternode:ip port:port]; +} + +- (BOOL)sendMessageOfType:(NSString *)messageType message:(NSData *)message withPeerIP:(UInt128)address port:(uint16_t)port warn:(BOOL)warn { + return [self.masternodeGroup forPeer:address port:port warn:warn withPredicate:^BOOL(DSPeer * _Nonnull peer) { + if ([messageType isEqualToString:DSCoinJoinAcceptMessage.type]) { + DSCoinJoinAcceptMessage *request = [DSCoinJoinAcceptMessage requestWithData:message]; + [peer sendRequest:request]; + } else if ([messageType isEqualToString:DSCoinJoinEntryMessage.type]) { + DSCoinJoinEntryMessage *request = [DSCoinJoinEntryMessage requestWithData:message]; + [peer sendRequest:request]; + } else if ([messageType isEqualToString:DSCoinJoinSignedInputs.type]) { + DSCoinJoinSignedInputs *request = [DSCoinJoinSignedInputs requestWithData:message]; + [peer sendRequest:request]; + } else { + DSLog(@"[%@] CoinJoin: unknown message type: %@", self.chain.name, messageType); + return NO; + } + + return YES; + }]; +} + +- (BOOL)addPendingMasternode:(UInt256)proTxHash clientSessionId:(UInt256)sessionId { + return [self.masternodeGroup addPendingMasternode:proTxHash clientSessionId:sessionId]; +} + +- (void)updateSuccessBlock { + self.cachedLastSuccessBlock = self.cachedBlockHeight; +} + +- (BOOL)isWaitingForNewBlock { + if (!self.isChainSynced) { + return true; + } + + if (self.options->coinjoin_multi_session == true) { + return false; + } + + return self.cachedBlockHeight - self.cachedLastSuccessBlock < MIN_BLOCKS_TO_WAIT; +} + +- (DCoinJoinTransactionType *)coinJoinTxTypeForTransaction:(DSTransaction *)transaction { + return [DSCoinJoinWrapper coinJoinTxTypeForTransaction:transaction]; +} + +- (void)calculateAnonymizableBalanceWithSkipDenominated:(BOOL)skipDenominated skipUnconfirmed:(BOOL)skipUnconfirmed completion:(void (^)(uint64_t balance))completion { + dispatch_async(self.processingQueue, ^{ + uint64_t balance = [self.wrapper getAnonymizableBalance:skipDenominated skipUnconfirmed:skipUnconfirmed]; + completion(balance); + }); +} + +- (void)minimumAnonymizableBalanceWithCompletion:(void (^)(uint64_t balance))completion { + dispatch_async(self.processingQueue, ^{ + uint64_t valueMin = [self.wrapper getSmallestDenomination]; + BOOL hasCollateralInputs = [self.wrapper hasCollateralInputs:YES]; + + if (hasCollateralInputs) { + valueMin += [self.wrapper getMaxCollateralAmount]; + } + + completion(valueMin); + }); +} + +- (uint64_t)getSmallestDenomination { + return [self.wrapper getSmallestDenomination]; +} + +- (DSCoinControl *)selectCoinJoinUTXOs { + DSCoinControl *coinControl = [[DSCoinControl alloc] init]; + [coinControl useCoinJoin:YES]; + NSArray *utxos = self.chain.wallets.firstObject.unspentOutputs; + + for (NSValue *value in utxos) { + DSUTXO utxo; + [value getValue:&utxo]; + + DSTransaction *tx = [self.chain transactionForHash:utxo.hash]; + if (!tx) continue; + + DSTransactionOutput *output = tx.outputs[utxo.n]; + if (!output) continue; + + if ([self isCoinJoinOutput:output utxo:utxo] && ![self.wrapper isLockedCoin:utxo]) { + [coinControl select:utxo]; + } + } + + return coinControl; +} + +- (void)printUsedKeys { + dispatch_async(self.processingQueue, ^{ + NSArray *issuedAddresses = [self getIssuedReceiveAddresses]; + NSArray *usedAddresses = [self getUsedReceiveAddresses]; + double percent = (double)usedAddresses.count * 100.0 / (double)issuedAddresses.count; + DSLog(@"[%@] CoinJoin init. Used addresses count %lu out of %lu (%.2f %%)", self.chain.name, (unsigned long)usedAddresses.count, (unsigned long)issuedAddresses.count, percent); + }); +} + +// Events + +- (void)onSessionStarted:(int32_t)baseId + clientSessionId:(UInt256)clientId + denomination:(uint32_t)denom + poolState:(DPoolState)state + poolMessage:(DPoolMessage)message + poolStatus:(DPoolStatus)status + ipAddress:(UInt128)address + isJoined:(BOOL)joined { + DSLog(@"[%@] CoinJoin: onSessionStarted: baseId: %d, clientId: %@, denom: %d, state: %d, message: %d, address: %@, isJoined: %s", self.chain.name, baseId, [uint256_hex(clientId) substringToIndex:7], denom, state, message, [self.masternodeGroup hostFor:address], joined ? "yes" : "no"); + [self.managerDelegate sessionStartedWithId:baseId + clientSessionId:clientId + denomination:denom + poolState:state + poolMessage:message + poolStatus:status + ipAddress:address + isJoined:joined]; +} + +- (void)onSessionComplete:(int32_t)baseId + clientSessionId:(UInt256)clientId + denomination:(uint32_t)denom + poolState:(DPoolState)state + poolMessage:(DPoolMessage)message + poolStatus:(DPoolStatus)status + ipAddress:(UInt128)address + isJoined:(BOOL)joined { + DSLog(@"[%@] CoinJoin: onSessionComplete: baseId: %d, clientId: %@, denom: %d, state: %d, status: %d, message: %d, address: %@, isJoined: %s", self.chain.name, baseId, [uint256_hex(clientId) substringToIndex:7], denom, state, status, message, [self.masternodeGroup hostFor:address], joined ? "yes" : "no"); + [self.managerDelegate sessionCompleteWithId:baseId + clientSessionId:clientId + denomination:denom + poolState:state + poolMessage:message + poolStatus:status + ipAddress:address + isJoined:joined]; +} + +- (void)onMixingStarted:(nonnull NSArray *)statuses { + DSLog(@"[%@] CoinJoin: onMixingStarted, statuses: %@", self.chain.name, statuses.count > 0 ? [NSString stringWithFormat:@"%@", statuses] : @"empty"); + [self.managerDelegate mixingStarted]; +} + +- (void)onMixingComplete:(nonnull NSArray *)statuses isInterrupted:(BOOL)isInterrupted { + if (self.isShuttingDown) { + [self stop]; + } + + DPoolStatus *returnStatus = NULL; + BOOL isError = YES; + + for (NSNumber *statusNumber in statuses) { + DPoolStatus *status = DPoolStatusFromIndex(statusNumber.intValue); + if (![self isError:status]) { + returnStatus = status; + isError = NO; + break; + } + + if (!dash_spv_coinjoin_messages_pool_status_PoolStatus_is_err_not_enough_funds(status)) { + returnStatus = status; + } + } + + [self.managerDelegate mixingComplete:isError errorStatus:returnStatus isInterrupted:isInterrupted]; +} + +- (void)onTransactionProcessed:(UInt256)txId type:(DCoinJoinTransactionType *)type { +#if DEBUG + DSLog(@"[%@] CoinJoin: onTransactionProcessed: %@, type: %d", self.chain.name, uint256_reverse_hex(txId), DCoinJoinTransactionTypeIndex(type)); +#else + DSLog(@"[%@] CoinJoin: onTransactionProcessed: %@, type: %d", self.chain.name, @"", type); +#endif + [self.managerDelegate transactionProcessedWithId:txId type:type]; +} + +- (BOOL)isError:(DPoolStatus *)status { + return (DPoolStatusValue(status) & 0x2000) != 0; +} + +@end diff --git a/DashSync/shared/Models/CoinJoin/DSCoinJoinWrapper.h b/DashSync/shared/Models/CoinJoin/DSCoinJoinWrapper.h new file mode 100644 index 000000000..f818167d1 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinJoinWrapper.h @@ -0,0 +1,96 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSMasternodeGroup.h" +#import "DSChainManager.h" + +NS_ASSUME_NONNULL_BEGIN + +#define DCoinType dash_spv_coinjoin_models_coin_control_CoinType +#define DCoinTypeIndex(ptr) dash_spv_coinjoin_models_coin_control_CoinType_index(ptr) +#define DCoinControl dash_spv_coinjoin_models_coin_control_CoinControl +#define DCoinControlDtor(ptr) dash_spv_coinjoin_models_coin_control_CoinControl_destroy(ptr) +#define DCompactTallyItem dash_spv_coinjoin_coin_selection_compact_tally_item_CompactTallyItem +#define DCompactTallyItemCtor(dest, amout, coins) dash_spv_coinjoin_coin_selection_compact_tally_item_CompactTallyItem_ctor(dest, amout, coins) +#define DCompactTallyItemDtor(ptr) dash_spv_coinjoin_coin_selection_compact_tally_item_CompactTallyItem_destroy(ptr) +#define DCompactTallyItems Vec_dash_spv_coinjoin_coin_selection_compact_tally_item_CompactTallyItem +#define DCompactTallyItemsCtor(count, values) Vec_dash_spv_coinjoin_coin_selection_compact_tally_item_CompactTallyItem_ctor(count, values) +#define DPoolState dash_spv_coinjoin_messages_pool_state_PoolState +#define DPoolStateValue(ptr) dash_spv_coinjoin_messages_pool_state_PoolState_value(ptr) +#define DPoolStateDtor(ptr) dash_spv_coinjoin_messages_pool_state_PoolState_destroy(ptr) +#define DPoolMessage dash_spv_coinjoin_messages_pool_message_PoolMessage +#define DPoolMessageValue(ptr) dash_spv_coinjoin_messages_pool_message_PoolMessage_value(ptr) +#define DPoolMessageDtor(ptr) dash_spv_coinjoin_messages_pool_message_PoolMessage_destroy(ptr) +#define DPoolStatus dash_spv_coinjoin_messages_pool_status_PoolStatus +#define DPoolStatusValue(ptr) dash_spv_coinjoin_messages_pool_status_PoolStatus_value(ptr) +#define DPoolStatusDtor(ptr) dash_spv_coinjoin_messages_pool_status_PoolStatus_destroy(ptr) +#define DPoolStatusFromIndex(index) dash_spv_coinjoin_messages_pool_status_PoolStatus_from_index(index) +#define DCoinJoinTransactionType dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType +#define DCoinJoinTransactionTypeIndex(ptr) dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_index(ptr) +#define DCoinJoinClientOptions dash_spv_coinjoin_models_coinjoin_client_options_CoinJoinClientOptions +#define DInputCoin dash_spv_coinjoin_coin_selection_input_coin_InputCoin +#define DInputCoinCtor(outpoint, out, value) dash_spv_coinjoin_coin_selection_input_coin_InputCoin_ctor(outpoint, out, value) +#define DInputCoinDtor(ptr) dash_spv_coinjoin_coin_selection_input_coin_InputCoin_destroy(ptr) +#define DInputCoins Vec_dash_spv_coinjoin_coin_selection_input_coin_InputCoin +#define DInputCoinsCtor(count, values) Vec_dash_spv_coinjoin_coin_selection_input_coin_InputCoin_ctor(count, values) +#define DPoolStatuses Vec_dash_spv_coinjoin_messages_pool_status_PoolStatus +#define DPoolStatusesDtor(ptr) Vec_dash_spv_coinjoin_messages_pool_status_PoolStatus_destroy(ptr) + +@class DSCoinJoinManager; + +@interface DSCoinJoinWrapper : NSObject + +@property (nonatomic, strong, nullable) DSChainManager *chainManager; +@property (nonatomic, strong) DSChain *chain; +@property (nonatomic, weak, nullable) DSCoinJoinManager *manager; +@property (nonatomic, assign, nullable) CoinJoinClientManager *clientManager; + +- (instancetype)initWithManagers:(DSCoinJoinManager *)manager chainManager:(DSChainManager *)chainManager; +- (void)processDSQueueFrom:(DSPeer *)peer message:(NSData *)message; +- (void)processMessageFrom:(DSPeer *)peer message:(NSData *)message type:(NSString *)type; +- (void)notifyNewBestBlock:(DSBlock *)block; +- (void)setStopOnNothingToDo:(BOOL)stop; +- (BOOL)startMixing; +- (BOOL)doAutomaticDenominatingWithDryRun:(BOOL)dryRun; +- (void)doMaintenance; +- (void)registerCoinJoin:(DCoinJoinClientOptions *)options; +- (BOOL)isRegistered; +- (BOOL)isMixingFeeTx:(UInt256)txId; +- (void)refreshUnusedKeys; +- (BOOL)isDenominatedAmount:(uint64_t)amount; +- (BOOL)isFullyMixed:(DSUTXO)utxo; +- (void)initiateShutdown; ++ (DCoinJoinTransactionType *)coinJoinTxTypeForTransaction:(DSTransaction *)transaction; ++ (DCoinJoinTransactionType *)coinJoinTxTypeForTransaction:(DSTransaction *)transaction account:(DSAccount *)account; +- (void)unlockOutputs:(DSTransaction *)transaction; +- (uint64_t)getAnonymizableBalance:(BOOL)skipDenominated skipUnconfirmed:(BOOL)skipUnconfirmed; +- (uint64_t)getSmallestDenomination; +- (void)updateOptions:(DCoinJoinClientOptions *)options; +- (NSArray *)getStandardDenominations; +- (uint64_t)getCollateralAmount; +- (uint64_t)getMaxCollateralAmount; +- (BOOL)hasCollateralInputs:(BOOL)onlyConfirmed; +- (uint32_t)amountToDenomination:(uint64_t)amount; +- (int32_t)getRealOutpointCoinJoinRounds:(DSUTXO)utxo; +- (BOOL)isLockedCoin:(DSUTXO)utxo; +- (void)stopAndResetClientManager; +- (NSArray *)getSessionStatuses; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/DSCoinJoinWrapper.m b/DashSync/shared/Models/CoinJoin/DSCoinJoinWrapper.m new file mode 100644 index 000000000..db67ef88d --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCoinJoinWrapper.m @@ -0,0 +1,680 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCoinJoinManager.h" +#import "DSWallet.h" +#import "DSTransaction+CoinJoin.h" +#import "DSAccount.h" +#import "DSChain+Params.h" +#import "DSChain+Transaction.h" +#import "DSChain+Wallet.h" +#import "DSChainManager.h" +#import "DSCoinJoinWrapper.h" +#import "DSBlock.h" +#import "NSArray+Dash.h" + +#define AS_OBJC(context) ((__bridge DSCoinJoinWrapper *)(context)) +#define AS_RUST(context) ((__bridge void *)(context)) + +#define DGetWalletTransaction Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_Option_dashcore_blockdata_transaction_Transaction +#define DSignTransaction Fn_ARGS_std_os_raw_c_void_dashcore_blockdata_transaction_Transaction_bool_RTRN_Option_dashcore_blockdata_transaction_Transaction +#define DIsMineInput Fn_ARGS_std_os_raw_c_void_dashcore_blockdata_transaction_outpoint_OutPoint_RTRN_bool +#define DSelectCoins Fn_ARGS_std_os_raw_c_void_bool_bool_bool_i32_dash_spv_coinjoin_wallet_ex_WalletEx_RTRN_Vec_dash_spv_coinjoin_coin_selection_compact_tally_item_CompactTallyItem +#define DInputsWithAmount Fn_ARGS_std_os_raw_c_void_u64_RTRN_u32 +#define DFreshCoinjoinAddress Fn_ARGS_std_os_raw_c_void_bool_RTRN_String +#define DAvailableCoins Fn_ARGS_std_os_raw_c_void_bool_dash_spv_coinjoin_models_coin_control_CoinControl_dash_spv_coinjoin_wallet_ex_WalletEx_RTRN_Vec_dash_spv_coinjoin_coin_selection_input_coin_InputCoin +#define DCommitTransaction Fn_ARGS_std_os_raw_c_void_Vec_dashcore_blockdata_transaction_txout_TxOut_dash_spv_coinjoin_models_coin_control_CoinControl_bool_Arr_u8_32_RTRN_bool +#define DSessionLifecycle Fn_ARGS_std_os_raw_c_void_bool_i32_Arr_u8_32_u32_dash_spv_coinjoin_messages_pool_state_PoolState_dash_spv_coinjoin_messages_pool_message_PoolMessage_dash_spv_coinjoin_messages_pool_status_PoolStatus_Option_std_net_SocketAddr_bool_RTRN_ +#define DMixingLifecycle Fn_ARGS_std_os_raw_c_void_bool_bool_Vec_dash_spv_coinjoin_messages_pool_status_PoolStatus_RTRN_ +#define DMasternodeByHash Fn_ARGS_std_os_raw_c_void_Arr_u8_32_RTRN_Option_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry + +@implementation DSCoinJoinWrapper + +- (instancetype)initWithManagers:(DSCoinJoinManager *)manager chainManager:(DSChainManager *)chainManager { + self = [super init]; + if (self) { + _chainManager = chainManager; + _manager = manager; + } + return self; +} + +- (void)registerCoinJoin:(DCoinJoinClientOptions *)options { + + @synchronized (self) { + if (_clientManager == NULL) { + DGetWalletTransaction get_wallet_transaction = { + .caller = &getTransaction, + }; + DSignTransaction sign_transaction = { + .caller = &signTransaction + }; + DIsMineInput is_mine_input = { + .caller = &isMineInput + }; + DSelectCoins select_coins = { + .caller = &selectCoinsGroupedByAddresses + }; + DInputsWithAmount inputs_with_amount = { + .caller = &countInputsWithAmount + }; + DFreshCoinjoinAddress fresh_coinjoin_address = { + .caller = &freshCoinJoinAddress + }; + DCommitTransaction commit_transaction = { + .caller = &commitTransaction + }; + Fn_ARGS_std_os_raw_c_void_RTRN_bool is_synced = { + .caller = &isBlockchainSynced + }; + Fn_ARGS_std_os_raw_c_void_std_net_SocketAddr_RTRN_bool is_masternode_or_disconnect_requested = { + .caller = &isMasternodeOrDisconnectRequested + }; + Fn_ARGS_std_os_raw_c_void_std_net_SocketAddr_RTRN_bool disconnect_masternode = { + .caller = &disconnectMasternode + }; + Fn_ARGS_std_os_raw_c_void_String_Vec_u8_std_net_SocketAddr_bool_RTRN_bool send_message = { + .caller = &sendMessage + }; + Fn_ARGS_std_os_raw_c_void_Arr_u8_32_Arr_u8_32_RTRN_bool add_pending_masternode = { + .caller = &addPendingMasternode + }; + Fn_ARGS_std_os_raw_c_void_RTRN_ start_manager_async = { + .caller = &startManagerAsync + }; + Fn_ARGS_std_os_raw_c_void_bool_RTRN_Vec_String get_coinjoin_keys = { + .caller = &getCoinJoinKeys + }; + Fn_ARGS_std_os_raw_c_void_Arr_u8_32_u32_RTRN_i64 get_input_value_by_prev_outpoint = { + .caller = &getInputValueByPrevoutHash + }; + Fn_ARGS_std_os_raw_c_void_u32_RTRN_bool has_chain_lock = { + .caller = &hasChainLock + }; + Fn_ARGS_std_os_raw_c_void_RTRN_dashcore_sml_masternode_list_MasternodeList get_masternode_list = { + .caller = &getMNList + }; + Fn_ARGS_std_os_raw_c_void_RTRN_ update_success_block = { + .caller = &updateSuccessBlock + }; + Fn_ARGS_std_os_raw_c_void_RTRN_bool is_waiting_for_new_block = { + .caller = &isWaitingForNewBlock + }; + DSessionLifecycle session_lifecycle_listener = { + .caller = &sessionLifecycleListener + }; + DMixingLifecycle mixing_lifecycle_listener = { + .caller = &mixingLifecycleListener + }; + DMasternodeByHash masternode_by_hash = { + .caller = &masternodeByHash + }; + Fn_ARGS_std_os_raw_c_void_RTRN_usize valid_mns_count = { + .caller = &validMNCount + }; + DAvailableCoins available_coins = { + .caller = &availableCoins + }; + WalletEx *wallet_ex = dash_spv_coinjoin_wallet_ex_WalletEx_new(AS_RUST(self), options, get_wallet_transaction, sign_transaction, is_mine_input, available_coins, select_coins, inputs_with_amount, fresh_coinjoin_address, commit_transaction, is_synced, is_masternode_or_disconnect_requested, disconnect_masternode, send_message, add_pending_masternode, start_manager_async, get_coinjoin_keys); + CoinJoin *coinjoin = dash_spv_coinjoin_coinjoin_CoinJoin_new(get_input_value_by_prev_outpoint, has_chain_lock, AS_RUST(self)); + _clientManager = dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_new(wallet_ex, coinjoin, options, get_masternode_list, update_success_block, is_waiting_for_new_block, session_lifecycle_listener, mixing_lifecycle_listener, masternode_by_hash, valid_mns_count, AS_RUST(self)); + } + } +} + +- (BOOL)isRegistered { + return self.clientManager != NULL; +} + +- (void)updateOptions:(DCoinJoinClientOptions *)options { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_change_options(self.clientManager, options); + } +} + +- (void)setStopOnNothingToDo:(BOOL)stop { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_set_stop_on_nothing_to_do(self.clientManager, stop); + } +} + +- (BOOL)startMixing { + @synchronized (self) { + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_start_mixing(self.clientManager); + } +} + +- (void)refreshUnusedKeys { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_refresh_unused_keys(self.clientManager); + } +} + +- (BOOL)doAutomaticDenominatingWithDryRun:(BOOL)dryRun { + @synchronized (self) { + DBalance *balance = [DSCoinJoinBalance ffi_to:[self.manager getBalance]]; + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_do_automatic_denominating(self.clientManager, balance, dryRun); + } +} + +- (void)doMaintenance { + @synchronized (self) { + DBalance *balance = [DSCoinJoinBalance ffi_to:[self.manager getBalance]]; + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_do_maintenance(self.clientManager, balance); + } +} + +- (void)initiateShutdown { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_initiate_shutdown(self.clientManager); + } +} + +- (BOOL)isDenominatedAmount:(uint64_t)amount { + return dash_spv_coinjoin_coinjoin_CoinJoin_is_denominated_amount(amount); +} + +- (BOOL)isFullyMixed:(DSUTXO)utxo { + @synchronized (self) { + DOutPoint *outpoint = DOutPointFromUTXO(utxo); + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_check_if_is_fully_mixed(self.clientManager, outpoint); + } +} + +- (void)processDSQueueFrom:(DSPeer *)peer message:(NSData *)message { + @synchronized (self) { + SocketAddr *addr = DSocketAddrFrom(u128_ctor_u(peer.address), peer.port); + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_process_ds_queue(self.clientManager, addr, slice_ctor(message)); + } +} + +- (void)processMessageFrom:(DSPeer *)peer message:(NSData *)message type:(NSString *)type { + @synchronized (self) { + SocketAddr *addr = DSocketAddrFrom(u128_ctor_u(peer.address), peer.port); + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_process_raw_message(self.clientManager, addr, slice_ctor(message), DChar(type)); + } +} + +- (void)notifyNewBestBlock:(DSBlock *)block { + if (block) { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_update_block_tip(self.clientManager, block.height); + } + } +} + +- (BOOL)isMixingFeeTx:(UInt256)txId { + @synchronized (self) { + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_is_mixing_fee_tx(self.clientManager, DTxidCtor(u256_ctor_u(txId))); + } +} + ++ (DCoinJoinTransactionType *)coinJoinTxTypeForTransaction:(DSTransaction *)transaction { + DSAccount *account = [transaction.chain firstAccountThatCanContainTransaction:transaction]; + return [DSCoinJoinWrapper coinJoinTxTypeForTransaction:transaction account:account]; +} + ++ (DCoinJoinTransactionType *)coinJoinTxTypeForTransaction:(DSTransaction *)transaction account:(DSAccount *)account { + NSArray *amountsSent = [account amountsSentByTransaction:transaction]; + DTransaction *tx = [transaction ffi_malloc:transaction.chain.chainType]; + uint64_t *inputValues = malloc(amountsSent.count * sizeof(uint64_t)); + for (uintptr_t i = 0; i < amountsSent.count; i++) { + inputValues[i] = [amountsSent[i] unsignedLongLongValue]; + } + return dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_from_tx(tx, Vec_u64_ctor(amountsSent.count, inputValues)); +} + +- (void)unlockOutputs:(DSTransaction *)transaction { + @synchronized (self) { + DTransaction *tx = [transaction ffi_malloc:transaction.chain.chainType]; + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_unlock_outputs(self.clientManager, tx); + } +} + +- (uint64_t)getAnonymizableBalance:(BOOL)skipDenominated skipUnconfirmed:(BOOL)skipUnconfirmed { + @synchronized (self) { + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_get_anonymizable_balance(self.clientManager, skipDenominated, skipUnconfirmed); + } +} + +- (uint64_t)getSmallestDenomination { + return dash_spv_coinjoin_coinjoin_CoinJoin_get_smallest_denomination(); +} + +- (NSArray *)getStandardDenominations { + @synchronized (self) { + Arr_u64_5 *denominations = dash_spv_coinjoin_coinjoin_CoinJoin_get_standard_denominations(); + NSMutableArray *result = [NSMutableArray arrayWithCapacity:denominations->count]; + for (size_t i = 0; i < denominations->count; i++) { + [result addObject:@(denominations->values[i])]; + } + Arr_u64_5_destroy(denominations); + return result; + } +} + +- (uint64_t)getCollateralAmount { + return dash_spv_coinjoin_coinjoin_CoinJoin_get_collateral_amount(); +} + +- (uint64_t)getMaxCollateralAmount { + return dash_spv_coinjoin_coinjoin_CoinJoin_get_max_collateral_amount(); +} + +- (BOOL)hasCollateralInputs:(BOOL)onlyConfirmed { + @synchronized (self) { + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_has_collateral_inputs(_clientManager, onlyConfirmed); + } +} + +- (uint32_t)amountToDenomination:(uint64_t)amount { + return dash_spv_coinjoin_coinjoin_CoinJoin_amount_to_denomination(amount); +} + +- (int32_t)getRealOutpointCoinJoinRounds:(DSUTXO)utxo { + @synchronized (self) { + DOutPoint *outpoint = DOutPointFromUTXO(utxo); + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_get_real_outpoint_coinjoin_rounds(_clientManager, outpoint, 0); + } +} + +- (NSArray *)getSessionStatuses { + @synchronized (self) { + DPoolStatuses *statuses = dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_get_sessions_status(_clientManager); + NSMutableArray *statusArray = [NSMutableArray arrayWithCapacity:statuses->count]; + for (int i = 0; i < statuses->count; i++) { + [statusArray addObject:@(DPoolStatusValue(statuses->values[i]))]; + } + DPoolStatusesDtor(statuses); + return statusArray; + } +} + +- (BOOL)isLockedCoin:(DSUTXO)utxo { + @synchronized (self) { + DOutPoint *outpoint = DOutPointFromUTXO(utxo); + return dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_is_locked_coin(self.clientManager, outpoint); + } +} + +- (void)stopAndResetClientManager { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_stop_and_reset(self.clientManager); + } +} + +- (DSChain *)chain { + return self.chainManager.chain; +} + +- (void)dealloc { + @synchronized (self) { + dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_destroy(self.clientManager); + _clientManager = NULL; + } +} + + +/// +/// MARK: Rust FFI callbacks +/// + +int64_t getInputValueByPrevoutHash(const void *context, u256 *tx_hash, uint32_t index) { + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + DSWallet *wallet = [wrapper.chain.wallets firstObject]; + UInt256 txHash = u256_cast(tx_hash); + u256_dtor(tx_hash); + return [wallet inputValue:txHash inputIndex:index]; + } +} + + +bool hasChainLock(const void *context, uint32_t block_height) { + BOOL hasChainLock = NO; + + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + hasChainLock = [wrapper.chain blockHeightChainLocked:block_height]; + } + + return hasChainLock; +} + +DTransaction *getTransaction(const void *context, u256 *tx_hash) { + // TODO: check if reversed + UInt256 txHash = u256_cast(tx_hash); + u256_dtor(tx_hash); + DTransaction *tx = NULL; + + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + DSTransaction *transaction = [wrapper.chain transactionForHash:txHash]; + if (transaction) + tx = [transaction ffi_malloc:wrapper.chain.chainType]; + } + + return tx; +} + +bool isMineInput(const void *context, DOutPoint *outpoint) { + UInt256 txHash = u256_cast(dashcore_hash_types_Txid_inner(outpoint->txid)); + uint32_t index = outpoint->vout; + BOOL result = NO; + DOutPointDtor(outpoint); + @synchronized (context) { + result = [AS_OBJC(context).manager isMineInput:txHash index:index]; + } + + return result; +} + +DInputCoins* availableCoins(const void *context, bool onlySafe, DCoinControl *coinControl, WalletEx *walletEx) { + DInputCoins *gatheredOutputs; + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + DChainType *chainType = wrapper.chain.chainType; + DSCoinControl *cc = [[DSCoinControl alloc] initWithFFICoinControl:coinControl chainType:wrapper.chain.chainType]; + NSArray *coins = [wrapper.manager availableCoins:walletEx + onlySafe:onlySafe + coinControl:cc + minimumAmount:1 + maximumAmount:MAX_MONEY + minimumSumAmount:MAX_MONEY + maximumCount:0]; + DCoinControlDtor(coinControl); + NSUInteger count = coins.count; + DInputCoin **values = malloc(count * sizeof(DInputCoin *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = [coins[i] ffi_malloc:chainType]; + } + gatheredOutputs = DInputCoinsCtor(count, values); + } + + return gatheredOutputs; +} + +DCompactTallyItems* selectCoinsGroupedByAddresses(const void *context, + bool skipDenominated, + bool anonymizable, + bool skipUnconfirmed, + int maxOupointsPerAddress, + WalletEx* walletEx) { + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + NSArray *tempVecTallyRet = [wrapper.manager selectCoinsGroupedByAddresses:walletEx + skipDenominated:skipDenominated + anonymizable:anonymizable + skipUnconfirmed:skipUnconfirmed + maxOupointsPerAddress:maxOupointsPerAddress]; + + NSUInteger count = tempVecTallyRet.count; + DCompactTallyItem **values = malloc(count * sizeof(DCompactTallyItem *)); + for (uint32_t i = 0; i < tempVecTallyRet.count; i++) { + values[i] = [tempVecTallyRet[i] ffi_malloc:wrapper.chain.chainType]; + } + return DCompactTallyItemsCtor(count, values); + } + +} + +DTransaction* signTransaction(const void *context, DTransaction *transaction, bool anyoneCanPay) { + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + DSTransaction *tx = [[DSTransaction alloc] initWithTransaction:transaction onChain:wrapper.chain]; + DTransactionDtor(transaction); + BOOL isSigned = [wrapper.chain.wallets.firstObject.accounts.firstObject signTransaction:tx anyoneCanPay:anyoneCanPay]; + if (isSigned) { + return [tx ffi_malloc:wrapper.chain.chainType]; + } + } + + return nil; +} + +uint32_t countInputsWithAmount(const void *context, uint64_t inputAmount) { + @synchronized (context) { + return [AS_OBJC(context).manager countInputsWithAmount:inputAmount]; + } +} + +char *freshCoinJoinAddress(const void *context, bool internal) { + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + NSString *address = [wrapper.manager freshAddress:internal]; + return DChar(address); + } +} + +bool commitTransaction(const void *context, + DTxOutputs *items, + DCoinControl *coin_control, + bool is_denominating, + u256 *client_session_id) { + NSMutableArray *amounts = [NSMutableArray array]; + NSMutableArray *scripts = [NSMutableArray array]; + for (uintptr_t i = 0; i < items->count; i++) { + DTxOut *recipient = items->values[i]; + [amounts addObject:@(recipient->value)]; + NSData *script = NSDataFromPtr(recipient->script_pubkey->_0); + [scripts addObject:script]; + } + DTxOutputsDtor(items); + bool result = false; + + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + DSCoinControl *cc = [[DSCoinControl alloc] initWithFFICoinControl:coin_control chainType:wrapper.chain.chainType]; + DCoinControlDtor(coin_control); + result = [wrapper.manager commitTransactionForAmounts:amounts outputs:scripts coinControl:cc onPublished:^(UInt256 txId, NSError * _Nullable error) { + @synchronized (context) { + if (error) { + DSLog(@"[%@] CoinJoin: commit tx error: %@, tx type: %@", wrapper.chain.name, error, is_denominating ? @"denominations" : @"collateral"); + } else if (is_denominating) { + #if DEBUG + DSLog(@"[%@] CoinJoin tx: Denominations Created: %@", wrapper.chain.name, uint256_reverse_hex(txId)); + #else + DSLog(@"[%@] CoinJoin tx: Denominations Created: %@", wrapper.chain.name, @""); + #endif + bool isFinished = dash_spv_coinjoin_coinjoin_client_manager_CoinJoinClientManager_finish_automatic_denominating(wrapper.clientManager, client_session_id); + + if (!isFinished) + DSLog(@"[%@] CoinJoin: auto_denom not finished", wrapper.chain.name); + [wrapper.manager onTransactionProcessed:txId type:dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_CreateDenomination_ctor()]; + } else { + #if DEBUG + DSLog(@"[%@] CoinJoin tx: Collateral Created: %@", wrapper.chain.name, uint256_reverse_hex(txId)); + #else + DSLog(@"[%@] CoinJoin tx: Collateral Created: %@", wrapper.chain.name, @""); + #endif + [wrapper.manager onTransactionProcessed:txId type:dash_spv_coinjoin_models_coinjoin_tx_type_CoinJoinTransactionType_MakeCollateralInputs_ctor()]; + } + u256_dtor(client_session_id); + } + }]; + } + + return result; +} + +DMasternodeEntry* masternodeByHash(const void *context, u256 *hash) { + UInt256 mnHash = u256_cast(hash); + DMasternodeEntry *masternode; + u256_dtor(hash); + @synchronized (context) { + masternode = [AS_OBJC(context).manager masternodeEntryByHash:mnHash]; + } + + return masternode; +} + +uintptr_t validMNCount(const void *context) { + @synchronized (context) { + return [AS_OBJC(context).manager validMNCount]; + } +} + +DMasternodeList* getMNList(const void *context) { + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + return [wrapper.manager mnList]; + } +} + +bool isBlockchainSynced(const void *context) { + @synchronized (context) { + return AS_OBJC(context).manager.isChainSynced; + } +} + +bool isMasternodeOrDisconnectRequested(const void *context, SocketAddr *addr) { + u128 *ip_address = DSocketAddrIp(addr); + uint16_t port = DSocketAddrPort(addr); + UInt128 ipAddress = u128_cast(ip_address); + u128_dtor(ip_address); + SocketAddr_destroy(addr); + @synchronized (context) { + return [AS_OBJC(context).manager isMasternodeOrDisconnectRequested:ipAddress port:port]; + } +} + +bool disconnectMasternode(const void *context, SocketAddr *addr) { + u128 *ip_address = DSocketAddrIp(addr); + uint16_t port = DSocketAddrPort(addr); + UInt128 ipAddress = u128_cast(ip_address); + u128_dtor(ip_address); + SocketAddr_destroy(addr); + + @synchronized (context) { + return [AS_OBJC(context).manager disconnectMasternode:ipAddress port:port]; + } +} +bool sendMessage(const void *context, char *message_type, Vec_u8 *message, SocketAddr *addr, bool warn) { + NSString *messageType = NSStringFromPtr(message_type); + str_destroy(message_type); + u128 *ip_address = DSocketAddrIp(addr); + uint16_t port = DSocketAddrPort(addr); + UInt128 ipAddress = u128_cast(ip_address); + SocketAddr_destroy(addr); + NSData *data = NSDataFromPtr(message); + bytes_dtor(message); + @synchronized (context) { + return [AS_OBJC(context).manager sendMessageOfType:messageType message:data withPeerIP:ipAddress port:port warn:warn]; + } +} + +bool addPendingMasternode(const void *context, u256 *pro_tx_hash, u256 *session_id) { + UInt256 sessionId = u256_cast(session_id); + UInt256 proTxHash = u256_cast(pro_tx_hash); + u256_dtor(session_id); + u256_dtor(pro_tx_hash); + @synchronized (context) { + return [AS_OBJC(context).manager addPendingMasternode:proTxHash clientSessionId:sessionId]; + } +} + +void startManagerAsync(const void *context) { + @synchronized (context) { + [AS_OBJC(context).manager startAsync]; + } +} + +void updateSuccessBlock(const void *context) { + @synchronized (context) { + [AS_OBJC(context).manager updateSuccessBlock]; + } +} + +bool isWaitingForNewBlock(const void *context) { + @synchronized (context) { + return [AS_OBJC(context).manager isWaitingForNewBlock]; + } +} + +void sessionLifecycleListener(const void *context, + bool is_complete, + int32_t base_session_id, + u256 *client_session_id, + uint32_t denomination, + DPoolState *state, + DPoolMessage *message, + DPoolStatus *status, + SocketAddr *addr, + bool joined + ) { + @synchronized (context) { + UInt256 clientSessionId = u256_cast(client_session_id); + u128 *ip = DSocketAddrIp(addr); + UInt128 ipAddress = u128_cast(ip); + u256_dtor(client_session_id); + DPoolState state_index = DPoolStateValue(state); + DPoolMessage message_index = DPoolMessageValue(message); + DPoolStatus status_index = DPoolStatusValue(status); + DPoolStateDtor(state); + DPoolMessageDtor(message); + DPoolStatusDtor(status); + SocketAddr_destroy(addr); + + if (is_complete) { + [AS_OBJC(context).manager onSessionComplete:base_session_id + clientSessionId:clientSessionId + denomination:denomination + poolState:state_index + poolMessage:message_index + poolStatus:status_index + ipAddress:ipAddress + isJoined:joined]; + } else { + [AS_OBJC(context).manager onSessionStarted:base_session_id + clientSessionId:clientSessionId + denomination:denomination + poolState:state_index + poolMessage:message_index + poolStatus:status_index + ipAddress:ipAddress + isJoined:joined]; + } + } +} + +void mixingLifecycleListener(const void *context, + bool is_complete, + bool is_interrupted, + DPoolStatuses *pool_statuses) { + @synchronized (context) { + NSMutableArray *statuses = [NSMutableArray array]; + for (uintptr_t i = 0; i < pool_statuses->count; i++) { + [statuses addObject:@(DPoolStatusValue(pool_statuses->values[i]))]; + } + DPoolStatusesDtor(pool_statuses); + if (is_complete || is_interrupted) { + [AS_OBJC(context).manager onMixingComplete:statuses isInterrupted:is_interrupted]; + } else { + [AS_OBJC(context).manager onMixingStarted:statuses]; + } + } +} + +Vec_String* getCoinJoinKeys(const void *context, bool used) { + @synchronized (context) { + DSCoinJoinWrapper *wrapper = AS_OBJC(context); + NSArray *addresses = used ? [wrapper.manager getIssuedReceiveAddresses] : [wrapper.manager getUsedReceiveAddresses]; + return [NSArray ffi_to_vec_of_string:addresses]; + } +} + +@end diff --git a/DashSync/shared/Models/Masternode/DSQuorumSnapshot+Mndiff.h b/DashSync/shared/Models/CoinJoin/DSCompactTallyItem.h similarity index 59% rename from DashSync/shared/Models/Masternode/DSQuorumSnapshot+Mndiff.h rename to DashSync/shared/Models/CoinJoin/DSCompactTallyItem.h index 262d3409c..cff7b3a54 100644 --- a/DashSync/shared/Models/Masternode/DSQuorumSnapshot+Mndiff.h +++ b/DashSync/shared/Models/CoinJoin/DSCompactTallyItem.h @@ -1,6 +1,6 @@ // -// Created by Vladimir Pirogov -// Copyright © 2022 Dash Core Group. All rights reserved. +// Created by Andrei Ashikhmin +// 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. @@ -16,20 +16,19 @@ // #import -#import "DSChain.h" -#import "DSQuorumSnapshot.h" -#import "dash_shared_core.h" +#import "DSInputCoin.h" NS_ASSUME_NONNULL_BEGIN -@interface DSQuorumSnapshot (Mndiff) +@interface DSCompactTallyItem : NSObject -+ (instancetype)quorumSnapshotWith:(LLMQSnapshot *)quorumSnapshot forBlockHash:(UInt256)blockHash; +@property (strong, nonatomic) NSData *txDestination; +@property (nonatomic, assign) uint64_t amount; +@property (strong, nonatomic) NSMutableArray *inputCoins; -- (LLMQSnapshot *)ffi_malloc; -+ (void)ffi_free:(LLMQSnapshot *)entry; +- (DCompactTallyItem *)ffi_malloc:(DChainType *)type; ++ (void)ffi_free:(DCompactTallyItem *)item; @end NS_ASSUME_NONNULL_END - diff --git a/DashSync/shared/Models/CoinJoin/DSCompactTallyItem.m b/DashSync/shared/Models/CoinJoin/DSCompactTallyItem.m new file mode 100644 index 000000000..0e918dcae --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSCompactTallyItem.m @@ -0,0 +1,48 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCompactTallyItem.h" +#import "DSCoinJoinWrapper.h" + +@implementation DSCompactTallyItem + +- (instancetype)init { + self = [super init]; + if (self) { + _amount = 0; + _inputCoins = [[NSMutableArray alloc] init]; + } + return self; +} + +- (DCompactTallyItem *)ffi_malloc:(DChainType *)type { + + NSUInteger count = self.inputCoins.count; + DInputCoin **values = malloc(count * sizeof(DInputCoin *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = [self.inputCoins[i] ffi_malloc:type]; + } + DInputCoins *input_coins = DInputCoinsCtor(count, values); + return DCompactTallyItemCtor(bytes_ctor(self.txDestination), self.amount, input_coins); +} + ++ (void)ffi_free:(DCompactTallyItem *)item { + if (!item) return; + DCompactTallyItemDtor(item); +} + +@end diff --git a/DashSync/shared/Models/CoinJoin/DSInputCoin.h b/DashSync/shared/Models/CoinJoin/DSInputCoin.h new file mode 100644 index 000000000..d86e99580 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSInputCoin.h @@ -0,0 +1,40 @@ +// +// Created by Andrei Ashikhmin +// 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 "BigIntTypes.h" +#import "DSKeyManager.h" +#import "DSCoinJoinWrapper.h" +#import "DSTransactionOutput.h" +#import "DSTransaction.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSInputCoin : NSObject + +@property (nonatomic, assign) UInt256 outpointHash; +@property (nonatomic, assign) uint32_t outpointIndex; +@property (strong, nonatomic) DSTransactionOutput *output; +@property (nonatomic, assign) uint64_t effectiveValue; + +- (instancetype)initWithTx:(DSTransaction *)tx index:(int32_t)i; +- (DInputCoin *)ffi_malloc:(DChainType *)type; ++ (void)ffi_free:(DInputCoin *)inputCoin; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/DSInputCoin.m b/DashSync/shared/Models/CoinJoin/DSInputCoin.m new file mode 100644 index 000000000..285b96ff8 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSInputCoin.m @@ -0,0 +1,45 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSInputCoin.h" + +@implementation DSInputCoin + +- (instancetype)initWithTx:(DSTransaction *)tx index:(int32_t)i { + self = [super init]; + if (self) { + _outpointHash = tx.txHash; + _outpointIndex = i; + _output = tx.outputs[i]; + _effectiveValue = tx.outputs[i].amount; + } + return self; +} + +- (DInputCoin *)ffi_malloc:(DChainType *)type { + // TODO: check outpoint hash reverse or not + DOutPoint *outpoint = DOutPointCtorU(self.outpointHash, self.outpointIndex); + DTxOut *tx_out = DTxOutCtor(self.output.amount, DScriptBufCtor(bytes_ctor(self.output.outScript))); + return DInputCoinCtor(outpoint, tx_out, self.effectiveValue); +} + ++ (void)ffi_free:(DInputCoin *)inputCoin { + if (!inputCoin) return; + DInputCoinDtor(inputCoin); +} + +@end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeList+Mndiff.h b/DashSync/shared/Models/CoinJoin/DSTransaction+CoinJoin.h similarity index 63% rename from DashSync/shared/Models/Masternode/DSMasternodeList+Mndiff.h rename to DashSync/shared/Models/CoinJoin/DSTransaction+CoinJoin.h index dd17ff557..d895a7ad3 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeList+Mndiff.h +++ b/DashSync/shared/Models/CoinJoin/DSTransaction+CoinJoin.h @@ -1,6 +1,6 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 Dash Core Group. All rights reserved. +// +// Created by Andrei Ashikhmin +// 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. @@ -15,19 +15,18 @@ // limitations under the License. // -#import "DSChain.h" -#import "DSMasternodeList.h" -#import "dash_shared_core.h" #import +#import "DSTransaction.h" +#import "DSKeyManager.h" NS_ASSUME_NONNULL_BEGIN -@interface DSMasternodeList (Mndiff) +@interface DSTransaction (CoinJoin) -+ (instancetype)masternodeListWith:(MasternodeList *)list onChain:(DSChain *)chain; +- (DSTransaction *)initWithTransaction:(DTransaction *)transaction onChain:(DSChain *)chain; -- (MasternodeList *)ffi_malloc; -+ (void)ffi_free:(MasternodeList *)list; +- (DTransaction *)ffi_malloc:(DChainType *)chainType; ++ (void)ffi_free:(DTransaction *)tx; @end diff --git a/DashSync/shared/Models/CoinJoin/DSTransaction+CoinJoin.m b/DashSync/shared/Models/CoinJoin/DSTransaction+CoinJoin.m new file mode 100644 index 000000000..84a86f975 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSTransaction+CoinJoin.m @@ -0,0 +1,99 @@ +// +// Created by Andrei Ashikhmin +// 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 "BigIntTypes.h" +#import "DSChain+Transaction.h" +#import "DSTransaction.h" +#import "DSTransaction+CoinJoin.h" +#import "DSTransactionInput+CoinJoin.h" +#import "NSData+Dash.h" +#import "DSKeyManager.h" + +@implementation DSTransaction (CoinJoin) + +- (DSTransaction *)initWithTransaction:(DTransaction *)transaction onChain:(DSChain *)chain { + NSMutableArray *hashes = [NSMutableArray array]; + NSMutableArray *indexes = [NSMutableArray array]; + NSMutableArray *scripts = [NSMutableArray array]; + NSMutableArray *inputSequences = [NSMutableArray array]; + NSMutableArray *addresses = [NSMutableArray array]; + NSMutableArray *amounts = [NSMutableArray array]; + + for (uintptr_t i = 0; i < transaction->input->count; i++) { + DTxIn *txin = transaction->input->values[i]; + uint32_t index = txin->previous_output->vout; + u256 *hash = dashcore_hash_types_Txid_inner(txin->previous_output->txid); + // TODO: check if it's reversed + UInt256 hashValue = u256_cast(hash); + Vec_u8 *script_sig = txin->script_sig->_0; + NSData *script = NSDataFromPtr(script_sig); + if (!script.length) { + DSTransaction *inputTx = [chain transactionForHash:hashValue]; + if (inputTx) + script = inputTx.outputs[index].outScript; + } + [hashes addObject:uint256_obj(hashValue)]; + [indexes addObject:@(index)]; + [scripts addObject:script]; + [inputSequences addObject:@(txin->sequence)]; + } + for (uintptr_t i = 0; i < transaction->output->count; i++) { + DTxOut *output = transaction->output->values[i]; + NSData *scriptPubKey = NSDataFromPtr(output->script_pubkey->_0); + NSString *address = [DSKeyManager addressWithScriptPubKey:scriptPubKey forChain:chain]; + NSNumber *amount = @(output->value); + + [addresses addObject:address ?: [NSNull null]]; // Use NSNull turned into OP_RETURN script later + [amounts addObject:amount]; + } + + DSTransaction *tx = [[DSTransaction alloc] initWithInputHashes:hashes + inputIndexes:indexes + inputScripts:scripts + inputSequences:inputSequences + outputAddresses:addresses + outputAmounts:amounts + onChain:chain]; + tx.version = transaction->version; + + return tx; +} + +- (DTransaction *)ffi_malloc:(DChainType *)chainType { + uintptr_t inputsCount = self.inputs.count; + uintptr_t outputsCount = self.outputs.count; + DTxIn **input_values = malloc(inputsCount * sizeof(DTxIn *)); + DTxOut **output_values = malloc(outputsCount * sizeof(DTxOut *)); + for (uintptr_t i = 0; i < inputsCount; ++i) { + input_values[i] = [self.inputs[i] ffi_malloc]; + } + + for (uintptr_t i = 0; i < outputsCount; ++i) { + DSTransactionOutput *output = self.outputs[i]; + output_values[i] = DTxOutCtor(output.amount, DScriptBufCtor(bytes_ctor(output.outScript))); + } + DTransaction *transaction = DTransactionCtor(self.version, self.lockTime, DTxInputsCtor(inputsCount, input_values), DTxOutputsCtor(outputsCount, output_values), NULL); + return transaction; +} + ++ (void)ffi_free:(DTransaction *)tx { + if (!tx) return; + DTransactionDtor(tx); +} + +@end + diff --git a/DashSync/shared/Models/Masternode/DSQuorumSnapshot.h b/DashSync/shared/Models/CoinJoin/DSTransactionInput+CoinJoin.h similarity index 58% rename from DashSync/shared/Models/Masternode/DSQuorumSnapshot.h rename to DashSync/shared/Models/CoinJoin/DSTransactionInput+CoinJoin.h index 7faab6aec..08cba834d 100644 --- a/DashSync/shared/Models/Masternode/DSQuorumSnapshot.h +++ b/DashSync/shared/Models/CoinJoin/DSTransactionInput+CoinJoin.h @@ -1,6 +1,6 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Created by Andrei Ashikhmin +// 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. @@ -15,20 +15,23 @@ // limitations under the License. // -#import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "DSTransactionInput.h" +#import "DSKeyManager.h" #import NS_ASSUME_NONNULL_BEGIN -@interface DSQuorumSnapshot : NSObject +@interface DSTransactionInput (CoinJoin) -@property (nonatomic) NSData *memberList; -@property (nonatomic) NSArray *skipList; -@property (nonatomic) LLMQSnapshotSkipMode skipListMode; +- (DTxIn *)ffi_malloc; ++ (void)ffi_free:(DTxIn *)input; -@property (nonatomic) UInt256 blockHash; +@end +@interface NSArray (Vec_dashcore_blockdata_transaction_txin_TxIn) ++ (DTxInputs *)ffi_to_tx_inputs:(NSArray *)obj; ++ (void)ffi_destroy_tx_inputs:(DTxInputs *)ffi_ref; @end + NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/DSTransactionInput+CoinJoin.m b/DashSync/shared/Models/CoinJoin/DSTransactionInput+CoinJoin.m new file mode 100644 index 000000000..971587e54 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/DSTransactionInput+CoinJoin.m @@ -0,0 +1,57 @@ +// +// Created by Andrei Ashikhmin +// 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 "BigIntTypes.h" +#import "DSTransactionInput.h" +#import "DSTransactionInput+CoinJoin.h" +#import "NSData+Dash.h" + +@implementation DSTransactionInput (CoinJoin) + +- (DTxIn *)ffi_malloc { + DOutPoint *outpoint = DOutPointCtorU(self.inputHash, self.index); + DScriptBuf *script; + if (self.inScript) + script = DScriptBufCtor(bytes_ctor(self.inScript)); + else + script = DScriptBufCtor(bytes_ctor(self.signature)); + + return DTxInCtor(outpoint, script, self.sequence); +} + ++ (void)ffi_free:(DTxIn *)input { + if (!input) return; + DTxInDtor(input); +} + +@end + +@implementation NSArray (Vec_dashcore_blockdata_transaction_txin_TxIn) ++ (DTxInputs *)ffi_to_tx_inputs:(NSArray *)obj { + NSUInteger count = obj.count; + DTxIn **values = malloc(count * sizeof(DTxIn *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = [obj[i] ffi_malloc]; + } + return DTxInputsCtor(count, values); + +} ++ (void)ffi_destroy_tx_inputs:(DTxInputs *)ffi_ref { + if (ffi_ref) DTxInputsDtor(ffi_ref); +} +@end + diff --git a/DashSync/shared/Models/CoinJoin/Utils/DSBackoff.h b/DashSync/shared/Models/CoinJoin/Utils/DSBackoff.h new file mode 100644 index 000000000..4d023e9a2 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/Utils/DSBackoff.h @@ -0,0 +1,33 @@ +// +// Created by Andrei Ashikhmin +// 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 + +@interface DSBackoff : NSObject + +@property (nonatomic, strong) NSDate *retryTime; +@property (nonatomic, assign) float_t backoff; +@property (nonatomic, readonly) float_t maxBackoff; +@property (nonatomic, readonly) float_t initialBackoff; +@property (nonatomic, readonly) float_t multiplier; + +- (instancetype)initInitialBackoff:(float_t)initial maxBackoff:(float_t)max multiplier:(float_t)multiplier; + +- (void)trackSuccess; +- (void)trackFailure; + +@end diff --git a/DashSync/shared/Models/CoinJoin/Utils/DSBackoff.m b/DashSync/shared/Models/CoinJoin/Utils/DSBackoff.m new file mode 100644 index 000000000..8954cef7b --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/Utils/DSBackoff.m @@ -0,0 +1,43 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSBackoff.h" + +@implementation DSBackoff + +- (instancetype)initInitialBackoff:(float_t)initial maxBackoff:(float_t)max multiplier:(float_t)multiplier { + self = [super init]; + if (self) { + _maxBackoff = max; + _initialBackoff = initial; + _multiplier = multiplier; + [self trackSuccess]; + } + return self; +} + +- (void)trackSuccess { + _backoff = _initialBackoff; + _retryTime = [NSDate date]; +} + +- (void)trackFailure { + _retryTime = [[NSDate date] dateByAddingTimeInterval:_backoff]; + _backoff = MIN(_backoff, _maxBackoff); +} + +@end diff --git a/DashSync/shared/Models/CoinJoin/Utils/DSMasternodeGroup.h b/DashSync/shared/Models/CoinJoin/Utils/DSMasternodeGroup.h new file mode 100644 index 000000000..4aafa9ae2 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/Utils/DSMasternodeGroup.h @@ -0,0 +1,43 @@ +// +// Created by Andrei Ashikhmin +// 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" +#import "DSPeer.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSCoinJoinManager; + +@interface DSMasternodeGroup : NSObject + +@property (atomic, readonly) BOOL isRunning; + +- (instancetype)initWithManager:(DSCoinJoinManager *)manager; + +- (void)startAsync; +- (void)stopAsync; +- (void)triggerConnections; +- (BOOL)isMasternodeOrDisconnectRequested:(UInt128)ip port:(uint16_t)port; +- (BOOL)disconnectMasternode:(UInt128)ip port:(uint16_t)port; +- (BOOL)addPendingMasternode:(UInt256)proTxHash clientSessionId:(UInt256)sessionId; +- (BOOL)forPeer:(UInt128)ip port:(uint16_t)port warn:(BOOL)warn withPredicate:(BOOL (^)(DSPeer *peer))predicate; +- (NSString *)hostFor:(UInt128)address; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/CoinJoin/Utils/DSMasternodeGroup.m b/DashSync/shared/Models/CoinJoin/Utils/DSMasternodeGroup.m new file mode 100644 index 000000000..3f41c15a8 --- /dev/null +++ b/DashSync/shared/Models/CoinJoin/Utils/DSMasternodeGroup.m @@ -0,0 +1,594 @@ +// +// Created by Andrei Ashikhmin +// 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 "NSData+Dash.h" +#import "DSMasternodeGroup.h" +#import "DSChainManager.h" +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChain+Wallet.h" +#import "DSCoinJoinManager.h" +#import "DSMasternodeManager.h" +#import "DSPeerManager.h" +#import "DSSendCoinJoinQueue.h" +#import "DSBackoff.h" +#import "DSBlock.h" +#import "DSPeerManager+Protected.h" +#import + +float_t const MIN_PEER_DISCOVERY_INTERVAL = 1; // One second +float_t const DEFAULT_INITIAL_BACKOFF = 1; // One second +float_t const DEFAULT_MAX_BACKOFF = 5; // Five seconds +float_t const GROUP_BACKOFF_MULTIPLIER = 1.5; +float_t const BACKOFF_MULTIPLIER = 1.001; + +@interface DSMasternodeGroup () + +@property (nonatomic, strong) DSChain *chain; +@property (nonatomic, weak, nullable) DSCoinJoinManager *coinJoinManager; +@property (nonatomic, strong) NSMutableSet *mutablePendingSessions; +@property (nonatomic, strong) NSMutableDictionary *masternodeMap; +@property (nonatomic, strong) NSMutableDictionary *sessionMap; +@property (nonatomic, strong) NSMutableDictionary *addressMap; +@property (atomic, readonly) NSUInteger maxConnections; +@property (nonatomic, strong) NSMutableArray *mutablePendingClosingMasternodes; +@property (nonatomic, strong) NSMutableSet *mutableConnectedPeers; +@property (nonatomic, strong) NSMutableSet *mutablePendingPeers; +@property (nonatomic, strong) NSObject *peersLock; +@property (nonatomic, readonly) BOOL shouldSendDsq; +@property (nullable, nonatomic, readwrite) DSPeer *downloadPeer; +@property (nonatomic, strong) DSBackoff *groupBackoff; +@property (nonatomic, strong) NSMutableDictionary *backoffMap; +@property (nonatomic) uint32_t lastSeenBlock; + +@end + +@implementation DSMasternodeGroup + +- (instancetype)initWithManager:(DSCoinJoinManager *)manager { + self = [super init]; + if (self) { + _coinJoinManager = manager; + _chain = manager.chain; + _mutablePendingSessions = [NSMutableSet set]; + _mutablePendingClosingMasternodes = [NSMutableArray array]; + _masternodeMap = [NSMutableDictionary dictionary]; + _sessionMap = [NSMutableDictionary dictionary]; + _addressMap = [NSMutableDictionary dictionary]; + _mutableConnectedPeers = [NSMutableSet set]; + _mutablePendingPeers = [NSMutableSet set]; + _peersLock = [[NSObject alloc] init]; + _downloadPeer = nil; + _maxConnections = 0; + _shouldSendDsq = true; + _groupBackoff = [[DSBackoff alloc] initInitialBackoff:DEFAULT_INITIAL_BACKOFF maxBackoff:DEFAULT_MAX_BACKOFF multiplier:GROUP_BACKOFF_MULTIPLIER]; + _backoffMap = [NSMutableDictionary dictionary]; + _lastSeenBlock = 0; + } + return self; +} + +- (void)startAsync { + _isRunning = true; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleSyncStateDidChangeNotification:) + name:DSChainManagerSyncStateDidChangeNotification + object:nil]; + [self triggerConnections]; +} + +- (dispatch_queue_t)networkingQueue { + return self.chain.networkingQueue; +} + +- (void)stopAsync { + _isRunning = false; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (BOOL)isMasternodeOrDisconnectRequested:(UInt128)ip port:(uint16_t)port { + BOOL found = [self forPeer:ip port:port warn:NO withPredicate:^BOOL(DSPeer *peer) { + return YES; + }]; + + if (!found) { + for (DSPeer *mn in self.pendingClosingMasternodes) { + if (uint128_eq(mn.address, ip) && mn.port == port) { + found = true; + } + } + } + + return found; +} + +- (BOOL)disconnectMasternode:(UInt128)ip port:(uint16_t)port { + return [self forPeer:ip port:port warn:YES withPredicate:^BOOL(DSPeer *peer) { + DSLog(@"[%@] CoinJoin: masternode[closing] %@", self.chain.name, [self hostFor:ip]); + + @synchronized (self.mutablePendingClosingMasternodes) { + [self.mutablePendingClosingMasternodes addObject:peer]; + } + + [self updateMaxConnections]; + [peer disconnect]; + + return true; + }]; +} + +- (BOOL)forPeer:(UInt128)ip port:(uint16_t)port warn:(BOOL)warn withPredicate:(BOOL (^)(DSPeer *peer))predicate { + NSMutableString *listOfPeers = [NSMutableString string]; + NSSet *peers = self.connectedPeers; + + for (DSPeer *peer in peers) { + [listOfPeers appendFormat:@"%@, ", peer.location]; + + if (uint128_eq(peer.address, ip) && peer.port == port) { + return predicate(peer); + } + } + + if (warn) { + if (![self isNodePending:ip port:port]) { + DSLog(@"[%@] CoinJoin: Cannot find %@ in the list of connected peers: %@", self.chain.name, [self hostFor:ip], listOfPeers); + NSAssert(NO, @"Cannot find %@", [self hostFor:ip]); + } else { + DSLog(@"[%@] CoinJoin: %@ in the list of pending peers", self.chain.name, [self hostFor:ip]); + } + } + + return NO; +} + +- (BOOL)isNodePending:(UInt128)ip port:(uint16_t)port { + for (DSPeer *peer in self.pendingPeers) { + if (uint128_eq(peer.address, ip) && peer.port == port) { + return true; + } + } + + return false; +} + +- (NSSet *)connectedPeers { + @synchronized(self.peersLock) { + return [self.mutableConnectedPeers copy]; + } +} + +- (NSSet *)pendingPeers { + @synchronized(self.peersLock) { + return [self.mutablePendingPeers copy]; + } +} + +- (NSArray *)pendingClosingMasternodes { + @synchronized(self.mutablePendingClosingMasternodes) { + return [self.mutablePendingClosingMasternodes copy]; + } +} + +- (NSSet *)pendingSessions { + @synchronized(self.mutablePendingSessions) { + return [self.mutablePendingSessions copy]; + } +} + +- (void)triggerConnections { + [self triggerConnectionsJobWithDelay:0]; +} + +- (void)triggerConnectionsJobWithDelay:(NSTimeInterval)delay { + dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); + dispatch_after(delayTime, self.networkingQueue, ^{ + if (!self.isRunning) { + return; + } + + if (!self.coinJoinManager.isChainSynced || self.coinJoinManager.isWaitingForNewBlock || !self.coinJoinManager.isMixing) { + return; + } + + NSUInteger numPeers = self.pendingPeers.count + self.connectedPeers.count; + + if (numPeers >= self.maxConnections) { + return; + } + + NSDate *now = [NSDate date]; + DSPeer *peerToTry = [self getNextPendingMasternode]; + + if (peerToTry) { + NSDate *retryTime = [self.backoffMap objectForKey:peerToTry.location].retryTime; + retryTime = [retryTime laterDate:self.groupBackoff.retryTime]; + NSTimeInterval delay = [retryTime timeIntervalSinceDate:now]; + + if (delay > 0.1) { + DSLog(@"[%@] CoinJoin: Waiting %fl s before next connect attempt to masternode to %@", self.chain.name, delay, peerToTry == NULL ? @"" : peerToTry.location); + [self triggerConnectionsJobWithDelay:delay]; + return; + } + + [self connectTo:peerToTry]; + } + + NSUInteger count = self.maxConnections; + + @synchronized (self.peersLock) { + if (peerToTry) { + [self.groupBackoff trackSuccess]; + } else { + [self.groupBackoff trackFailure]; + } + + count = self.mutablePendingPeers.count + self.mutableConnectedPeers.count; + } + + if (count < self.maxConnections) { + [self triggerConnectionsJobWithDelay:0]; // Try next peer immediately. + } + }); +} + +- (DSPeer *)getNextPendingMasternode { + NSArray *pendingClosingMasternodesCopy = self.pendingClosingMasternodes; + DSPeer *peerWithLeastBackoff = nil; + NSValue *sessionValueWithLeastBackoff = nil; + UInt256 sessionId = UINT256_ZERO; + NSDate *leastBackoffTime = [NSDate distantFuture]; + + for (NSValue *sessionValue in self.pendingSessions) { + [sessionValue getValue:&sessionId]; + DMasternodeEntry *mixingMasternodeInfo = [self mixingMasternodeAddressFor:sessionId]; + + if (mixingMasternodeInfo) { + SocketAddr *addr = mixingMasternodeInfo->masternode_list_entry->service_address; + u128 *ip = DSocketAddrIp(addr); + uint16_t port = DSocketAddrPort(addr); + UInt128 ipAddress = u128_cast(ip); + DSPeer *peer = [self peerForLocation:ipAddress port:port]; + + if (peer == nil) { + peer = [DSPeer peerWithAddress:ipAddress andPort:port onChain:self.chain]; + } + + if (![pendingClosingMasternodesCopy containsObject:peer] && ![self isNodeConnected:peer] && ![self isNodePending:peer]) { + DSBackoff *backoff = [self.backoffMap objectForKey:peer.location]; + + if (!backoff) { + backoff = [[DSBackoff alloc] initInitialBackoff:DEFAULT_INITIAL_BACKOFF maxBackoff:DEFAULT_MAX_BACKOFF multiplier:BACKOFF_MULTIPLIER]; + [self.backoffMap setObject:backoff forKey:peer.location]; + } + + if ([backoff.retryTime compare:leastBackoffTime] == NSOrderedAscending) { + leastBackoffTime = backoff.retryTime; + peerWithLeastBackoff = peer; + sessionValueWithLeastBackoff = sessionValue; + } + } + } + } + + if (peerWithLeastBackoff) { + @synchronized(self.addressMap) { + [self.addressMap setObject:sessionValueWithLeastBackoff forKey:peerWithLeastBackoff.location]; + DSLog(@"[%@] CoinJoin: discovery: %@ -> %@", self.chain.name, peerWithLeastBackoff.location, uint256_hex(sessionId)); + } + } + + return peerWithLeastBackoff; +} + +- (DMasternodeEntry *)mixingMasternodeAddressFor:(UInt256)sessionId { + NSValue *sessionIdKey = [NSValue value:&sessionId withObjCType:@encode(UInt256)]; + NSValue *proTxHashValue = [self.sessionMap objectForKey:sessionIdKey]; + + if (proTxHashValue) { + UInt256 proTxHash = UINT256_ZERO; + [proTxHashValue getValue:&proTxHash]; + + return [self.coinJoinManager masternodeEntryByHash:proTxHash]; + } + + return nil; +} + +- (BOOL)addPendingMasternode:(UInt256)proTxHash clientSessionId:(UInt256)sessionId { + @synchronized (self.mutablePendingSessions) { + DSLog(@"[%@] CoinJoin: adding masternode for mixing. maxConnections = %lu, protx: %@, sessionId: %@", self.chain.name, (unsigned long)_maxConnections, uint256_hex(proTxHash), uint256_hex(sessionId)); + NSValue *sessionIdValue = [NSValue valueWithBytes:&sessionId objCType:@encode(UInt256)]; + [self.mutablePendingSessions addObject:sessionIdValue]; + + NSValue *proTxHashKey = [NSValue value:&proTxHash withObjCType:@encode(UInt256)]; + [self.masternodeMap setObject:sessionIdValue forKey:proTxHashKey]; + [self.sessionMap setObject:proTxHashKey forKey:sessionIdValue]; + } + + [self checkMasternodesWithoutSessions]; + [self updateMaxConnections]; + + return true; +} + +- (void)updateMaxConnections { + _maxConnections = self.mutablePendingSessions.count; + NSUInteger connections = MIN(self.maxConnections, self.coinJoinManager.options->coinjoin_sessions); + DSLog(@"[%@] CoinJoin: updating max connections to min(%lu, %lu)", self.chain.name, (unsigned long)_maxConnections, (unsigned long)self.coinJoinManager.options->coinjoin_sessions); + + [self updateMaxConnections:connections]; +} + +- (void)updateMaxConnections:(NSUInteger)connections { + _maxConnections = connections; + + if (!self.isRunning) { + return; + } + + // We may now have too many or too few open connections. Add more or drop some to get to the right amount. + NSInteger adjustment = 0; + + @synchronized (self.peersLock) { + NSUInteger pendingCount = self.mutablePendingPeers.count; + NSUInteger connectedCount = self.mutableConnectedPeers.count; + NSUInteger numPeers = pendingCount + connectedCount; + adjustment = self.maxConnections - numPeers; + DSLogPrivate(@"CoinJoin: updateMaxConnections adjustment %lu, pendingCount: %lu, connectedCount: %lu", adjustment, pendingCount, connectedCount); + } + + if (adjustment > 0) { + [self triggerConnections]; + } +} + +- (void)checkMasternodesWithoutSessions { + NSMutableArray *masternodesToDrop = [NSMutableArray array]; + NSSet *pendingSessions = self.pendingSessions; + + for (DSPeer *peer in self.connectedPeers) { + BOOL found = false; + + for (NSValue *value in pendingSessions) { + UInt256 sessionId; + [value getValue:&sessionId]; + DMasternodeEntry *mixingMasternodeAddress = [self mixingMasternodeAddressFor:sessionId]; + if (mixingMasternodeAddress) { + SocketAddr *addr = mixingMasternodeAddress->masternode_list_entry->service_address; + u128 *ip = DSocketAddrIp(addr); + uint16_t port = DSocketAddrPort(addr); + UInt128 ipAddress = u128_cast(ip); + + if (uint128_eq(ipAddress, peer.address) && port == peer.port) { + found = YES; + } + } else { + // TODO(DashJ): we may not need this anymore + DSLog(@"[%@] CoinJoin: session is not connected to a masternode: %@", self.chain.name, uint256_hex(sessionId)); + } + } + + if (!found) { + DSLog(@"[%@] CoinJoin: masternode is not connected to a session: %@", self.chain.name, peer.location); + [masternodesToDrop addObject:peer]; + } + } + + DSLogPrivate(@"CoinJoin: need to drop %lu masternodes", (unsigned long)masternodesToDrop.count); + + for (DSPeer *peer in masternodesToDrop) { + DMasternodeEntry *mn = [self.chain.chainManager.masternodeManager masternodeAtLocation:peer.address port:peer.port]; + DSLog(@"[%@] CoinJoin: masternode will be disconnected: %@: %@", self.chain.name, peer.location, u256_hex(dashcore_hash_types_ProTxHash_inner(mn->masternode_list_entry->pro_reg_tx_hash))); + + @synchronized (self.mutablePendingClosingMasternodes) { + [self.mutablePendingClosingMasternodes addObject:peer]; + } + + [peer disconnect]; + } +} + +- (NSString *)hostFor:(UInt128)address { + char s[INET6_ADDRSTRLEN]; + + if (address.u64[0] == 0 && address.u32[2] == CFSwapInt32HostToBig(0xffff)) { + return @(inet_ntop(AF_INET, &address.u32[3], s, sizeof(s))); + } else + return @(inet_ntop(AF_INET6, &address, s, sizeof(s))); +} + +- (BOOL)connectTo:(DSPeer *)peer { + DSLogPrivate(@"[%@] CoinJoin: connectTo: %@", self.chain.name, peer.location); + + if (![self isMasternodeSessionByPeer:peer]) { + DSLog(@"[%@] CoinJoin: %@ not a masternode session, exit", self.chain.name, peer.location); + return NO; + } + + if ([self isNodeConnected:peer] || [self isNodePending:peer]) { + DSLog(@"[%@] CoinJoin: attempting to connect to the same masternode again: %@", self.chain.name, peer.location); + return NO; // do not connect to the same peer again + } + + DMasternodeEntry *mn = [_chain.chainManager.masternodeManager masternodeAtLocation:peer.address port:peer.port]; + UInt256 sessionId = UINT256_ZERO; + UInt256 proTxHash = u256_cast(dashcore_hash_types_ProTxHash_inner(mn->masternode_list_entry->pro_reg_tx_hash)); + + @synchronized (self.masternodeMap) { + NSValue *proTxHashKey = [NSValue value:&proTxHash withObjCType:@encode(UInt256)]; + NSValue *sessionObject = [self.masternodeMap objectForKey:proTxHashKey]; + + if (sessionObject) { + [sessionObject getValue:&sessionId]; + } + } + + if (uint256_is_zero(sessionId)) { + DSLog(@"[%@] CoinJoin: session is not connected to a masternode, proTxHashKey not found in masternodeMap", self.chain.name); + return NO; + } + + DMasternodeEntry *mixingMasternodeAddress = [self mixingMasternodeAddressFor:sessionId]; + + if (!mixingMasternodeAddress) { + DSLog(@"[%@] CoinJoin: session is not connected to a masternode, sessionId: %@", self.chain.name, uint256_hex(sessionId)); + return NO; + } + + DSLog(@"[%@] CoinJoin: masternode[connecting] %@: %@; %@", self.chain.name, peer.location, uint256_hex(proTxHash), uint256_hex(sessionId)); + + [peer setChainDelegate:self.chain.chainManager + peerDelegate:self transactionDelegate:self.chain.chainManager.transactionManager + governanceDelegate:self.chain.chainManager.governanceSyncManager + sporkDelegate:self.chain.chainManager.sporkManager + masternodeDelegate:self.chain.chainManager.masternodeManager + queue:self.networkingQueue]; + peer.earliestKeyTime = self.chain.earliestWalletCreationTime;; + + @synchronized (self.peersLock) { + [self.mutablePendingPeers addObject:peer]; + } + + [peer connect]; + + return YES; +} + +- (BOOL)isMasternodeSessionByPeer:(DSPeer *)peer { + @synchronized (self.addressMap) { + return [self.addressMap objectForKey:peer.location] != nil; + } +} + +- (BOOL)isNodeConnected:(DSPeer *)node { + return [self forPeer:node.address port:node.port warn:NO withPredicate:^BOOL(DSPeer * _Nonnull peer) { + return YES; + }]; +} + +- (BOOL)isNodePending:(DSPeer *)node { + for (DSPeer *peer in self.pendingPeers) { + if (uint128_eq(node.address, peer.address) && node.port == peer.port) { + return YES; + } + } + + return NO; +} + +- (void)peerConnected:(nonnull DSPeer *)peer { + @synchronized (self.peersLock) { + [self.groupBackoff trackSuccess]; + [[self.backoffMap objectForKey:peer.location] trackSuccess]; + + DSLog(@"[%@] CoinJoin: New peer %@ ({%lu connected, %lu pending, %lu max)", self.chain.name, peer.location, self.mutableConnectedPeers.count, self.mutablePendingPeers.count, self.maxConnections); + + [self.mutablePendingPeers removeObject:peer]; + [self.mutableConnectedPeers addObject:peer]; + } + + if (self.shouldSendDsq) { + [peer sendRequest:[DSSendCoinJoinQueue requestWithShouldSend:true]]; + } +} + +- (void)peer:(nonnull DSPeer *)peer disconnectedWithError:(nonnull NSError *)error { + NSUInteger numPeers = self.maxConnections; + + @synchronized (self.peersLock) { + [self.mutablePendingPeers removeObject:peer]; + [self.mutableConnectedPeers removeObject:peer]; + [self.groupBackoff trackFailure]; + [[self.backoffMap objectForKey:peer.location] trackFailure]; + numPeers = self.mutablePendingPeers.count + self.mutableConnectedPeers.count; + } + + if (numPeers < self.maxConnections) { + [self triggerConnections]; + } + + DSPeer *masternode = NULL; + NSArray *pendingClosingMasternodes = self.pendingClosingMasternodes; + + for (DSPeer *mn in pendingClosingMasternodes) { + if ([peer.location isEqualToString:mn.location]) { + masternode = mn; + } + } + + DSLog(@"[%@] CoinJoin: handling this mn peer death: %@ -> %@", self.chain.name, peer.location, masternode != NULL ? masternode.location : @"not found in closing list"); + + if (masternode) { + NSString *address = peer.location; + + if ([pendingClosingMasternodes containsObject:masternode]) { + // if this is part of pendingClosingMasternodes, where we want to close the connection, + // we don't want to increase the backoff time + [[self.backoffMap objectForKey:address] trackSuccess]; + } + + @synchronized (self.mutablePendingClosingMasternodes) { + [self.mutablePendingClosingMasternodes removeObject:masternode]; + } + DMasternodeEntry *mn = [self.chain.chainManager.masternodeManager masternodeAtLocation:masternode.address port:masternode.port]; + UInt256 proTxHash = u256_cast(dashcore_hash_types_ProTxHash_inner(mn->masternode_list_entry->pro_reg_tx_hash)); + NSValue *proTxHashKey = [NSValue valueWithBytes:&proTxHash objCType:@encode(UInt256)]; + NSValue *sessionIdObject = [self.masternodeMap objectForKey:proTxHashKey]; + + @synchronized (self.mutablePendingSessions) { + if (sessionIdObject) { + [self.mutablePendingSessions removeObject:sessionIdObject]; + [self.sessionMap removeObjectForKey:sessionIdObject]; + } + + [self.masternodeMap removeObjectForKey:proTxHashKey]; + [self.addressMap removeObjectForKey:masternode.location]; + } + + [self checkMasternodesWithoutSessions]; + } +} + +- (void)peer:(nonnull DSPeer *)peer relayedPeers:(nonnull NSArray *)peers { + // TODO ? +} + +- (DSPeer *)peerForLocation:(UInt128)ipAddress port:(uint16_t)port { + for (DSPeer *peer in self.connectedPeers) { + if (uint128_eq(peer.address, ipAddress) && peer.port == port) { + return peer; + } + } + + for (DSPeer *peer in self.pendingPeers) { + if (uint128_eq(peer.address, ipAddress) && peer.port == port) { + return peer; + } + } + + return [self.chain.chainManager.peerManager peerForLocation:ipAddress port:port]; +} + +- (void)handleSyncStateDidChangeNotification:(NSNotification *)note { + if ([note.userInfo[DSChainManagerNotificationChainKey] isEqual:[self chain]] && self.chain.lastSyncBlock.height > self.lastSeenBlock) { + self.lastSeenBlock = self.chain.lastSyncBlock.height; + DSLogPrivate(@"[%@] CoinJoin: new block found, restarting masternode connections job", self.chain.name); + [self triggerConnections]; + } +} + +@end diff --git a/DashSync/shared/Models/DAPI/DSDAPIClient.h b/DashSync/shared/Models/DAPI/DSDAPIClient.h deleted file mode 100644 index 713fc1da3..000000000 --- a/DashSync/shared/Models/DAPI/DSDAPIClient.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 - -NS_ASSUME_NONNULL_BEGIN - -extern NSErrorDomain const DSDAPIClientErrorDomain; - -typedef NS_ENUM(NSUInteger, DSDAPIClientErrorCode) -{ - DSDAPIClientErrorCodeSignTransitionFailed = 1, - DSDAPIClientErrorCodeNoKnownDAPINodes = 2, -}; - -@class DSChain, DSBlockchainIdentity, DPDocument, DSTransition, DPSTPacket, DPContract, DSDAPIPlatformNetworkService, DSDAPICoreNetworkService, DSPeer, DSSimplifiedMasternodeEntry; - -@interface DSDAPIClient : NSObject - -@property (readonly, nonatomic) DSChain *chain; -@property (nonatomic, nullable, readonly) DSDAPIPlatformNetworkService *DAPIPlatformNetworkService; -@property (nonatomic, nullable, readonly) DSDAPICoreNetworkService *DAPICoreNetworkService; -@property (atomic, readonly) dispatch_queue_t coreNetworkingDispatchQueue; -@property (atomic, readonly) dispatch_queue_t platformMetadataDispatchQueue; - -- (instancetype)initWithChain:(DSChain *)chain NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -- (void)addDAPINodeByAddress:(NSString *)host; - -- (void)removeDAPINodeByAddress:(NSString *)host; - -- (void)getAllStateTransitionsForUser:(DSBlockchainIdentity *)blockchainIdentity completion:(void (^)(NSError *_Nullable error))completion; - -- (void)sendDocument:(DPDocument *)document - forIdentity:(DSBlockchainIdentity *)blockchainIdentity - contract:(DPContract *)contract - completion:(void (^)(NSError *_Nullable error))completion; - -- (void)publishTransition:(DSTransition *)stateTransition - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSError *error))failure; - -- (void)publishTransition:(DSTransition *)stateTransition - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSError *error))failure; - -- (void)checkPingTimesForMasternodes:(NSArray *)masternodes completion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *))completion; -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/DSDAPIClient.m b/DashSync/shared/Models/DAPI/DSDAPIClient.m deleted file mode 100644 index 17526ea26..000000000 --- a/DashSync/shared/Models/DAPI/DSDAPIClient.m +++ /dev/null @@ -1,372 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DSDAPIClient.h" - -#import "DSChain+Protected.h" -#import "DSDAPICoreNetworkService.h" -#import "DSDAPIPlatformNetworkService.h" -#import "DSDashPlatform.h" -#import "DSDocumentTransition.h" -#import "DSIdentitiesManager+Protected.h" -#import "NSError+Dash.h" -#import -#import -#import - -NSErrorDomain const DSDAPIClientErrorDomain = @"DSDAPIClientErrorDomain"; - -#define DAPI_SINGLE_NODE @"54.191.199.25" -#define DAPI_CONNECT_SINGLE_NODE FALSE -#define DAPI_DEFAULT_PUBLISH_TRANSITION_RETRY_COUNT 10 - -@interface DSDAPIClient () - -@property (nonatomic, strong) DSChain *chain; -@property (nonatomic, strong) NSMutableSet *availablePeers; -@property (nonatomic, strong) NSMutableSet *usedPeers; -@property (nonatomic, strong) NSMutableArray *activePlatformServices; -@property (nonatomic, strong) NSMutableArray *activeCoreServices; -@property (atomic, strong) dispatch_queue_t coreNetworkingDispatchQueue; -@property (atomic, strong) dispatch_queue_t platformMetadataDispatchQueue; - -@end - -@implementation DSDAPIClient - -- (instancetype)initWithChain:(DSChain *)chain { - self = [super init]; - if (self) { - _chain = chain; - self.availablePeers = [NSMutableSet set]; - self.activePlatformServices = [NSMutableArray array]; - self.activeCoreServices = [NSMutableArray array]; - self.coreNetworkingDispatchQueue = self.chain.networkingQueue; - self.platformMetadataDispatchQueue = self.chain.dapiMetadataQueue; - } - return self; -} - -//- (void)sendDocument:(DPDocument *)document -// forUser:(DSBlockchainIdentity*)blockchainIdentity -// contract:(DPContract *)contract -// completion:(void (^)(NSError *_Nullable error))completion { -// NSParameterAssert(document); -// NSParameterAssert(contract); -// -// NSArray *documents = @[ document ]; -// -// DSDashPlatform *platform = [DSDashPlatform sharedInstanceForChain:self.chain]; -// -// DSDocumentTransition *transition = [blockchainIdentity documentTransition]; -// -// DPSTPacket *stPacket = [platform.stPacketFactory packetWithContractId:contract.identifier documents:documents]; -// [self sendPacket:stPacket forUser:blockchainIdentity completion:completion]; -//} - -- (void)sendDocument:(DPDocument *)document - forIdentity:(DSBlockchainIdentity *)blockchainIdentity - contract:(DPContract *)contract - completion:(void (^)(NSError *_Nullable error))completion { - NSParameterAssert(document); - NSParameterAssert(contract); - - DSDocumentTransition *documentTransition = [[DSDocumentTransition alloc] initForDocuments:@[document] withTransitionVersion:1 blockchainIdentityUniqueId:blockchainIdentity.uniqueID onChain:self.chain]; - - __weak typeof(self) weakSelf = self; - [blockchainIdentity signStateTransition:documentTransition - completion:^(BOOL success) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion([NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - } - return; - } - - if (success) { - [strongSelf publishTransition:documentTransition - success:^(NSDictionary *_Nonnull successDictionary, BOOL added) { - if (completion) { - completion(nil); - } - } - failure:^(NSError *_Nonnull error) { - if (completion) { - completion(error); - } - }]; - } else { - if (completion) { - NSError *error = [NSError errorWithDomain:DSDAPIClientErrorDomain - code:DSDAPIClientErrorCodeSignTransitionFailed - userInfo:nil]; - completion(error); - } - } - }]; -} - -- (void)getAllStateTransitionsForUser:(DSBlockchainIdentity *)blockchainIdentity completion:(void (^)(NSError *_Nullable error))completion { - // DSDAPINetworkService * service = self.DAPINetworkService; - // if (!service) { - // completion([NSError errorWithDomain:DSDAPIClientErrorDomain - // code:DSDAPIClientErrorCodeNoKnownDAPINodes - // userInfo:@{NSLocalizedDescriptionKey:@"No known DAPI Nodes"}]); - // return; - // } - // [service getUserById:uint256_reverse_hex(blockchainIdentity.registrationTransitionHash) success:^(NSDictionary * _Nonnull blockchainIdentityDictionary) { - // if ([blockchainIdentityDictionary objectForKey:@"subtx"] && [[blockchainIdentityDictionary objectForKey:@"subtx"] isKindOfClass:[NSArray class]]) { - // NSArray * subscriptionTransactions = [blockchainIdentityDictionary objectForKey:@"subtx"]; - // NSMutableArray * oldSubscriptionTransactionHashes = [NSMutableArray array]; - // for (DSTransaction * transaction in blockchainIdentity.allTransitions) { - // [oldSubscriptionTransactionHashes addObject:[NSData dataWithUInt256:transaction.txHash]]; - // } - // NSMutableArray * novelSubscriptionTransactionHashes = [NSMutableArray array]; - // for (NSString * possiblyNewSubscriptionTransactionHashString in subscriptionTransactions) { - // NSData * data = possiblyNewSubscriptionTransactionHashString.hexToData; - // if (![oldSubscriptionTransactionHashes containsObject:data]) { - // [novelSubscriptionTransactionHashes addObject:data]; - // } - // } - // for (NSData * unknownSubscriptionTransactionHash in novelSubscriptionTransactionHashes) { - // //dispatch_semaphore_t sem = dispatch_semaphore_create(0); - // [service getTransactionById:unknownSubscriptionTransactionHash.hexString success:^(NSDictionary * _Nonnull tx) { - // if (tx[@"version"] && tx[@"blockheight"] && tx[@"extraPayload"] && tx[@"valueIn"] && tx[@"valueOut"] && ([tx[@"valueIn"] integerValue] + [tx[@"valueOut"] integerValue] == 0)) { - // DSLogPrivate(@"state transition %@",tx); - // //this is a transition - // NSString * extraPayload = tx[@"extraPayload"]; - // uint16_t version = [tx[@"version"] shortValue]; - // DSTransition * transition = [[DSTransition alloc] initWithVersion:version payloadData:extraPayload.hexToData onChain:blockchainIdentity.wallet.chain]; - // transition.blockHeight = [tx[@"blockheight"] unsignedIntValue]; - // [blockchainIdentity.wallet.specialTransactionsHolder registerTransaction:transition]; - // [blockchainIdentity updateWithTransition:transition save:TRUE]; - // if (completion) { - // completion(nil); - // } - // } - // //dispatch_semaphore_signal(sem); - // } failure:^(NSError * _Nonnull error) { - // if (completion) { - // completion(error); - // } - // //dispatch_semaphore_signal(sem); - // }]; - // //dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - // } - // } - // - // } failure:^(NSError * _Nonnull error) { - // if (completion) { - // completion(error); - // } - // }]; - // -} - -//check ping times of all DAPI nodes -- (void)checkPingTimesForMasternodes:(NSArray *)masternodes completion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *))completion { - dispatch_async(self.platformMetadataDispatchQueue, ^{ - HTTPLoaderFactory *loaderFactory = [DSNetworkingCoordinator sharedInstance].loaderFactory; - __block dispatch_group_t dispatch_group = dispatch_group_create(); - __block NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; - __block NSMutableDictionary *pingTimeDictionary = [NSMutableDictionary dictionary]; - dispatch_semaphore_t dispatchSemaphore = dispatch_semaphore_create(32); - - for (DSSimplifiedMasternodeEntry *masternode in masternodes) { - if (uint128_is_zero(masternode.address)) continue; - if (!masternode.isValid) continue; - dispatch_semaphore_wait(dispatchSemaphore, DISPATCH_TIME_FOREVER); - dispatch_group_enter(dispatch_group); - DSDAPICoreNetworkService *coreNetworkService = [[DSDAPICoreNetworkService alloc] initWithDAPINodeIPAddress:masternode.ipAddressString httpLoaderFactory:loaderFactory usingGRPCDispatchQueue:self.coreNetworkingDispatchQueue onChain:self.chain]; - __block NSDate *time = [NSDate date]; - [coreNetworkService - getStatusWithCompletionQueue:self.platformMetadataDispatchQueue - success:^(NSDictionary *_Nonnull status) { - NSTimeInterval platformPing = -[time timeIntervalSinceNow] * 1000; - pingTimeDictionary[uint256_data(masternode.providerRegistrationTransactionHash)] = @(platformPing); - [masternode setPlatformPing:platformPing at:[NSDate date]]; - dispatch_semaphore_signal(dispatchSemaphore); - dispatch_group_leave(dispatch_group); - } - failure:^(NSError *_Nonnull error) { - errorDictionary[uint256_data(masternode.providerRegistrationTransactionHash)] = error; - dispatch_semaphore_signal(dispatchSemaphore); - dispatch_group_leave(dispatch_group); - }]; - } - - dispatch_group_notify(dispatch_group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - if (completion) { - completion(pingTimeDictionary, errorDictionary); - } - }); - }); -} - -#pragma mark - Peers - -- (void)addDAPINodeByAddress:(NSString *)host { -#if DAPI_CONNECT_SINGLE_NODE - return; -#endif - [self.availablePeers addObject:host]; - DSDAPIPlatformNetworkService *foundNetworkService = nil; - for (DSDAPIPlatformNetworkService *networkService in self.activePlatformServices) { - if ([networkService.ipAddress isEqualToString:host]) { - foundNetworkService = networkService; - break; - } - } - if (!foundNetworkService) { - HTTPLoaderFactory *loaderFactory = [DSNetworkingCoordinator sharedInstance].loaderFactory; - DSDAPIPlatformNetworkService *DAPINetworkService = [[DSDAPIPlatformNetworkService alloc] initWithDAPINodeIPAddress:host httpLoaderFactory:loaderFactory usingGRPCDispatchQueue:self.coreNetworkingDispatchQueue onChain:self.chain]; - [self.activePlatformServices addObject:DAPINetworkService]; - } -} - -- (void)removeDAPINodeByAddress:(NSString *)host { -#if DAPI_CONNECT_SINGLE_NODE - return; -#endif - @synchronized(self) { - [self.availablePeers removeObject:host]; - for (DSDAPIPlatformNetworkService *networkService in [self.activePlatformServices copy]) { - if ([networkService.ipAddress isEqualToString:host]) { - [self.activePlatformServices removeObject:networkService]; - } - } - } -} - -- (DSDAPIPlatformNetworkService *)DAPIPlatformNetworkService { - @synchronized(self) { - if ([self.activePlatformServices count]) { - if ([self.activePlatformServices count] == 1) return [self.activePlatformServices objectAtIndex:0]; //if only 1 service, just use first one - return [self.activePlatformServices objectAtIndex:arc4random_uniform((uint32_t)[self.activePlatformServices count])]; //use a random service - } else if ([self.availablePeers count] || DAPI_CONNECT_SINGLE_NODE) { -#if DAPI_CONNECT_SINGLE_NODE - NSString *peerHost = DAPI_SINGLE_NODE; -#else - NSString *peerHost = self.availablePeers.anyObject; -#endif - HTTPLoaderFactory *loaderFactory = [DSNetworkingCoordinator sharedInstance].loaderFactory; - DSDAPIPlatformNetworkService *DAPINetworkService = [[DSDAPIPlatformNetworkService alloc] initWithDAPINodeIPAddress:peerHost httpLoaderFactory:loaderFactory usingGRPCDispatchQueue:self.coreNetworkingDispatchQueue onChain:self.chain]; - [self.activePlatformServices addObject:DAPINetworkService]; - return DAPINetworkService; - } - return nil; - } -} - -- (DSDAPICoreNetworkService *)DAPICoreNetworkService { - @synchronized(self) { - if ([self.activeCoreServices count]) { - if ([self.activeCoreServices count] == 1) return [self.activeCoreServices objectAtIndex:0]; //if only 1 service, just use first one - return [self.activeCoreServices objectAtIndex:arc4random_uniform((uint32_t)[self.activeCoreServices count])]; //use a random service - } else if ([self.availablePeers count]) { -#if DAPI_CONNECT_SINGLE_NODE - NSString *peerHost = DAPI_SINGLE_NODE; -#else - NSString *peerHost = [[self.availablePeers allObjects] objectAtIndex:arc4random_uniform((uint32_t)[self.availablePeers count])]; -#endif - HTTPLoaderFactory *loaderFactory = [DSNetworkingCoordinator sharedInstance].loaderFactory; - DSDAPICoreNetworkService *DAPINetworkService = [[DSDAPICoreNetworkService alloc] initWithDAPINodeIPAddress:peerHost httpLoaderFactory:loaderFactory usingGRPCDispatchQueue:self.coreNetworkingDispatchQueue onChain:self.chain]; - [self.activeCoreServices addObject:DAPINetworkService]; - return DAPINetworkService; - } - return nil; - } -} - -- (void)publishTransition:(DSTransition *)stateTransition - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSError *error))failure { - //default to 10 attempts - [self publishTransition:stateTransition - completionQueue:self.chain.chainManager.identitiesManager.identityQueue - success:success - failure:failure]; -} - -- (void)publishTransition:(DSTransition *)stateTransition - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSError *error))failure { - //default to 10 attempts - [self publishTransition:stateTransition - retryCount:DAPI_DEFAULT_PUBLISH_TRANSITION_RETRY_COUNT - delay:2 - delayIncrease:1 - currentAttempt:0 - currentErrors:@{} - completionQueue:completionQueue - success:success - failure:^(NSDictionary *_Nonnull errorPerAttempt) { - if (failure) { - failure(errorPerAttempt[@(4)]); - } - }]; -} - -- (void)publishTransition:(DSTransition *)transition - retryCount:(uint32_t)retryCount - delay:(uint32_t)delay - delayIncrease:(uint32_t)delayIncrease - currentAttempt:(uint32_t)currentAttempt - currentErrors:(NSDictionary *)errorPerAttempt - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSDictionary *errorPerAttempt))failure { - DSDAPIPlatformNetworkService *service = self.DAPIPlatformNetworkService; - if (!service) { - NSMutableDictionary *mErrorsPerAttempt = [errorPerAttempt mutableCopy]; - NSError *error = [NSError errorWithDomain:DSDAPIClientErrorDomain - code:DSDAPIClientErrorCodeNoKnownDAPINodes - userInfo:@{NSLocalizedDescriptionKey: @"No known DAPI Nodes"}]; - mErrorsPerAttempt[@(currentAttempt)] = error; - if (retryCount) { - [self publishTransition:transition retryCount:retryCount - 1 delay:delay + delayIncrease delayIncrease:delayIncrease currentAttempt:currentAttempt + 1 currentErrors:mErrorsPerAttempt completionQueue:completionQueue success:success failure:failure]; - } else if (failure) { - failure([mErrorsPerAttempt copy]); - } - return; - } - - [service publishTransition:transition - completionQueue:completionQueue - success:success - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self removeDAPINodeByAddress:service.ipAddress]; - } - NSMutableDictionary *mErrorsPerAttempt = [errorPerAttempt mutableCopy]; - if (error) { - mErrorsPerAttempt[@(currentAttempt)] = error; - } - if (retryCount) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), self.coreNetworkingDispatchQueue, ^{ - [self publishTransition:transition retryCount:retryCount - 1 delay:delay + delayIncrease delayIncrease:delayIncrease currentAttempt:currentAttempt + 1 currentErrors:mErrorsPerAttempt completionQueue:completionQueue success:success failure:failure]; - }); - } else if (failure) { - failure([mErrorsPerAttempt copy]); - } - }]; -} - - -@end diff --git a/DashSync/shared/Models/DAPI/DSPlatformDocumentsRequest.h b/DashSync/shared/Models/DAPI/DSPlatformDocumentsRequest.h deleted file mode 100644 index beb84997d..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformDocumentsRequest.h +++ /dev/null @@ -1,86 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "BigIntTypes.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, DSPlatformDocumentType) -{ - DSPlatformDocumentType_Contract = 1, - DSPlatformDocumentType_Document = 2, -}; - -typedef NS_ENUM(NSUInteger, DSPlatformQueryType) -{ - DSPlatformQueryType_OneElement, - DSPlatformQueryType_IndividualElements, - DSPlatformQueryType_RangeOverValue, - DSPlatformQueryType_RangeOverIndex, -}; - -#define DSPROVE_PLATFORM FALSE -#define DSPROVE_PUSH_PLATFORM FALSE -#define DSPROVE_PLATFORM_SINDEXES FALSE - -@class GetDocumentsRequest, DPContract, DSDirectionalKey, DSPlatformQuery; - -@interface DSPlatformDocumentsRequest : NSObject - -@property (nonatomic, strong) NSPredicate *pathPredicate; -@property (nonatomic, strong) NSPredicate *predicate; -@property (nonatomic, strong) NSArray *sortDescriptors; -@property (nonatomic, strong, nullable) NSData * startAt; -@property (nonatomic, assign) bool startAtIncluded; -@property (nonatomic, assign) uint32_t limit; -@property (nonatomic, assign) BOOL prove; -@property (nonatomic, strong) NSString *tableName; -@property (nonatomic, strong) DPContract *contract; -@property (nonatomic, assign) DSPlatformDocumentType type; -@property (nonatomic, readonly) DSPlatformQueryType queryType; -@property (nonatomic, readonly) NSArray *orderByRanges; -@property (nonatomic, readonly) NSArray *path; -@property (nonatomic, readonly) DSPlatformQuery *expectedResponseQuery; - -+ (instancetype)dpnsRequestForUserId:(NSData *)userId; - -+ (instancetype)dpnsRequestForUsername:(NSString *)username inDomain:(NSString *)domain; - -+ (instancetype)dpnsRequestForUsernames:(NSArray *)usernames inDomain:(NSString *)domain; - -+ (instancetype)dpnsRequestForUsernameStartsWithSearch:(NSString *)usernamePrefix inDomain:(NSString *)domain; - -+ (instancetype)dpnsRequestForUsernameStartsWithSearch:(NSString *)usernamePrefix inDomain:(NSString *)domain startAfter:(NSData* _Nullable)startAfter limit:(uint32_t)limit; - -+ (instancetype)dpnsRequestForPreorderSaltedHashes:(NSArray *)preorderSaltedHashes; - -+ (instancetype)dashpayRequestForContactRequestForSendingUserId:(NSData *)userId toRecipientUserId:(NSData *)toUserId; - -+ (instancetype)dashpayRequestForContactRequestsForSendingUserId:(NSData *)userId since:(NSTimeInterval)timestamp startAfter:(NSData* _Nullable)startAfter; - -+ (instancetype)dashpayRequestForContactRequestsForRecipientUserId:(NSData *)userId since:(NSTimeInterval)timestamp startAfter:(NSData* _Nullable)startAfter; - -+ (instancetype)dashpayRequestForProfileWithUserId:(NSData *)userId; - -+ (instancetype)dashpayRequestForProfilesWithUserIds:(NSArray *)userIds; - -- (GetDocumentsRequest *)getDocumentsRequest; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/DSPlatformDocumentsRequest.m b/DashSync/shared/Models/DAPI/DSPlatformDocumentsRequest.m deleted file mode 100644 index 1fde02804..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformDocumentsRequest.m +++ /dev/null @@ -1,280 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSPlatformDocumentsRequest.h" -#import "DPContract.h" -#import "DSDirectionalKey.h" -#import "DSDirectionalRange.h" -#import "DSPlatformQuery.h" -#import "NSData+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSObject+DSCborEncoding.h" -#import "NSPredicate+CBORData.h" -#import "NSString+Bitcoin.h" -#import -#import - -@interface DSPlatformDocumentsRequest () - -@property (nonatomic, readonly) NSData *secondaryIndexPathData; -@property (nonatomic, assign) DSPlatformQueryType queryType; - -@end - -@implementation DSPlatformDocumentsRequest - -- (instancetype)init { - self = [super init]; - return self; -} - -+ (instancetype)dpnsRequestForUsername:(NSString *)username inDomain:(NSString *)domain { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"normalizedParentDomainName == %@", [domain lowercaseString]]; - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"normalizedLabel == %@", [username lowercaseString]]; - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = 1; - platformDocumentsRequest.queryType = DSPlatformQueryType_OneElement; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"domain"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dpnsRequestForUserId:(NSData *)userId { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"records.dashUniqueIdentityId == %@", userId]; - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = 100; - platformDocumentsRequest.queryType = DSPlatformQueryType_RangeOverIndex; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"domain"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dpnsRequestForUsernames:(NSArray *)usernames inDomain:(NSString *)domain { - NSMutableArray *lowercaseUsernames = [NSMutableArray array]; - for (NSString *username in usernames) { - [lowercaseUsernames addObject:[username lowercaseString]]; - } - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"normalizedParentDomainName == %@", [domain lowercaseString]]; - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"normalizedLabel IN %@", lowercaseUsernames]; - platformDocumentsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"normalizedLabel" ascending:YES]]; - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = (uint32_t)usernames.count; - platformDocumentsRequest.queryType = DSPlatformQueryType_IndividualElements; // Many non consecutive elements in the tree - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"domain"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dpnsRequestForUsernameStartsWithSearch:(NSString *)usernamePrefix inDomain:(NSString *)domain { - return [self dpnsRequestForUsernameStartsWithSearch:usernamePrefix inDomain:domain startAfter:nil limit:100]; -} - -+ (instancetype)dpnsRequestForUsernameStartsWithSearch:(NSString *)usernamePrefix inDomain:(NSString *)domain startAfter:(NSData* _Nullable)startAfter limit:(uint32_t)limit { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"normalizedParentDomainName == %@", [domain lowercaseString]]; - if (usernamePrefix.length) { - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"normalizedLabel BEGINSWITH %@", usernamePrefix]; - platformDocumentsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"normalizedLabel" ascending:YES]]; - } - platformDocumentsRequest.startAt = startAfter; - platformDocumentsRequest.startAtIncluded = false; - platformDocumentsRequest.limit = limit; - platformDocumentsRequest.queryType = DSPlatformQueryType_RangeOverValue; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"domain"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dashpayRequestForContactRequestsForSendingUserId:(NSData *)userId since:(NSTimeInterval)timestamp startAfter:(NSData* _Nullable)startAfter { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - uint64_t millisecondTimestamp = timestamp * 1000; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"%K == %@", @"$ownerId", userId]; - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"%K >= %@", @"$createdAt", @(millisecondTimestamp)]; - platformDocumentsRequest.startAt = startAfter; - platformDocumentsRequest.startAtIncluded = false; - platformDocumentsRequest.limit = 100; - platformDocumentsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"$createdAt" ascending:YES]]; - platformDocumentsRequest.queryType = DSPlatformQueryType_RangeOverValue; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"contactRequest"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dashpayRequestForContactRequestsForRecipientUserId:(NSData *)userId since:(NSTimeInterval)timestamp startAfter:(NSData* _Nullable)startAfter { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - uint64_t millisecondTimestamp = timestamp * 1000; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"toUserId == %@", userId]; - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"%K >= %@", @"$createdAt", @(millisecondTimestamp)]; - platformDocumentsRequest.startAt = startAfter; - platformDocumentsRequest.startAtIncluded = false; - platformDocumentsRequest.limit = 100; - platformDocumentsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"$createdAt" ascending:YES]]; - platformDocumentsRequest.queryType = DSPlatformQueryType_RangeOverValue; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"contactRequest"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dashpayRequestForContactRequestForSendingUserId:(NSData *)userId toRecipientUserId:(NSData *)toUserId { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.pathPredicate = [NSPredicate predicateWithFormat:@"%K == %@ && toUserId == %@", @"$ownerId", userId, toUserId]; - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = 100; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.queryType = DSPlatformQueryType_RangeOverIndex; - platformDocumentsRequest.tableName = @"contactRequest"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dashpayRequestForProfileWithUserId:(NSData *)userId { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"$ownerId", userId]; - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = 1; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.queryType = DSPlatformQueryType_OneElement; - platformDocumentsRequest.tableName = @"profile"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dashpayRequestForProfilesWithUserIds:(NSArray *)userIds { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"%K IN %@", @"$ownerId", userIds]; - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = (uint32_t)userIds.count; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.queryType = DSPlatformQueryType_IndividualElements; - platformDocumentsRequest.tableName = @"profile"; - platformDocumentsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"$ownerId" ascending:YES]]; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -+ (instancetype)dpnsRequestForPreorderSaltedHashes:(NSArray *)preorderSaltedHashes { - DSPlatformDocumentsRequest *platformDocumentsRequest = [[DSPlatformDocumentsRequest alloc] init]; - if (preorderSaltedHashes.count == 1) { - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"saltedDomainHash == %@", [preorderSaltedHashes firstObject]]; - platformDocumentsRequest.queryType = DSPlatformQueryType_OneElement; - } else { - platformDocumentsRequest.predicate = [NSPredicate predicateWithFormat:@"saltedDomainHash IN %@", preorderSaltedHashes]; - platformDocumentsRequest.queryType = DSPlatformQueryType_IndividualElements; - platformDocumentsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"saltedDomainHash" ascending:YES]]; - } - platformDocumentsRequest.startAt = nil; - platformDocumentsRequest.limit = (uint32_t)preorderSaltedHashes.count; - platformDocumentsRequest.type = DSPlatformDocumentType_Document; - platformDocumentsRequest.tableName = @"preorder"; - platformDocumentsRequest.prove = DSPROVE_PLATFORM_SINDEXES; - return platformDocumentsRequest; -} - -- (NSData *)whereData { - NSPredicate *predicate = nil; - if (self.pathPredicate && self.predicate) { - predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[self.pathPredicate, self.predicate]]; - } else if (self.pathPredicate) { - predicate = self.pathPredicate; - } else if (self.predicate) { - predicate = self.predicate; - } else { - NSAssert(NO, @"We should always have a predicate or a path predicate"); - return nil; - } - return [predicate dashPlatormWhereData]; -} - -- (NSData *)orderByData { - return [[self orderByRanges] ds_cborEncodedObject]; -} - -- (GetDocumentsRequest *)getDocumentsRequest { - NSAssert(self.tableName, @"Table name must be set"); - GetDocumentsRequest *getDocumentsRequest = [[GetDocumentsRequest alloc] init]; - getDocumentsRequest.documentType = self.tableName; - getDocumentsRequest.dataContractId = uint256_data(self.contract.contractId); - getDocumentsRequest.where = [self whereData]; - if ([self.sortDescriptors count]) { - getDocumentsRequest.orderBy = [self orderByData]; - } - if (self.startAt) { - if (self.startAtIncluded) { - getDocumentsRequest.startAt = self.startAt; - } else { - getDocumentsRequest.startAfter = self.startAt; - } - } - getDocumentsRequest.limit = self.limit; - getDocumentsRequest.prove = self.prove; - DSLog(@"Sending request to Contract %@", getDocumentsRequest.dataContractId.base58String); - return getDocumentsRequest; -} - -- (NSArray *)orderByRanges { - NSMutableArray *sortDescriptorsArray = [NSMutableArray array]; - for (NSSortDescriptor *sortDescriptor in self.sortDescriptors) { - [sortDescriptorsArray addObject:@[sortDescriptor.key, sortDescriptor.ascending?@"asc":@"desc"]]; - } - return [sortDescriptorsArray copy]; -} - -- (NSData *)secondaryIndexPathData { - return [self.predicate secondaryIndexPathForQueryType:self.queryType]; -} - -- (NSArray *)path { - NSMutableArray *paths = [NSMutableArray array]; - // First we need to add the documents tree - [paths addObject:[NSData dataWithUInt8:DSPlatformDictionary_Documents]]; - // Then we need to add the contract id - [paths addObject:uint256_data(self.contract.contractId)]; - // Then we need to add the secondary index - [paths addObject:self.secondaryIndexPathData]; - - return [paths copy]; -} - -- (DSPlatformQuery *)expectedResponseQuery { - return nil; - // switch (self.queryType) { - // case DSPlatformQueryType_OneElement: - // return [DSPlatformQuery platformQueryForKeys:@[[self.predicate singleElementQueryKey]] inPath:self.path]; - // case DSPlatformQueryType_IndividualElements: - // return [DSPlatformQuery platformQueryForKeys:[self.predicate multipleElementQueryKey] inPath:self.path]; - // case DSPlatformQueryType_RangeOverValue: - // return [DSPlatformQuery platformQueryDocumentTreeQuery:self.predicate.platformTreeQuery]; - // case DSPlatformQueryType_RangeOverIndex: - // { - // //Todo, this might be wrong, need to think about it more - // //DSDirectionalRange * indexRange = [[DSDirectionalRange alloc] initForKey:nil withLowerBounds:[NSData dataWithUInt32:self.startAt] ascending:YES includeLowerBounds:YES]; - // //return [DSPlatformQuery platformQueryForRanges:@[indexRange] inPath:self.pathPredicate]; - // return nil; - // } - // } -} - -@end diff --git a/DashSync/shared/Models/DAPI/DSPlatformPathQuery.h b/DashSync/shared/Models/DAPI/DSPlatformPathQuery.h deleted file mode 100644 index 53d8180b8..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformPathQuery.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "dash_shared_core.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DSDirectionalRange; - -@interface DSPlatformPathQuery : NSObject - -@property (nonatomic, readonly) NSArray *path; -@property (nonatomic, readonly) NSArray *platformQueryKeys; -@property (nonatomic, readonly) NSArray *platformQueryKeyRanges; -@property (nonatomic, readonly) Keys *keys; - -+ (DSPlatformPathQuery *)platformPath:(NSArray *)path queryForKeys:(NSArray *)keys; - -+ (DSPlatformPathQuery *)platformPath:(NSArray *)path queryForRanges:(NSArray *)keys; - -+ (DSPlatformPathQuery *)platformPath:(NSArray *)path queryForKeys:(NSArray *)keys andRanges:(NSArray *)keys; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/DSPlatformPathQuery.m b/DashSync/shared/Models/DAPI/DSPlatformPathQuery.m deleted file mode 100644 index fc364ea70..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformPathQuery.m +++ /dev/null @@ -1,103 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSPlatformPathQuery.h" -#import "DSDirectionalRange.h" - -@interface DSPlatformPathQuery () - -@property (nonatomic, strong) NSArray *path; -@property (nonatomic, strong) NSArray *platformQueryKeys; -@property (nonatomic, strong) NSArray *platformQueryKeyRanges; -@property (nonatomic, assign) Keys *keys; - -@end - -@implementation DSPlatformPathQuery - -+ (DSPlatformPathQuery *)platformPath:(NSArray *)path queryForKeys:(NSArray *)keys { - return [[self alloc] initWithPath:(NSArray *)path forKeys:keys andRanges:nil]; -} - -+ (DSPlatformPathQuery *)platformPath:(NSArray *)path queryForRanges:(NSArray *)keyRanges { - return [[self alloc] initWithPath:(NSArray *)path forKeys:nil andRanges:keyRanges]; -} - -+ (DSPlatformPathQuery *)platformPath:(NSArray *)path queryForKeys:(NSArray *)keys andRanges:(NSArray *)keyRanges { - return [[self alloc] initWithPath:(NSArray *)path forKeys:keys andRanges:keyRanges]; -} - -- (instancetype)initWithPath:(NSArray *)path forKeys:(NSArray *)keys andRanges:(NSArray *)keyRanges { - self = [super init]; - if (self) { - self.path = path; - self.platformQueryKeys = keys; - self.platformQueryKeyRanges = keyRanges; - [self createMerkKeys]; - } - return self; -} - -- (void)createMerkKeys { - Keys *k = malloc(sizeof(Keys)); - k->element_count = self.platformQueryKeys.count + self.platformQueryKeyRanges.count; - k->elements = malloc(k->element_count * sizeof(Query *)); - int i = 0; - for (NSData *data in self.platformQueryKeys) { - Query *query = malloc(sizeof(Query)); - query->key_length = data.length; - query->key = malloc(data.length); - query->key_end_length = 0; - memcpy(query->key, data.bytes, data.length); - k->elements[i * sizeof(Query *)] = query; - i++; - } - for (DSDirectionalRange *range in self.platformQueryKeyRanges) { - NSData *startKey = range.lowerBoundsValue; - NSData *endKey = range.upperBoundsValue; - Query *query = malloc(sizeof(Query)); - query->key_length = startKey.length; - query->key = malloc(startKey.length); - memcpy(query->key, startKey.bytes, startKey.length); - query->key_end_length = endKey.length; - query->key_end = malloc(endKey.length); - memcpy(query->key_end, endKey.bytes, endKey.length); - k->elements[i * sizeof(Query *)] = query; - i++; - } - self.keys = k; -} - -- (void)destroyMerkKeys { - Keys *k = self.keys; - for (int i = 0; i < k->element_count; i++) { - Query *q = k->elements[i * sizeof(Query *)]; - free(q->key); - if (q->key_end_length) { - free(q->key_end); - } - free(q); - } - free(k->elements); - free(k); -} - -- (void)dealloc { - [self destroyMerkKeys]; -} - -@end diff --git a/DashSync/shared/Models/DAPI/DSPlatformQuery.h b/DashSync/shared/Models/DAPI/DSPlatformQuery.h deleted file mode 100644 index 32d3d8f33..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformQuery.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSPlatformTreeQuery.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, DSPlatformDictionary) -{ - DSPlatformDictionary_Contracts = 3, - DSPlatformDictionary_Documents = 4, - DSPlatformDictionary_Identities = 1, - DSPlatformDictionary_PublicKeyHashesToIdentityIds = 2, -}; - -@class DPDocument, DSDirectionalRange; - -@interface DSPlatformQuery : NSObject - -@property (nonatomic, readonly) NSDictionary *treeQueries; - -+ (DSPlatformQuery *)platformQueryForIdentityID:(NSData *)identityID; -+ (DSPlatformQuery *)platformQueryForContractID:(NSData *)contractID; -+ (DSPlatformQuery *)platformQueryForGetContractsByContractIDs:(NSArray *)contractIDs; -+ (DSPlatformQuery *)platformQueryForIndividualDocumentKeys:(NSArray *)documentKeys inPath:(NSArray *)path; -+ (DSPlatformQuery *)platformQueryForDocuments:(NSArray *)documents; -+ (DSPlatformQuery *)platformQueryDocumentTreeQuery:(DSPlatformTreeQuery *)documentTreeQuery; -+ (DSPlatformQuery *)platformQueryForKeys:(NSArray *)keys inPath:(NSArray *)path; -+ (DSPlatformQuery *)platformQueryForRanges:(NSArray *)ranges inPath:(NSArray *)path; -+ (DSPlatformQuery *)platformQueryForGetIdentityIDsByPublicKeyHashes:(NSArray *)publicKeyHashes; -+ (DSPlatformQuery *)platformQueryForGetIdentitiesByPublicKeyHashes:(NSArray *)publicKeyHashes; - -- (DSPlatformTreeQuery *)treeQueryForType:(DSPlatformDictionary)treeType; - -- (BOOL)verifyPublicKeyHashesForIdentityDictionaries:(NSArray *)identities; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/DSPlatformQuery.m b/DashSync/shared/Models/DAPI/DSPlatformQuery.m deleted file mode 100644 index 6baba5e7c..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformQuery.m +++ /dev/null @@ -1,153 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSPlatformQuery.h" -#import "DPDocument.h" -#import "DSDocumentType.h" -#import "DSPlatformDocumentsRequest.h" -#import "DSPlatformPathQuery.h" -#import "DSPlatformTreeQuery.h" -#import "NSData+Dash.h" - -@interface DSPlatformQuery () - -@property (nonatomic, strong) NSDictionary *treeQueries; - -@end - -@implementation DSPlatformQuery - -+ (DSPlatformQuery *)platformQueryForIdentityID:(NSData *)identityID { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformTreeQuery *identitiesQuery = [DSPlatformTreeQuery platformTreeQueryForKeys:@[identityID]]; - query.treeQueries = @{@(DSPlatformDictionary_Identities): identitiesQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForContractID:(NSData *)contractID { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformTreeQuery *contractsQuery = [DSPlatformTreeQuery platformTreeQueryForKeys:@[contractID]]; - query.treeQueries = @{@(DSPlatformDictionary_Contracts): contractsQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForIndividualDocumentKeys:(NSArray *)documentKeys inPath:(NSArray *)path { - //todo improve when we have secondary indexes - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformTreeQuery *documentKeysQuery = [DSPlatformTreeQuery platformTreeQueryForKeys:documentKeys]; - query.treeQueries = @{@(DSPlatformDictionary_Documents): documentKeysQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForGetIdentityIDsByPublicKeyHashes:(NSArray *)publicKeyHashes { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformTreeQuery *identitiesPublicKeyHashesQuery = [DSPlatformTreeQuery platformTreeQueryForKeys:publicKeyHashes]; - query.treeQueries = @{@(DSPlatformDictionary_PublicKeyHashesToIdentityIds): identitiesPublicKeyHashesQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForGetIdentitiesByPublicKeyHashes:(NSArray *)publicKeyHashes { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformTreeQuery *identitiesPublicKeyHashesQuery = [DSPlatformTreeQuery platformTreeQueryForKeys:publicKeyHashes]; - query.treeQueries = @{@(DSPlatformDictionary_PublicKeyHashesToIdentityIds): identitiesPublicKeyHashesQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForGetContractsByContractIDs:(NSArray *)contractIDs { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformTreeQuery *contractsQuery = [DSPlatformTreeQuery platformTreeQueryForKeys:contractIDs]; - query.treeQueries = @{@(DSPlatformDictionary_Contracts): contractsQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForDocuments:(NSArray *)documents { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - // We should group all documents of the same type - NSMutableDictionary *> *keysByTypePath = [NSMutableDictionary dictionary]; - NSMutableDictionary *> *pathsByTypePath = [NSMutableDictionary dictionary]; - for (DPDocument *document in documents) { - NSData *serializedPath = document.documentType.serializedPath; - if (![keysByTypePath objectForKey:serializedPath]) { - keysByTypePath[serializedPath] = [NSMutableArray array]; - pathsByTypePath[serializedPath] = document.documentType.path; - } - [keysByTypePath[serializedPath] addObject:document.mainIndexKey]; - } - NSMutableArray *queryPaths = [NSMutableArray array]; - for (NSData *documentType in keysByTypePath) { - NSArray *path = pathsByTypePath[documentType]; - NSArray *keys = keysByTypePath[documentType]; - DSPlatformPathQuery *pathQuery = [DSPlatformPathQuery platformPath:path queryForKeys:keys]; - [queryPaths addObject:pathQuery]; - } - DSPlatformTreeQuery *documentKeysQuery = [DSPlatformTreeQuery platformTreeQueryForPaths:queryPaths]; - query.treeQueries = @{@(DSPlatformDictionary_Documents): documentKeysQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryDocumentTreeQuery:(DSPlatformTreeQuery *)documentTreeQuery { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - query.treeQueries = @{@(DSPlatformDictionary_Documents): documentTreeQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForKeys:(NSArray *)keys inPath:(NSArray *)path { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformPathQuery *pathQuery = [DSPlatformPathQuery platformPath:path queryForKeys:keys]; - DSPlatformTreeQuery *documentKeysQuery = [DSPlatformTreeQuery platformTreeQueryForPaths:@[pathQuery]]; - query.treeQueries = @{@(DSPlatformDictionary_Documents): documentKeysQuery}; - return query; -} - -+ (DSPlatformQuery *)platformQueryForRanges:(NSArray *)ranges inPath:(NSArray *)path { - DSPlatformQuery *query = [[DSPlatformQuery alloc] init]; - DSPlatformPathQuery *pathQuery = [DSPlatformPathQuery platformPath:path queryForRanges:ranges]; - DSPlatformTreeQuery *documentKeysQuery = [DSPlatformTreeQuery platformTreeQueryForPaths:@[pathQuery]]; - query.treeQueries = @{@(DSPlatformDictionary_Documents): documentKeysQuery}; - return query; -} - - -- (DSPlatformTreeQuery *)treeQueryForType:(DSPlatformDictionary)treeType { - return [self.treeQueries objectForKey:@(treeType)]; -} - -- (BOOL)verifyPublicKeyHashesForIdentityDictionaries:(NSArray *)identities { - DSPlatformTreeQuery *identitiesPublicKeyHashesQuery = self.treeQueries[@(DSPlatformDictionary_PublicKeyHashesToIdentityIds)]; - NSMutableArray *publicKeyHashes = [identitiesPublicKeyHashesQuery.platformQueryKeys mutableCopy]; - for (NSDictionary *identityDictionary in identities) { - NSArray *publicKeys = identityDictionary[@"publicKeys"]; - // We need to find the key with id 0 - for (NSDictionary *publicKeyDictionary in publicKeys) { - if ([publicKeyDictionary[@"id"] isEqual:@(0)]) { - NSData *keyData = publicKeyDictionary[@"data"]; - NSData *keyHash = uint160_data([keyData hash160]); - if ([publicKeyHashes containsObject:keyHash]) { - [publicKeyHashes removeObject:keyHash]; - } else { - return NO; - } - } - } - } - DSPlatformTreeQuery *identitiesPublicKeyHashesQueryAfterChanges = [DSPlatformTreeQuery platformTreeQueryForKeys:publicKeyHashes]; - self.treeQueries = @{@(DSPlatformDictionary_PublicKeyHashesToIdentityIds): identitiesPublicKeyHashesQueryAfterChanges}; - return YES; -} - - -@end diff --git a/DashSync/shared/Models/DAPI/DSPlatformTreeQuery.h b/DashSync/shared/Models/DAPI/DSPlatformTreeQuery.h deleted file mode 100644 index a96d9d811..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformTreeQuery.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "dash_shared_core.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DSPlatformPathQuery; - -@interface DSPlatformTreeQuery : NSObject - -@property (nonatomic, readonly) NSArray *platformQueryPaths; -@property (nonatomic, readonly) NSArray *platformQueryKeys; -@property (nonatomic, readonly) NSArray *> *platformQueryKeyRanges; -@property (nonatomic, readonly) Keys *keys; - -+ (DSPlatformTreeQuery *)platformTreeQueryForPaths:(NSArray *)pathQueries; - -+ (DSPlatformTreeQuery *)platformTreeQueryForKeys:(NSArray *)keys; - -+ (DSPlatformTreeQuery *)platformTreeQueryForRanges:(NSArray *> *)keys; - -+ (DSPlatformTreeQuery *)platformTreeQueryForKeys:(NSArray *)keys andRanges:(NSArray *> *)keys; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/DSPlatformTreeQuery.m b/DashSync/shared/Models/DAPI/DSPlatformTreeQuery.m deleted file mode 100644 index 64c1f3d66..000000000 --- a/DashSync/shared/Models/DAPI/DSPlatformTreeQuery.m +++ /dev/null @@ -1,124 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSPlatformTreeQuery.h" -#import "NSMutableData+Dash.h" - -@interface DSPlatformTreeQuery () - -@property (nonatomic, strong) NSArray *platformQueryPaths; -@property (nonatomic, strong) NSArray *platformQueryKeys; -@property (nonatomic, strong) NSArray *> *platformQueryKeyRanges; -@property (nonatomic, assign) Keys *keys; -@property (nonatomic, assign) bool createdKeys; - -@end - -@implementation DSPlatformTreeQuery - -+ (DSPlatformTreeQuery *)platformTreeQueryForKeys:(NSArray *)keys { - return [[self alloc] initWithKeys:keys andRanges:nil]; -} - -+ (DSPlatformTreeQuery *)platformTreeQueryForRanges:(NSArray *> *)keyRanges { - return [[self alloc] initWithKeys:nil andRanges:keyRanges]; -} - -+ (DSPlatformTreeQuery *)platformTreeQueryForKeys:(NSArray *)keys andRanges:(NSArray *> *)keyRanges { - return [[self alloc] initWithKeys:keys andRanges:keyRanges]; -} - -// This is for secondary index trees -+ (DSPlatformTreeQuery *)platformTreeQueryForPaths:(NSArray *)pathQueries { - return [[self alloc] initWithPathQueries:pathQueries]; -} - -- (instancetype)initWithPathQueries:(NSArray *)pathQueries { - self = [super init]; - if (self) { - self.platformQueryPaths = pathQueries; - self.platformQueryKeyRanges = @[]; - self.platformQueryKeys = @[]; - [self createMerkKeys]; - } - return self; -} - -- (instancetype)initWithKeys:(NSArray *)keys andRanges:(NSArray *> *)keyRanges { - self = [super init]; - if (self) { - self.platformQueryKeys = keys; - self.platformQueryKeyRanges = keyRanges; - [self createMerkKeys]; - } - return self; -} - -- (void)createMerkKeys { - Keys *k = malloc(sizeof(Keys)); - k->element_count = self.platformQueryKeys.count + self.platformQueryKeyRanges.count; - k->elements = malloc(k->element_count * sizeof(Query *)); - int i = 0; - for (NSData *data in self.platformQueryKeys) { - Query *query = malloc(sizeof(Query)); - query->key_length = data.length; - query->key = malloc(data.length); - query->key_end_length = 0; - memcpy(query->key, data.bytes, data.length); - k->elements[i] = query; - i++; - } - for (NSArray *range in self.platformQueryKeyRanges) { - NSData *startKey = range.firstObject; - NSData *endKey = range.lastObject; - Query *query = malloc(sizeof(Query)); - query->key_length = startKey.length; - query->key = malloc(startKey.length); - memcpy(query->key, startKey.bytes, startKey.length); - query->key_end_length = endKey.length; - query->key_end = malloc(endKey.length); - memcpy(query->key_end, endKey.bytes, endKey.length); - k->elements[i] = query; - i++; - } - self.keys = k; - self.createdKeys = true; -} - -- (void)destroyMerkKeys { - if (self.createdKeys) { - Keys *k = self.keys; - for (int i = 0; i < k->element_count; i++) { - Query *q = k->elements[i]; - free(q->key); - if (q->key_end_length) { - free(q->key_end); - } - free(q); - } - free(k->elements); - free(k); - } else { - self.createdKeys = false; - } -} - -- (void)dealloc { - [self destroyMerkKeys]; -} - -@end diff --git a/DashSync/shared/Models/DAPI/NSPredicate+CBORData.h b/DashSync/shared/Models/DAPI/NSPredicate+CBORData.h deleted file mode 100644 index b86cbdd61..000000000 --- a/DashSync/shared/Models/DAPI/NSPredicate+CBORData.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSPlatformDocumentsRequest.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DSPlatformTreeQuery; - -typedef NS_ENUM(NSUInteger, NSPredicateCBORDataOptions) -{ - NSPredicateCBORDataOptions_None = 0, - NSPredicateCBORDataOptions_DataToBase64 = 1 -}; - -@interface NSPredicate (CBORData) - -- (NSData *)dashPlatormWhereData; -- (NSData *)singleElementQueryKey; -- (NSArray *)multipleElementQueryKey; -- (DSPlatformTreeQuery *)platformTreeQuery; -- (NSData *)secondaryIndexPathForQueryType:(DSPlatformQueryType)queryType; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/NSPredicate+CBORData.m b/DashSync/shared/Models/DAPI/NSPredicate+CBORData.m deleted file mode 100644 index c39c8f25e..000000000 --- a/DashSync/shared/Models/DAPI/NSPredicate+CBORData.m +++ /dev/null @@ -1,262 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSDirectionalRange.h" -#import "DSPlatformTreeQuery.h" -#import "NSData+Dash.h" -#import "NSObject+DSCborEncoding.h" -#import "NSPredicate+CBORData.h" - - -@implementation NSPredicate (CBORData) - -- (NSArray *)whereClauseArray { - return [self whereClauseArrayWithOptions:NSPredicateCBORDataOptions_None]; -} - -- (NSArray *)whereClauseArrayWithOptions:(NSPredicateCBORDataOptions)options { - if ([self isMemberOfClass:[NSCompoundPredicate class]]) { - return [self whereClauseNestedArrayWithOptions:options]; - } else { - return @[[self whereClauseNestedArrayWithOptions:options]]; - } -} - -- (NSData *)singleElementQueryKey { - NSAssert(![self isMemberOfClass:[NSCompoundPredicate class]], @"This should only be queried on non compound queries"); - NSAssert([self isKindOfClass:[NSComparisonPredicate class]], @"This should only be queried on a comparison predicate"); - NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)self; - NSAssert(comparisonPredicate.predicateOperatorType == NSEqualToPredicateOperatorType, @"This should only be queried on a comparison predicate"); - NSExpression *leftExpression = comparisonPredicate.leftExpression; - NSExpression *rightExpression = comparisonPredicate.rightExpression; - id key; - switch (leftExpression.expressionType) { - case NSConstantValueExpressionType: - key = leftExpression.constantValue; - break; - case NSKeyPathExpressionType: { - switch (rightExpression.expressionType) { - case NSConstantValueExpressionType: - key = rightExpression.constantValue; - break; - case NSKeyPathExpressionType: - NSAssert(NO, @"We must be querying a value"); - break; - case NSVariableExpressionType: - key = rightExpression.variable; - break; - - default: - NSAssert(FALSE, @"Not supported yet"); - break; - } - } break; - case NSVariableExpressionType: - key = leftExpression.variable; - break; - - default: - NSAssert(FALSE, @"Not supported yet"); - break; - } - if ([key isKindOfClass:[NSData class]]) { - return key; - } else if ([key isKindOfClass:[NSString class]]) { - return [(NSString *)key dataUsingEncoding:NSUTF8StringEncoding]; - } else if ([key isKindOfClass:[NSNumber class]]) { - return [NSData dataWithUInt64:[key unsignedLongLongValue]]; - } else { - NSAssert(FALSE, @"Not supported key type"); - return nil; - } -} - -- (NSArray *)multipleElementQueryKey { - NSAssert(![self isMemberOfClass:[NSCompoundPredicate class]], @"This should only be queried on non compound queries"); - NSAssert([self isKindOfClass:[NSComparisonPredicate class]], @"This should only be queried on a comparison predicate"); - NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)self; - NSAssert(comparisonPredicate.predicateOperatorType == NSInPredicateOperatorType, @"This should only be queried on a comparison predicate"); - NSExpression *leftExpression = comparisonPredicate.leftExpression; - NSExpression *rightExpression = comparisonPredicate.rightExpression; - id key; - switch (leftExpression.expressionType) { - case NSConstantValueExpressionType: - key = leftExpression.constantValue; - break; - case NSKeyPathExpressionType: { - switch (rightExpression.expressionType) { - case NSConstantValueExpressionType: - key = rightExpression.constantValue; - break; - case NSKeyPathExpressionType: - NSAssert(NO, @"We must be querying a value"); - break; - case NSVariableExpressionType: - key = rightExpression.variable; - break; - - default: - NSAssert(FALSE, @"Not supported yet"); - break; - } - } break; - case NSVariableExpressionType: - key = leftExpression.variable; - break; - - default: - NSAssert(FALSE, @"Not supported yet"); - break; - } - if ([key isKindOfClass:[NSArray class]]) { - return key; - } else { - NSAssert(FALSE, @"Not supported key type"); - return nil; - } -} - -- (NSArray *)whereClauseNestedArrayWithOptions:(NSPredicateCBORDataOptions)options { - if ([self isMemberOfClass:[NSCompoundPredicate class]]) { - NSMutableArray *mArray = [NSMutableArray array]; - NSCompoundPredicate *compoundPredicate = (NSCompoundPredicate *)self; - NSAssert(compoundPredicate.compoundPredicateType == NSAndPredicateType, @"We currently only support AND predicates"); - for (NSPredicate *predicate in compoundPredicate.subpredicates) { - [mArray addObject:[predicate whereClauseNestedArrayWithOptions:options]]; - } - return mArray; - } else { - NSMutableArray *mArray = [NSMutableArray array]; - NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)self; - NSExpression *leftExpression = comparisonPredicate.leftExpression; - NSExpression *rightExpression = comparisonPredicate.rightExpression; - NSString *operator; - switch (comparisonPredicate.predicateOperatorType) { - case NSEqualToPredicateOperatorType: - operator= @"=="; - break; - case NSLessThanPredicateOperatorType: - operator= @"<"; - break; - case NSLessThanOrEqualToPredicateOperatorType: - operator= @"<="; - break; - case NSGreaterThanPredicateOperatorType: - operator= @">"; - break; - case NSGreaterThanOrEqualToPredicateOperatorType: - operator= @">="; - break; - case NSNotEqualToPredicateOperatorType: - operator= @"!="; - NSAssert(FALSE, @"Not supported yet"); - break; - case NSBeginsWithPredicateOperatorType: - operator= @"startsWith"; - break; - case NSInPredicateOperatorType: - operator= @"in"; - break; - default: - operator= @"!"; - NSAssert(FALSE, @"Not supported yet"); - break; - } - switch (leftExpression.expressionType) { - case NSConstantValueExpressionType: - if (options & NSPredicateCBORDataOptions_DataToBase64 && [rightExpression.constantValue isKindOfClass:[NSData class]]) { - [mArray addObject:[((NSData *)leftExpression.constantValue) base64String]]; - } else { - [mArray addObject:leftExpression.constantValue]; - } - break; - case NSKeyPathExpressionType: - [mArray addObject:leftExpression.keyPath]; - break; - case NSVariableExpressionType: - [mArray addObject:leftExpression.variable]; - break; - - default: - NSAssert(FALSE, @"Not supported yet"); - break; - } - [mArray addObject:operator]; - switch (rightExpression.expressionType) { - case NSConstantValueExpressionType: - if (options & NSPredicateCBORDataOptions_DataToBase64 && [rightExpression.constantValue isKindOfClass:[NSData class]]) { - [mArray addObject:[((NSData *)rightExpression.constantValue) base64String]]; - } else if (options & NSPredicateCBORDataOptions_DataToBase64 && [rightExpression.constantValue isKindOfClass:[NSArray class]]) { - //We might have an array of data - NSMutableArray *base64Array = [NSMutableArray array]; - for (NSObject *member in rightExpression.constantValue) { - if ([member isKindOfClass:[NSData class]]) { - [base64Array addObject:[((NSData *)member) base64String]]; - } else { - [base64Array addObject:member]; - } - } - [mArray addObject:base64Array]; - } else { - [mArray addObject:rightExpression.constantValue]; - } - break; - case NSKeyPathExpressionType: - [mArray addObject:rightExpression.keyPath]; - break; - case NSVariableExpressionType: - [mArray addObject:rightExpression.variable]; - break; - - default: - NSAssert(FALSE, @"Not supported yet"); - break; - } - return mArray; - } -} - -- (DSPlatformTreeQuery *)platformTreeQuery { - //todo - return nil; -} - -- (NSData *)dashPlatormWhereData { - NSArray *array = [self whereClauseArrayWithOptions:NSPredicateCBORDataOptions_DataToBase64]; - NSData *json = [NSJSONSerialization dataWithJSONObject:array options:0 error:nil]; - DSLogPrivate(@"json where %@", [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding]); - DSLogPrivate(@"cbor hex %@", [[self whereClauseArray] ds_cborEncodedObject].hexString); - return [[self whereClauseArray] ds_cborEncodedObject]; -} - -- (NSData *)secondaryIndexPathForQueryType:(DSPlatformQueryType)queryType { - //ToDo: We probably need to not have the last element of the whereClause - NSArray *array = [self whereClauseArrayWithOptions:NSPredicateCBORDataOptions_DataToBase64]; - //This will be replaced - switch (queryType) { - case DSPlatformQueryType_OneElement: - return [[array componentsJoinedByString:@"|"] dataUsingEncoding:NSUTF8StringEncoding]; - case DSPlatformQueryType_IndividualElements: - return [[array componentsJoinedByString:@"|"] dataUsingEncoding:NSUTF8StringEncoding]; - case DSPlatformQueryType_RangeOverValue: - return [[array componentsJoinedByString:@"|"] dataUsingEncoding:NSUTF8StringEncoding]; - case DSPlatformQueryType_RangeOverIndex: - return [[array componentsJoinedByString:@"|"] dataUsingEncoding:NSUTF8StringEncoding]; - } -} - -@end diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIClientFetchDapObjectsOptions.h b/DashSync/shared/Models/DAPI/Networking/DSDAPIClientFetchDapObjectsOptions.h deleted file mode 100644 index e2a166900..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIClientFetchDapObjectsOptions.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 - -NS_ASSUME_NONNULL_BEGIN - -@interface DSDAPIClientFetchDapObjectsOptions : NSObject - -@property (readonly, nullable, copy, nonatomic) NSDictionary *where; -@property (readonly, nullable, copy, nonatomic) NSDictionary *orderBy; -@property (readonly, nullable, strong, nonatomic) NSNumber *limit; -@property (readonly, nullable, strong, nonatomic) NSNumber *startAt; -@property (readonly, nullable, strong, nonatomic) NSNumber *startAfter; - -/** - DSDAPIClientFetchDapObjectsOptions represents Fetch DAP Objects options - - @param where Mongo-like query https://docs.mongodb.com/manual/reference/operator/query/ - @param orderBy Mongo-like sort field https://docs.mongodb.com/manual/reference/method/cursor.sort/ - @param limit How many objects to fetch https://docs.mongodb.com/manual/reference/method/cursor.limit/ - @param startAt Number of objects to skip https://docs.mongodb.com/manual/reference/method/cursor.skip/ - @param startAfter Exclusive skip https://docs.mongodb.com/manual/reference/method/cursor.skip/ - @return An initialized options object - */ -- (instancetype)initWithWhereQuery:(nullable NSDictionary *)where - orderBy:(nullable NSDictionary *)orderBy - limit:(nullable NSNumber *)limit - startAt:(nullable NSNumber *)startAt - startAfter:(nullable NSNumber *)startAfter; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIClientFetchDapObjectsOptions.m b/DashSync/shared/Models/DAPI/Networking/DSDAPIClientFetchDapObjectsOptions.m deleted file mode 100644 index 9e61f1188..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIClientFetchDapObjectsOptions.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DSDAPIClientFetchDapObjectsOptions.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation DSDAPIClientFetchDapObjectsOptions - -- (instancetype)initWithWhereQuery:(nullable NSDictionary *)where - orderBy:(nullable NSDictionary *)orderBy - limit:(nullable NSNumber *)limit - startAt:(nullable NSNumber *)startAt - startAfter:(nullable NSNumber *)startAfter { - self = [super init]; - if (self) { - _where = [where copy]; - _orderBy = [orderBy copy]; - _limit = limit; - _startAt = startAt; - _startAfter = startAfter; - } - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkService.h b/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkService.h deleted file mode 100644 index ff56073fd..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkService.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSDAPICoreNetworkServiceProtocol.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class HTTPLoaderFactory, DSChain; - -@interface DSDAPICoreNetworkService : NSObject - -@property (readonly, nonatomic) NSString *ipAddress; - -- (instancetype)initWithDAPINodeIPAddress:(NSString *)ipAddress httpLoaderFactory:(HTTPLoaderFactory *)httpLoaderFactory usingGRPCDispatchQueue:(dispatch_queue_t)grpcDispatchQueue onChain:(DSChain *)chain NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkService.m b/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkService.m deleted file mode 100644 index 64bfac058..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkService.m +++ /dev/null @@ -1,109 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSDAPICoreNetworkService.h" -#import "DPErrors.h" -#import "DSChain.h" -#import "DSChainLock.h" -#import "DSDAPIGRPCResponseHandler.h" -#import "DSDashPlatform.h" -#import "DSHTTPJSONRPCClient.h" -#import "DSPeer.h" -#import "DSTransactionFactory.h" -#import "NSData+Dash.h" -#import "NSError+Dash.h" - -@interface DSDAPICoreNetworkService () - -@property (strong, nonatomic) DSHTTPJSONRPCClient *httpJSONRPCClient; -@property (strong, nonatomic) Core *gRPCClient; -@property (strong, nonatomic) DSChain *chain; -@property (strong, nonatomic) NSString *ipAddress; -@property (strong, atomic) dispatch_queue_t grpcDispatchQueue; - -@end - -@implementation DSDAPICoreNetworkService - -- (instancetype)initWithDAPINodeIPAddress:(NSString *)ipAddress httpLoaderFactory:(HTTPLoaderFactory *)httpLoaderFactory usingGRPCDispatchQueue:(dispatch_queue_t)grpcDispatchQueue onChain:(DSChain *)chain { - NSParameterAssert(ipAddress); - NSParameterAssert(httpLoaderFactory); - - if (!(self = [super init])) return nil; - - self.ipAddress = ipAddress; - NSURL *dapiNodeURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", ipAddress, chain.standardDapiJRPCPort]]; - _httpJSONRPCClient = [DSHTTPJSONRPCClient clientWithEndpointURL:dapiNodeURL httpLoaderFactory:httpLoaderFactory]; - self.chain = chain; - GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; - // this example does not use TLS (secure channel); use insecure channel instead - options.transportType = GRPCTransportTypeInsecure; - options.userAgentPrefix = [NSString stringWithFormat:@"%@/", USER_AGENT]; - options.timeout = 30; - self.grpcDispatchQueue = grpcDispatchQueue; - - NSString *dapiGRPCHost = [NSString stringWithFormat:@"%@:%d", ipAddress, chain.standardDapiGRPCPort]; - - _gRPCClient = [Core serviceWithHost:dapiGRPCHost callOptions:options]; - - return self; -} - -- (id)getStatusWithCompletionQueue:(dispatch_queue_t)completionQueue success:(void (^)(NSDictionary *status))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(completionQueue); - GetStatusRequest *statusRequest = [[GetStatusRequest alloc] init]; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initWithChain:self.chain requireProof:NO]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getStatusWithMessage:statusRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getTransactionWithHash:(UInt256)transactionHash completionQueue:(dispatch_queue_t)completionQueue success:(void (^)(DSTransaction *transaction))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(completionQueue); - GetTransactionRequest *transactionRequest = [[GetTransactionRequest alloc] init]; - transactionRequest.id_p = uint256_hex(transactionHash); - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initWithChain:self.chain requireProof:NO]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = ^(NSDictionary *successDictionary) { - DSTransaction *transaction = [DSTransactionFactory transactionWithMessage:successDictionary[@"transactionData"] onChain:self.chain]; - //ToDo: set block height properly - transaction.blockHeight = self.chain.lastChainLock ? self.chain.lastChainLock.height : self.chain.lastTerminalBlockHeight; - if (transaction) { - if (success) { - success(transaction); - } - } else if (failure) { - failure([NSError errorWithCode:404 localizedDescriptionKey:@"Transaction does not exist"]); - } - }; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getTransactionWithMessage:transactionRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - - -@end diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkServiceProtocol.h b/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkServiceProtocol.h deleted file mode 100644 index 9b10a537b..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPICoreNetworkServiceProtocol.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "BigIntTypes.h" -#import "DSDAPINetworkServiceRequest.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DSTransaction; - -@protocol DSDAPICoreNetworkServiceProtocol - -- (id)getStatusWithCompletionQueue:(dispatch_queue_t)completionQueue success:(void (^)(NSDictionary *status))success - failure:(void (^)(NSError *error))failure; - -- (id)getTransactionWithHash:(UInt256)transactionHash completionQueue:(dispatch_queue_t)completionQueue success:(void (^)(DSTransaction *transaction))success failure:(void (^)(NSError *error))failure; - - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIGRPCResponseHandler.h b/DashSync/shared/Models/DAPI/Networking/DSDAPIGRPCResponseHandler.h deleted file mode 100644 index fbf7ed4bf..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIGRPCResponseHandler.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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.h" -#import "DSPlatformDocumentsRequest.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DSQuorumEntry, DSPlatformQuery, DSTransition; - -@interface DSDAPIGRPCResponseHandler : NSObject - -@property (atomic, strong) dispatch_queue_t dispatchQueue; -@property (atomic, strong) dispatch_queue_t completionQueue; -@property (nonatomic, strong) NSString *host; //for debuging purposes -@property (nonatomic, strong) DSPlatformDocumentsRequest *request; //for debuging purposes -@property (nonatomic, readonly) DSPlatformQuery *query; - -@property (nonatomic, copy) void (^successHandler)(id successObject); -@property (nonatomic, copy) void (^errorHandler)(NSError *error); - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithChain:(DSChain *)chain requireProof:(BOOL)requireProof; -- (instancetype)initForIdentityRequest:(NSData *)identityId withChain:(DSChain *)chain requireProof:(BOOL)requireProof; -- (instancetype)initForContractRequest:(NSData *)contractId withChain:(DSChain *)chain requireProof:(BOOL)requireProof; - -- (instancetype)initForStateTransition:(DSTransition *)stateTransition withChain:(DSChain *)chain requireProof:(BOOL)requireProof; -- (instancetype)initForDocumentsQueryRequest:(DSPlatformDocumentsRequest *)platformDocumentsRequest withChain:(DSChain *)chain requireProof:(BOOL)requireProof; - -- (instancetype)initForGetContractsByContractIDs:(NSArray *)contractIDs withChain:(DSChain *)chain requireProof:(BOOL)requireProof; -- (instancetype)initForGetIdentityIDsByPublicKeyHashesRequest:(NSArray *)hashes withChain:(DSChain *)chain requireProof:(BOOL)requireProof; -- (instancetype)initForGetIdentitiesByPublicKeyHashesRequest:(NSArray *)hashes withChain:(DSChain *)chain requireProof:(BOOL)requireProof; - -+ (NSDictionary *)verifyAndExtractFromProof:(Proof *)proof withMetadata:(ResponseMetadata *)metaData query:(DSPlatformQuery *_Nullable)query forQuorumEntry:(DSQuorumEntry *)quorumEntry quorumType:(LLMQType)quorumType error:(NSError **)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIGRPCResponseHandler.m b/DashSync/shared/Models/DAPI/Networking/DSDAPIGRPCResponseHandler.m deleted file mode 100644 index 2bc9ef4d9..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIGRPCResponseHandler.m +++ /dev/null @@ -1,623 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSDAPIGRPCResponseHandler.h" -#import "DPContract.h" -#import "DSChain.h" -#import "DSChainManager.h" -#import "DSDocumentTransition.h" -#import "DSMasternodeManager.h" -#import "DSPlatformQuery.h" -#import "DSPlatformRootMerkleTree.h" -#import "DSQuorumEntry.h" -#import "DSTransition.h" -#import "NSData+DSCborDecoding.h" -#import "NSData+DSHash.h" -#import "NSData+DSMerkAVLTree.h" -#import "NSData+Dash.h" -#import "NSError+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSString+Dash.h" -#import -#import -#import -#import - -#define PLATFORM_VERIFY_SIGNATURE 0 - -@interface DSDAPIGRPCResponseHandler () - -@property (nonatomic, strong) id responseObject; -@property (nonatomic, strong) NSError *decodingError; -@property (nonatomic, strong) DSChain *chain; -@property (nonatomic, assign) BOOL requireProof; -@property (nonatomic, strong) DSPlatformQuery *query; - -@end - -@implementation DSDAPIGRPCResponseHandler - -- (instancetype)initWithChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [super init]; - if (self) { - self.chain = chain; - self.requireProof = requireProof; - } - return self; -} - -- (instancetype)initForIdentityRequest:(NSData *)identityId withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = [DSPlatformQuery platformQueryForIdentityID:identityId]; - } - return self; -} - -- (instancetype)initForContractRequest:(NSData *)contractId withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = [DSPlatformQuery platformQueryForContractID:contractId]; - } - return self; -} - -- (instancetype)initForStateTransition:(DSTransition *)stateTransition withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (stateTransition.type == DSPlatformDictionary_Documents) { - DSDocumentTransition *documentTransition = (DSDocumentTransition *)stateTransition; - self.query = documentTransition.expectedResponseQuery; - } else { - } - return self; -} - -- (instancetype)initForDocumentsQueryRequest:(DSPlatformDocumentsRequest *)platformDocumentsRequest withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = platformDocumentsRequest.expectedResponseQuery; - } - return self; -} - -- (instancetype)initForDocumentsRequest:(NSArray *)documentKeys inPath:(NSArray *)path withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = [DSPlatformQuery platformQueryForIndividualDocumentKeys:documentKeys inPath:path]; - } - return self; -} - -- (instancetype)initForGetIdentityIDsByPublicKeyHashesRequest:(NSArray *)hashes withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = [DSPlatformQuery platformQueryForGetIdentityIDsByPublicKeyHashes:hashes]; - } - return self; -} - -- (instancetype)initForGetIdentitiesByPublicKeyHashesRequest:(NSArray *)hashes withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = [DSPlatformQuery platformQueryForGetIdentitiesByPublicKeyHashes:hashes]; - } - return self; -} - -- (instancetype)initForGetContractsByContractIDs:(NSArray *)contractIDs withChain:(DSChain *)chain requireProof:(BOOL)requireProof { - self = [self initWithChain:chain requireProof:requireProof]; - if (self) { - self.query = [DSPlatformQuery platformQueryForGetContractsByContractIDs:contractIDs]; - } - return self; -} - -- (void)parseIdentityMessage:(GetIdentityResponse *)identityResponse { - NSError *error = nil; - if (self.requireProof && !identityResponse.hasProof) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no proof when we requested it"]; - return; - } else if (!self.requireProof && !identityResponse.hasProof) { - NSData *cborData = identityResponse.identity; - uint32_t version = [cborData UInt32AtOffset:0]; - NSData *identityData = [cborData subdataWithRange:NSMakeRange(4, cborData.length - 4)]; - NSDictionary *identityDictionary = [identityData ds_decodeCborError:&error]; - self.responseObject = @{@(DSPlatformStoredMessage_Version): @(version), @(DSPlatformStoredMessage_Item): identityDictionary}; - } else { - Proof *proof = identityResponse.proof; - ResponseMetadata *metaData = identityResponse.metadata; - NSDictionary *dictionaries = [self verifyAndExtractFromProof:proof withMetadata:metaData error:&error]; - if (error) { - self.decodingError = error; - return; - } - NSAssert(dictionaries.count == 1 && [dictionaries objectForKey:@(DSPlatformDictionary_Identities)], @"Dictionary must have 1 internal dictionary corresponding to identities"); - NSDictionary *identitiesDictionary = dictionaries[@(DSPlatformDictionary_Identities)]; - NSAssert(identitiesDictionary.count == 1, @"Identity dictionary must have at most 1 element corresponding to the searched identity"); - id response = [[identitiesDictionary allValues] firstObject]; - if ([response isEqual:@(DSPlatformStoredMessage_NotPresent)]) { - self.responseObject = nil; - } else { - self.responseObject = response; - } - } -} - -- (void)parseDocumentsMessage:(GetDocumentsResponse *)documentsResponse { - NSArray *documentsArray = nil; - NSError *error = nil; - if (self.requireProof && !documentsResponse.hasProof) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no proof when we requested it"]; - } else if (!self.requireProof && !documentsResponse.hasProof) { - documentsArray = documentsResponse.documentsArray; - } else { - Proof *proof = documentsResponse.proof; - ResponseMetadata *metaData = documentsResponse.metadata; - NSError *error = nil; - NSDictionary *documentsDictionary = [self verifyAndExtractFromProof:proof withMetadata:metaData error:&error]; - if (error) { - self.decodingError = error; - return; - } - if (documentsDictionary.count == 0) { - self.responseObject = @[]; - return; - } - - documentsArray = [documentsDictionary allValues]; - } - NSMutableArray *mArray = [NSMutableArray array]; - for (NSData *cborData in documentsArray) { - //uint32_t version = [cborData UInt32AtOffset:0]; - NSData *documentData = [cborData subdataWithRange:NSMakeRange(4, cborData.length - 4)]; - id document = [documentData ds_decodeCborError:&error]; - if (document && !error) { - [mArray addObject:document]; - } - if (error) { - DSLog(@"Decoding error for parseDocumentsMessage cborData %@", cborData); - if (self.request) { - DSLog(@"request was %@", self.request.predicate); - } - break; - } - } - self.responseObject = [mArray copy]; - if (error) { - self.decodingError = error; - } -} - -- (void)parseDataContractMessage:(GetDataContractResponse *)dataContractResponse { - NSData *dataContractData = nil; - NSError *error = nil; - if (self.requireProof && !dataContractResponse.hasProof) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no proof when we requested it"]; - } else if (!self.requireProof && !dataContractResponse.hasProof) { - dataContractData = dataContractResponse.dataContract; - } else { - Proof *proof = dataContractResponse.proof; - ResponseMetadata *metaData = dataContractResponse.metadata; - NSDictionary *dataContractsDictionary = [self verifyAndExtractFromProof:proof withMetadata:metaData error:&error]; - if (error) { - self.decodingError = error; - return; - } - if (dataContractsDictionary.count == 0) { - self.responseObject = nil; - return; - } - dataContractData = dataContractsDictionary.allValues[0]; - } - self.responseObject = [dataContractData ds_decodeCborError:&error]; - if (error) { - DSLog(@"Decoding error for parseDataContractMessage cborData %@", dataContractData); - self.decodingError = error; - } -} - -- (void)parseWaitForStateTransitionResultMessage:(WaitForStateTransitionResultResponse *)waitResponse { - NSArray *documentsArray = nil; - StateTransitionBroadcastError *broadcastError = nil; - if (([waitResponse responsesOneOfCase] & WaitForStateTransitionResultResponse_Responses_OneOfCase_Error) > 0) { - broadcastError = [waitResponse error]; - } - - BOOL hasProof = (([waitResponse responsesOneOfCase] & WaitForStateTransitionResultResponse_Responses_OneOfCase_Proof) > 0); - - if (self.requireProof && !hasProof) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no proof when we requested it"]; - } else if (!self.requireProof && !hasProof) { - // In this case just assume things went well if there's no error - if (broadcastError) { - self.decodingError = [NSError errorWithDomain:@"DashPlatform" - code:broadcastError.code - userInfo:@{NSLocalizedDescriptionKey: broadcastError.message}]; - } else { - self.responseObject = @[]; //Todo - } - } else { - Proof *proof = waitResponse.proof; - ResponseMetadata *metaData = waitResponse.metadata; - NSError *error = nil; - NSDictionary *documentsDictionary = [self verifyAndExtractFromProof:proof withMetadata:metaData error:&error]; - if (error) { - self.decodingError = error; - return; - } - if (documentsDictionary.count == 0) { - self.responseObject = @[]; - return; - } - - documentsArray = [documentsDictionary allValues]; - - NSMutableArray *mArray = [NSMutableArray array]; - for (NSData *cborData in documentsArray) { - id document = [cborData ds_decodeCborError:&error]; - if (document && !error) { - [mArray addObject:document]; - } - if (error) { - DSLog(@"Decoding error for parseDocumentsMessage cborData %@", cborData); - if (self.request) { - DSLog(@"request was %@", self.request.predicate); - } - break; - } - } - self.responseObject = [mArray copy]; - if (error) { - self.decodingError = error; - } - } -} - -- (void)parseGetIdentitiesByPublicKeyHashesMessage:(GetIdentitiesByPublicKeyHashesResponse *)getIdentitiesResponse { - NSAssert(self.chain, @"The chain must be set"); - NSMutableArray *identityDictionaries = [NSMutableArray array]; - - - if (self.requireProof && !getIdentitiesResponse.hasProof) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no proof when we requested it"]; - } else if (!self.requireProof && !getIdentitiesResponse.hasProof) { - NSError *error = nil; - - for (NSData *cborData in getIdentitiesResponse.identitiesArray) { - if (!cborData.length) continue; - - NSArray *arrayOfIdentities = [cborData ds_decodeCborError:&error]; - if (arrayOfIdentities.count == 0) continue; - - NSData *identityData = arrayOfIdentities.firstObject; - uint32_t version = [identityData UInt32AtOffset:0]; - - identityData = [identityData subdataWithRange:NSMakeRange(4, identityData.length - 4)]; - NSDictionary *identityDictionary = [identityData ds_decodeCborError:&error]; - - if (error) { - self.decodingError = error; - return; - } - NSData *identityIdData = [identityDictionary objectForKey:@"id"]; - UInt256 identityId = identityIdData.UInt256; - if (uint256_is_zero(identityId)) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned an incorrect value as an identity ID"]; - return; - } - - NSDictionary *result = @{@(DSPlatformStoredMessage_Version): @(version), - @(DSPlatformStoredMessage_Item): identityDictionary - }; - - [identityDictionaries addObject:result]; - } - self.responseObject = identityDictionaries; - if (error) { - self.decodingError = error; - } - } else { - Proof *proof = getIdentitiesResponse.proof; - ResponseMetadata *metaData = getIdentitiesResponse.metadata; - - NSError *error = nil; - NSDictionary *identitiesDictionaries = [self verifyAndExtractFromProof:proof withMetadata:metaData error:&error]; - if (error) { - self.decodingError = error; - return; - } - NSAssert(identitiesDictionaries.count == 2, @"Identities dictionary must have 2 internal dictionaries"); - NSDictionary *identitiesDictionary = identitiesDictionaries[@(DSPlatformDictionary_Identities)]; - if (identitiesDictionary.count == 0) { - self.responseObject = @[]; - return; - } - self.responseObject = [[identitiesDictionary allValues] copy]; - if (error) { - self.decodingError = error; - } - } -} - -- (void)parseGetIdentityIdsByPublicKeyHashesMessage:(GetIdentityIdsByPublicKeyHashesResponse *)getIdentitiesResponse { - NSAssert(self.chain, @"The chain must be set"); - // GetIdentityIdsByPublicKeyHashesResponse *identitiesByPublicKeyHashesResponse = (GetIdentityIdsByPublicKeyHashesResponse *)message; - NSMutableArray *identityDictionaries = [NSMutableArray array]; - // Proof *proof = identitiesByPublicKeyHashesResponse.proof; - // ResponseMetadata *metaData = identitiesByPublicKeyHashesResponse.metadata; - NSError *error = nil; - // NSDictionary *identitiesDictionary = [self verifyAndExtractFromProof:proof withMetadata:metaData error:&error]; - // if (error) { - // self.decodingError = error; - // return; - // } - - for (NSData *data in getIdentitiesResponse.identityIdsArray) { - if (!data.length) continue; - NSDictionary *identityDictionary = [data ds_decodeCborError:&error]; - if (error) { - self.decodingError = error; - return; - } - NSData *identityIdData = [identityDictionary objectForKey:@"id"]; - UInt256 identityId = identityIdData.UInt256; - if (uint256_is_zero(identityId)) { - self.decodingError = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned an incorrect value as an identity ID"]; - return; - } - [identityDictionaries addObject:identityDictionary]; - } - self.responseObject = identityDictionaries; - if (error) { - self.decodingError = error; - } -} - -- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata { - DSLog(@"didReceiveInitialMetadata"); -} - -- (void)didReceiveProtoMessage:(nullable GPBMessage *)message { - if ([message isMemberOfClass:[GetIdentityResponse class]]) { - [self parseIdentityMessage:(GetIdentityResponse *)message]; - } else if ([message isMemberOfClass:[GetDocumentsResponse class]]) { - [self parseDocumentsMessage:(GetDocumentsResponse *)message]; - } else if ([message isMemberOfClass:[GetDataContractResponse class]]) { - [self parseDataContractMessage:(GetDataContractResponse *)message]; - } else if ([message isMemberOfClass:[WaitForStateTransitionResultResponse class]]) { - [self parseWaitForStateTransitionResultMessage:(WaitForStateTransitionResultResponse *)message]; - } else if ([message isMemberOfClass:[GetIdentitiesByPublicKeyHashesResponse class]]) { - [self parseGetIdentitiesByPublicKeyHashesMessage:(GetIdentitiesByPublicKeyHashesResponse *)message]; - } else if ([message isMemberOfClass:[GetIdentityIdsByPublicKeyHashesResponse class]]) { - [self parseGetIdentityIdsByPublicKeyHashesMessage:(GetIdentityIdsByPublicKeyHashesResponse *)message]; - } else if ([message isMemberOfClass:[GetTransactionResponse class]]) { - GetTransactionResponse *transactionResponse = (GetTransactionResponse *)message; - NSError *error = nil; - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setObject:[transactionResponse transaction] forKey:@"transactionData"]; - self.responseObject = dictionary; - if (error) { - self.decodingError = error; - } - } - DSLog(@"didReceiveProtoMessage"); -} - -- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata - error:(nullable NSError *)error { - NSAssert(self.completionQueue, @"Completion queue must be set"); - if (!error && self.decodingError) { - error = self.decodingError; - } - if (error) { - if (self.errorHandler) { - dispatch_async(self.completionQueue, ^{ - self.errorHandler(error); - }); - } - DSLog(@"error in didCloseWithTrailingMetadata from IP %@ %@", self.host ? self.host : @"Unknown", error); - if (self.request) { - DSLog(@"request contract ID was %@", self.request.contract.base58ContractId); - } - - } else { - if (self.successHandler) { - dispatch_async(self.completionQueue, ^{ - self.successHandler(self.responseObject); - }); - } - } - DSLog(@"didCloseWithTrailingMetadata"); -} - -- (void)didWriteMessage { - DSLog(@"didWriteMessage"); -} - -- (NSDictionary *)verifyAndExtractFromProof:(Proof *)proof withMetadata:(ResponseMetadata *)metaData error:(NSError **)error { - return [DSDAPIGRPCResponseHandler verifyAndExtractFromProof:proof withMetadata:metaData query:self.query onChain:self.chain error:error]; -} - -+ (NSDictionary *)verifyAndExtractFromProof:(Proof *)proof withMetadata:(ResponseMetadata *)metaData query:(DSPlatformQuery *)query onChain:(DSChain *)chain error:(NSError **)error { - NSData *quorumHashData = proof.signatureLlmqHash; - if (!quorumHashData) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no quorum hash data"]; - } - UInt256 quorumHash = quorumHashData.reverse.UInt256; - if (uint256_is_zero(quorumHash)) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned an empty quorum hash"]; - } - DSQuorumEntry *quorumEntry = [chain.chainManager.masternodeManager quorumEntryForPlatformHavingQuorumHash:quorumHash forBlockHeight:metaData.coreChainLockedHeight]; - if (quorumEntry && quorumEntry.verified) { - return [self verifyAndExtractFromProof:proof withMetadata:metaData query:query forQuorumEntry:quorumEntry quorumType:quorum_type_for_platform(chain.chainType) error:error]; - } else if (quorumEntry) { - *error = [NSError errorWithCode:400 descriptionKey:DSLocalizedString(@"Quorum entry %@ found but is not yet verified", uint256_hex(quorumEntry.quorumHash))]; - DSLog(@"quorum entry %@ found but is not yet verified", uint256_hex(quorumEntry.quorumHash)); - } else { - DSLog(@"no quorum entry found for quorum hash %@", uint256_hex(quorumHash)); - } - return nil; -} - -+ (NSDictionary *)verifyAndExtractFromProof:(Proof *)proof withMetadata:(ResponseMetadata *)metaData query:(DSPlatformQuery *)query forQuorumEntry:(DSQuorumEntry *)quorumEntry quorumType:(LLMQType)quorumType error:(NSError **)error { - NSData *signatureData = proof.signature; - if (!signatureData) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no signature data"]; - return nil; - } - UInt768 signature = signatureData.UInt768; - if (uint256_is_zero(signature)) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned an empty or wrongly sized signature"]; - return nil; - } - - return @{}; - -// // We first need to get the merk Root -// -// NSDictionary *identitiesDictionary = nil; -// NSDictionary *documentsDictionary = nil; -// NSDictionary *contractsDictionary = nil; -// NSDictionary *publicKeyHashesToIdentityIdsProofDictionary = nil; -// StoreTreeProofs *proofs = proof.storeTreeProofs; -// -// NSData *identitiesRoot = nil; -// NSData *documentsRoot = nil; -// NSData *contractsRoot = nil; -// NSData *publicKeyHashesToIdentityIdsRoot = nil; -// -// NSMutableDictionary *rootElementsToProve = [NSMutableDictionary dictionary]; -// -// if (proofs.identitiesProof.length > 0) { -// DSPlatformTreeQuery *treeQuery = [query treeQueryForType:DSPlatformDictionary_Identities]; -// identitiesRoot = [proofs.identitiesProof executeProofReturnElementDictionary:&identitiesDictionary query:treeQuery decode:TRUE usesVersion:TRUE error:error]; -// if (*error) { -// return nil; -// } -// if (!treeQuery) { -// DSPlatformTreeQuery *treeQueryForPublicKeyHashesToIdentityIds = [query treeQueryForType:DSPlatformDictionary_PublicKeyHashesToIdentityIds]; -// if (treeQueryForPublicKeyHashesToIdentityIds) { -// NSMutableArray *identitiesWithoutVersions = [NSMutableArray array]; -// for (NSDictionary *identityDictionaryWithVersion in [identitiesDictionary allValues]) { -// if([identityDictionaryWithVersion respondsToSelector:@selector(objectForKey:)]) { -// [identitiesWithoutVersions addObject:[identityDictionaryWithVersion objectForKey:@(DSPlatformStoredMessage_Item)]]; -// } -// } -// BOOL verified = [query verifyPublicKeyHashesForIdentityDictionaries:identitiesWithoutVersions]; -// if (!verified) { -// *error = [NSError errorWithCode:500 localizedDescriptionKey:"Platform returned a proof that does not satisfy our query"]; -// return nil; -// } -// } -// } -// [rootElementsToProve setObject:identitiesRoot -// forKey:@(DSPlatformDictionary_Identities)]; -// } -// -// if (proofs.publicKeyHashesToIdentityIdsProof.length > 0) { -// DSPlatformTreeQuery *treeQuery = [query treeQueryForType:DSPlatformDictionary_PublicKeyHashesToIdentityIds]; -// publicKeyHashesToIdentityIdsRoot = [proofs.publicKeyHashesToIdentityIdsProof executeProofReturnElementDictionary:&publicKeyHashesToIdentityIdsProofDictionary query:treeQuery decode:FALSE usesVersion:FALSE error:error]; -// if (*error) { -// return nil; -// } -// [rootElementsToProve setObject:publicKeyHashesToIdentityIdsRoot -// forKey:@(DSPlatformDictionary_PublicKeyHashesToIdentityIds)]; -// } -// -// if (proofs.documentsProof.length > 0) { -// DSPlatformTreeQuery *treeQuery = [query treeQueryForType:DSPlatformDictionary_Documents]; -// documentsRoot = [proofs.documentsProof executeProofReturnElementDictionary:&documentsDictionary query:treeQuery decode:TRUE usesVersion:TRUE error:error]; -// if (*error) { -// return nil; -// } -// [rootElementsToProve setObject:documentsRoot -// forKey:@(DSPlatformDictionary_Documents)]; -// } -// -// if (proofs.dataContractsProof.length > 0) { -// DSPlatformTreeQuery *treeQuery = [query treeQueryForType:DSPlatformDictionary_Contracts]; -// contractsRoot = [proofs.dataContractsProof executeProofReturnElementDictionary:&contractsDictionary query:treeQuery decode:TRUE usesVersion:TRUE error:error]; -// if (*error) { -// return nil; -// } -// [rootElementsToProve setObject:contractsRoot -// forKey:@(DSPlatformDictionary_Contracts)]; -// } -// -// DSPlatformRootMerkleTree *merkleTree = [DSPlatformRootMerkleTree merkleTreeWithElementsToProve:rootElementsToProve proofData:proof.rootTreeProof hashFunction:DSMerkleTreeHashFunction_BLAKE3 fixedElementCount:6]; -// -// UInt256 stateHash = merkleTree.merkleRoot; -// if (uint256_is_zero(stateHash)) { -// *error = [NSError errorWithCode:500 localizedDescriptionKey:"Platform returned an incorrect rootTreeProof"]; -// return nil; -// } -// -// -//#if PLATFORM_VERIFY_SIGNATURE -// NSMutableData *stateData = [NSMutableData data]; -// [stateData appendInt64:metaData.height - 1]; -// [stateData appendUInt256:stateHash]; -// UInt256 stateMessageHash = [stateData SHA256]; -// BOOL signatureVerified = [self verifyStateSignature:signature forStateMessageHash:stateMessageHash height:metaData.height - 1 againstQuorum:quorumEntry quorumType:quorumType]; -// if (!signatureVerified) { -// *error = [NSError errorWithCode:500 localizedDescriptionKey:"Platform returned an empty or wrongly sized signature"]; -// DSLog(@"unable to verify platform signature"); -// return nil; -// } -//#endif -// -// NSMutableDictionary *elementsDictionary = [NSMutableDictionary dictionary]; -// if (identitiesDictionary) { -// [elementsDictionary setObject:identitiesDictionary forKey:@(DSPlatformDictionary_Identities)]; -// } -// if (documentsDictionary) { -// [elementsDictionary setObject:documentsDictionary forKey:@(DSPlatformDictionary_Documents)]; -// } -// if (contractsDictionary) { -// [elementsDictionary setObject:contractsDictionary forKey:@(DSPlatformDictionary_Contracts)]; -// } -// if (publicKeyHashesToIdentityIdsProofDictionary) { -// [elementsDictionary setObject:publicKeyHashesToIdentityIdsProofDictionary forKey:@(DSPlatformDictionary_PublicKeyHashesToIdentityIds)]; -// } -// -// return elementsDictionary; -} - -+ (UInt256)requestIdForHeight:(int64_t)height { - NSMutableData *data = [NSMutableData data]; - [data appendBytes:@"dpsvote".UTF8String length:7]; - [data appendUInt64:height]; - return [data SHA256]; -} - -+ (UInt256)signIDForQuorumEntry:(DSQuorumEntry *)quorumEntry quorumType:(LLMQType)quorumType forStateMessageHash:(UInt256)stateMessageHash height:(int64_t)height { - UInt256 requestId = [self requestIdForHeight:height]; - NSMutableData *data = [NSMutableData data]; - [data appendUInt8:quorumType]; - [data appendUInt256:quorumEntry.quorumHash]; - [data appendUInt256:uint256_reverse(requestId)]; - [data appendUInt256:uint256_reverse(stateMessageHash)]; - return [data SHA256_2]; -} - -+ (BOOL)verifyStateSignature:(UInt768)signature forStateMessageHash:(UInt256)stateMessageHash height:(int64_t)height againstQuorum:(DSQuorumEntry *)quorumEntry quorumType:(LLMQType)quorumType { - UInt256 signId = [self signIDForQuorumEntry:quorumEntry quorumType:quorumType forStateMessageHash:stateMessageHash height:height]; - return key_bls_verify(quorumEntry.quorumPublicKey.u8, quorumEntry.useLegacyBLSScheme, signId.u8, signature.u8); -} - - -@end diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkService.h b/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkService.h deleted file mode 100644 index 35f4351c9..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkService.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DSDAPIPlatformNetworkServiceProtocol.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class HTTPLoaderFactory, DSChain; - -@interface DSDAPIPlatformNetworkService : NSObject - -@property (readonly, nonatomic) NSString *ipAddress; - -- (instancetype)initWithDAPINodeIPAddress:(NSString *)ipAddress httpLoaderFactory:(HTTPLoaderFactory *)httpLoaderFactory usingGRPCDispatchQueue:(dispatch_queue_t)grpcDispatchQueue onChain:(DSChain *)chain NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkService.m b/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkService.m deleted file mode 100644 index eb061c47b..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkService.m +++ /dev/null @@ -1,852 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DSDAPIPlatformNetworkService.h" - -#import "DPContract.h" -#import "DPErrors.h" -#import "DSChain.h" -#import "DSDAPIGRPCResponseHandler.h" -#import "DSDashPlatform.h" -#import "DSHTTPJSONRPCClient.h" -#import "DSPeer.h" -#import "DSPlatformDocumentsRequest.h" -#import "DSTransition.h" -#import "NSData+Dash.h" - -NSString *const DSDAPINetworkServiceErrorDomain = @"dash.dapi-network-service.error"; - -#define DSPlatformRequestLog(frmt, ...) DDLogInfo(frmt, ##__VA_ARGS__) - - -@interface DSDAPIPlatformNetworkService () - -@property (strong, nonatomic) DSHTTPJSONRPCClient *httpJSONRPCClient; -@property (strong, nonatomic) Platform *gRPCClient; -@property (strong, nonatomic) DSChain *chain; -@property (strong, nonatomic) NSString *ipAddress; -@property (strong, atomic) dispatch_queue_t grpcDispatchQueue; - -@end - -@implementation DSDAPIPlatformNetworkService - -- (instancetype)initWithDAPINodeIPAddress:(NSString *)ipAddress httpLoaderFactory:(HTTPLoaderFactory *)httpLoaderFactory usingGRPCDispatchQueue:(dispatch_queue_t)grpcDispatchQueue onChain:(DSChain *)chain { - NSParameterAssert(ipAddress); - NSParameterAssert(httpLoaderFactory); - - if (!(self = [super init])) return nil; - - self.ipAddress = ipAddress; - NSURL *dapiNodeURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", ipAddress, chain.standardDapiJRPCPort]]; - _httpJSONRPCClient = [DSHTTPJSONRPCClient clientWithEndpointURL:dapiNodeURL httpLoaderFactory:httpLoaderFactory]; - self.chain = chain; - GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; - // this example does not use TLS (secure channel); use insecure channel instead - options.transportType = GRPCTransportTypeInsecure; - options.userAgentPrefix = [NSString stringWithFormat:@"%@/", USER_AGENT]; - options.timeout = 30; - self.grpcDispatchQueue = grpcDispatchQueue; - - NSString *dapiGRPCHost = [NSString stringWithFormat:@"%@:%d", ipAddress, chain.standardDapiGRPCPort]; - - _gRPCClient = [Platform serviceWithHost:dapiGRPCHost callOptions:options]; - return self; -} - -#pragma mark - DSDAPIProtocol -#pragma mark Layer 1 Deprecated - -- (void)estimateFeeWithNumberOfBlocksToWait:(NSUInteger)numberOfBlocksToWait - success:(void (^)(NSNumber *duffsPerKilobyte))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"estimateFee" - parameters:@{@"blocks": @(numberOfBlocksToWait)} - validateAgainstClass:NSNumber.class - success:success - failure:failure]; -} - -- (void)getAddressSummary:(NSArray *)addresses - noTxList:(BOOL)noTxList - from:(NSNumber *)from - to:(NSNumber *)to - fromHeight:(nullable NSNumber *)fromHeight - toHeight:(nullable NSNumber *)toHeight - success:(void (^)(NSDictionary *addressSummary))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSParameterAssert(from); - NSParameterAssert(to); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - parameters[@"address"] = addresses; - parameters[@"noTxList"] = @(noTxList); - parameters[@"from"] = from; - parameters[@"to"] = to; - parameters[@"fromHeight"] = fromHeight; - parameters[@"toHeight"] = toHeight; - - [self requestWithMethod:@"getAddressSummary" - parameters:parameters - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getAddressTotalReceived:(NSArray *)addresses - success:(void (^)(NSNumber *duffsReceivedByAddress))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - [self requestWithMethod:@"getAddressTotalReceived" - parameters:@{@"address": addresses} - validateAgainstClass:NSNumber.class - success:success - failure:failure]; -} - -- (void)getAddressTotalSent:(NSArray *)addresses - success:(void (^)(NSNumber *duffsSentByAddress))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - [self requestWithMethod:@"getAddressTotalSent" - parameters:@{@"address": addresses} - validateAgainstClass:NSNumber.class - success:success - failure:failure]; -} - -- (void)getAddressUnconfirmedBalance:(NSArray *)addresses - success:(void (^)(NSNumber *unconfirmedBalance))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - [self requestWithMethod:@"getAddressUnconfirmedBalance" - parameters:@{@"address": addresses} - validateAgainstClass:NSNumber.class - success:success - failure:failure]; -} - -- (void)getBalanceForAddress:(NSArray *)addresses - success:(void (^)(NSNumber *balance))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - [self requestWithMethod:@"getBalance" - parameters:@{@"address": addresses} - validateAgainstClass:NSNumber.class - success:success - failure:failure]; -} - -- (void)getBestBlockHashSuccess:(void (^)(NSString *blockHeight))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"getBestBlockHash" - parameters:nil - validateAgainstClass:NSString.class - success:success - failure:failure]; -} - -- (void)getBestBlockHeightSuccess:(void (^)(NSNumber *blockHeight))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"getBestBlockHeight" - parameters:nil - validateAgainstClass:NSNumber.class - success:success - failure:failure]; -} - -- (void)getBlockHashForHeight:(NSUInteger)height - success:(void (^)(NSString *blockHash))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"getBlockHash" - parameters:@{@"height": @(height)} - validateAgainstClass:NSString.class - success:success - failure:failure]; -} - -- (void)getBlockHeaderForHash:(NSString *)blockHash - success:(void (^)(NSArray *blockHeaders))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(blockHash); - - [self requestWithMethod:@"getBlockHeader" - parameters:@{@"blockHash": blockHash} - validateAgainstClass:NSArray.class - success:success - failure:failure]; -} - -- (void)getBlockHeadersFromOffset:(NSUInteger)offset - limit:(NSUInteger)limit - success:(void (^)(NSArray *blockHeaders))success - failure:(void (^)(NSError *error))failure { - NSAssert(limit <= 25, @"Limit should be <= 25"); - - [self requestWithMethod:@"getBlockHeaders" - parameters:@{ - @"offset": @(offset), - @"limit": @(limit), - } - validateAgainstClass:NSArray.class - success:success - failure:failure]; -} - -- (void)getBlocksStartingDate:(NSDate *)date - limit:(NSUInteger)limit - success:(void (^)(NSArray *blockHeaders))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(date); - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - dateFormatter.dateFormat = @"yyyy-MM-dd"; - NSString *dateString = [dateFormatter stringFromDate:date]; - - [self requestWithMethod:@"getBlocks" - parameters:@{ - @"blockDate": dateString, - @"limit": @(limit), - } - validateAgainstClass:NSArray.class - success:success - failure:failure]; -} - -- (void)getHistoricBlockchainDataSyncStatusSuccess:(void (^)(NSDictionary *historicStatus))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"getHistoricBlockchainDataSyncStatus" - parameters:nil - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getMempoolInfoSuccess:(void (^)(NSNumber *blockHeight))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"getMempoolInfo" - parameters:nil - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getMNListSuccess:(void (^)(NSArray *mnList))success - failure:(void (^)(NSError *error))failure { - [self requestWithMethod:@"getMNList" - parameters:nil - validateAgainstClass:NSArray.class - success:success - failure:failure]; -} - -- (void)getMNListDiffBaseBlockHash:(NSString *)baseBlockHash - blockHash:(NSString *)blockHash - success:(void (^)(NSDictionary *mnListDiff))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(baseBlockHash); - NSParameterAssert(blockHash); - - [self requestWithMethod:@"getMnListDiff" - parameters:@{ - @"baseBlockHash": baseBlockHash, - @"blockHash": blockHash, - } - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getRawBlock:(NSString *)blockHash - success:(void (^)(NSDictionary *rawBlock))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(blockHash); - - [self requestWithMethod:@"getRawBlock" - parameters:@{@"blockHash": blockHash} - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getSpvDataForFilter:(nullable NSString *)filter - success:(void (^)(NSDictionary *blockHeaders))success - failure:(void (^)(NSError *error))failure { - if (!filter) { - filter = @""; - } - - [self requestWithMethod:@"getSpvData" - parameters:@{@"filter": filter} - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getTransactionById:(NSString *)txid - success:(void (^)(NSDictionary *tx))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(txid); - - [self requestWithMethod:@"getTransactionById" - parameters:@{@"txid": txid} - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getTransactionsByAddress:(NSArray *)addresses - from:(NSNumber *)from - to:(NSNumber *)to - fromHeight:(nullable NSNumber *)fromHeight - toHeight:(nullable NSNumber *)toHeight - success:(void (^)(NSDictionary *result))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSParameterAssert(from); - NSParameterAssert(to); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - parameters[@"address"] = addresses; - parameters[@"from"] = from; - parameters[@"to"] = to; - parameters[@"fromHeight"] = fromHeight; - parameters[@"toHeight"] = toHeight; - - [self requestWithMethod:@"getTransactionsByAddress" - parameters:parameters - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)getUTXOForAddress:(NSArray *)addresses - from:(nullable NSNumber *)from - to:(nullable NSNumber *)to - fromHeight:(nullable NSNumber *)fromHeight - toHeight:(nullable NSNumber *)toHeight - success:(void (^)(NSDictionary *result))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(addresses); - NSParameterAssert(from); - NSParameterAssert(to); - NSAssert(addresses.count > 0, @"Empty address list is not allowed"); - - NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - parameters[@"address"] = addresses; - parameters[@"from"] = from; - parameters[@"to"] = to; - parameters[@"fromHeight"] = fromHeight; - parameters[@"toHeight"] = toHeight; - - [self requestWithMethod:@"getUTXO" - parameters:parameters - validateAgainstClass:NSDictionary.class - success:success - failure:failure]; -} - -- (void)sendRawIxTransaction:(NSString *)rawIxTransaction - success:(void (^)(NSString *txid))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(rawIxTransaction); - - [self requestWithMethod:@"sendRawIxTransaction" - parameters:@{@"rawIxTransaction": rawIxTransaction} - validateAgainstClass:NSString.class - success:success - failure:failure]; -} - -- (void)sendRawTransaction:(NSString *)rawTransaction - success:(void (^)(NSString *txid))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(rawTransaction); - - [self requestWithMethod:@"sendRawTransaction" - parameters:@{@"rawTransaction": rawTransaction} - validateAgainstClass:NSString.class - success:success - failure:failure]; -} - -- (void)addToBloomFilterWithOriginalFilter:(NSString *)originalFilter - element:(NSString *)element - success:(void (^)(BOOL result))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(originalFilter); - NSParameterAssert(element); - - [self requestWithMethod:@"addToBloomFilter" - parameters:@{ - @"originalFilter": originalFilter, - @"element": element, - } - validateAgainstClass:NSNumber.class - success:^(id _Nonnull responseObject) { - if (success) { - success([(NSNumber *)responseObject boolValue]); - } - } - failure:failure]; -} - -- (void)clearBloomFilter:(NSString *)filter - success:(void (^)(BOOL result))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(filter); - - [self requestWithMethod:@"clearBloomFilter" - parameters:@{@"filter": filter} - validateAgainstClass:NSNumber.class - success:^(id _Nonnull responseObject) { - if (success) { - success([(NSNumber *)responseObject boolValue]); - } - } - failure:failure]; -} - -- (void)loadBloomFilter:(NSString *)filter - success:(void (^)(BOOL result))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(filter); - - [self requestWithMethod:@"loadBloomFilter" - parameters:@{@"filter": filter} - validateAgainstClass:NSNumber.class - success:^(id _Nonnull responseObject) { - if (success) { - success([(NSNumber *)responseObject boolValue]); - } - } - failure:failure]; -} - -#pragma mark Layer 2 - -- (id)fetchIdentityIdsByKeyHashes:(NSArray *)keyHashesArray - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *identityIds))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(keyHashesArray); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"fetchIdentityIdsByKeyHashes %@", keyHashesArray); - GetIdentityIdsByPublicKeyHashesRequest *getIdentityIdsByPublicKeyHashesRequest = [[GetIdentityIdsByPublicKeyHashesRequest alloc] init]; - getIdentityIdsByPublicKeyHashesRequest.publicKeyHashesArray = [keyHashesArray mutableCopy]; - getIdentityIdsByPublicKeyHashesRequest.prove = DSPROVE_PLATFORM; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForGetIdentityIDsByPublicKeyHashesRequest:keyHashesArray withChain:self.chain requireProof:DSPROVE_PLATFORM]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getIdentityIdsByPublicKeyHashesWithMessage:getIdentityIdsByPublicKeyHashesRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)fetchIdentitiesByKeyHashes:(NSArray *)keyHashesArray - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *identityDictionaries))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(keyHashesArray); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"fetchIdentitiesByKeyHashes %@", keyHashesArray); - GetIdentitiesByPublicKeyHashesRequest *getIdentitiesByPublicKeyHashesRequest = [[GetIdentitiesByPublicKeyHashesRequest alloc] init]; - getIdentitiesByPublicKeyHashesRequest.publicKeyHashesArray = [keyHashesArray mutableCopy]; - getIdentitiesByPublicKeyHashesRequest.prove = DSPROVE_PLATFORM; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForGetIdentitiesByPublicKeyHashesRequest:keyHashesArray withChain:self.chain requireProof:DSPROVE_PLATFORM]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getIdentitiesByPublicKeyHashesWithMessage:getIdentitiesByPublicKeyHashesRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)fetchContractForId:(NSData *)contractId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *contract))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(contractId); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"fetchContractForId (base58) %@", contractId.base58String); - GetDataContractRequest *getDataContractRequest = [[GetDataContractRequest alloc] init]; - getDataContractRequest.id_p = contractId; - getDataContractRequest.prove = DSPROVE_PLATFORM; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForContractRequest:contractId withChain:self.chain requireProof:DSPROVE_PLATFORM]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDataContractWithMessage:getDataContractRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDPNSDocumentsForPreorderSaltedDomainHashes:(NSArray *)saltedDomainHashes - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSAssert(saltedDomainHashes.count, @"saltedDomainHash must not be empty"); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getDPNSDocumentsForPreorderSaltedDomainHashes %@", saltedDomainHashes); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dpnsRequestForPreorderSaltedHashes:saltedDomainHashes]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDPNSDocumentsForIdentityWithUserId:(NSData *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(userId); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getDPNSDocumentsForIdentityWithUserId (base58) %@", userId.base58String); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dpnsRequestForUserId:userId]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDPNSDocumentsForUsernames:(NSArray *)usernames - inDomain:(NSString *)domain - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSAssert(usernames.count, @"usernames must not be empty"); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getDPNSDocumentsForUsernames %@", usernames); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dpnsRequestForUsernames:usernames inDomain:domain]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)searchDPNSDocumentsForUsernamePrefix:(NSString *)usernamePrefix - inDomain:(NSString *)domain - startAfter:(NSData* _Nullable)startAfter - limit:(uint32_t)limit - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(usernamePrefix); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"searchDPNSDocumentsForUsernamePrefix %@ inDomain %@ startAfter(bs58) %@, limit %u", usernamePrefix, domain, startAfter.base58String, limit); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dpnsRequestForUsernameStartsWithSearch:[usernamePrefix lowercaseString] inDomain:domain startAfter:startAfter limit:limit]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getIdentityByName:(NSString *)username - inDomain:(NSString *)domain - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *_Nullable blockchainIdentity))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(username); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getIdentityByName %@ inDomain %@", username, domain); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dpnsRequestForUsername:username inDomain:domain]; - if (uint256_is_zero(self.chain.dpnsContractID)) return nil; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = ^(NSArray *dpnsDictionaries) { - if ([dpnsDictionaries count]) { - NSDictionary *dpnsDictionary = [dpnsDictionaries firstObject]; - NSData *ownerIdData = nil; - if (!dpnsDictionary || !(ownerIdData = dpnsDictionary[@"$ownerId"])) { - if (failure) { - failure([NSError errorWithDomain:DPErrorDomain - code:DPErrorCode_InvalidDocumentType - userInfo:@{ - NSLocalizedDescriptionKey: - [NSString stringWithFormat:@"DPNS returned document is malformed"], - }]); - } - return; - } - [self getIdentityById:ownerIdData - completionQueue:completionQueue - success:success - failure:failure]; - } else { - //no identity - success(nil); - } - }; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getIdentityById:(NSData *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *blockchainIdentity))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(userId); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getIdentityById (base58) %@", userId.base58String); - GetIdentityRequest *getIdentityRequest = [[GetIdentityRequest alloc] init]; - getIdentityRequest.id_p = userId; - getIdentityRequest.prove = DSPROVE_PLATFORM; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForIdentityRequest:userId withChain:self.chain requireProof:DSPROVE_PLATFORM]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getIdentityWithMessage:getIdentityRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)publishTransition:(DSTransition *)stateTransition - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(stateTransition); - NSParameterAssert(completionQueue); - DSLogPrivate(@"Broadcasting state transition to ip %@ with data %@ rawData %@", self.ipAddress, stateTransition.keyValueDictionary, stateTransition.data.hexString); - - WaitForStateTransitionResultRequest *waitForStateTransitionResultRequest = [[WaitForStateTransitionResultRequest alloc] init]; - waitForStateTransitionResultRequest.prove = DSPROVE_PUSH_PLATFORM; - waitForStateTransitionResultRequest.stateTransitionHash = uint256_data(stateTransition.transitionHash); - - // In v21, we can not verify Document proofs. - bool requireProof = (stateTransition.type == DSTransitionType_Documents) ? (DSPROVE_PLATFORM_SINDEXES & DSPROVE_PUSH_PLATFORM) : DSPROVE_PUSH_PLATFORM; - - DSDAPIGRPCResponseHandler *waitResponseHandler = [[DSDAPIGRPCResponseHandler alloc] initForStateTransition:stateTransition withChain:self.chain requireProof:requireProof]; - waitResponseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - waitResponseHandler.dispatchQueue = self.grpcDispatchQueue; - waitResponseHandler.completionQueue = completionQueue; - waitResponseHandler.successHandler = ^(NSDictionary *successDictionary) { - NSLog(@"%@", successDictionary); - - //todo : verify proof - if (success) { - success(successDictionary, TRUE); - } - }; - waitResponseHandler.errorHandler = failure; - - GRPCUnaryProtoCall *waitCall = [self.gRPCClient waitForStateTransitionResultWithMessage:waitForStateTransitionResultRequest responseHandler:waitResponseHandler callOptions:nil]; - [waitCall start]; - - BroadcastStateTransitionRequest *broadcastStateRequest = [[BroadcastStateTransitionRequest alloc] init]; - broadcastStateRequest.stateTransition = stateTransition.data; - // There is no way to prove that the message was added to the mempool, so we should not require proof - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initWithChain:self.chain requireProof:NO]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = ^(NSDictionary *successDictionary) { - NSLog(@"%@", successDictionary); - }; - responseHandler.errorHandler = ^(NSError *error) { - - }; - GRPCUnaryProtoCall *call = [self.gRPCClient broadcastStateTransitionWithMessage:broadcastStateRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDashpayIncomingContactRequestsForUserId:(NSData *)userId since:(NSTimeInterval)timestamp - startAfter:(NSData* _Nullable)startAfter - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(userId); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getDashpayIncomingContactRequestsForUserId (bs58) %@ since %f startAfter (bs58) %@", userId.base58String, timestamp, startAfter); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dashpayRequestForContactRequestsForRecipientUserId:userId since:timestamp startAfter:startAfter]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - responseHandler.request = platformDocumentsRequest; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDashpayOutgoingContactRequestsForUserId:(NSData *)userId since:(NSTimeInterval)timestamp startAfter:(NSData* _Nullable)startAfter - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(userId); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getDashpayOutgoingContactRequestsForUserId (base58) %@ since %f startAfter (bs58) %@", userId.base58String, timestamp, startAfter); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dashpayRequestForContactRequestsForSendingUserId:userId since:timestamp startAfter:startAfter]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - responseHandler.request = platformDocumentsRequest; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDashpayProfileForUserId:(NSData *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(userId); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"getDashpayProfileForUserId (base58) %@", userId.base58String); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dashpayRequestForProfileWithUserId:userId]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)getDashpayProfilesForUserIds:(NSArray *)userIds - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(userIds); - NSParameterAssert(completionQueue); - NSAssert(userIds.count > 0, @"You must query at least 1 userId"); - DSPlatformRequestLog(@"getDashpayProfilesForUserIds %@", userIds); - DSPlatformDocumentsRequest *platformDocumentsRequest = [DSPlatformDocumentsRequest dashpayRequestForProfilesWithUserIds:userIds]; - platformDocumentsRequest.contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - -- (id)fetchDocumentsWithRequest:(DSPlatformDocumentsRequest *)platformDocumentsRequest - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure { - NSParameterAssert(platformDocumentsRequest); - NSParameterAssert(completionQueue); - DSPlatformRequestLog(@"fetchDocumentsWithRequest %@", platformDocumentsRequest); - DSDAPIGRPCResponseHandler *responseHandler = [[DSDAPIGRPCResponseHandler alloc] initForDocumentsQueryRequest:platformDocumentsRequest withChain:self.chain requireProof:DSPROVE_PLATFORM_SINDEXES]; - responseHandler.host = [NSString stringWithFormat:@"%@:%d", self.ipAddress, self.chain.standardDapiGRPCPort]; - responseHandler.dispatchQueue = self.grpcDispatchQueue; - responseHandler.completionQueue = completionQueue; - responseHandler.successHandler = success; - responseHandler.errorHandler = failure; - GRPCUnaryProtoCall *call = [self.gRPCClient getDocumentsWithMessage:platformDocumentsRequest.getDocumentsRequest responseHandler:responseHandler callOptions:nil]; - [call start]; - return (id)call; -} - - -#pragma mark - Private - -- (void)requestWithMethod:(NSString *)method - parameters:(nullable NSDictionary *)parameters - validateAgainstClass:(Class)responseClass - success:(void (^)(id responseObject))success - failure:(void (^)(NSError *error))failure { - void (^internalSuccess)(id) = ^(id _Nonnull responseObject) { - if ([responseObject isKindOfClass:responseClass]) { - if (success) { - success(responseObject); - } - } else { - if (failure) { - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: DSLocalizedString(@"Invalid DAPI Response", nil), - NSDebugDescriptionErrorKey: responseObject, - }; - NSError *error = [NSError errorWithDomain:DSDAPINetworkServiceErrorDomain - code:DSDAPINetworkServiceErrorCodeInvalidResponse - userInfo:userInfo]; - - failure(error); - } - } - }; - - [self.httpJSONRPCClient invokeMethod:method - withParameters:parameters - success:internalSuccess - failure:failure]; -} - -@end diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkServiceProtocol.h b/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkServiceProtocol.h deleted file mode 100644 index ae5dc9118..000000000 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPIPlatformNetworkServiceProtocol.h +++ /dev/null @@ -1,517 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2018-2019 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 "BigIntTypes.h" -#import "DSDAPIClientFetchDapObjectsOptions.h" -#import "DSDAPINetworkServiceRequest.h" -#import - -typedef NS_ENUM(NSInteger, DSPlatformStoredMessage) -{ - /// The item does not exist for the specified key - DSPlatformStoredMessage_NotPresent = 0, - /// The version is prepended before all items - DSPlatformStoredMessage_Version, - /// An item can be returned if decode is set to true - DSPlatformStoredMessage_Item, - /// A data item that can be returned if decode is set to false - DSPlatformStoredMessage_Data, -}; - -#define DAPI_DOCUMENT_RESPONSE_COUNT_LIMIT 100 - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const DSDAPINetworkServiceErrorDomain; - -typedef NS_ENUM(NSUInteger, DSDAPINetworkServiceErrorCode) -{ - DSDAPINetworkServiceErrorCodeInvalidResponse = 100, -}; - -@class DSTransition, DSPlatformDocumentsRequest, DSBlockchainIdentity; - -@protocol DSDAPIPlatformNetworkServiceProtocol - -///-------------- -/// @name Layer 1 -///-------------- - -/** - Estimates the transaction fee necessary for confirmation to occur within a certain number of blocks - - @param numberOfBlocksToWait Number of blocks for fee estimate - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)estimateFeeWithNumberOfBlocksToWait:(NSUInteger)numberOfBlocksToWait - success:(void (^)(NSNumber *duffsPerKilobyte))success - failure:(void (^)(NSError *error))failure; - -/** - Get an address summary given an addresses - - @param addresses Dash addresses - @param noTxList true if a list of all txs should NOT be included in result - @param from start of range for the tx to be included in the tx list - @param to end of range for the tx to be included in the tx list - @param fromHeight which height to start from (optional, overriding from/to) - @param toHeight on which height to end (optional, overriding from/to) - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getAddressSummary:(NSArray *)addresses - noTxList:(BOOL)noTxList - from:(NSNumber *)from - to:(NSNumber *)to - fromHeight:(nullable NSNumber *)fromHeight - toHeight:(nullable NSNumber *)toHeight - success:(void (^)(NSDictionary *addressSummary))success - failure:(void (^)(NSError *error))failure; - -/** - Get the total amount of duffs received by an addresses - - @param addresses Dash addresses - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getAddressTotalReceived:(NSArray *)addresses - success:(void (^)(NSNumber *duffsReceivedByAddress))success - failure:(void (^)(NSError *error))failure; - -/** - Get the total amount of duffs sent by an addresses - - @param addresses Dash addresses - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getAddressTotalSent:(NSArray *)addresses - success:(void (^)(NSNumber *duffsSentByAddress))success - failure:(void (^)(NSError *error))failure; - -/** - Get the total unconfirmed balance for the addresses - - @param addresses Dash addresses - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getAddressUnconfirmedBalance:(NSArray *)addresses - success:(void (^)(NSNumber *unconfirmedBalance))success - failure:(void (^)(NSError *error))failure; - -/** - Get the calculated balance for the addresses - - @param addresses Dash addresses - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBalanceForAddress:(NSArray *)addresses - success:(void (^)(NSNumber *balance))success - failure:(void (^)(NSError *error))failure; - -/** - Returns block hash of chaintip - - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBestBlockHashSuccess:(void (^)(NSString *blockHeight))success - failure:(void (^)(NSError *error))failure; - -/** - Get the best block height - - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBestBlockHeightSuccess:(void (^)(NSNumber *blockHeight))success - failure:(void (^)(NSError *error))failure; - -/** - Get the block hash for the given height - - @param height Block height - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBlockHashForHeight:(NSUInteger)height - success:(void (^)(NSString *blockHash))success - failure:(void (^)(NSError *error))failure; - - -/** - Get the block header corresponding to the requested block hash - - @param blockHash Block hash - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBlockHeaderForHash:(NSString *)blockHash - success:(void (^)(NSArray *blockHeaders))success - failure:(void (^)(NSError *error))failure; - -/** - Get the requested number of block headers starting at the requested height - - @param offset Lowest block height to include - @param limit The number of headers to return (0 < limit <=25) - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBlockHeadersFromOffset:(NSUInteger)offset - limit:(NSUInteger)limit - success:(void (^)(NSArray *blockHeaders))success - failure:(void (^)(NSError *error))failure; - -/** - Get info for blocks meeting the provided criteria - - @param date Starting date for blocks to get - @param limit Number of blocks to return - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getBlocksStartingDate:(NSDate *)date - limit:(NSUInteger)limit - success:(void (^)(NSArray *blockHeaders))success - failure:(void (^)(NSError *error))failure; - -/** - Get historic blockchain data sync status - - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getHistoricBlockchainDataSyncStatusSuccess:(void (^)(NSDictionary *historicStatus))success - failure:(void (^)(NSError *error))failure; - -/** - Returns mempool usage info - - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getMempoolInfoSuccess:(void (^)(NSNumber *blockHeight))success - failure:(void (^)(NSError *error))failure; - -/** - Get masternode list - - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getMNListSuccess:(void (^)(NSArray *mnList))success - failure:(void (^)(NSError *error))failure; - -/** - Get masternode list diff for the provided block hashes - - @param baseBlockHash Block hash - @param blockHash Block hash - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getMNListDiffBaseBlockHash:(NSString *)baseBlockHash - blockHash:(NSString *)blockHash - success:(void (^)(NSDictionary *mnListDiff))success - failure:(void (^)(NSError *error))failure; - -/** - Get the raw block for the provided block hash - - @param blockHash Block hash - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getRawBlock:(NSString *)blockHash - success:(void (^)(NSDictionary *rawBlock))success - failure:(void (^)(NSError *error))failure; - -/** - Get block headers - - @param filter A bloom filter - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getSpvDataForFilter:(nullable NSString *)filter - success:(void (^)(NSDictionary *blockHeaders))success - failure:(void (^)(NSError *error))failure; - -/** - Get transaction for the given hash - - @param txid The TXID of the transaction - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getTransactionById:(NSString *)txid - success:(void (^)(NSDictionary *tx))success - failure:(void (^)(NSError *error))failure; - -/** - Get transactions for a given address or multiple addresses - - @param addresses Dash addresses - @param from start of range for the tx to be included in the tx list - @param to end of range for the tx to be included in the tx list - @param fromHeight which height to start from (optional, overriding from/to) - @param toHeight on which height to end (optional, overriding from/to) - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getTransactionsByAddress:(NSArray *)addresses - from:(NSNumber *)from - to:(NSNumber *)to - fromHeight:(nullable NSNumber *)fromHeight - toHeight:(nullable NSNumber *)toHeight - success:(void (^)(NSDictionary *result))success - failure:(void (^)(NSError *error))failure; - -/** - Get UTXO for a given address or multiple addresses (max result 1000) - - @param addresses Dash addresses - @param from start of range in the ordered list of latest UTXO (optional) - @param to end of range in the ordered list of latest UTXO (optional) - @param fromHeight which height to start from (optional, overriding from/to) - @param toHeight on which height to end (optional, overriding from/to) - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)getUTXOForAddress:(NSArray *)addresses - from:(nullable NSNumber *)from - to:(nullable NSNumber *)to - fromHeight:(nullable NSNumber *)fromHeight - toHeight:(nullable NSNumber *)toHeight - success:(void (^)(NSDictionary *result))success - failure:(void (^)(NSError *error))failure; - -/** - Sends raw InstantSend transaction and returns the transaction ID - - @param rawIxTransaction Hex-serialized InstaSend transaction - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)sendRawIxTransaction:(NSString *)rawIxTransaction - success:(void (^)(NSString *txid))success - failure:(void (^)(NSError *error))failure; - - -/** - Sends raw transaction and returns the transaction ID - - @param rawTransaction Hex-serialized transaction - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)sendRawTransaction:(NSString *)rawTransaction - success:(void (^)(NSString *txid))success - failure:(void (^)(NSError *error))failure; - -/** - Adds an element to an existing bloom filter - - @param originalFilter Original filter - @param element Element to add to filter - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)addToBloomFilterWithOriginalFilter:(NSString *)originalFilter - element:(NSString *)element - success:(void (^)(BOOL result))success - failure:(void (^)(NSError *error))failure; - -/** - Clear the bloom filter - - @param filter Original filter - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)clearBloomFilter:(NSString *)filter - success:(void (^)(BOOL result))success - failure:(void (^)(NSError *error))failure; - -/** - Load a bloom filter - - @param filter Filter to load - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (void)loadBloomFilter:(NSString *)filter - success:(void (^)(BOOL result))success - failure:(void (^)(NSError *error))failure; - -///-------------- -/// @name Layer 2 -///-------------- - -/** - Fetch a user's Contract - - @param contractId A user's Contract ID - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (id)fetchContractForId:(NSData *)contractId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *contract))success - failure:(void (^)(NSError *error))failure; - -/** - Get a blockchain user by username - - @param username Blockchain user's username - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (id _Nullable)getIdentityByName:(NSString *)username - inDomain:(NSString *)domain - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *_Nullable blockchainIdentity))success - failure:(void (^)(NSError *error))failure; - -/** - Get a blockchain user by ID - - @param userId Blockchain user's ID - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (id)getIdentityById:(NSData *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *_Nullable blockchainIdentity))success - failure:(void (^)(NSError *error))failure; - -/** - Sends raw state transition to the network - - @param stateTransition Hex-string representing state transition header - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (id)publishTransition:(DSTransition *)stateTransition - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSDictionary *successDictionary, BOOL added))success - failure:(void (^)(NSError *error))failure; - -/** - Fetches user documents for a given condition - - @param platformDocumentsRequest The fetch request - @param success A block object to be executed when the request operation finishes successfully - @param failure A block object to be executed when the request operation finishes unsuccessfully - */ -- (id)fetchDocumentsWithRequest:(DSPlatformDocumentsRequest *)platformDocumentsRequest - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDPNSDocumentsForPreorderSaltedDomainHashes:(NSArray *)saltedDomainHashes - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDPNSDocumentsForUsernames:(NSArray *)usernames - inDomain:(NSString *)domain - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDPNSDocumentsForIdentityWithUserId:(NSData *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -/** -Get a list of users after matching search criteria - -@param usernamePrefix The username prefix that will be searched upon -@param domain The domain in which to search -@param startAfter Starting amount of results to return -@param limit Limit of search results to return -@param success A block object to be executed when the request operation finishes successfully -@param failure A block object to be executed when the request operation finishes unsuccessfully -*/ -- (id)searchDPNSDocumentsForUsernamePrefix:(NSString *)usernamePrefix - inDomain:(NSString *)domain - startAfter:(NSData* _Nullable)startAfter - limit:(uint32_t)limit - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDashpayIncomingContactRequestsForUserId:(NSData *)userId - since:(NSTimeInterval)timestamp - startAfter:(NSData* _Nullable)startAfter - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDashpayOutgoingContactRequestsForUserId:(NSData *)userId - since:(NSTimeInterval)timestamp - startAfter:(NSData* _Nullable)startAfter - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDashpayProfileForUserId:(NSData *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -- (id)getDashpayProfilesForUserIds:(NSArray *)userId - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *documents))success - failure:(void (^)(NSError *error))failure; - -/** -Get a list of identities knowing only keys they possess - -@param keyHashesArray An array of hashes of keys -@param completionQueue The queue in which to return the result on -@param success A block object to be executed when the request operation finishes successfully -@param failure A block object to be executed when the request operation finishes unsuccessfully -*/ -- (id)fetchIdentitiesByKeyHashes:(NSArray *)keyHashesArray - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *identityDictionaries))success - failure:(void (^)(NSError *error))failure; - -/** -Get a list of identity Ids knowing only keys they possess - -@param keyHashesArray An array of hashes of keys -@param completionQueue The queue in which to return the result on -@param success A block object to be executed when the request operation finishes successfully -@param failure A block object to be executed when the request operation finishes unsuccessfully -*/ -- (id)fetchIdentityIdsByKeyHashes:(NSArray *)keyHashesArray - completionQueue:(dispatch_queue_t)completionQueue - success:(void (^)(NSArray *identityIds))success - failure:(void (^)(NSError *error))failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/DAPI/Networking/DSDAPINetworkServiceRequest.h b/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath+Protected.h similarity index 67% rename from DashSync/shared/Models/DAPI/Networking/DSDAPINetworkServiceRequest.h rename to DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath+Protected.h index ea5e6fb6a..4dcedc173 100644 --- a/DashSync/shared/Models/DAPI/Networking/DSDAPINetworkServiceRequest.h +++ b/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath+Protected.h @@ -15,10 +15,16 @@ // limitations under the License. // +#import "DSAssetLockDerivationPath.h" + NS_ASSUME_NONNULL_BEGIN -@protocol DSDAPINetworkServiceRequest -- (void)cancel; +@interface DSAssetLockDerivationPath () + ++ (instancetype)identityRegistrationFundingDerivationPathForChain:(DSChain *)chain; ++ (instancetype)identityTopupFundingDerivationPathForChain:(DSChain *)chain; ++ (instancetype)identityInvitationFundingDerivationPathForChain:(DSChain *)chain; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath.h similarity index 60% rename from DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath.h rename to DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath.h index cf6604efa..31ce2f6a5 100644 --- a/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath.h @@ -21,16 +21,14 @@ NS_ASSUME_NONNULL_BEGIN @class DSChain; -@interface DSCreditFundingDerivationPath : DSSimpleIndexedDerivationPath +@interface DSAssetLockDerivationPath : DSSimpleIndexedDerivationPath -+ (instancetype)blockchainIdentityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet; -+ (instancetype)blockchainIdentityTopupFundingDerivationPathForWallet:(DSWallet *)wallet; -+ (instancetype)blockchainIdentityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet; ++ (instancetype)identityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet; ++ (instancetype)identityTopupFundingDerivationPathForWallet:(DSWallet *)wallet; ++ (instancetype)identityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet; - (NSString *)receiveAddress; -- (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *)authprompt completion:(TransactionValidityCompletionBlock)completion; - @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath.m new file mode 100644 index 000000000..9001e8a5c --- /dev/null +++ b/DashSync/shared/Models/Derivation Paths/DSAssetLockDerivationPath.m @@ -0,0 +1,101 @@ +// +// Created by Sam Westrich +// Copyright © 2019 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+Params.h" +#import "DSAssetLockDerivationPath.h" +#import "DSDerivationPath+Protected.h" +#import "DSDerivationPathFactory.h" +#import "DSGapLimit.h" +#import "DSMasternodeManager.h" +#import "DSSimpleIndexedDerivationPath+Protected.h" +#import "DSWallet+Protected.h" + +@implementation DSAssetLockDerivationPath + ++ (instancetype)identityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet { + return [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:wallet]; +} + ++ (instancetype)identityTopupFundingDerivationPathForWallet:(DSWallet *)wallet { + return [[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:wallet]; +} + ++ (instancetype)identityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet { + return [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:wallet]; +} + ++ (instancetype)identityRegistrationFundingDerivationPathForChain:(DSChain *)chain { + UInt256 indexes[] = { + uint256_from_long(FEATURE_PURPOSE), + uint256_from_long(chain.coinType), + uint256_from_long(FEATURE_PURPOSE_IDENTITIES), + uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_REGISTRATION) + }; + BOOL hardenedIndexes[] = {YES, YES, YES, YES}; + return [DSAssetLockDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_CreditFunding + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_IdentityCreditRegistrationFunding + onChain:chain]; +} + ++ (instancetype)identityTopupFundingDerivationPathForChain:(DSChain *)chain { + UInt256 indexes[] = { + uint256_from_long(FEATURE_PURPOSE), + uint256_from_long(chain.coinType), + uint256_from_long(FEATURE_PURPOSE_IDENTITIES), + uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_TOPUP) + }; + BOOL hardenedIndexes[] = {YES, YES, YES, YES}; + return [DSAssetLockDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_CreditFunding + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_IdentityCreditTopupFunding + onChain:chain]; +} + ++ (instancetype)identityInvitationFundingDerivationPathForChain:(DSChain *)chain { + UInt256 indexes[] = { + uint256_from_long(FEATURE_PURPOSE), + uint256_from_long(chain.coinType), + uint256_from_long(FEATURE_PURPOSE_IDENTITIES), + uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_INVITATIONS) + }; + BOOL hardenedIndexes[] = {YES, YES, YES, YES}; + return [DSAssetLockDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_CreditFunding + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_IdentityCreditInvitationFunding + onChain:chain]; +} + +- (NSString *)receiveAddress { + NSString *addr = [self registerAddressesWithSettings:[DSGapLimit single]].lastObject; + return addr ?: self.mOrderedAddresses.lastObject; +} + +- (DSGapLimit *)defaultGapSettings { + return [DSGapLimit withLimit:5]; +} + +@end diff --git a/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath+Protected.h b/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath+Protected.h index bfce7655d..c7b1fd6f6 100644 --- a/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath+Protected.h +++ b/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath+Protected.h @@ -15,8 +15,8 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)providerOwnerKeysDerivationPathForChain:(DSChain *)chain; + (instancetype)providerOperatorKeysDerivationPathForChain:(DSChain *)chain; + (instancetype)platformNodeKeysDerivationPathForChain:(DSChain *)chain; -+ (instancetype)blockchainIdentityECDSAKeysDerivationPathForChain:(DSChain *)chain; -+ (instancetype)blockchainIdentityBLSKeysDerivationPathForChain:(DSChain *)chain; ++ (instancetype)identityECDSAKeysDerivationPathForChain:(DSChain *)chain; ++ (instancetype)identityBLSKeysDerivationPathForChain:(DSChain *)chain; @end diff --git a/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.h index 7cb5c5e57..ab32972f0 100644 --- a/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.h @@ -5,7 +5,7 @@ // Created by Sam Westrich on 2/10/19. // -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSDerivationPath+Protected.h" #import "DSSimpleIndexedDerivationPath.h" @@ -13,23 +13,21 @@ NS_ASSUME_NONNULL_BEGIN @interface DSAuthenticationKeysDerivationPath : DSSimpleIndexedDerivationPath -@property (nonatomic, readonly) BOOL hasExtendedPrivateKey; @property (nonatomic, readonly) BOOL usesHardenedKeys; + (instancetype)providerVotingKeysDerivationPathForWallet:(DSWallet *)wallet; + (instancetype)providerOwnerKeysDerivationPathForWallet:(DSWallet *)wallet; + (instancetype)providerOperatorKeysDerivationPathForWallet:(DSWallet *)wallet; + (instancetype)platformNodeKeysDerivationPathForWallet:(DSWallet *)wallet; -+ (instancetype)blockchainIdentitiesBLSKeysDerivationPathForWallet:(DSWallet *)wallet; -+ (instancetype)blockchainIdentitiesECDSAKeysDerivationPathForWallet:(DSWallet *)wallet; ++ (instancetype)identitiesBLSKeysDerivationPathForWallet:(DSWallet *)wallet; ++ (instancetype)identitiesECDSAKeysDerivationPathForWallet:(DSWallet *)wallet; - (NSData *)firstUnusedPublicKey; -- (OpaqueKey *)firstUnusedPrivateKeyFromSeed:(NSData *)seed; -- (OpaqueKey *)privateKeyForAddress:(NSString *)address fromSeed:(NSData *)seed; -- (OpaqueKey *)privateKeyForHash160:(UInt160)hash160 fromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *)firstUnusedPrivateKeyFromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *)privateKeyForHash160:(UInt160)hash160 fromSeed:(NSData *)seed; - (NSData *)publicKeyDataForHash160:(UInt160)hash160; -- (OpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath; +- (DMaybeOpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath; @end diff --git a/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.m index 55b753183..22c295854 100644 --- a/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.m +++ b/DashSync/shared/Models/Derivation Paths/DSAuthenticationKeysDerivationPath.m @@ -6,13 +6,17 @@ // #import "DSAuthenticationKeysDerivationPath.h" -#import "DSCreditFundingDerivationPath.h" +#import "DSAccount.h" +#import "DSAddressEntity+CoreDataClass.h" +#import "DSChain+Params.h" +#import "DSAssetLockDerivationPath.h" #import "DSDerivationPathFactory.h" +#import "DSGapLimit.h" #import "DSKeyManager.h" #import "DSSimpleIndexedDerivationPath+Protected.h" #import "NSError+Dash.h" #import "NSIndexPath+Dash.h" -#import "dash_shared_core.h" +#import "NSManagedObject+Sugar.h" @interface DSAuthenticationKeysDerivationPath () @@ -36,152 +40,153 @@ + (instancetype)providerOperatorKeysDerivationPathForWallet:(DSWallet *)wallet { + (instancetype)platformNodeKeysDerivationPathForWallet:(DSWallet *)wallet { return [[DSDerivationPathFactory sharedInstance] platformNodeKeysDerivationPathForWallet:wallet]; } -+ (instancetype)blockchainIdentitiesBLSKeysDerivationPathForWallet:(DSWallet *)wallet { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:wallet]; ++ (instancetype)identitiesBLSKeysDerivationPathForWallet:(DSWallet *)wallet { + return [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:wallet]; } -+ (instancetype)blockchainIdentitiesECDSAKeysDerivationPathForWallet:(DSWallet *)wallet { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityECDSAKeysDerivationPathForWallet:wallet]; ++ (instancetype)identitiesECDSAKeysDerivationPathForWallet:(DSWallet *)wallet { + return [[DSDerivationPathFactory sharedInstance] identityECDSAKeysDerivationPathForWallet:wallet]; } -- (NSUInteger)defaultGapLimit { - return 10; +- (DSGapLimit *)defaultGapSettings { + // TODO: identityID always 0?? + return self.type == DSDerivationPathType_SingleUserAuthentication + ? [super defaultGapSettings] + : [DSGapLimitIdentity withLimit:10 identityID:0]; } - (instancetype)initWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type - signingAlgorithm:(KeyKind)signingAlgorithm + signingAlgorithm:(DKeyKind *)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { - DSAuthenticationKeysDerivationPath *authenticationKeysDerivationPath = [super initWithIndexes:indexes hardened:hardenedIndexes length:length type:type signingAlgorithm:signingAlgorithm reference:reference onChain:chain]; - authenticationKeysDerivationPath.shouldStoreExtendedPrivateKey = NO; + DSAuthenticationKeysDerivationPath *path = [super initWithIndexes:indexes + hardened:hardenedIndexes + length:length + type:type + signingAlgorithm:signingAlgorithm + reference:reference + onChain:chain]; + path.shouldStoreExtendedPrivateKey = NO; self.addressesByIdentity = [NSMutableDictionary dictionary]; - return authenticationKeysDerivationPath; + return path; } + (instancetype _Nullable)derivationPathWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type - signingAlgorithm:(KeyKind)signingAlgorithm + signingAlgorithm:(DKeyKind *)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { - DSAuthenticationKeysDerivationPath *derivationPath = [super derivationPathWithIndexes:indexes hardened:hardenedIndexes length:length type:type signingAlgorithm:signingAlgorithm reference:reference onChain:chain]; - derivationPath.shouldStoreExtendedPrivateKey = NO; - return derivationPath; + DSAuthenticationKeysDerivationPath *path = [super derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:length + type:type + signingAlgorithm:signingAlgorithm + reference:reference + onChain:chain]; + path.shouldStoreExtendedPrivateKey = NO; + return path; } + (instancetype)providerVotingKeysDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(3), uint256_from_long(1)}; + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(3), uint256_from_long(1)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_SingleUserAuthentication signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ProviderVotingKeys onChain:chain]; + return [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_SingleUserAuthentication + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ProviderVotingKeys + onChain:chain]; } + (instancetype)providerOwnerKeysDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(3), uint256_from_long(2)}; + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(3), uint256_from_long(2)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_SingleUserAuthentication signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ProviderOwnerKeys onChain:chain]; + return [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_SingleUserAuthentication + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ProviderOwnerKeys + onChain:chain]; } + (instancetype)providerOperatorKeysDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(3), uint256_from_long(3)}; + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(3), uint256_from_long(3)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_SingleUserAuthentication signingAlgorithm:KeyKind_BLS reference:DSDerivationPathReference_ProviderOperatorKeys onChain:chain]; + return [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_SingleUserAuthentication + signingAlgorithm:DKeyKindBLS() + reference:DSDerivationPathReference_ProviderOperatorKeys + onChain:chain]; } + (instancetype)platformNodeKeysDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(3), uint256_from_long(4)}; + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(3), uint256_from_long(4)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_SingleUserAuthentication signingAlgorithm:KeyKind_ED25519 reference:DSDerivationPathReference_ProviderPlatformNodeKeys onChain:chain]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_SingleUserAuthentication + signingAlgorithm:DKeyKindED25519() + reference:DSDerivationPathReference_ProviderPlatformNodeKeys + onChain:chain]; path.shouldStoreExtendedPrivateKey = YES; path.usesHardenedKeys = YES; return path; } -+ (instancetype)blockchainIdentityECDSAKeysDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_AUTHENTICATION), uint256_from_long(0)}; ++ (instancetype)identityECDSAKeysDerivationPathForChain:(DSChain *)chain { + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_AUTHENTICATION), uint256_from_long(0)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES, YES}; - DSAuthenticationKeysDerivationPath *blockchainIdentityECDSAKeysDerivationPath = [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:5 type:DSDerivationPathType_MultipleUserAuthentication signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_BlockchainIdentities onChain:chain]; - blockchainIdentityECDSAKeysDerivationPath.shouldStoreExtendedPrivateKey = YES; - blockchainIdentityECDSAKeysDerivationPath.usesHardenedKeys = YES; - return blockchainIdentityECDSAKeysDerivationPath; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:5 + type:DSDerivationPathType_MultipleUserAuthentication + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_Identities + onChain:chain]; + path.shouldStoreExtendedPrivateKey = YES; + path.usesHardenedKeys = YES; + return path; } -+ (instancetype)blockchainIdentityBLSKeysDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_AUTHENTICATION), uint256_from_long(1)}; ++ (instancetype)identityBLSKeysDerivationPathForChain:(DSChain *)chain { + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_AUTHENTICATION), uint256_from_long(1)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES, YES}; - DSAuthenticationKeysDerivationPath *blockchainIdentityBLSKeysDerivationPath = [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:5 type:DSDerivationPathType_MultipleUserAuthentication signingAlgorithm:KeyKind_BLS reference:DSDerivationPathReference_BlockchainIdentities onChain:chain]; - blockchainIdentityBLSKeysDerivationPath.shouldStoreExtendedPrivateKey = YES; - blockchainIdentityBLSKeysDerivationPath.usesHardenedKeys = YES; - return blockchainIdentityBLSKeysDerivationPath; -} - -- (void)loadAddresses { - @synchronized(self) { - if (!self.addressesLoaded) { - [self.managedObjectContext performBlockAndWait:^{ - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; - self.syncBlockHeight = derivationPathEntity.syncBlockHeight; - NSArray *addresses = [derivationPathEntity.addresses sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]]; - for (DSAddressEntity *e in addresses) { - @autoreleasepool { - while (e.index >= self.mOrderedAddresses.count) [self.mOrderedAddresses addObject:[NSNull null]]; - - if (![DSKeyManager isValidDashAddress:e.address forChain:self.wallet.chain]) { -#if DEBUG - DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.wallet.chain.name, e.address); -#else - DSLog(@"[%@] address %@ loaded but was not valid on chain", self.wallet.chain.name, @""); -#endif - continue; - } - self.mOrderedAddresses[e.index] = e.address; - [self.mAllAddresses addObject:e.address]; - if ([e.usedInInputs count] || [e.usedInOutputs count] || [e.usedInSpecialTransactions count] || [e.usedInSimplifiedMasternodeEntries count]) { - [self.mUsedAddresses addObject:e.address]; - } - } - } - }]; - self.addressesLoaded = TRUE; - if ([self type] == DSDerivationPathType_SingleUserAuthentication) { - [self registerAddressesWithGapLimit:10 error:nil]; - } else { - [self registerAddressesWithGapLimit:10 forIdentityIndex:0 error:nil]; - } - } - } + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:5 + type:DSDerivationPathType_MultipleUserAuthentication + signingAlgorithm:DKeyKindBLS() + reference:DSDerivationPathReference_Identities + onChain:chain]; + path.shouldStoreExtendedPrivateKey = YES; + path.usesHardenedKeys = YES; + return path; } +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings + inContext:(NSManagedObjectContext *)context { + if (self.type == DSDerivationPathType_SingleUserAuthentication) { + return [super registerAddressesWithSettings:settings inContext:context]; + } else { + DSGapLimitIdentity *identitySettings = (DSGapLimitIdentity *)settings; + uint32_t identityIndex = identitySettings.identityID; + uintptr_t gapLimit = identitySettings.gapLimit; + if (!self.account.wallet.isTransient) + NSAssert(self.addressesLoaded, @"addresses must be loaded before calling this function"); -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit forIdentityIndex:(uint32_t)identityIndex error:(NSError **)error { - if (!self.account.wallet.isTransient) { - NSAssert(self.addressesLoaded, @"addresses must be loaded before calling this function"); - } - NSAssert(self.type != DSDerivationPathType_SingleUserAuthentication, @"This should not be called for single user authentication. Use '- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError**)error' instead."); - - if (self.usesHardenedKeys && !self.hasExtendedPrivateKey) { - return [NSArray array]; - } - - if (![self.addressesByIdentity objectForKey:@(identityIndex)]) { - [self.addressesByIdentity setObject:[NSMutableArray array] forKey:@(identityIndex)]; - } - - NSMutableArray *a = [NSMutableArray arrayWithArray:[self.addressesByIdentity objectForKey:@(identityIndex)]]; - NSUInteger i = a.count; - - // keep only the trailing contiguous block of addresses with no transactions - while (i > 0 && ![self.usedAddresses containsObject:a[i - 1]]) { - i--; - } - - if (i > 0) [a removeObjectsInRange:NSMakeRange(0, i)]; - if (a.count >= gapLimit) return [a subarrayWithRange:NSMakeRange(0, gapLimit)]; + if (self.usesHardenedKeys && !self.hasExtendedPrivateKey) + return [NSArray array]; - @synchronized(self) { - //It seems weird to repeat this, but it's correct because of the original call receive address and change address - [a setArray:[self.addressesByIdentity objectForKey:@(identityIndex)]]; - i = a.count; + if (![self.addressesByIdentity objectForKey:@(identityIndex)]) + [self.addressesByIdentity setObject:[NSMutableArray array] forKey:@(identityIndex)]; - unsigned n = (unsigned)i; + NSMutableArray *a = [NSMutableArray arrayWithArray:[self.addressesByIdentity objectForKey:@(identityIndex)]]; + NSUInteger i = a.count; // keep only the trailing contiguous block of addresses with no transactions while (i > 0 && ![self.usedAddresses containsObject:a[i - 1]]) { @@ -191,91 +196,79 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit forIdentityIndex if (i > 0) [a removeObjectsInRange:NSMakeRange(0, i)]; if (a.count >= gapLimit) return [a subarrayWithRange:NSMakeRange(0, gapLimit)]; - while (a.count < gapLimit) { // generate new addresses up to gapLimit - const NSUInteger hardenedIndexes[] = {identityIndex | BIP32_HARD, n | BIP32_HARD}; - const NSUInteger softIndexes[] = {identityIndex, n}; - const NSUInteger *indexes = self.usesHardenedKeys ? hardenedIndexes : softIndexes; - NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:indexes length:2]]; - NSString *addr = [DSKeyManager NSStringFrom:key_address_with_public_key_data(pubKey.bytes, pubKey.length, self.chain.chainType)]; - - if (!addr) { - DSLog(@"[%@] error generating keys", self.chain.name); - if (error) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Error generating public keys"]; - } - return nil; + @synchronized(self) { + //It seems weird to repeat this, but it's correct because of the original call receive address and change address + [a setArray:[self.addressesByIdentity objectForKey:@(identityIndex)]]; + i = a.count; + + unsigned n = (unsigned)i; + + // keep only the trailing contiguous block of addresses with no transactions + while (i > 0 && ![self.usedAddresses containsObject:a[i - 1]]) { + i--; } - if (!self.account.wallet.isTransient) { - [self.managedObjectContext performBlock:^{ // store new address in core data - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; - DSAddressEntity *e = [DSAddressEntity managedObjectInContext:self.managedObjectContext]; - e.derivationPath = derivationPathEntity; - NSAssert([DSKeyManager isValidDashAddress:addr forChain:self.chain], @"the address is being saved to the wrong derivation path"); - e.address = addr; - e.index = n; - e.identityIndex = identityIndex; - e.standalone = NO; - }]; + if (i > 0) [a removeObjectsInRange:NSMakeRange(0, i)]; + if (a.count >= gapLimit) return [a subarrayWithRange:NSMakeRange(0, gapLimit)]; + + while (a.count < gapLimit) { // generate new addresses up to gapLimit + const NSUInteger hardenedIndexes[] = {identityIndex | BIP32_HARD, n | BIP32_HARD}; + const NSUInteger softIndexes[] = {identityIndex, n}; + const NSUInteger *indexes = self.usesHardenedKeys ? hardenedIndexes : softIndexes; + NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:indexes length:2]]; + NSString *addr = [DSKeyManager NSStringFrom:DAddressWithPubKeyData(slice_ctor(pubKey), self.chain.chainType)]; + if (!addr) { + DSLog(@"[%@] error generating keys", self.chain.name); + return nil; + } + if (!self.account.wallet.isTransient) + [self storeNewAddressInContext:addr atIndex:n identityIndex:identityIndex context:self.managedObjectContext]; + [self.mAllAddresses addObject:addr]; + [[self.addressesByIdentity objectForKey:@(identityIndex)] addObject:addr]; + [a addObject:addr]; + n++; } - [self.mAllAddresses addObject:addr]; - [[self.addressesByIdentity objectForKey:@(identityIndex)] addObject:addr]; - [a addObject:addr]; - n++; + return a; } - - return a; } } - (NSData *)firstUnusedPublicKey { - return [self publicKeyDataAtIndex:(uint32_t)[self firstUnusedIndex]]; -} - -- (OpaqueKey *)firstUnusedPrivateKeyFromSeed:(NSData *)seed { - return [self privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:[self firstUnusedIndex]] fromSeed:seed]; + uint32_t index = [self firstUnusedIndex]; + return [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:index]]; } -- (OpaqueKey *)privateKeyForAddress:(NSString *)address fromSeed:(NSData *)seed { - NSUInteger index = [self indexOfKnownAddress:address]; +- (DMaybeOpaqueKey *)firstUnusedPrivateKeyFromSeed:(NSData *)seed { + uint32_t index = [self firstUnusedIndex]; return [self privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:index] fromSeed:seed]; } -- (NSData *)publicKeyDataForAddress:(NSString *)address { - uint32_t index = (uint32_t)[self indexOfKnownAddress:address]; - return [self publicKeyDataAtIndex:index]; -} - -- (OpaqueKey *)privateKeyForHash160:(UInt160)hash160 fromSeed:(NSData *)seed { - NSString *address = [DSKeyManager addressFromHash160:hash160 forChain:self.chain]; - return [self privateKeyForAddress:address fromSeed:seed]; +- (DMaybeOpaqueKey *)privateKeyForHash160:(UInt160)hash160 + fromSeed:(NSData *)seed { + NSUInteger index = [self indexOfKnownAddressHash:hash160]; + return [self privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:index] fromSeed:seed]; } - (NSData *)publicKeyDataForHash160:(UInt160)hash160 { - NSString *address = [DSKeyManager addressFromHash160:hash160 forChain:self.chain]; - return [self publicKeyDataForAddress:address]; -} - -- (OpaqueKey *)generateExtendedPublicKeyFromSeed:(NSData *)seed storeUnderWalletUniqueId:(NSString *)walletUniqueId { - return [super generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:walletUniqueId storePrivateKey:self.shouldStoreExtendedPrivateKey]; + uint32_t index = (uint32_t)[self indexOfKnownAddressHash:hash160]; + return [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:index]]; } -- (BOOL)hasExtendedPrivateKey { - NSError *error = nil; - return hasKeychainData([self walletBasedExtendedPrivateKeyLocationString], &error); +- (DMaybeOpaqueKey *)generateExtendedPublicKeyFromSeed:(NSData *)seed + storeUnderWalletUniqueId:(NSString *)walletUniqueId { + return [super generateExtendedPublicKeyFromSeed:seed + storeUnderWalletUniqueId:walletUniqueId + storePrivateKey:self.shouldStoreExtendedPrivateKey]; } -- (NSData *)extendedPrivateKeyData { - NSError *error = nil; - NSData *data = getKeychainData([self walletBasedExtendedPrivateKeyLocationString], &error); - return data; -} -- (OpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath { - return [DSKeyManager deriveKeyFromExtenedPrivateKeyDataAtIndexPath:self.extendedPrivateKeyData - indexPath:indexPath - forKeyType:self.signingAlgorithm]; +- (DMaybeOpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath { + NSData *extendedPrivateKeyData = self.extendedPrivateKeyData; + if (!extendedPrivateKeyData) return nil; + Slice_u8 *slice = slice_ctor(extendedPrivateKeyData); + Vec_u32 *index_path = [NSIndexPath ffi_to:indexPath]; + return DMaybeDeriveOpaqueKeyFromExtendedPrivateKeyDataForIndexPath(self.signingAlgorithm, slice, index_path); } - (NSData *)publicKeyDataAtIndexPath:(NSIndexPath *)indexPath { @@ -287,7 +280,15 @@ - (NSData *)publicKeyDataAtIndexPath:(NSIndexPath *)indexPath { } if (hasHardenedDerivation || self.reference == DSDerivationPathReference_ProviderPlatformNodeKeys) { if ([self hasExtendedPrivateKey]) { - return [DSKeyManager publicKeyData:[self privateKeyAtIndexPath:indexPath]]; + DMaybeOpaqueKey *result = [self privateKeyAtIndexPath:indexPath]; + if (!result) return nil; + if (!result->ok) { + DMaybeOpaqueKeyDtor(result); + return nil; + } + NSData *data = [DSKeyManager publicKeyData:result->ok]; + DMaybeOpaqueKeyDtor(result); + return data; } else { return nil; } @@ -296,4 +297,20 @@ - (NSData *)publicKeyDataAtIndexPath:(NSIndexPath *)indexPath { } } +- (void)storeNewAddressInContext:(NSString *)address + atIndex:(uint32_t)n + identityIndex:(uint32_t)identityIndex + context:(NSManagedObjectContext *)context { + [self.managedObjectContext performBlock:^{ // store new address in core data + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; + DSAddressEntity *e = [DSAddressEntity managedObjectInContext:self.managedObjectContext]; + e.derivationPath = derivationPathEntity; + NSAssert(DIsValidDashAddress(DChar(address), self.chain.chainType), @"the address is being saved to the wrong derivation path"); + e.address = address; + e.index = n; + e.identityIndex = identityIndex; + e.standalone = NO; + }]; +} + @end diff --git a/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath+Protected.h b/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath+Protected.h deleted file mode 100644 index 3c2b9c8e6..000000000 --- a/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath+Protected.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSCreditFundingDerivationPath.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSCreditFundingDerivationPath () - -+ (instancetype)blockchainIdentityRegistrationFundingDerivationPathForChain:(DSChain *)chain; -+ (instancetype)blockchainIdentityTopupFundingDerivationPathForChain:(DSChain *)chain; -+ (instancetype)blockchainIdentityInvitationFundingDerivationPathForChain:(DSChain *)chain; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath.m deleted file mode 100644 index 1b3e212c5..000000000 --- a/DashSync/shared/Models/Derivation Paths/DSCreditFundingDerivationPath.m +++ /dev/null @@ -1,91 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2019 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 "DSCreditFundingDerivationPath.h" -#import "DSDerivationPath+Protected.h" -#import "DSDerivationPathFactory.h" -#import "DSMasternodeManager.h" -#import "DSSimpleIndexedDerivationPath+Protected.h" -#import "DSWallet+Protected.h" - -@implementation DSCreditFundingDerivationPath - -+ (instancetype)blockchainIdentityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:wallet]; -} - -+ (instancetype)blockchainIdentityTopupFundingDerivationPathForWallet:(DSWallet *)wallet { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityTopupFundingDerivationPathForWallet:wallet]; -} - -+ (instancetype)blockchainIdentityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:wallet]; -} - -+ (instancetype)blockchainIdentityRegistrationFundingDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_REGISTRATION)}; - BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [DSCreditFundingDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_CreditFunding signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_BlockchainIdentityCreditRegistrationFunding onChain:chain]; -} - -+ (instancetype)blockchainIdentityTopupFundingDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_TOPUP)}; - BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [DSCreditFundingDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_CreditFunding signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_BlockchainIdentityCreditTopupFunding onChain:chain]; -} - -+ (instancetype)blockchainIdentityInvitationFundingDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_IDENTITIES), uint256_from_long(FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_INVITATIONS)}; - BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [DSCreditFundingDerivationPath derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_CreditFunding signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_BlockchainIdentityCreditInvitationFunding onChain:chain]; -} - -- (NSString *)receiveAddress { - NSString *addr = [self registerAddressesWithGapLimit:1 error:nil].lastObject; - return (addr) ? addr : self.mOrderedAddresses.lastObject; -} - -- (NSUInteger)defaultGapLimit { - return 5; -} - -// sign any inputs in the given transaction that can be signed using private keys from the wallet -- (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *)authprompt completion:(TransactionValidityCompletionBlock)completion; -{ - if ([transaction inputAddresses].count != 1) { - completion(NO, NO); - return; - } - - NSUInteger index = [self indexOfKnownAddress:[[transaction inputAddresses] firstObject]]; - - @autoreleasepool { // @autoreleasepool ensures sensitive data will be dealocated immediately - self.wallet.secureSeedRequestBlock(authprompt, MASTERNODE_COST, ^void(NSData *_Nullable seed, BOOL cancelled) { - if (!seed) { - if (completion) completion(NO, cancelled); - } else { - OpaqueKey *key = [self privateKeyAtIndex:(uint32_t)index fromSeed:seed]; - NSValue *val = [NSValue valueWithPointer:key]; - BOOL signedSuccessfully = [transaction signWithPrivateKeys:@[val]]; - processor_destroy_opaque_key(key); - if (completion) completion(signedSuccessfully, NO); - } - }); - } -} - -@end diff --git a/DashSync/shared/Models/Derivation Paths/DSDerivationPath+Protected.h b/DashSync/shared/Models/Derivation Paths/DSDerivationPath+Protected.h index 30a499839..485d52fc7 100644 --- a/DashSync/shared/Models/Derivation Paths/DSDerivationPath+Protected.h +++ b/DashSync/shared/Models/Derivation Paths/DSDerivationPath+Protected.h @@ -5,25 +5,9 @@ // Created by Sam Westrich on 2/10/19. // -#import "DSAccount.h" -#import "DSAddressEntity+CoreDataClass.h" -#import "DSBlockchainIdentity.h" -#import "DSChain.h" #import "DSDerivationPath.h" #import "DSDerivationPathEntity+CoreDataClass.h" -#import "DSKeySequence.h" -#import "DSPeerManager.h" -#import "DSPriceManager.h" -#import "DSTransaction.h" -#import "DSTransactionEntity+CoreDataClass.h" -#import "DSTxInputEntity+CoreDataClass.h" -#import "DSTxOutputEntity+CoreDataClass.h" #import "DSWallet.h" -#import "NSData+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" -#import "NSString+Dash.h" NS_ASSUME_NONNULL_BEGIN @@ -32,13 +16,14 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL addressesLoaded; @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; @property (nonatomic, strong) NSMutableSet *mAllAddresses, *mUsedAddresses; -@property (nonatomic, assign) OpaqueKey *extendedPublicKey; //master public key used to generate wallet addresses +@property (nonatomic, assign) DMaybeOpaqueKey *extendedPublicKey; //master public key used to generate wallet addresses @property (nonatomic, strong) NSString *standaloneExtendedPublicKeyUniqueID; @property (nonatomic, weak) DSWallet *wallet; @property (nonatomic, nullable, readonly) NSString *standaloneExtendedPublicKeyLocationString; -@property (nonatomic, readonly) DSDerivationPathEntity *derivationPathEntity; +@property (nonatomic, strong, readonly) NSData *extendedPrivateKeyData; -- (DSDerivationPathEntity *)derivationPathEntityInContext:(NSManagedObjectContext *)context; +- (NSString *)walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:(NSString *)uniqueID; +- (NSString *)createIdentifierForDerivationPath; @end diff --git a/DashSync/shared/Models/Derivation Paths/DSDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSDerivationPath.h index a0721d9d8..87c5253e3 100644 --- a/DashSync/shared/Models/Derivation Paths/DSDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSDerivationPath.h @@ -22,10 +22,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "dash_shared_core.h" -#import "DSChain.h" -#import "DSDerivationPath.h" +#import "dash_spv_apple_bindings.h" #import "DSKeyManager.h" +#import "DSChain.h" #import "DSTransaction.h" #import "DSUInt256IndexPath.h" #import "NSData+Dash.h" @@ -46,8 +45,9 @@ typedef void (^TransactionValidityCompletionBlock)(BOOL signedTransaction, BOOL #define FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_TOPUP 2 #define FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_INVITATIONS 3 #define FEATURE_PURPOSE_DASHPAY 15 +#define FEATURE_PURPOSE_COINJOIN 4 -@class DSTransaction, DSAccount, DSDerivationPath, DSKeyManager; +@class DSTransaction, DSAccount, DSDerivationPath, DSGapLimit, DSKeyManager, DSWallet; typedef NS_ENUM(NSUInteger, DSDerivationPathType) { @@ -70,7 +70,7 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) DSDerivationPathReference_Unknown = 0, DSDerivationPathReference_BIP32 = 1, DSDerivationPathReference_BIP44 = 2, - DSDerivationPathReference_BlockchainIdentities = 3, + DSDerivationPathReference_Identities = 3, DSDerivationPathReference_ProviderFunds = 4, DSDerivationPathReference_ProviderVotingKeys = 5, DSDerivationPathReference_ProviderOperatorKeys = 6, @@ -78,10 +78,11 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) DSDerivationPathReference_ContactBasedFunds = 8, DSDerivationPathReference_ContactBasedFundsRoot = 9, DSDerivationPathReference_ContactBasedFundsExternal = 10, - DSDerivationPathReference_BlockchainIdentityCreditRegistrationFunding = 11, - DSDerivationPathReference_BlockchainIdentityCreditTopupFunding = 12, - DSDerivationPathReference_BlockchainIdentityCreditInvitationFunding = 13, + DSDerivationPathReference_IdentityCreditRegistrationFunding = 11, + DSDerivationPathReference_IdentityCreditTopupFunding = 12, + DSDerivationPathReference_IdentityCreditInvitationFunding = 13, DSDerivationPathReference_ProviderPlatformNodeKeys = 14, + DSDerivationPathReference_CoinJoin = 15, DSDerivationPathReference_Root = 255, }; @@ -93,7 +94,7 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) //is this an open account @property (nonatomic, assign, readonly) DSDerivationPathType type; -@property (nonatomic, assign, readonly) KeyKind signingAlgorithm; +@property (nonatomic, assign, readonly) DKeyKind *signingAlgorithm; // account for the derivation path @property (nonatomic, readonly) DSChain *chain; @@ -107,6 +108,8 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) @property (nonatomic, readonly) BOOL hasExtendedPublicKey; +@property (nonatomic, readonly) BOOL hasExtendedPrivateKey; + // this returns the derivation path's visual representation (e.g. m/44'/5'/0') @property (nonatomic, readonly) NSString *stringRepresentation; @@ -126,7 +129,7 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) @property (nonatomic, readonly) NSUInteger purpose; // currently the derivationPath is synced to this block height -@property (nonatomic, assign) uint32_t syncBlockHeight; +//@property (nonatomic, assign) uint32_t syncBlockHeight; // all previously generated addresses @property (nonatomic, readonly) NSSet *allAddresses; @@ -141,23 +144,32 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) // the reference of type of derivation path @property (nonatomic, readonly) NSString *referenceName; -// there might be times where the derivationPath is actually unknown, for example when importing from an extended public key -@property (nonatomic, readonly) BOOL derivationPathIsKnown; +@property (nonatomic, readonly) NSNumber *depth; -+ (instancetype)masterBlockchainIdentityContactsDerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain; -+ (instancetype _Nullable)derivationPathWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type signingAlgorithm:(KeyKind)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain; ++ (instancetype)masterIdentityContactsDerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain; -+ (instancetype _Nullable)derivationPathWithSerializedExtendedPrivateKey:(NSString *)serializedExtendedPrivateKey fundsType:(DSDerivationPathType)fundsType signingAlgorithm:(KeyKind)signingAlgorithm onChain:(DSChain *)chain; ++ (instancetype _Nullable)derivationPathWithIndexes:(const UInt256[_Nullable])indexes + hardened:(const BOOL[_Nullable])hardenedIndexes + length:(NSUInteger)length + type:(DSDerivationPathType)type + signingAlgorithm:(DKeyKind *)signingAlgorithm + reference:(DSDerivationPathReference)reference + onChain:(DSChain *)chain; -+ (instancetype _Nullable)derivationPathWithSerializedExtendedPublicKey:(NSString *)serializedExtendedPublicKey onChain:(DSChain *)chain; ++ (instancetype _Nullable)derivationPathWithSerializedExtendedPublicKey:(NSString *)serializedExtendedPublicKey + onChain:(DSChain *)chain; -- (instancetype _Nullable)initWithExtendedPublicKeyIdentifier:(NSString *)extendedPublicKeyIdentifier onChain:(DSChain *)chain; +- (instancetype _Nullable)initWithExtendedPublicKeyIdentifier:(NSString *)extendedPublicKeyIdentifier + onChain:(DSChain *)chain; -- (instancetype _Nullable)initWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type signingAlgorithm:(KeyKind)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain; - -- (BOOL)isBIP32Only; -- (BOOL)isBIP43Based; +- (instancetype _Nullable)initWithIndexes:(const UInt256[_Nullable])indexes + hardened:(const BOOL[_Nullable])hardenedIndexes + length:(NSUInteger)length + type:(DSDerivationPathType)type + signingAlgorithm:(DKeyKind *)signingAlgorithm + reference:(DSDerivationPathReference)reference + onChain:(DSChain *)chain; - (NSIndexPath *)baseIndexPath; @@ -166,13 +178,11 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) // true if the address is controlled by the wallet - (BOOL)containsAddress:(NSString *)address; +- (BOOL)containsAddressHash:(UInt160)hash; // true if the address was previously used as an input or output in any wallet transaction - (BOOL)addressIsUsed:(NSString *)address; -// true if the address at index path was previously used as an input or output in any wallet transaction -- (BOOL)addressIsUsedAtIndexPath:(NSIndexPath *)indexPath; - // inform the derivation path that the address has been used by a transaction, true if the derivation path contains the address - (BOOL)registerTransactionAddress:(NSString *)address; @@ -180,50 +190,48 @@ typedef NS_ENUM(NSUInteger, DSDerivationPathReference) - (NSString *)addressAtIndexPath:(NSIndexPath *)indexPath; // gets a private key at an index path -- (OpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath fromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath + fromSeed:(NSData *)seed; -- (OpaqueKey *_Nullable)privateKeyForKnownAddress:(NSString *)address fromSeed:(NSData *)seed; - -- (OpaqueKey *_Nullable)deprecatedIncorrectExtendedPublicKeyFromSeed:(NSData *_Nullable)seed; //you can set wallet unique Id to nil if you don't wish to store the extended Public Key -- (OpaqueKey *_Nullable)generateExtendedPublicKeyFromSeed:(NSData *)seed storeUnderWalletUniqueId:(NSString *_Nullable)walletUniqueId; +- (DMaybeOpaqueKey *_Nullable)generateExtendedPublicKeyFromSeed:(NSData *)seed + storeUnderWalletUniqueId:(NSString *_Nullable)walletUniqueId; //addition option to store the private key, this should generally not be used unless the key is meant to be used without authentication -- (OpaqueKey *_Nullable)generateExtendedPublicKeyFromSeed:(NSData *)seed storeUnderWalletUniqueId:(NSString *_Nullable)walletUniqueId storePrivateKey:(BOOL)storePrivateKey; +- (DMaybeOpaqueKey *_Nullable)generateExtendedPublicKeyFromSeed:(NSData *)seed + storeUnderWalletUniqueId:(NSString *_Nullable)walletUniqueId + storePrivateKey:(BOOL)storePrivateKey; //you can set wallet unique Id to nil if you don't wish to store the extended Public Key -- (OpaqueKey *_Nullable)generateExtendedPublicKeyFromParentDerivationPath:(DSDerivationPath *)parentDerivationPath storeUnderWalletUniqueId:(NSString *_Nullable)walletUniqueId; - -//sometimes we need to store the public key but not at generation time, use this method for that -- (BOOL)storeExtendedPublicKeyUnderWalletUniqueId:(NSString *_Nonnull)walletUniqueId; - -- (NSString *_Nullable)serializedExtendedPublicKey; - -- (NSString *_Nullable)serializedExtendedPrivateKeyFromSeedAtIndexPath:(NSData *)seed indexPath:(NSIndexPath *)indexPath; -- (NSString *_Nullable)serializedExtendedPrivateKeyFromSeed:(NSData *_Nullable)seed; -+ (NSData *_Nullable)deserializedExtendedPrivateKey:(NSString *)extendedPrivateKeyString onChain:(DSChain *)chain; +- (DMaybeOpaqueKey *_Nullable)generateExtendedPublicKeyFromParentDerivationPath:(DSDerivationPath *)parentDerivationPath + storeUnderWalletUniqueId:(NSString *_Nullable)walletUniqueId; -+ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString onChain:(DSChain *)chain; -- (NSData *_Nullable)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString; -- (OpaqueKey *_Nullable)publicKeyAtIndexPath:(NSIndexPath *)indexPath; +- (DMaybeOpaqueKey *_Nullable)publicKeyAtIndexPath:(NSIndexPath *)indexPath; - (NSData *_Nullable)publicKeyDataAtIndexPath:(NSIndexPath *)indexPath; -- (NSArray *_Nullable)privateKeysAtIndexPaths:(NSArray *)indexPaths fromSeed:(NSData *)seed; - -- (NSArray *_Nullable)serializedPrivateKeysAtIndexPaths:(NSArray *)indexPaths fromSeed:(NSData *)seed; - //this loads the derivation path once it is set to an account that has a wallet; - (void)loadAddresses; - - (void)reloadAddresses; - - (BOOL)isHardenedAtPosition:(NSUInteger)position; - - (BOOL)isDerivationPathEqual:(id)object; +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings; + +// Derivation paths are composed of chains of addresses. Each chain is traversed until a gap of a certain number of addresses is +// found that haven't been used in any transactions. This method returns an array of unused addresses +// following the last used address in the chain. The internal chain is used for change addresses and the external chain +// for receive addresses. These have a hardened purpose scheme depending on the derivation path +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings + inContext:(NSManagedObjectContext *)context; + +@end + +@interface DSDerivationPath (dash_spv_crypto_keys_key_IndexPathU256) ++ (DIndexPathU256 *)ffi_to:(DSDerivationPath *)obj; ++ (void)ffi_destroy:(DIndexPathU256 *)ffi_ref; @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSDerivationPath.m index 4bf8267bf..0fca7e84f 100644 --- a/DashSync/shared/Models/Derivation Paths/DSDerivationPath.m +++ b/DashSync/shared/Models/Derivation Paths/DSDerivationPath.m @@ -22,21 +22,21 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#import "DSAccount.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" +#import "DSChain+Params.h" #import "DSChainManager.h" #import "DSDashpayUserEntity+CoreDataClass.h" +#import "DSDerivationPathFactory.h" #import "DSDerivationPath+Protected.h" #import "DSFriendRequestEntity+CoreDataClass.h" #import "DSIncomingFundsDerivationPath.h" +#import "DSWallet+Identity.h" #import "NSIndexPath+FFI.h" #import "NSManagedObject+Sugar.h" #import "NSMutableData+Dash.h" -#define DERIVATION_PATH_EXTENDED_PUBLIC_KEY_WALLET_BASED_LOCATION @"DP_EPK_WBL" -#define DERIVATION_PATH_EXTENDED_PUBLIC_KEY_STANDALONE_BASED_LOCATION @"DP_EPK_SBL" -#define DERIVATION_PATH_EXTENDED_SECRET_KEY_WALLET_BASED_LOCATION @"DP_ESK_WBL" -#define DERIVATION_PATH_STANDALONE_INFO_DICTIONARY_LOCATION @"DP_SIDL" #define DERIVATION_PATH_STANDALONE_INFO_TERMINAL_INDEX @"DP_SI_T_INDEX" #define DERIVATION_PATH_STANDALONE_INFO_TERMINAL_HARDENED @"DP_SI_T_HARDENED" #define DERIVATION_PATH_STANDALONE_INFO_DEPTH @"DP_SI_DEPTH" @@ -56,70 +56,101 @@ @implementation DSDerivationPath // MARK: - Derivation Path initialization -+ (instancetype)masterBlockchainIdentityContactsDerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long((uint64_t) chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_DASHPAY), uint256_from_long(accountNumber)}; ++ (instancetype)masterIdentityContactsDerivationPathForAccountNumber:(uint32_t)accountNumber + onChain:(DSChain *)chain { + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(FEATURE_PURPOSE_DASHPAY), uint256_from_long(accountNumber)}; //todo full uint256 derivation BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [self derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_PartialPath signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ContactBasedFundsRoot onChain:chain]; + + return [self derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_PartialPath + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ContactBasedFundsRoot + onChain:chain]; } -+ (instancetype _Nullable)derivationPathWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length ++ (instancetype _Nullable)derivationPathWithIndexes:(const UInt256[_Nullable])indexes + hardened:(const BOOL[_Nullable])hardenedIndexes + length:(NSUInteger)length type:(DSDerivationPathType)type - signingAlgorithm:(KeyKind)signingAlgorithm + signingAlgorithm:(DKeyKind *)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { - return [[self alloc] initWithIndexes:indexes hardened:hardenedIndexes length:length type:type signingAlgorithm:signingAlgorithm reference:reference onChain:chain]; -} - -+ (instancetype _Nullable)derivationPathWithSerializedExtendedPrivateKey:(NSString *)serializedExtendedPrivateKey fundsType:(DSDerivationPathType)fundsType signingAlgorithm:(KeyKind)signingAlgorithm onChain:(DSChain *)chain { - UInt256 indexes[] = {}; - BOOL hardenedIndexes[] = {}; - DSDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes hardened:hardenedIndexes length:0 type:fundsType signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_Unknown onChain:chain]; - NSData *extendedPrivateKey = [self deserializedExtendedPrivateKey:serializedExtendedPrivateKey onChain:chain]; - derivationPath.extendedPublicKey = key_create_ecdsa_from_secret(extendedPrivateKey.bytes, 32, true); - [derivationPath standaloneSaveExtendedPublicKeyToKeyChain]; - return derivationPath; + return [[self alloc] initWithIndexes:indexes + hardened:hardenedIndexes + length:length + type:type + signingAlgorithm:signingAlgorithm + reference:reference + onChain:chain]; } -+ (instancetype _Nullable)derivationPathWithSerializedExtendedPublicKey:(NSString *)serializedExtendedPublicKey onChain:(DSChain *)chain { ++ (instancetype _Nullable)derivationPathWithSerializedExtendedPublicKey:(NSString *)serializedExtendedPublicKey + onChain:(DSChain *)chain { uint8_t depth = 0; BOOL terminalHardened; UInt256 terminalIndex = UINT256_ZERO; - NSData *extendedPublicKeyData = [self deserializedExtendedPublicKey:serializedExtendedPublicKey onChain:chain rDepth:&depth rTerminalHardened:&terminalHardened rTerminalIndex:&terminalIndex]; + NSData *extendedPublicKeyData = [DSDerivationPathFactory deserializedExtendedPublicKey:serializedExtendedPublicKey + onChain:chain + rDepth:&depth + rTerminalHardened:&terminalHardened + rTerminalIndex:&terminalIndex]; UInt256 indexes[] = {terminalIndex}; BOOL hardenedIndexes[] = {terminalHardened}; - DSDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes hardened:hardenedIndexes length:0 type:DSDerivationPathType_ViewOnlyFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_Unknown onChain:chain]; //we are going to assume this is only ecdsa for now - derivationPath.extendedPublicKey = key_create_ecdsa_from_extended_public_key_data(extendedPublicKeyData.bytes, extendedPublicKeyData.length); + DKeyKind *key_kind = DKeyKindECDSA(); + DSDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes + hardened:hardenedIndexes + length:0 + type:DSDerivationPathType_ViewOnlyFunds + signingAlgorithm:key_kind + reference:DSDerivationPathReference_Unknown + onChain:chain]; //we are going to assume this is only ecdsa for now + Slice_u8 *slice = slice_ctor(extendedPublicKeyData); + DMaybeOpaqueKey *result = DMaybeOpaqueKeyInitWithExtendedPublicKeyData(key_kind, slice); + derivationPath.extendedPublicKey = result; derivationPath.depth = @(depth); [derivationPath standaloneSaveExtendedPublicKeyToKeyChain]; [derivationPath loadAddresses]; return derivationPath; } -- (instancetype _Nullable)initWithExtendedPublicKeyIdentifier:(NSString *_Nonnull)extendedPublicKeyIdentifier onChain:(DSChain *_Nonnull)chain { +- (instancetype _Nullable)initWithExtendedPublicKeyIdentifier:(NSString *_Nonnull)extendedPublicKeyIdentifier + onChain:(DSChain *_Nonnull)chain { NSError *error = nil; - NSDictionary *infoDictionary = getKeychainDict([DSDerivationPath standaloneInfoDictionaryLocationStringForUniqueID:extendedPublicKeyIdentifier], @[[NSString class], [NSNumber class]], &error); + NSDictionary *infoDictionary = getKeychainDict([DSDerivationPathFactory standaloneInfoDictionaryLocationStringForUniqueID:extendedPublicKeyIdentifier], @[[NSString class], [NSNumber class]], &error); if (error) return nil; UInt256 terminalIndex = [((NSData *)infoDictionary[DERIVATION_PATH_STANDALONE_INFO_TERMINAL_INDEX]) UInt256]; BOOL terminalHardened = [((NSNumber *)infoDictionary[DERIVATION_PATH_STANDALONE_INFO_TERMINAL_HARDENED]) boolValue]; UInt256 indexes[] = {terminalIndex}; BOOL hardenedIndexes[] = {terminalHardened}; - if (!(self = [self initWithIndexes:indexes hardened:hardenedIndexes length:0 type:DSDerivationPathType_ViewOnlyFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_Unknown onChain:chain])) return nil; + DKeyKind *key_kind = DKeyKindECDSA(); + if (!(self = [self initWithIndexes:indexes + hardened:hardenedIndexes + length:0 + type:DSDerivationPathType_ViewOnlyFunds + signingAlgorithm:key_kind + reference:DSDerivationPathReference_Unknown + onChain:chain])) return nil; _walletBasedExtendedPublicKeyLocationString = extendedPublicKeyIdentifier; - NSData *data = getKeychainData([DSDerivationPath standaloneExtendedPublicKeyLocationStringForUniqueID:extendedPublicKeyIdentifier], &error); + NSData *data = getKeychainData([DSDerivationPathFactory standaloneExtendedPublicKeyLocationStringForUniqueID:extendedPublicKeyIdentifier], &error); if (error) return nil; - _extendedPublicKey = key_create_ecdsa_from_extended_public_key_data(data.bytes, data.length); + Slice_u8 *slice = slice_ctor(data); + DMaybeOpaqueKey *result = DMaybeOpaqueKeyWithExtendedPublicKeyData(key_kind, slice); + _extendedPublicKey = result; _depth = infoDictionary[DERIVATION_PATH_STANDALONE_INFO_DEPTH]; - [self loadAddresses]; return self; } -- (instancetype)initWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length +- (instancetype)initWithIndexes:(const UInt256[_Nullable])indexes + hardened:(const BOOL[_Nullable])hardenedIndexes + length:(NSUInteger)length type:(DSDerivationPathType)type - signingAlgorithm:(KeyKind)signingAlgorithm + signingAlgorithm:(DKeyKind *)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { if (length) { @@ -132,7 +163,6 @@ - (instancetype)initWithIndexes:(const UInt256[_Nullable])indexes hardened:(cons _reference = reference; _type = type; _signingAlgorithm = signingAlgorithm; - _derivationPathIsKnown = YES; self.addressesLoaded = FALSE; self.mAllAddresses = [NSMutableSet set]; self.mUsedAddresses = [NSMutableSet set]; @@ -156,9 +186,11 @@ - (void)dealloc { if (_hardenedIndexes != NULL) { free(_hardenedIndexes); } - if (_extendedPublicKey != NULL) { - processor_destroy_opaque_key(_extendedPublicKey); - } + if (_signingAlgorithm != NULL) + DKeyKindDtor(_signingAlgorithm); + + if (_extendedPublicKey != NULL) + DMaybeOpaqueKeyDtor(_extendedPublicKey); } @@ -184,32 +216,12 @@ - (BOOL)terminalHardened { - (NSIndexPath *)baseIndexPath { NSUInteger indexes[self.length]; for (NSUInteger position = 0; position < self.length; position++) { - if ([self isHardenedAtPosition:position]) { - indexes[position] = [self indexAtPosition:position].u64[0] | BIP32_HARD; - } else { - indexes[position] = [self indexAtPosition:position].u64[0]; - } + uint64_t indexAtPos = [self indexAtPosition:position].u64[0]; + indexes[position] = [self isHardenedAtPosition:position] ? indexAtPos | BIP32_HARD : indexAtPos; } return [NSIndexPath indexPathWithIndexes:indexes length:self.length]; } -// MARK: - Purpose - -- (BOOL)isBIP32Only { - if (self.length == 1) return true; - return false; -} - -- (BOOL)isBIP43Based { - if (self.length != 1) return true; - return false; -} - -- (NSUInteger)purpose { - if ([self isBIP43Based]) return [self indexAtPosition:0].u64[0]; - return 0; -} - // MARK: - Account - (NSUInteger)accountNumber { @@ -241,67 +253,28 @@ - (DSChain *)chain { - (BOOL)hasExtendedPublicKey { if (_extendedPublicKey) return YES; - if (self.wallet && (self.length || self.reference == DSDerivationPathReference_Root)) { - return hasKeychainData([self walletBasedExtendedPublicKeyLocationString], nil); - } else { - return hasKeychainData([self standaloneExtendedPublicKeyLocationString], nil); - } - return NO; + NSString *location = self.wallet && (self.length || self.reference == DSDerivationPathReference_Root) + ? [self walletBasedExtendedPublicKeyLocationString] + : [self standaloneExtendedPublicKeyLocationString]; + return hasKeychainData(location, nil); } - (NSData *)extendedPublicKeyData { - if (self.extendedPublicKey != NULL) - return [DSKeyManager extendedPublicKeyData:self.extendedPublicKey]; + if (self.extendedPublicKey != NULL && self.extendedPublicKey->ok != NULL) + return [DSKeyManager extendedPublicKeyData:self.extendedPublicKey->ok]; else return nil; } -- (void)maybeRevertBLSMigration:(NSData *)extendedPublicKeyData { - // revert - // for those who already migrated from legacy to basic BLS derivation scheme - // we revert back their extended public key to legacy - BOOL isBasicBLS = self.signingAlgorithm == KeyKind_BLSBasic; - if (isBasicBLS) { - _extendedPublicKey = key_bls_migrate_from_basic_extended_public_key_data(extendedPublicKeyData.bytes, extendedPublicKeyData.length); - if (_extendedPublicKey) { - setKeychainData([DSKeyManager extendedPublicKeyData:_extendedPublicKey], [self standaloneExtendedPublicKeyLocationString], NO); - } - } -} - -- (OpaqueKey *)extendedPublicKey { +- (DMaybeOpaqueKey *)extendedPublicKey { if (!_extendedPublicKey) { if (self.wallet && (self.length || self.reference == DSDerivationPathReference_Root)) { NSData *extendedPublicKeyData = getKeychainData([self walletBasedExtendedPublicKeyLocationString], nil); - if (extendedPublicKeyData) { - _extendedPublicKey = key_create_from_extended_public_key_data(extendedPublicKeyData.bytes, extendedPublicKeyData.length, (int16_t) self.signingAlgorithm); - [self maybeRevertBLSMigration:extendedPublicKeyData]; - NSAssert(_extendedPublicKey, @"extended public key not set"); - } + if (extendedPublicKeyData) + _extendedPublicKey = DMaybeOpaqueKeyWithExtendedPublicKeyData(self.signingAlgorithm, slice_ctor(extendedPublicKeyData)); } else { NSData *extendedPublicKeyData = getKeychainData([self standaloneExtendedPublicKeyLocationString], nil); -#ifdef DEBUG - if (!extendedPublicKeyData) { - if ([self isKindOfClass:[DSIncomingFundsDerivationPath class]]) { - DSFriendRequestEntity *friendRequest = [DSFriendRequestEntity anyObjectInContext:self.managedObjectContext matching:@"derivationPath.publicKeyIdentifier == %@", self.standaloneExtendedPublicKeyUniqueID]; - - NSAssert(friendRequest, @"friend request must exist"); - - DSBlockchainIdentityUsernameEntity *sourceUsernameEntity = [friendRequest.sourceContact.associatedBlockchainIdentity.usernames anyObject]; - DSBlockchainIdentityUsernameEntity *destinationUsernameEntity = [friendRequest.destinationContact.associatedBlockchainIdentity.usernames anyObject]; -#if DEBUG - DSLogPrivate(@"[%@] No extended public key set for the relationship between %@ and %@ (%@ receiving payments) ", self.chain.name, sourceUsernameEntity.stringValue, destinationUsernameEntity.stringValue, sourceUsernameEntity.stringValue); -#else - DSLog(@"[%@] No extended public key set for the relationship between %@ and %@ (%@ receiving payments) ", self.chain.name, - @"", - @"", - @""); -#endif /* DEBUG */ - } - } -#endif - _extendedPublicKey = key_create_from_extended_public_key_data(extendedPublicKeyData.bytes, extendedPublicKeyData.length, (int16_t) self.signingAlgorithm); - [self maybeRevertBLSMigration:extendedPublicKeyData]; + _extendedPublicKey = DMaybeOpaqueKeyWithExtendedPublicKeyData(self.signingAlgorithm, slice_ctor(extendedPublicKeyData)); } } return _extendedPublicKey; @@ -310,7 +283,10 @@ - (OpaqueKey *)extendedPublicKey { - (void)standaloneSaveExtendedPublicKeyToKeyChain { if (!_extendedPublicKey) return; setKeychainData([self extendedPublicKeyData], [self standaloneExtendedPublicKeyLocationString], NO); - setKeychainDict(@{DERIVATION_PATH_STANDALONE_INFO_TERMINAL_INDEX: uint256_data([self terminalIndex]), DERIVATION_PATH_STANDALONE_INFO_TERMINAL_HARDENED: @([self terminalHardened]), DERIVATION_PATH_STANDALONE_INFO_DEPTH: self.depth}, [self standaloneInfoDictionaryLocationString], NO); + + NSString *dictionaryLocationString = self.standaloneExtendedPublicKeyUniqueID ? [DSDerivationPathFactory standaloneInfoDictionaryLocationStringForUniqueID:_standaloneExtendedPublicKeyUniqueID] : nil; + + setKeychainDict(@{DERIVATION_PATH_STANDALONE_INFO_TERMINAL_INDEX: uint256_data([self terminalIndex]), DERIVATION_PATH_STANDALONE_INFO_TERMINAL_HARDENED: @([self terminalHardened]), DERIVATION_PATH_STANDALONE_INFO_DEPTH: self.depth}, dictionaryLocationString, NO); [self.managedObjectContext performBlockAndWait:^{ [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; }]; @@ -326,29 +302,27 @@ - (NSIndexPath *)indexPathForKnownAddress:(NSString *)address { // gets an address at an index path - (NSString *)addressAtIndexPath:(NSIndexPath *)indexPath { NSData *pubKey = [self publicKeyDataAtIndexPath:indexPath]; - return [DSKeyManager NSStringFrom:key_address_with_public_key_data(pubKey.bytes, pubKey.length, self.chain.chainType)]; + return [DSKeyManager NSStringFrom:DAddressWithPubKeyData(slice_ctor(pubKey), self.chain.chainType)]; } // true if the address is controlled by the wallet - (BOOL)containsAddress:(NSString *)address { - return (address && [self.mAllAddresses containsObject:address]) ? YES : NO; + return address && [self.mAllAddresses containsObject:address]; +} +- (BOOL)containsAddressHash:(UInt160)hash { + NSString *address = [DSKeyManager addressFromHash160:hash forChain:self.chain]; + return [self containsAddress:address]; } // true if the address was previously used as an input or output in any wallet transaction - (BOOL)addressIsUsed:(NSString *)address { - return (address && [self.mUsedAddresses containsObject:address]) ? YES : NO; -} - -// true if the address at index path was previously used as an input or output in any wallet transaction -- (BOOL)addressIsUsedAtIndexPath:(NSIndexPath *)indexPath { - return [self addressIsUsed:[self addressAtIndexPath:indexPath]]; + return address && [self.mUsedAddresses containsObject:address]; } - (BOOL)registerTransactionAddress:(NSString *_Nonnull)address { if ([self containsAddress:address]) { - if (![self.mUsedAddresses containsObject:address]) { + if (![self.mUsedAddresses containsObject:address]) [self.mUsedAddresses addObject:address]; - } return TRUE; } return FALSE; @@ -358,7 +332,6 @@ - (NSSet *)allAddresses { return [self.mAllAddresses copy]; } - - (NSSet *)usedAddresses { return [self.mUsedAddresses copy]; } @@ -369,16 +342,6 @@ - (void)loadAddresses { - (void)reloadAddresses { } -// MARK: - Derivation Path Information - -- (DSDerivationPathEntity *)derivationPathEntity { - return [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; -} - -- (DSDerivationPathEntity *)derivationPathEntityInContext:(NSManagedObjectContext *)context { - return [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:context]; -} - - (NSNumber *)depth { if (_depth != nil) return _depth; @@ -398,32 +361,13 @@ - (NSUInteger)hash { return [self.standaloneExtendedPublicKeyUniqueID hash]; } -+ (NSString *)stringRepresentationOfIndex:(UInt256)index hardened:(BOOL)hardened inContext:(NSManagedObjectContext *)context { - if (uint256_is_31_bits(index)) { - return [NSString stringWithFormat:@"/%lu%@", (unsigned long)index.u64[0], hardened ? @"'" : @""]; - } else if (context) { - __block NSString *rString = nil; - [context performBlockAndWait:^{ - DSDashpayUserEntity *dashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentity.uniqueID == %@", uint256_data(index)]; - if (dashpayUserEntity) { - DSBlockchainIdentityUsernameEntity *usernameEntity = [dashpayUserEntity.associatedBlockchainIdentity.usernames anyObject]; - rString = [NSString stringWithFormat:@"/%@%@", usernameEntity.stringValue, hardened ? @"'" : @""]; - } else { - rString = [NSString stringWithFormat:@"/0x%@%@", uint256_hex(index), hardened ? @"'" : @""]; - } - }]; - return rString; - } else { - return [NSString stringWithFormat:@"/0x%@%@", uint256_hex(index), hardened ? @"'" : @""]; - } -} - (NSString *)stringRepresentation { if (_stringRepresentation) return _stringRepresentation; NSMutableString *mutableString = [NSMutableString stringWithFormat:@"m"]; if (self.length) { for (NSInteger i = 0; i < self.length; i++) { - [mutableString appendString:[DSDerivationPath stringRepresentationOfIndex:[self indexAtPosition:i] hardened:[self isHardenedAtPosition:i] inContext:self.managedObjectContext]]; + [mutableString appendString:[DSDerivationPathFactory stringRepresentationOfIndex:[self indexAtPosition:i] hardened:[self isHardenedAtPosition:i] inContext:self.managedObjectContext]]; } } else if ([self.depth integerValue]) { for (NSInteger i = 0; i < [self.depth integerValue] - 1; i++) { @@ -431,22 +375,22 @@ - (NSString *)stringRepresentation { } UInt256 terminalIndex = [self terminalIndex]; BOOL terminalHardened = [self terminalHardened]; - [mutableString appendString:[DSDerivationPath stringRepresentationOfIndex:terminalIndex hardened:terminalHardened inContext:self.managedObjectContext]]; + [mutableString appendString:[DSDerivationPathFactory stringRepresentationOfIndex:terminalIndex hardened:terminalHardened inContext:self.managedObjectContext]]; } else { if ([self isKindOfClass:[DSIncomingFundsDerivationPath class]]) { mutableString = [NSMutableString stringWithFormat:@"inc"]; DSIncomingFundsDerivationPath *incomingFundsDerivationPath = (DSIncomingFundsDerivationPath *)self; [self.managedObjectContext performBlockAndWait:^{ - DSDashpayUserEntity *sourceDashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:self.managedObjectContext matching:@"associatedBlockchainIdentity.uniqueID == %@", uint256_data(incomingFundsDerivationPath.contactSourceBlockchainIdentityUniqueId)]; + DSDashpayUserEntity *sourceDashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:self.managedObjectContext matching:@"associatedBlockchainIdentity.uniqueID == %@", uint256_data(incomingFundsDerivationPath.contactSourceIdentityUniqueId)]; if (sourceDashpayUserEntity) { DSBlockchainIdentityUsernameEntity *usernameEntity = [sourceDashpayUserEntity.associatedBlockchainIdentity.usernames anyObject]; [mutableString appendFormat:@"/%@", usernameEntity.stringValue]; } else { - [mutableString appendFormat:@"/0x%@", uint256_hex(incomingFundsDerivationPath.contactSourceBlockchainIdentityUniqueId)]; + [mutableString appendFormat:@"/0x%@", uint256_hex(incomingFundsDerivationPath.contactSourceIdentityUniqueId)]; } }]; - DSBlockchainIdentity *blockchainIdentity = [self.wallet blockchainIdentityForUniqueId:incomingFundsDerivationPath.contactDestinationBlockchainIdentityUniqueId]; - [mutableString appendFormat:@"/%@", blockchainIdentity.currentDashpayUsername]; + DSIdentity *identity = [self.wallet identityForUniqueId:incomingFundsDerivationPath.contactDestinationIdentityUniqueId]; + [mutableString appendFormat:@"/%@", identity.currentDashpayUsername]; } } _stringRepresentation = [mutableString copy]; @@ -476,7 +420,7 @@ - (NSString *)referenceName { case DSDerivationPathReference_ProviderVotingKeys: return @"Provider Voting Keys"; break; - case DSDerivationPathReference_BlockchainIdentities: + case DSDerivationPathReference_Identities: return @"Blockchain Identities"; break; case DSDerivationPathReference_ContactBasedFunds: @@ -488,15 +432,18 @@ - (NSString *)referenceName { case DSDerivationPathReference_ContactBasedFundsRoot: return @"Contact Funds Root"; break; - case DSDerivationPathReference_BlockchainIdentityCreditRegistrationFunding: + case DSDerivationPathReference_IdentityCreditRegistrationFunding: return @"BI Credit Registration Funding"; break; - case DSDerivationPathReference_BlockchainIdentityCreditTopupFunding: + case DSDerivationPathReference_IdentityCreditTopupFunding: return @"BI Credit Topup Funding"; break; - case DSDerivationPathReference_BlockchainIdentityCreditInvitationFunding: + case DSDerivationPathReference_IdentityCreditInvitationFunding: return @"BI Credit Invitation Funding"; break; + case DSDerivationPathReference_CoinJoin: + return @"CoinJoin"; + break; default: return @"Unknown"; break; @@ -512,8 +459,10 @@ - (NSString *)debugDescription { //Derivation paths can be stored based on the wallet and derivation or based solely on the public key - (NSString *)createIdentifierForDerivationPath { - // TODO: rust migration u64 - return [NSData dataWithUInt256:[[self extendedPublicKeyData] SHA256]].shortHexString; + Result_ok_u8_arr_32_err_dash_spv_crypto_keys_KeyError *result = DOpaqueKeyCreateIdentifier(self.extendedPublicKey->ok); + NSData *identifier = NSDataFromPtr(result->ok); + Result_ok_u8_arr_32_err_dash_spv_crypto_keys_KeyError_destroy(result); + return identifier.shortHexString; } - (NSString *)standaloneExtendedPublicKeyUniqueID { @@ -527,26 +476,9 @@ - (NSString *)standaloneExtendedPublicKeyUniqueID { return _standaloneExtendedPublicKeyUniqueID; } -+ (NSString *)standaloneExtendedPublicKeyLocationStringForUniqueID:(NSString *)uniqueID { - return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_EXTENDED_PUBLIC_KEY_STANDALONE_BASED_LOCATION, uniqueID]; -} - - (NSString *)standaloneExtendedPublicKeyLocationString { if (!self.standaloneExtendedPublicKeyUniqueID) return nil; - return [DSDerivationPath standaloneExtendedPublicKeyLocationStringForUniqueID:self.standaloneExtendedPublicKeyUniqueID]; -} - -+ (NSString *)standaloneInfoDictionaryLocationStringForUniqueID:(NSString *)uniqueID { - return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_STANDALONE_INFO_DICTIONARY_LOCATION, uniqueID]; -} - -- (NSString *)standaloneInfoDictionaryLocationString { - if (!self.standaloneExtendedPublicKeyUniqueID) return nil; - return [DSDerivationPath standaloneInfoDictionaryLocationStringForUniqueID:self.standaloneExtendedPublicKeyUniqueID]; -} - -+ (NSString *)walletBasedExtendedPublicKeyLocationStringForUniqueID:(NSString *)uniqueID { - return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_EXTENDED_PUBLIC_KEY_WALLET_BASED_LOCATION, uniqueID]; + return [DSDerivationPathFactory standaloneExtendedPublicKeyLocationStringForUniqueID:self.standaloneExtendedPublicKeyUniqueID]; } - (NSString *)walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:(NSString *)uniqueID { @@ -554,9 +486,12 @@ - (NSString *)walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:(NSStr for (NSInteger i = 0; i < self.length; i++) { [mutableString appendFormat:@"_%lu", (unsigned long)([self isHardenedAtPosition:i] ? [self indexAtPosition:i].u64[0] | BIP32_HARD : [self indexAtPosition:i].u64[0])]; } + char *key_storage_prefix = DKeyKindStoragePrefix(self.signingAlgorithm); + NSString *keyStoragePrefix = NSStringFromPtr(key_storage_prefix); + DCharDtor(key_storage_prefix); return [NSString stringWithFormat:@"%@%@%@", - [DSDerivationPath walletBasedExtendedPublicKeyLocationStringForUniqueID:uniqueID], - [DSKeyManager keyStoragePrefix:self.signingAlgorithm], + [DSDerivationPathFactory walletBasedExtendedPublicKeyLocationStringForUniqueID:uniqueID], + keyStoragePrefix, mutableString]; } @@ -566,9 +501,6 @@ - (NSString *)walletBasedExtendedPublicKeyLocationString { return _walletBasedExtendedPublicKeyLocationString; } -+ (NSString *)walletBasedExtendedPrivateKeyLocationStringForUniqueID:(NSString *)uniqueID { - return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_EXTENDED_SECRET_KEY_WALLET_BASED_LOCATION, uniqueID]; -} - (NSString *)walletBasedExtendedPrivateKeyLocationStringForWalletUniqueID:(NSString *)uniqueID { NSMutableString *mutableString = [NSMutableString string]; @@ -576,9 +508,13 @@ - (NSString *)walletBasedExtendedPrivateKeyLocationStringForWalletUniqueID:(NSSt [mutableString appendFormat:@"_%lu", (unsigned long)([self isHardenedAtPosition:i] ? [self indexAtPosition:i].u64[0] | BIP32_HARD : [self indexAtPosition:i].u64[0])]; } // TODO: ED25519 has own prefix + char *key_storage_prefix = DKeyKindStoragePrefix(self.signingAlgorithm); + NSString *keyStoragePrefix = NSStringFromPtr(key_storage_prefix); + DCharDtor(key_storage_prefix); + return [NSString stringWithFormat:@"%@%@%@", - [DSDerivationPath walletBasedExtendedPrivateKeyLocationStringForUniqueID:uniqueID], - [DSKeyManager keyStoragePrefix:self.signingAlgorithm], + [DSDerivationPathFactory walletBasedExtendedPrivateKeyLocationStringForUniqueID:uniqueID], + keyStoragePrefix, mutableString]; } @@ -590,238 +526,131 @@ - (NSString *)walletBasedExtendedPrivateKeyLocationString { // MARK: - Key Generation -- (OpaqueKey *)generateExtendedPublicKeyFromSeed:(NSData *)seed storeUnderWalletUniqueId:(NSString *)walletUniqueId { - return [self generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:walletUniqueId storePrivateKey:NO]; +- (DMaybeOpaqueKey *_Nullable)generateExtendedPublicKeyFromSeed:(NSData *)seed + storeUnderWalletUniqueId:(NSString *)walletUniqueId { + return [self generateExtendedPublicKeyFromSeed:seed + storeUnderWalletUniqueId:walletUniqueId + storePrivateKey:NO]; } -- (OpaqueKey *)generateExtendedPublicKeyFromSeed:(NSData *)seed storeUnderWalletUniqueId:(NSString *)walletUniqueId storePrivateKey:(BOOL)storePrivateKey { +- (DMaybeOpaqueKey *_Nullable)generateExtendedPublicKeyFromSeed:(NSData *)seed + storeUnderWalletUniqueId:(NSString *)walletUniqueId + storePrivateKey:(BOOL)storePrivateKey { if (!seed) return nil; if (![self length] && self.reference != DSDerivationPathReference_Root) return nil; //there needs to be at least 1 length @autoreleasepool { if (_extendedPublicKey) - processor_destroy_opaque_key(_extendedPublicKey); - _extendedPublicKey = generate_extended_public_key_from_seed(seed.bytes, seed.length, (int16_t) self.signingAlgorithm, (const uint8_t *) self->_indexes, self->_hardenedIndexes, self->_length); + DMaybeOpaqueKeyDtor(_extendedPublicKey); + + Slice_u8 *slice = slice_ctor(seed); + DIndexPathU256 *path = [DSDerivationPath ffi_to:self]; + DMaybeOpaqueKey *result = DMaybeOpaqueKeyFromExtendedPublicKeyDataAtU256(self.signingAlgorithm, slice, path); + _extendedPublicKey = result; NSAssert(_extendedPublicKey, @"extendedPublicKey should be set"); if (_extendedPublicKey == NULL) { return nil; } if (walletUniqueId) { - NSData *publicKeyData = [DSKeyManager extendedPublicKeyData:_extendedPublicKey]; + NSData *publicKeyData = [DSKeyManager extendedPublicKeyData:_extendedPublicKey->ok]; setKeychainData(publicKeyData, [self walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:walletUniqueId], NO); if (storePrivateKey) { - NSData *privateKeyData = [DSKeyManager extendedPrivateKeyData:_extendedPublicKey]; + NSData *privateKeyData = [DSKeyManager extendedPrivateKeyData:_extendedPublicKey->ok]; setKeychainData(privateKeyData, [self walletBasedExtendedPrivateKeyLocationStringForWalletUniqueID:walletUniqueId], YES); } } - forget_private_key(_extendedPublicKey); + DOpaqueKeyForgetPrivateKey(_extendedPublicKey->ok); } return _extendedPublicKey; } -- (OpaqueKey *)generateExtendedPublicKeyFromParentDerivationPath:(DSDerivationPath *)parentDerivationPath storeUnderWalletUniqueId:(NSString *)walletUniqueId { - NSAssert(parentDerivationPath.signingAlgorithm == self.signingAlgorithm, @"The signing algorithms must be the same"); +- (DMaybeOpaqueKey *)generateExtendedPublicKeyFromParentDerivationPath:(DSDerivationPath *)parentDerivationPath + storeUnderWalletUniqueId:(NSString *)walletUniqueId { + + NSAssert(DKeyKindIndex(parentDerivationPath.signingAlgorithm) == DKeyKindIndex(self.signingAlgorithm), @"The signing algorithms must be the same"); NSParameterAssert(parentDerivationPath); NSAssert(self.length > parentDerivationPath.length, @"length must be inferior to the parent derivation path length"); NSAssert(parentDerivationPath.extendedPublicKey, @"the parent derivation path must have an extended public key"); if (![self length]) return nil; //there needs to be at least 1 length if (self.length <= parentDerivationPath.length) return nil; // we need to be longer if (!parentDerivationPath.extendedPublicKey) return nil; //parent derivation path - if (parentDerivationPath.signingAlgorithm != self.signingAlgorithm) return nil; + if (DKeyKindIndex(parentDerivationPath.signingAlgorithm) != DKeyKindIndex(self.signingAlgorithm)) return nil; for (NSInteger i = 0; i < [parentDerivationPath length] - 1; i++) { NSAssert(uint256_eq([parentDerivationPath indexAtPosition:i], [self indexAtPosition:i]), @"This derivation path must start with elements of the parent derivation path"); if (!uint256_eq([parentDerivationPath indexAtPosition:i], [self indexAtPosition:i])) return nil; } if (_extendedPublicKey) - processor_destroy_opaque_key(_extendedPublicKey); - _extendedPublicKey = [DSKeyManager keyPublicDeriveTo256Bit:parentDerivationPath childIndexes:self->_indexes childHardened:self->_hardenedIndexes length:self.length]; + DMaybeOpaqueKeyDtor(_extendedPublicKey); + + DIndexPathU256 *path = [DSDerivationPath ffi_to:self]; + _extendedPublicKey = DOpaqueKeyDerivateTo256WithOffset(parentDerivationPath.extendedPublicKey->ok, path, parentDerivationPath.length); NSAssert(_extendedPublicKey, @"extendedPublicKey should be set"); if (walletUniqueId) { - NSData *publicKeyData = [DSKeyManager extendedPublicKeyData:_extendedPublicKey]; + NSData *publicKeyData = [DSKeyManager extendedPublicKeyData:_extendedPublicKey->ok]; setKeychainData(publicKeyData, [self walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:walletUniqueId], NO); } return _extendedPublicKey; } -- (OpaqueKey *)privateKeyForKnownAddress:(NSString *)address fromSeed:(NSData *)seed { - NSIndexPath *indexPathForAddress = [self indexPathForKnownAddress:address]; - return [self privateKeyAtIndexPath:indexPathForAddress fromSeed:seed]; +- (DMaybeOpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath + fromSeed:(NSData *)seed { + NSParameterAssert(indexPath); + NSParameterAssert(seed); + if (!seed || !indexPath) return nil; + if (!self->_length) return nil; //there needs to be at least 1 length + Slice_u8 *seed_slice = slice_ctor(seed); + Vec_u32 *index_path = [NSIndexPath ffi_to:indexPath]; + DIndexPathU256 *path = [DSDerivationPath ffi_to:self]; + DMaybeOpaqueKey *result = DMaybeOpaquePrivateKeyAtIndexPathWrapped(self.signingAlgorithm, seed_slice, index_path, path); + return result; } -- (OpaqueKey *_Nullable)privateKeyAtIndexPath:(NSIndexPath *)indexPath fromSeed:(NSData *)seed { - return [DSKeyManager privateKeyAtIndexPath:self.signingAlgorithm indexes:self->_indexes hardened:self->_hardenedIndexes length:self.length indexPath:indexPath fromSeed:seed]; -} - -- (OpaqueKey *)publicKeyAtIndexPath:(NSIndexPath *)indexPath { - return [DSKeyManager publicKeyAtIndexPath:self.extendedPublicKey indexPath:indexPath]; +- (DMaybeOpaqueKey *_Nullable)publicKeyAtIndexPath:(NSIndexPath *)indexPath { + NSData *publicKeyData = [self publicKeyDataAtIndexPath:indexPath]; + return DMaybeOpaqueKeyWithPublicKeyData(self.signingAlgorithm, slice_ctor(publicKeyData)); } - (NSData *)publicKeyDataAtIndexPath:(NSIndexPath *)indexPath { - return [DSKeyManager publicKeyDataAtIndexPath:self.extendedPublicKey indexPath:indexPath]; -} - -- (NSArray *)privateKeysAtIndexPaths:(NSArray *)indexPaths fromSeed:(NSData *)seed { - if (!seed || !indexPaths) return nil; - if (indexPaths.count == 0) return @[]; - - NSUInteger count = indexPaths.count; - IndexPathData *data = malloc(sizeof(IndexPathData) * count); - for (NSUInteger i = 0; i < count; i++) { - NSIndexPath *indexPath = indexPaths[i]; - NSUInteger length = indexPath.length; - NSUInteger *indexes = malloc(sizeof(NSUInteger) * length); - [indexPath getIndexes:indexes]; - data[i].indexes = indexes; - data[i].len = length; - } - OpaqueKeys *keys = key_private_keys_at_index_paths(seed.bytes, seed.length, (int16_t) self.signingAlgorithm, data, count, (const uint8_t *) self->_indexes, self->_hardenedIndexes, self->_length); - for (NSUInteger i = 0; i < count; i++) { - free((void *)data[i].indexes); - } - free(data); - NSMutableArray *privateKeys = [NSMutableArray arrayWithCapacity:keys->len]; - for (NSUInteger i = 0; i < keys->len; i++) { - [privateKeys addObject:[NSValue valueWithPointer:keys->keys[i]]]; - } - // TODO: destroy when keys don't need anymore - // processor_destroy_opaque_keys(keys); - -// NSMutableArray *privateKeys = [NSMutableArray arrayWithCapacity:indexPaths.count]; -// DSKey *topKey = [DSKey keyWithSeedData:seed forKeyType:self.signingAlgorithm]; -// DSKey *derivationPathExtendedKey = [topKey privateDeriveTo256BitDerivationPath:self]; -// -//#if DEBUG -// if (_extendedPublicKey) { -// NSData *publicKey = _extendedPublicKey.extendedPublicKeyData; -// NSAssert([publicKey isEqualToData:derivationPathExtendedKey.extendedPublicKeyData], @"The derivation doesn't match the public key"); -// } -//#endif - - return privateKeys; -} - - -- (NSArray *)serializedPrivateKeysAtIndexPaths:(NSArray *)indexPaths fromSeed:(NSData *)seed { - if (!seed || !indexPaths) return nil; - if (indexPaths.count == 0) return @[]; - - NSUInteger count = indexPaths.count; - IndexPathData *data = malloc(sizeof(IndexPathData) * count); - for (NSUInteger i = 0; i < count; i++) { - NSIndexPath *indexPath = indexPaths[i]; - NSUInteger length = indexPath.length; - NSUInteger *indexes = malloc(sizeof(NSUInteger) * length); - [indexPath getIndexes:indexes]; - data[i].indexes = indexes; - data[i].len = length; - } - OpaqueSerializedKeys *keys = serialized_key_private_keys_at_index_paths(seed.bytes, seed.length, (int16_t) self.signingAlgorithm, data, count, (const uint8_t *) self->_indexes, self->_hardenedIndexes, self.length, self.chain.chainType); - for (NSUInteger i = 0; i < count; i++) { - free((void *)data[i].indexes); - } - free(data); - NSMutableArray *privateKeys = [NSMutableArray arrayWithCapacity:keys->len]; - for (NSUInteger i = 0; i < keys->len; i++) { - [privateKeys addObject:[NSString stringWithUTF8String:keys->keys[i]]]; - } - processor_destroy_serialized_opaque_keys(keys); - return privateKeys; + return [DSKeyManager publicKeyDataAtIndexPath:self.extendedPublicKey->ok indexPath:indexPath]; } - -// MARK: - Deprecated - -//this is for upgrade purposes only -// TODO: check if this needed -- (OpaqueKey *)deprecatedIncorrectExtendedPublicKeyFromSeed:(NSData *)seed { - if (!seed) return nil; - if (![self length]) return nil; //there needs to be at least 1 length - return [DSKeyManager keyDeprecatedExtendedPublicKeyFromSeed:seed indexes:self->_indexes hardened:self->_hardenedIndexes length:self.length]; +- (BOOL)hasExtendedPrivateKey { + NSError *error = nil; + return hasKeychainData([self walletBasedExtendedPrivateKeyLocationString], &error); } -// MARK: - Storage - -- (BOOL)storeExtendedPublicKeyUnderWalletUniqueId:(NSString *)walletUniqueId { - if (!_extendedPublicKey) return FALSE; - NSParameterAssert(walletUniqueId); - NSData *data = [DSKeyManager extendedPublicKeyData:_extendedPublicKey]; - setKeychainData(data, [self walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:walletUniqueId], NO); - return TRUE; +- (NSData *)extendedPrivateKeyData { + NSError *error = nil; + return getKeychainData([self walletBasedExtendedPrivateKeyLocationString], &error); } -// MARK: - Serializations - -- (NSString *_Nullable)serializedExtendedPrivateKeyFromSeedAtIndexPath:(NSData *)seed indexPath:(NSIndexPath *)indexPath { - OpaqueKey *key = [self privateKeyAtIndexPath:indexPath fromSeed:seed]; - NSString *pk = [DSKeyManager NSStringFrom:key_serialized_private_key_for_chain(key, self.chain.chainType)]; - processor_destroy_opaque_key(key); - return pk; -} -- (NSString *)serializedExtendedPrivateKeyFromSeed:(NSData *)seed { - @autoreleasepool { - if (!seed) return nil; - return [DSKeyManager NSStringFrom:key_serialized_extended_private_key_from_seed(seed.bytes, seed.length, (const uint8_t *) self->_indexes, self->_hardenedIndexes, self->_length, self.chain.chainType)]; - } +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings { + return [self registerAddressesWithSettings:settings inContext:self.managedObjectContext]; } -+ (NSData *)deserializedExtendedPrivateKey:(NSString *)extendedPrivateKeyString onChain:(DSChain *)chain { - @autoreleasepool { - uint8_t depth; - uint32_t fingerprint; - UInt256 child; - BOOL hardened; - UInt256 chainHash; - NSData *privkey = nil; - NSMutableData *masterPrivateKey = [NSMutableData secureData]; - BOOL valid = deserialize(extendedPrivateKeyString, &depth, &fingerprint, &hardened, &child, &chainHash, &privkey, [chain isMainnet]); - if (!valid) return nil; - [masterPrivateKey appendUInt32:fingerprint]; - [masterPrivateKey appendBytes:&chainHash length:32]; - [masterPrivateKey appendData:privkey]; - return [masterPrivateKey copy]; - } +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings inContext:(NSManagedObjectContext *)context { + NSAssert(FALSE, @"This must be implemented in subclasses"); + return nil; } -- (NSString *)serializedExtendedPublicKey { - //todo make sure this works with BLS keys - NSData *extPubKeyData = self.extendedPublicKeyData; - if (extPubKeyData.length < 36) return nil; - uint32_t fingerprint = [extPubKeyData UInt32AtOffset:0]; - UInt256 chain = [extPubKeyData UInt256AtOffset:4]; - DSECPoint pubKey = [extPubKeyData ECPointAtOffset:36]; - UInt256 child = UINT256_ZERO; - BOOL isHardened = NO; - if (self.length) { - child = [self indexAtPosition:[self length] - 1]; - isHardened = [self isHardenedAtPosition:[self length] - 1]; - } +@end - return serialize([self.depth unsignedCharValue], fingerprint, isHardened, child, chain, [NSData dataWithBytes:&pubKey length:sizeof(pubKey)], [self.chain isMainnet]); -} -+ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString onChain:(DSChain *)chain rDepth:(uint8_t *)depth rTerminalHardened:(BOOL *)terminalHardened rTerminalIndex:(UInt256 *)terminalIndex { - uint32_t fingerprint; - UInt256 chainHash; - NSData *pubkey = nil; - NSMutableData *masterPublicKey = [NSMutableData secureData]; - BOOL valid = deserialize(extendedPublicKeyString, depth, &fingerprint, terminalHardened, terminalIndex, &chainHash, &pubkey, [chain isMainnet]); - if (!valid) return nil; - [masterPublicKey appendUInt32:fingerprint]; - [masterPublicKey appendBytes:&chainHash length:32]; - [masterPublicKey appendData:pubkey]; - return [masterPublicKey copy]; -} +@implementation DSDerivationPath (dash_spv_crypto_keys_key_IndexPathU256) -+ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString onChain:(DSChain *)chain { - __unused uint8_t depth = 0; - __unused BOOL terminalHardened = 0; - __unused UInt256 terminalIndex = UINT256_ZERO; - NSData *extendedPublicKey = [self deserializedExtendedPublicKey:extendedPublicKeyString onChain:chain rDepth:&depth rTerminalHardened:&terminalHardened rTerminalIndex:&terminalIndex]; - return extendedPublicKey; ++ (DIndexPathU256 *)ffi_to:(DSDerivationPath *)obj { + uintptr_t length = obj.length; + u256 **indexes = malloc(length * sizeof(u256 *)); + bool *hardened = malloc(length * sizeof(bool)); + for (NSUInteger i = 0; i < length; i++) { + indexes[i] = u256_ctor_u(obj->_indexes[i]); + hardened[i] = obj->_hardenedIndexes[i]; + } + Vec_u8_32 *i = Vec_u8_32_ctor(length, indexes); + Vec_bool *h = Vec_bool_ctor(length, hardened); + return dash_spv_crypto_keys_key_IndexPathU256_ctor(i, h); } - -- (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString { - return [DSDerivationPath deserializedExtendedPublicKey:extendedPublicKeyString onChain:self.chain]; ++ (void)ffi_destroy:(DIndexPathU256 *)ffi_ref { + dash_spv_crypto_keys_key_IndexPathU256_destroy(ffi_ref); } - @end diff --git a/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.h b/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.h index 0a06858fe..fa5465d0b 100644 --- a/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.h +++ b/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.h @@ -6,10 +6,12 @@ // #import +#import "DSKeyManager.h" + NS_ASSUME_NONNULL_BEGIN -@class DSAuthenticationKeysDerivationPath, DSWallet, DSMasternodeHoldingsDerivationPath, DSDerivationPath, DSCreditFundingDerivationPath; +@class DSAuthenticationKeysDerivationPath, DSWallet, DSMasternodeHoldingsDerivationPath, DSDerivationPath, DSAssetLockDerivationPath; @interface DSDerivationPathFactory : NSObject @@ -21,11 +23,11 @@ NS_ASSUME_NONNULL_BEGIN - (DSAuthenticationKeysDerivationPath *)platformNodeKeysDerivationPathForWallet:(DSWallet *)wallet; - (DSMasternodeHoldingsDerivationPath *)providerFundsDerivationPathForWallet:(DSWallet *)wallet; -- (DSCreditFundingDerivationPath *)blockchainIdentityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet; -- (DSCreditFundingDerivationPath *)blockchainIdentityTopupFundingDerivationPathForWallet:(DSWallet *)wallet; -- (DSCreditFundingDerivationPath *)blockchainIdentityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet; -- (DSAuthenticationKeysDerivationPath *)blockchainIdentityBLSKeysDerivationPathForWallet:(DSWallet *)wallet; -- (DSAuthenticationKeysDerivationPath *)blockchainIdentityECDSAKeysDerivationPathForWallet:(DSWallet *)wallet; +- (DSAssetLockDerivationPath *)identityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet; +- (DSAssetLockDerivationPath *)identityTopupFundingDerivationPathForWallet:(DSWallet *)wallet; +- (DSAssetLockDerivationPath *)identityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet; +- (DSAuthenticationKeysDerivationPath *)identityBLSKeysDerivationPathForWallet:(DSWallet *)wallet; +- (DSAuthenticationKeysDerivationPath *)identityECDSAKeysDerivationPathForWallet:(DSWallet *)wallet; - (NSArray *)loadedSpecializedDerivationPathsForWallet:(DSWallet *)wallet; - (NSArray *)unloadedSpecializedDerivationPathsForWallet:(DSWallet *)wallet; @@ -33,6 +35,36 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)fundDerivationPathsNeedingExtendedPublicKeyForWallet:(DSWallet *)wallet; + + + + ++ (DMaybeOpaqueKeys *)privateKeysAtIndexPaths:(NSArray *)indexPaths + fromSeed:(NSData *)seed + derivationPath:(DSDerivationPath *)derivationPath; ++ (NSArray *)serializedPrivateKeysAtIndexPaths:(NSArray *)indexPaths + fromSeed:(NSData *)seed + derivationPath:(DSDerivationPath *)derivationPath; ++ (NSString *_Nullable)serializedExtendedPublicKey:(DSDerivationPath *)derivationPath; + ++ (NSString *)serializedExtendedPrivateKeyFromSeed:(NSData *)seed + derivationPath:(DSDerivationPath *)derivationPath; ++ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString onChain:(DSChain *)chain; ++ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString + onChain:(DSChain *)chain + rDepth:(uint8_t *)depth + rTerminalHardened:(BOOL *)terminalHardened + rTerminalIndex:(UInt256 *)terminalIndex; ++ (NSData *)deserializedExtendedPublicKey:(DSDerivationPath *)derivationPath extendedPublicKeyString:(NSString *)extendedPublicKeyString; + ++ (NSString *)standaloneExtendedPublicKeyLocationStringForUniqueID:(NSString *)uniqueID; ++ (NSString *)standaloneInfoDictionaryLocationStringForUniqueID:(NSString *)uniqueID; ++ (NSString *)walletBasedExtendedPublicKeyLocationStringForUniqueID:(NSString *)uniqueID; ++ (NSString *)walletBasedExtendedPrivateKeyLocationStringForUniqueID:(NSString *)uniqueID; + + ++ (NSString *)stringRepresentationOfIndex:(UInt256)index hardened:(BOOL)hardened inContext:(NSManagedObjectContext *)context; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.m b/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.m index caebe861c..233933a7d 100644 --- a/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.m +++ b/DashSync/shared/Models/Derivation Paths/DSDerivationPathFactory.m @@ -6,9 +6,22 @@ // #import "DSDerivationPathFactory.h" +#import "DSAccount.h" #import "DSAuthenticationKeysDerivationPath+Protected.h" -#import "DSCreditFundingDerivationPath+Protected.h" +#import "DSChain+Params.h" +#import "DSAssetLockDerivationPath+Protected.h" #import "DSMasternodeHoldingsDerivationPath+Protected.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" +#import "DSDashpayUserEntity+CoreDataClass.h" +#import "NSManagedObject+Sugar.h" +#import "NSMutableData+Dash.h" + +#define DERIVATION_PATH_EXTENDED_PUBLIC_KEY_WALLET_BASED_LOCATION @"DP_EPK_WBL" +#define DERIVATION_PATH_EXTENDED_PUBLIC_KEY_STANDALONE_BASED_LOCATION @"DP_EPK_SBL" +#define DERIVATION_PATH_STANDALONE_INFO_DICTIONARY_LOCATION @"DP_SIDL" +#define DERIVATION_PATH_EXTENDED_SECRET_KEY_WALLET_BASED_LOCATION @"DP_ESK_WBL" + @interface DSDerivationPathFactory () @@ -17,11 +30,11 @@ @interface DSDerivationPathFactory () @property (nonatomic, strong) NSMutableDictionary *operatorKeysDerivationPathByWallet; @property (nonatomic, strong) NSMutableDictionary *platformNodeKeysDerivationPathByWallet; @property (nonatomic, strong) NSMutableDictionary *providerFundsDerivationPathByWallet; -@property (nonatomic, strong) NSMutableDictionary *blockchainIdentityRegistrationFundingDerivationPathByWallet; -@property (nonatomic, strong) NSMutableDictionary *blockchainIdentityTopupFundingDerivationPathByWallet; -@property (nonatomic, strong) NSMutableDictionary *blockchainIdentityInvitationFundingDerivationPathByWallet; -@property (nonatomic, strong) NSMutableDictionary *blockchainIdentityBLSDerivationPathByWallet; -@property (nonatomic, strong) NSMutableDictionary *blockchainIdentityECDSADerivationPathByWallet; +@property (nonatomic, strong) NSMutableDictionary *identityRegistrationFundingDerivationPathByWallet; +@property (nonatomic, strong) NSMutableDictionary *identityTopupFundingDerivationPathByWallet; +@property (nonatomic, strong) NSMutableDictionary *identityInvitationFundingDerivationPathByWallet; +@property (nonatomic, strong) NSMutableDictionary *identityBLSDerivationPathByWallet; +@property (nonatomic, strong) NSMutableDictionary *identityECDSADerivationPathByWallet; @end @@ -134,104 +147,104 @@ - (DSMasternodeHoldingsDerivationPath *)providerFundsDerivationPathForWallet:(DS return [self.providerFundsDerivationPathByWallet objectForKey:wallet.uniqueIDString]; } -// MARK: - Blockchain Identity Funding +// MARK: - Identity Funding -- (DSCreditFundingDerivationPath *)blockchainIdentityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet { - static dispatch_once_t blockchainIdentityRegistrationFundingDerivationPathByWalletToken = 0; - dispatch_once(&blockchainIdentityRegistrationFundingDerivationPathByWalletToken, ^{ - self.blockchainIdentityRegistrationFundingDerivationPathByWallet = [NSMutableDictionary dictionary]; +- (DSAssetLockDerivationPath *)identityRegistrationFundingDerivationPathForWallet:(DSWallet *)wallet { + static dispatch_once_t identityRegistrationFundingDerivationPathByWalletToken = 0; + dispatch_once(&identityRegistrationFundingDerivationPathByWalletToken, ^{ + self.identityRegistrationFundingDerivationPathByWallet = [NSMutableDictionary dictionary]; }); @synchronized(self) { - if (![self.blockchainIdentityRegistrationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { - DSCreditFundingDerivationPath *derivationPath = [DSCreditFundingDerivationPath blockchainIdentityRegistrationFundingDerivationPathForChain:wallet.chain]; + if (![self.identityRegistrationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { + DSAssetLockDerivationPath *derivationPath = [DSAssetLockDerivationPath identityRegistrationFundingDerivationPathForChain:wallet.chain]; derivationPath.wallet = wallet; if (derivationPath.hasExtendedPublicKey) { [derivationPath loadAddresses]; } - [self.blockchainIdentityRegistrationFundingDerivationPathByWallet setObject:derivationPath - forKey:wallet.uniqueIDString]; + [self.identityRegistrationFundingDerivationPathByWallet setObject:derivationPath + forKey:wallet.uniqueIDString]; } } - return [self.blockchainIdentityRegistrationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]; + return [self.identityRegistrationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]; } -- (DSCreditFundingDerivationPath *)blockchainIdentityTopupFundingDerivationPathForWallet:(DSWallet *)wallet { - static dispatch_once_t blockchainIdentityTopupFundingDerivationPathByWalletToken = 0; - dispatch_once(&blockchainIdentityTopupFundingDerivationPathByWalletToken, ^{ - self.blockchainIdentityTopupFundingDerivationPathByWallet = [NSMutableDictionary dictionary]; +- (DSAssetLockDerivationPath *)identityTopupFundingDerivationPathForWallet:(DSWallet *)wallet { + static dispatch_once_t identityTopupFundingDerivationPathByWalletToken = 0; + dispatch_once(&identityTopupFundingDerivationPathByWalletToken, ^{ + self.identityTopupFundingDerivationPathByWallet = [NSMutableDictionary dictionary]; }); @synchronized(self) { - if (![self.blockchainIdentityTopupFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { - DSCreditFundingDerivationPath *derivationPath = [DSCreditFundingDerivationPath blockchainIdentityTopupFundingDerivationPathForChain:wallet.chain]; + if (![self.identityTopupFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { + DSAssetLockDerivationPath *derivationPath = [DSAssetLockDerivationPath identityTopupFundingDerivationPathForChain:wallet.chain]; derivationPath.wallet = wallet; if (derivationPath.hasExtendedPublicKey) { [derivationPath loadAddresses]; } - [self.blockchainIdentityTopupFundingDerivationPathByWallet setObject:derivationPath - forKey:wallet.uniqueIDString]; + [self.identityTopupFundingDerivationPathByWallet setObject:derivationPath + forKey:wallet.uniqueIDString]; } } - return [self.blockchainIdentityTopupFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]; + return [self.identityTopupFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]; } -- (DSCreditFundingDerivationPath *)blockchainIdentityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet { - static dispatch_once_t blockchainIdentityInvitationFundingDerivationPathByWalletToken = 0; - dispatch_once(&blockchainIdentityInvitationFundingDerivationPathByWalletToken, ^{ - self.blockchainIdentityInvitationFundingDerivationPathByWallet = [NSMutableDictionary dictionary]; +- (DSAssetLockDerivationPath *)identityInvitationFundingDerivationPathForWallet:(DSWallet *)wallet { + static dispatch_once_t identityInvitationFundingDerivationPathByWalletToken = 0; + dispatch_once(&identityInvitationFundingDerivationPathByWalletToken, ^{ + self.identityInvitationFundingDerivationPathByWallet = [NSMutableDictionary dictionary]; }); @synchronized(self) { - if (![self.blockchainIdentityInvitationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { - DSCreditFundingDerivationPath *derivationPath = [DSCreditFundingDerivationPath blockchainIdentityInvitationFundingDerivationPathForChain:wallet.chain]; + if (![self.identityInvitationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { + DSAssetLockDerivationPath *derivationPath = [DSAssetLockDerivationPath identityInvitationFundingDerivationPathForChain:wallet.chain]; derivationPath.wallet = wallet; if (derivationPath.hasExtendedPublicKey) { [derivationPath loadAddresses]; } - [self.blockchainIdentityInvitationFundingDerivationPathByWallet setObject:derivationPath + [self.identityInvitationFundingDerivationPathByWallet setObject:derivationPath forKey:wallet.uniqueIDString]; } } - return [self.blockchainIdentityInvitationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]; + return [self.identityInvitationFundingDerivationPathByWallet objectForKey:wallet.uniqueIDString]; } -// MARK: - Blockchain Identity Authentication +// MARK: - Identity Authentication -- (DSAuthenticationKeysDerivationPath *)blockchainIdentityBLSKeysDerivationPathForWallet:(DSWallet *)wallet { - static dispatch_once_t blockchainIdentityBLSDerivationPathByWalletToken = 0; - dispatch_once(&blockchainIdentityBLSDerivationPathByWalletToken, ^{ - self.blockchainIdentityBLSDerivationPathByWallet = [NSMutableDictionary dictionary]; +- (DSAuthenticationKeysDerivationPath *)identityBLSKeysDerivationPathForWallet:(DSWallet *)wallet { + static dispatch_once_t identityBLSDerivationPathByWalletToken = 0; + dispatch_once(&identityBLSDerivationPathByWalletToken, ^{ + self.identityBLSDerivationPathByWallet = [NSMutableDictionary dictionary]; }); @synchronized(self) { - if (![self.blockchainIdentityBLSDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentityBLSKeysDerivationPathForChain:wallet.chain]; + if (![self.identityBLSDerivationPathByWallet objectForKey:wallet.uniqueIDString]) { + DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath identityBLSKeysDerivationPathForChain:wallet.chain]; derivationPath.wallet = wallet; if (derivationPath.hasExtendedPrivateKey || (derivationPath.hasExtendedPublicKey && !derivationPath.usesHardenedKeys)) { [derivationPath loadAddresses]; } - [self.blockchainIdentityBLSDerivationPathByWallet setObject:derivationPath - forKey:wallet.uniqueIDString]; + [self.identityBLSDerivationPathByWallet setObject:derivationPath + forKey:wallet.uniqueIDString]; } } - return [self.blockchainIdentityBLSDerivationPathByWallet objectForKey:wallet.uniqueIDString]; + return [self.identityBLSDerivationPathByWallet objectForKey:wallet.uniqueIDString]; } -- (DSAuthenticationKeysDerivationPath *)blockchainIdentityECDSAKeysDerivationPathForWallet:(DSWallet *)wallet { +- (DSAuthenticationKeysDerivationPath *)identityECDSAKeysDerivationPathForWallet:(DSWallet *)wallet { NSParameterAssert(wallet); - static dispatch_once_t blockchainIdentityECDSADerivationPathByWalletToken = 0; - dispatch_once(&blockchainIdentityECDSADerivationPathByWalletToken, ^{ - self.blockchainIdentityECDSADerivationPathByWallet = [NSMutableDictionary dictionary]; + static dispatch_once_t identityECDSADerivationPathByWalletToken = 0; + dispatch_once(&identityECDSADerivationPathByWalletToken, ^{ + self.identityECDSADerivationPathByWallet = [NSMutableDictionary dictionary]; }); @synchronized(self) { - if (![self.blockchainIdentityECDSADerivationPathByWallet objectForKey:wallet.uniqueIDString]) { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentityECDSAKeysDerivationPathForChain:wallet.chain]; + if (![self.identityECDSADerivationPathByWallet objectForKey:wallet.uniqueIDString]) { + DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath identityECDSAKeysDerivationPathForChain:wallet.chain]; derivationPath.wallet = wallet; if (derivationPath.hasExtendedPrivateKey || (derivationPath.hasExtendedPublicKey && !derivationPath.usesHardenedKeys)) { [derivationPath loadAddresses]; } - [self.blockchainIdentityECDSADerivationPathByWallet setObject:derivationPath - forKey:wallet.uniqueIDString]; + [self.identityECDSADerivationPathByWallet setObject:derivationPath + forKey:wallet.uniqueIDString]; } } - return [self.blockchainIdentityECDSADerivationPathByWallet objectForKey:wallet.uniqueIDString]; + return [self.identityECDSADerivationPathByWallet objectForKey:wallet.uniqueIDString]; } - (NSArray *)loadedSpecializedDerivationPathsForWallet:(DSWallet *)wallet { @@ -242,10 +255,10 @@ - (DSAuthenticationKeysDerivationPath *)blockchainIdentityECDSAKeysDerivationPat [mArray addObject:[[DSDerivationPathFactory sharedInstance] platformNodeKeysDerivationPathForWallet:wallet]]; [mArray addObject:[[DSDerivationPathFactory sharedInstance] providerFundsDerivationPathForWallet:wallet]]; if (wallet.chain.isEvolutionEnabled) { - [mArray addObject:[[DSDerivationPathFactory sharedInstance] blockchainIdentityECDSAKeysDerivationPathForWallet:wallet]]; - [mArray addObject:[[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:wallet]]; - [mArray addObject:[[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:wallet]]; - [mArray addObject:[[DSDerivationPathFactory sharedInstance] blockchainIdentityTopupFundingDerivationPathForWallet:wallet]]; + [mArray addObject:[[DSDerivationPathFactory sharedInstance] identityECDSAKeysDerivationPathForWallet:wallet]]; + [mArray addObject:[[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:wallet]]; + [mArray addObject:[[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:wallet]]; + [mArray addObject:[[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:wallet]]; } return mArray; } @@ -281,10 +294,10 @@ - (DSAuthenticationKeysDerivationPath *)blockchainIdentityECDSAKeysDerivationPat } if (wallet.chain.isEvolutionEnabled) { for (DSAccount *account in wallet.accounts) { - DSDerivationPath *masterBlockchainIdentityContactsDerivationPath = [DSDerivationPath masterBlockchainIdentityContactsDerivationPathForAccountNumber:account.accountNumber onChain:wallet.chain]; - masterBlockchainIdentityContactsDerivationPath.wallet = wallet; - if (![masterBlockchainIdentityContactsDerivationPath hasExtendedPublicKey]) { - [mArray addObject:masterBlockchainIdentityContactsDerivationPath]; + DSDerivationPath *masterIdentityContactsDerivationPath = [DSDerivationPath masterIdentityContactsDerivationPathForAccountNumber:account.accountNumber onChain:wallet.chain]; + masterIdentityContactsDerivationPath.wallet = wallet; + if (![masterIdentityContactsDerivationPath hasExtendedPublicKey]) { + [mArray addObject:masterIdentityContactsDerivationPath]; } } } @@ -319,29 +332,173 @@ - (DSAuthenticationKeysDerivationPath *)blockchainIdentityECDSAKeysDerivationPat [mArray addObject:providerPlatformNodeKeysDerivationPath]; if (wallet.chain.isEvolutionEnabled) { - // Blockchain Identities - DSAuthenticationKeysDerivationPath *blockchainIdentitiesECDSADerivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentityECDSAKeysDerivationPathForChain:wallet.chain]; - blockchainIdentitiesECDSADerivationPath.wallet = wallet; - [mArray addObject:blockchainIdentitiesECDSADerivationPath]; - - DSAuthenticationKeysDerivationPath *blockchainIdentitiesBLSDerivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentityBLSKeysDerivationPathForChain:wallet.chain]; - blockchainIdentitiesBLSDerivationPath.wallet = wallet; - [mArray addObject:blockchainIdentitiesBLSDerivationPath]; - - DSCreditFundingDerivationPath *blockchainIdentitiesRegistrationDerivationPath = [DSCreditFundingDerivationPath blockchainIdentityRegistrationFundingDerivationPathForChain:wallet.chain]; - blockchainIdentitiesRegistrationDerivationPath.wallet = wallet; - [mArray addObject:blockchainIdentitiesRegistrationDerivationPath]; - - DSCreditFundingDerivationPath *blockchainIdentitiesTopupDerivationPath = [DSCreditFundingDerivationPath blockchainIdentityTopupFundingDerivationPathForChain:wallet.chain]; - blockchainIdentitiesTopupDerivationPath.wallet = wallet; - [mArray addObject:blockchainIdentitiesTopupDerivationPath]; - - DSCreditFundingDerivationPath *blockchainIdentitiesInvitationsDerivationPath = [DSCreditFundingDerivationPath blockchainIdentityInvitationFundingDerivationPathForChain:wallet.chain]; - blockchainIdentitiesInvitationsDerivationPath.wallet = wallet; - [mArray addObject:blockchainIdentitiesInvitationsDerivationPath]; + // Identities + DSAuthenticationKeysDerivationPath *identitiesECDSADerivationPath = [DSAuthenticationKeysDerivationPath identityECDSAKeysDerivationPathForChain:wallet.chain]; + identitiesECDSADerivationPath.wallet = wallet; + [mArray addObject:identitiesECDSADerivationPath]; + + DSAuthenticationKeysDerivationPath *identitiesBLSDerivationPath = [DSAuthenticationKeysDerivationPath identityBLSKeysDerivationPathForChain:wallet.chain]; + identitiesBLSDerivationPath.wallet = wallet; + [mArray addObject:identitiesBLSDerivationPath]; + + DSAssetLockDerivationPath *identitiesRegistrationDerivationPath = [DSAssetLockDerivationPath identityRegistrationFundingDerivationPathForChain:wallet.chain]; + identitiesRegistrationDerivationPath.wallet = wallet; + [mArray addObject:identitiesRegistrationDerivationPath]; + + DSAssetLockDerivationPath *identitiesTopupDerivationPath = [DSAssetLockDerivationPath identityTopupFundingDerivationPathForChain:wallet.chain]; + identitiesTopupDerivationPath.wallet = wallet; + [mArray addObject:identitiesTopupDerivationPath]; + + DSAssetLockDerivationPath *identitiesInvitationsDerivationPath = [DSAssetLockDerivationPath identityInvitationFundingDerivationPathForChain:wallet.chain]; + identitiesInvitationsDerivationPath.wallet = wallet; + [mArray addObject:identitiesInvitationsDerivationPath]; } return [mArray copy]; } + ++ (DMaybeOpaqueKeys *)privateKeysAtIndexPaths:(NSArray *)indexPaths + fromSeed:(NSData *)seed + derivationPath:(DSDerivationPath *)derivationPath { + if (!seed || !indexPaths || !derivationPath) + return nil; + if (indexPaths.count == 0) + return Result_ok_Vec_dash_spv_crypto_keys_key_OpaqueKey_err_dash_spv_crypto_keys_KeyError_ctor(Vec_dash_spv_crypto_keys_key_OpaqueKey_ctor(0, NULL), NULL); + NSUInteger count = indexPaths.count; + Slice_u8 *seed_slice = slice_ctor(seed); + Vec_u32 **values = malloc(count * sizeof(Vec_u32 *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = [NSIndexPath ffi_to:indexPaths[i]]; + } + Vec_Vec_u32 *index_paths = Vec_Vec_u32_ctor(count, values); + DIndexPathU256 *path = [DSDerivationPath ffi_to:derivationPath]; + return DMaybeOpaquePrivateKeysAtIndexPathsWrapped(derivationPath.signingAlgorithm, seed_slice, index_paths, path); +} + ++ (NSString *)serializedExtendedPrivateKeyFromSeed:(NSData *)seed + derivationPath:(DSDerivationPath *)derivationPath { + @autoreleasepool { + if (!seed) return nil; + Slice_u8 *slice = slice_ctor(seed); + DIndexPathU256 *path = [DSDerivationPath ffi_to:derivationPath]; + DMaybeKeyString *result = DECDSAKeySerializedPrivateKeyFromSeedAtU256(slice, path, derivationPath.chain.chainType); + NSString *serializedKey = result->ok ? NSStringFromPtr(result->ok) : nil; + DMaybeKeyStringDtor(result); + return serializedKey; + } +} + ++ (NSArray *)serializedPrivateKeysAtIndexPaths:(NSArray *)indexPaths + fromSeed:(NSData *)seed + derivationPath:(DSDerivationPath *)derivationPath { + if (!seed || !indexPaths) return nil; + if (indexPaths.count == 0) return @[]; + + NSUInteger count = indexPaths.count; + Vec_u32 **values = malloc(count * sizeof(Vec_u32 *)); + for (NSUInteger i = 0; i < count; i++) { + values[i] = [NSIndexPath ffi_to:indexPaths[i]]; + } + Vec_Vec_u32 *index_paths = Vec_Vec_u32_ctor(count, values); + DIndexPathU256 *path = [DSDerivationPath ffi_to:derivationPath]; + Slice_u8 *seed_slice = slice_ctor(seed); + Result_ok_Vec_String_err_dash_spv_crypto_keys_KeyError *result = DMaybeSerializedOpaquePrivateKeysAtIndexPathsWrapped(derivationPath.signingAlgorithm, seed_slice, index_paths, path, derivationPath.chain.chainType); + Vec_String *keys = result->ok; + NSMutableArray *privateKeys = [NSMutableArray arrayWithCapacity:keys->count]; + for (NSUInteger i = 0; i < keys->count; i++) { + [privateKeys addObject:NSStringFromPtr(keys->values[i])]; + } + Result_ok_Vec_String_err_dash_spv_crypto_keys_KeyError_destroy(result); + return privateKeys; +} + ++ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString + onChain:(DSChain *)chain + rDepth:(uint8_t *)depth + rTerminalHardened:(BOOL *)terminalHardened + rTerminalIndex:(UInt256 *)terminalIndex { + uint32_t fingerprint; + UInt256 chainHash; + NSData *pubkey = nil; + NSMutableData *masterPublicKey = [NSMutableData secureData]; + BOOL valid = deserialize(extendedPublicKeyString, depth, &fingerprint, terminalHardened, terminalIndex, &chainHash, &pubkey, [chain isMainnet]); + if (!valid) return nil; + [masterPublicKey appendUInt32:fingerprint]; + [masterPublicKey appendBytes:&chainHash length:32]; + [masterPublicKey appendData:pubkey]; + return [masterPublicKey copy]; +} ++ (NSData *)deserializedExtendedPublicKey:(NSString *)extendedPublicKeyString onChain:(DSChain *)chain { + __unused uint8_t depth = 0; + __unused BOOL terminalHardened = 0; + __unused UInt256 terminalIndex = UINT256_ZERO; + NSData *extendedPublicKey = [self deserializedExtendedPublicKey:extendedPublicKeyString onChain:chain rDepth:&depth rTerminalHardened:&terminalHardened rTerminalIndex:&terminalIndex]; + return extendedPublicKey; +} + ++ (NSString *)serializedExtendedPublicKey:(DSDerivationPath *)derivationPath { + //todo make sure this works with BLS keys + NSData *extPubKeyData = derivationPath.extendedPublicKeyData; + if (extPubKeyData.length < 36) return nil; + uint32_t fingerprint = [extPubKeyData UInt32AtOffset:0]; + UInt256 chain = [extPubKeyData UInt256AtOffset:4]; + DSECPoint pubKey = [extPubKeyData ECPointAtOffset:36]; + UInt256 child = UINT256_ZERO; + BOOL isHardened = NO; + if (derivationPath.length) { + child = [derivationPath indexAtPosition:[derivationPath length] - 1]; + isHardened = [derivationPath isHardenedAtPosition:[derivationPath length] - 1]; + } + + return serialize([derivationPath.depth unsignedCharValue], fingerprint, isHardened, child, chain, [NSData dataWithBytes:&pubKey length:sizeof(pubKey)], [derivationPath.chain isMainnet]); +} + + + ++ (NSData *)deserializedExtendedPublicKey:(DSDerivationPath *)derivationPath extendedPublicKeyString:(NSString *)extendedPublicKeyString { + return [DSDerivationPathFactory deserializedExtendedPublicKey:extendedPublicKeyString onChain:derivationPath.chain]; +} + + + ++ (NSString *)standaloneExtendedPublicKeyLocationStringForUniqueID:(NSString *)uniqueID { + return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_EXTENDED_PUBLIC_KEY_STANDALONE_BASED_LOCATION, uniqueID]; +} + ++ (NSString *)standaloneInfoDictionaryLocationStringForUniqueID:(NSString *)uniqueID { + return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_STANDALONE_INFO_DICTIONARY_LOCATION, uniqueID]; +} + ++ (NSString *)walletBasedExtendedPublicKeyLocationStringForUniqueID:(NSString *)uniqueID { + return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_EXTENDED_PUBLIC_KEY_WALLET_BASED_LOCATION, uniqueID]; +} + ++ (NSString *)walletBasedExtendedPrivateKeyLocationStringForUniqueID:(NSString *)uniqueID { + return [NSString stringWithFormat:@"%@_%@", DERIVATION_PATH_EXTENDED_SECRET_KEY_WALLET_BASED_LOCATION, uniqueID]; +} + + ++ (NSString *)stringRepresentationOfIndex:(UInt256)index + hardened:(BOOL)hardened + inContext:(NSManagedObjectContext *)context { + if (uint256_is_31_bits(index)) { + return [NSString stringWithFormat:@"/%lu%@", (unsigned long)index.u64[0], hardened ? @"'" : @""]; + } else if (context) { + __block NSString *rString = nil; + [context performBlockAndWait:^{ + DSDashpayUserEntity *dashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentity.uniqueID == %@", uint256_data(index)]; + if (dashpayUserEntity) { + DSBlockchainIdentityUsernameEntity *usernameEntity = [dashpayUserEntity.associatedBlockchainIdentity.usernames anyObject]; + rString = [NSString stringWithFormat:@"/%@%@", usernameEntity.stringValue, hardened ? @"'" : @""]; + } else { + rString = [NSString stringWithFormat:@"/0x%@%@", uint256_hex(index), hardened ? @"'" : @""]; + } + }]; + return rString; + } else { + return [NSString stringWithFormat:@"/0x%@%@", uint256_hex(index), hardened ? @"'" : @""]; + } +} + @end diff --git a/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.h index 6a134286b..f06ac1a2b 100644 --- a/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.h @@ -10,6 +10,7 @@ #define SEQUENCE_GAP_LIMIT_EXTERNAL 10 #define SEQUENCE_GAP_LIMIT_INTERNAL 5 #define SEQUENCE_GAP_LIMIT_INITIAL 100 +#define SEQUENCE_GAP_LIMIT_INITIAL_COINJOIN 400 #define SEQUENCE_UNUSED_GAP_LIMIT_EXTERNAL 10 #define SEQUENCE_UNUSED_GAP_LIMIT_INTERNAL 5 @@ -53,20 +54,7 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)bip44DerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain; -// Derivation paths are composed of chains of addresses. Each chain is traversed until a gap of a certain number of addresses is -// found that haven't been used in any transactions. This method returns an array of unused addresses -// following the last used address in the chain. The internal chain is used for change addresses and the external chain -// for receive addresses. These have a hardened purpose scheme depending on the derivation path -- (NSArray *_Nullable)registerAddressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal error:(NSError **)error; - -- (NSString *_Nullable)privateKeyStringAtIndex:(uint32_t)n internal:(BOOL)internal fromSeed:(NSData *)seed; -- (NSArray *_Nullable)serializedPrivateKeys:(NSArray *)n internal:(BOOL)internal fromSeed:(NSData *)seed; -- (NSArray *_Nullable)privateKeys:(NSArray *)n internal:(BOOL)internal fromSeed:(NSData *)seed; - -- (NSData *_Nullable)publicKeyDataAtIndex:(uint32_t)n internal:(BOOL)internal; - -// gets an addess at an index one level down based on bip32 -- (NSString *)addressAtIndex:(uint32_t)index internal:(BOOL)internal; ++ (instancetype)coinJoinDerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain; - (NSArray *)addressesForExportWithInternalRange:(NSRange)exportInternalRange externalCount:(NSRange)exportExternalRange; diff --git a/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.m index 1e8493331..5e26fd913 100644 --- a/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.m +++ b/DashSync/shared/Models/Derivation Paths/DSFundsDerivationPath.m @@ -6,13 +6,17 @@ // #import "DSFundsDerivationPath.h" +#import "DSAddressEntity+CoreDataClass.h" #import "DSAccount.h" -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" +#import "DSChain+Params.h" #import "DSDashpayUserEntity+CoreDataClass.h" #import "DSDerivationPath+Protected.h" +#import "DSGapLimit.h" #import "DSKeyManager.h" #import "DSLogger.h" #import "NSError+Dash.h" +#import "NSManagedObject+Sugar.h" #define DERIVATION_PATH_IS_USED_KEY @"DERIVATION_PATH_IS_USED_KEY" @@ -30,16 +34,52 @@ @implementation DSFundsDerivationPath + (instancetype _Nonnull)bip32DerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain { UInt256 indexes[] = {uint256_from_long(accountNumber)}; BOOL hardenedIndexes[] = {YES}; - return [self derivationPathWithIndexes:indexes hardened:hardenedIndexes length:1 type:DSDerivationPathType_ClearFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_BIP32 onChain:chain]; + return [self derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:1 + type:DSDerivationPathType_ClearFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_BIP32 + onChain:chain]; } + (instancetype _Nonnull)bip44DerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(44), uint256_from_long(chain_coin_type(chain.chainType)), uint256_from_long(accountNumber)}; + UInt256 indexes[] = {uint256_from_long(44), uint256_from_long(chain.coinType), uint256_from_long(accountNumber)}; BOOL hardenedIndexes[] = {YES, YES, YES}; - return [self derivationPathWithIndexes:indexes hardened:hardenedIndexes length:3 type:DSDerivationPathType_ClearFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_BIP44 onChain:chain]; + return [self derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:3 + type:DSDerivationPathType_ClearFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_BIP44 + onChain:chain]; } ++ (instancetype _Nonnull)coinJoinDerivationPathForAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain { + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long(chain.coinType), uint256_from_long(FEATURE_PURPOSE_COINJOIN), uint256_from_long(accountNumber)}; + BOOL hardenedIndexes[] = {YES, YES, YES, YES}; + return [self derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_AnonymousFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_CoinJoin + onChain:chain]; +} + -- (instancetype)initWithIndexes:(const UInt256[])indexes hardened:(const BOOL[])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type signingAlgorithm:(KeyKind)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { - if (!(self = [super initWithIndexes:indexes hardened:hardenedIndexes length:length type:type signingAlgorithm:signingAlgorithm reference:reference onChain:chain])) return nil; +- (instancetype)initWithIndexes:(const UInt256[])indexes + hardened:(const BOOL[])hardenedIndexes + length:(NSUInteger)length + type:(DSDerivationPathType)type + signingAlgorithm:(DKeyKind *)signingAlgorithm + reference:(DSDerivationPathReference)reference + onChain:(DSChain *)chain { + if (!(self = [super initWithIndexes:indexes + hardened:hardenedIndexes + length:length + type:type + signingAlgorithm:signingAlgorithm + reference:reference + onChain:chain])) return nil; UInt256 lastIndex = indexes[length - 1]; self.isForFirstAccount = uint256_is_zero(lastIndex); @@ -54,7 +94,7 @@ - (BOOL)shouldUseReducedGapLimit { NSError *error = nil; uint64_t hasKnownBalance = getKeychainInt([self hasKnownBalanceUniqueIDString], &error); if (!error) { - self.hasKnownBalanceInternal = hasKnownBalance ? TRUE : FALSE; + self.hasKnownBalanceInternal = hasKnownBalance; self.checkedInitialHasKnownBalance = TRUE; } } @@ -69,6 +109,8 @@ - (void)setHasKnownBalance { } - (NSString *)hasKnownBalanceUniqueIDString { + // NSString accountUniqueID = [NSString stringWithFormat:@"%@-0-%u", self.wallet.uniqueIDString, self.accountNumber]; //0 is for type 0 + return [NSString stringWithFormat:@"%@_%@_%lu", DERIVATION_PATH_IS_USED_KEY, [self.account uniqueID], (unsigned long)self.reference]; } @@ -82,33 +124,11 @@ - (void)reloadAddresses { - (void)loadAddresses { if (!self.addressesLoaded) { - [self.managedObjectContext performBlockAndWait:^{ - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; - self.syncBlockHeight = derivationPathEntity.syncBlockHeight; - for (DSAddressEntity *e in derivationPathEntity.addresses) { - @autoreleasepool { - NSMutableArray *a = (e.internal) ? self.internalAddresses : self.externalAddresses; - - while (e.index >= a.count) [a addObject:[NSNull null]]; - if (![DSKeyManager isValidDashAddress:e.address forChain:self.account.wallet.chain]) { -#if DEBUG - DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.account.wallet.chain.name, e.address); -#else - DSLog(@"[%@] address %@ loaded but was not valid on chain %@", self.account.wallet.chain.name, @""); -#endif /* DEBUG */ - continue; - } - a[e.index] = e.address; - [self.mAllAddresses addObject:e.address]; - if ([e.usedInInputs count] || [e.usedInOutputs count]) { - [self.mUsedAddresses addObject:e.address]; - } - } - } - }]; + [self loadAddressesInContext:self.managedObjectContext]; self.addressesLoaded = TRUE; - [self registerAddressesWithGapLimit:(self.shouldUseReducedGapLimit ? SEQUENCE_UNUSED_GAP_LIMIT_INITIAL : SEQUENCE_GAP_LIMIT_INITIAL) internal:YES error:nil]; - [self registerAddressesWithGapLimit:(self.shouldUseReducedGapLimit ? SEQUENCE_UNUSED_GAP_LIMIT_INITIAL : SEQUENCE_GAP_LIMIT_INITIAL) internal:NO error:nil]; + uintptr_t gapLimit = self.shouldUseReducedGapLimit ? SEQUENCE_UNUSED_GAP_LIMIT_INITIAL : (self.type == DSDerivationPathType_AnonymousFunds ? SEQUENCE_GAP_LIMIT_INITIAL_COINJOIN : SEQUENCE_GAP_LIMIT_INITIAL); + [self registerAddressesWithSettings:[DSGapLimitFunds internal:gapLimit]]; + [self registerAddressesWithSettings:[DSGapLimitFunds external:gapLimit]]; } } @@ -118,11 +138,10 @@ - (BOOL)registerTransactionAddress:(NSString *_Nonnull)address { if ([self containsAddress:address]) { if (![self.mUsedAddresses containsObject:address]) { [self.mUsedAddresses addObject:address]; - if ([self.allChangeAddresses containsObject:address]) { - [self registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INTERNAL internal:YES error:nil]; - } else { - [self registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_EXTERNAL internal:NO error:nil]; - } + DSGapLimit *gapLimit = [self.allChangeAddresses containsObject:address] + ? [DSGapLimitFunds internal:SEQUENCE_GAP_LIMIT_INTERNAL] + : [DSGapLimitFunds external:SEQUENCE_GAP_LIMIT_EXTERNAL]; + [self registerAddressesWithSettings:gapLimit]; } return TRUE; } @@ -133,12 +152,17 @@ - (BOOL)registerTransactionAddress:(NSString *_Nonnull)address { // found that haven't been used in any transactions. This method returns an array of unused addresses // following the last used address in the chain. The internal chain is used for change addresses and the external chain // for receive addresses. -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal error:(NSError **)error { +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings + inContext:(NSManagedObjectContext *)context { + DSGapLimitFunds *gapLimitSettings = (DSGapLimitFunds *)settings; + uintptr_t gapLimit = gapLimitSettings.gapLimit; + BOOL internal = (gapLimitSettings.direction & DSGapLimitFundsDirection_Internal) == DSGapLimitFundsDirection_Internal; if (!self.account.wallet.isTransient) { NSAssert(self.addressesLoaded, @"addresses must be loaded before calling this function"); } + @synchronized(self) { - NSMutableArray *a = [NSMutableArray arrayWithArray:(internal) ? self.internalAddresses : self.externalAddresses]; + NSMutableArray *a = [NSMutableArray arrayWithArray:internal ? self.internalAddresses : self.externalAddresses]; NSUInteger i = a.count; // keep only the trailing contiguous block of addresses with no transactions while (i > 0 && ![self.usedAddresses containsObject:a[i - 1]]) { @@ -154,7 +178,7 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)i } //It seems weird to repeat this, but it's correct because of the original call receive address and change address - [a setArray:(internal) ? self.internalAddresses : self.externalAddresses]; + [a setArray:internal ? self.internalAddresses : self.externalAddresses]; i = a.count; unsigned n = (unsigned)i; @@ -170,55 +194,39 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)i NSMutableDictionary *addAddresses = [NSMutableDictionary dictionary]; while (a.count < gapLimit) { // generate new addresses up to gapLimit - NSData *pubKey = [self publicKeyDataAtIndex:n internal:internal]; + NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:(const NSUInteger[]){(internal ? 1 : 0), n} length:2]; + NSData *pubKey = [self publicKeyDataAtIndexPath:indexPath]; NSString *addr = [DSKeyManager ecdsaKeyAddressFromPublicKeyData:pubKey forChainType:self.chain.chainType]; - if (!addr) { DSLog(@"[%@] error generating keys", self.account.wallet.chain.name); - if (error) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Error generating public keys"]; - } return nil; } [self.mAllAddresses addObject:addr]; - [(internal) ? self.internalAddresses : self.externalAddresses addObject:addr]; + [internal ? self.internalAddresses : self.externalAddresses addObject:addr]; [a addObject:addr]; [addAddresses setObject:addr forKey:@(n)]; n++; } - if (!self.account.wallet.isTransient) { - [self.managedObjectContext performBlock:^{ // store new address in core data - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; - for (NSNumber *number in addAddresses) { - NSString *address = [addAddresses objectForKey:number]; - NSAssert([DSKeyManager isValidDashAddress:address forChain:self.chain], @"the address is being saved to the wrong derivation path"); - DSAddressEntity *e = [DSAddressEntity managedObjectInContext:self.managedObjectContext]; - e.derivationPath = derivationPathEntity; - e.address = address; - e.index = [number intValue]; - e.internal = internal; - e.standalone = NO; - } - [self.managedObjectContext ds_save]; - }]; - } + if (!self.account.wallet.isTransient) + [self storeNewAddressesInContext:addAddresses internal:internal context:context]; return a; } } + - (NSArray *)addressesForExportWithInternalRange:(NSRange)exportInternalRange externalCount:(NSRange)exportExternalRange { NSMutableArray *addresses = [NSMutableArray array]; for (NSUInteger i = exportInternalRange.location; i < exportInternalRange.length + exportInternalRange.location; i++) { - NSData *pubKey = [self publicKeyDataAtIndex:(uint32_t)i internal:YES]; + NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:(const NSUInteger[]){1, i} length:2]]; NSString *addr = [DSKeyManager ecdsaKeyAddressFromPublicKeyData:pubKey forChainType:self.chain.chainType]; [addresses addObject:addr]; } for (NSUInteger i = exportExternalRange.location; i < exportExternalRange.location + exportExternalRange.length; i++) { - NSData *pubKey = [self publicKeyDataAtIndex:(uint32_t)i internal:NO]; + NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:(const NSUInteger[]){0, i} length:2]]; NSString *addr = [DSKeyManager ecdsaKeyAddressFromPublicKeyData:pubKey forChainType:self.chain.chainType]; [addresses addObject:addr]; } @@ -226,30 +234,21 @@ - (NSArray *)addressesForExportWithInternalRange:(NSRange)exportInternalRange ex return [addresses copy]; } -// gets an address at an index path -- (NSString *)addressAtIndex:(uint32_t)index internal:(BOOL)internal { - NSData *pubKey = [self publicKeyDataAtIndex:index internal:internal]; - NSString *addr = [DSKeyManager ecdsaKeyAddressFromPublicKeyData:pubKey forChainType:self.chain.chainType]; - return addr; -} - // returns the first unused external address - (NSString *)receiveAddress { //TODO: limit to 10,000 total addresses and utxos for practical usability with bloom filters - NSString *addr = [self registerAddressesWithGapLimit:1 internal:NO error:nil].lastObject; - return (addr) ? addr : self.allReceiveAddresses.lastObject; + return [self registerAddressesWithSettings:[DSGapLimitFunds externalSingle]].lastObject ?: self.allReceiveAddresses.lastObject; } - (NSString *)receiveAddressAtOffset:(NSUInteger)offset { //TODO: limit to 10,000 total addresses and utxos for practical usability with bloom filters - NSString *addr = [self registerAddressesWithGapLimit:offset + 1 internal:NO error:nil].lastObject; - return (addr) ? addr : self.allReceiveAddresses.lastObject; + return [self registerAddressesWithSettings:[DSGapLimitFunds external:offset + 1]].lastObject ?: self.allReceiveAddresses.lastObject; } // returns the first unused internal address - (NSString *)changeAddress { //TODO: limit to 10,000 total addresses and utxos for practical usability with bloom filters - return [self registerAddressesWithGapLimit:1 internal:YES error:nil].lastObject; + return [self registerAddressesWithSettings:[DSGapLimitFunds internalSingle]].lastObject; } // all previously generated external addresses @@ -284,35 +283,6 @@ - (NSArray *)usedChangeAddresses { return [intersection allObjects]; } -- (NSData *)publicKeyDataAtIndex:(uint32_t)n internal:(BOOL)internal { - NSUInteger indexes[] = {(internal ? 1 : 0), n}; - return [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:indexes length:2]]; -} - -- (NSString *)privateKeyStringAtIndex:(uint32_t)n internal:(BOOL)internal fromSeed:(NSData *)seed { - return seed ? [self serializedPrivateKeys:@[@(n)] internal:internal fromSeed:seed].lastObject : nil; -} - -- (NSArray *)privateKeys:(NSArray *)n internal:(BOOL)internal fromSeed:(NSData *)seed { - NSMutableArray *mArray = [NSMutableArray array]; - for (NSNumber *index in n) { - NSUInteger indexes[] = {(internal ? 1 : 0), index.unsignedIntValue}; - [mArray addObject:[NSIndexPath indexPathWithIndexes:indexes length:2]]; - } - - return [self privateKeysAtIndexPaths:mArray fromSeed:seed]; -} - -- (NSArray *)serializedPrivateKeys:(NSArray *)n internal:(BOOL)internal fromSeed:(NSData *)seed { - NSMutableArray *mArray = [NSMutableArray array]; - for (NSNumber *index in n) { - NSUInteger indexes[] = {(internal ? 1 : 0), index.unsignedIntValue}; - [mArray addObject:[NSIndexPath indexPathWithIndexes:indexes length:2]]; - } - - return [self serializedPrivateKeysAtIndexPaths:mArray fromSeed:seed]; -} - - (NSIndexPath *)indexPathForKnownAddress:(NSString *)address { if ([self.allChangeAddresses containsObject:address]) { NSUInteger indexes[] = {1, [self.allChangeAddresses indexOfObject:address]}; @@ -324,4 +294,53 @@ - (NSIndexPath *)indexPathForKnownAddress:(NSString *)address { return nil; } + +// CoreData +- (void)loadAddressesInContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; + for (DSAddressEntity *e in derivationPathEntity.addresses) { + @autoreleasepool { + NSMutableArray *a = e.internal ? self.internalAddresses : self.externalAddresses; + + while (e.index >= a.count) + [a addObject:[NSNull null]]; + if (!DIsValidDashAddress(DChar(e.address), self.chain.chainType)) { +#if DEBUG + DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.chain.name, e.address); +#else + DSLog(@"[%@] address %@ loaded but was not valid on chain %@", self.chain.name, @""); +#endif /* DEBUG */ + continue; + } + a[e.index] = e.address; + [self.mAllAddresses addObject:e.address]; + if ([e.usedInInputs count] || [e.usedInOutputs count]) { + [self.mUsedAddresses addObject:e.address]; + } + } + } + }]; + +} +- (void)storeNewAddressesInContext:(NSDictionary *)addAddresses + internal:(BOOL)internal + context:(NSManagedObjectContext *)context { + [context performBlock:^{ // store new address in core data + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; + for (NSNumber *number in addAddresses) { + NSString *address = [addAddresses objectForKey:number]; + NSAssert(DIsValidDashAddress(DChar(address), self.chain.chainType), @"the address is being saved to the wrong derivation path"); + DSAddressEntity *e = [DSAddressEntity managedObjectInContext:self.managedObjectContext]; + e.derivationPath = derivationPathEntity; + e.address = address; + e.index = [number intValue]; + e.internal = internal; + e.standalone = NO; + } + [self.managedObjectContext ds_save]; + }]; + +} + @end diff --git a/DashSync/shared/Models/Derivation Paths/DSGapLimit.h b/DashSync/shared/Models/Derivation Paths/DSGapLimit.h new file mode 100644 index 000000000..b8773800f --- /dev/null +++ b/DashSync/shared/Models/Derivation Paths/DSGapLimit.h @@ -0,0 +1,50 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, DSGapLimitFundsDirection) { + DSGapLimitFundsDirection_Internal = 1, + DSGapLimitFundsDirection_External = 2, + DSGapLimitFundsDirection_Both = DSGapLimitFundsDirection_Internal | DSGapLimitFundsDirection_External, +}; + + +@interface DSGapLimit : NSObject +@property (readwrite, nonatomic, assign) uintptr_t gapLimit; ++ (instancetype)withLimit:(uintptr_t)limit; ++ (instancetype)single; +@end + +@interface DSGapLimitFunds : DSGapLimit +@property (readwrite, nonatomic) DSGapLimitFundsDirection direction; ++ (instancetype)withLimit:(uintptr_t)limit direction:(DSGapLimitFundsDirection)direction; ++ (instancetype)internalSingle; ++ (instancetype)externalSingle; ++ (instancetype)internal:(uintptr_t)limit; ++ (instancetype)external:(uintptr_t)limit; +@end + +@interface DSGapLimitIdentity : DSGapLimit +@property (readwrite, nonatomic, assign) uint32_t identityID; ++ (instancetype)withLimit:(uintptr_t)limit identityID:(uint32_t)identityID; +@end + + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSGapLimit.m b/DashSync/shared/Models/Derivation Paths/DSGapLimit.m new file mode 100644 index 000000000..42a2a9ec5 --- /dev/null +++ b/DashSync/shared/Models/Derivation Paths/DSGapLimit.m @@ -0,0 +1,79 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "DSGapLimit.h" + +@implementation DSGapLimit ++ (instancetype)withLimit:(uintptr_t)limit { + DSGapLimit *inst = [[self alloc] init]; + inst.gapLimit = limit; + return inst; +} ++ (instancetype)single { + return [DSGapLimit withLimit:1]; +} +@end + +@implementation DSGapLimitFunds + ++ (instancetype)withLimit:(uintptr_t)limit { + DSGapLimitFunds *inst = [[self alloc] init]; + inst.gapLimit = limit; + return inst; +} + ++ (instancetype)withLimit:(uintptr_t)limit direction:(DSGapLimitFundsDirection)direction { + DSGapLimitFunds *inst = [DSGapLimitFunds withLimit:limit]; + inst.direction = direction; + return inst; +} ++ (instancetype)internalSingle { + DSGapLimitFunds *inst = [DSGapLimitFunds withLimit:1]; + inst.direction = DSGapLimitFundsDirection_Internal; + return inst; +} ++ (instancetype)externalSingle { + DSGapLimitFunds *inst = [DSGapLimitFunds withLimit:1]; + inst.direction = DSGapLimitFundsDirection_External; + return inst; +} ++ (instancetype)internal:(uintptr_t)limit { + DSGapLimitFunds *inst = [DSGapLimitFunds withLimit:limit]; + inst.direction = DSGapLimitFundsDirection_Internal; + return inst; +} ++ (instancetype)external:(uintptr_t)limit { + DSGapLimitFunds *inst = [DSGapLimitFunds withLimit:limit]; + inst.direction = DSGapLimitFundsDirection_External; + return inst; +} + +@end + +@implementation DSGapLimitIdentity ++ (instancetype)withLimit:(uintptr_t)limit { + DSGapLimitIdentity *inst = [[self alloc] init]; + inst.gapLimit = limit; + return inst; +} + ++ (instancetype)withLimit:(uintptr_t)limit identityID:(uint32_t)identityID { + DSGapLimitIdentity *inst = [DSGapLimitIdentity withLimit:limit]; + inst.identityID = identityID; + return inst; +} +@end diff --git a/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.h index 72f788ab6..5968d955e 100644 --- a/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.h @@ -21,20 +21,25 @@ NS_ASSUME_NONNULL_BEGIN @interface DSIncomingFundsDerivationPath : DSDerivationPath -@property (nonatomic, readonly) UInt256 contactSourceBlockchainIdentityUniqueId; -@property (nonatomic, readonly) UInt256 contactDestinationBlockchainIdentityUniqueId; -@property (nonatomic, readonly) DSBlockchainIdentity *contactSourceBlockchainIdentity; -@property (nonatomic, readonly) DSBlockchainIdentity *contactDestinationBlockchainIdentity; -@property (nonatomic, readonly) BOOL sourceIsLocal; -@property (nonatomic, readonly) BOOL destinationIsLocal; +@property (nonatomic, readonly) UInt256 contactSourceIdentityUniqueId; +@property (nonatomic, readonly) UInt256 contactDestinationIdentityUniqueId; -+ (instancetype)contactBasedDerivationPathWithDestinationBlockchainIdentityUniqueId:(UInt256)destinationBlockchainIdentityUniqueId sourceBlockchainIdentityUniqueId:(UInt256)sourceBlockchainIdentityUniqueId forAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain; ++ (instancetype)contactBasedDerivationPathWithDestinationIdentityUniqueId:(UInt256)destinationIdentityUniqueId + sourceIdentityUniqueId:(UInt256)sourceIdentityUniqueId + forAccount:(DSAccount *)account + onChain:(DSChain *)chain; //The extended public key will be saved to disk (storeExternalDerivationPathExtendedPublicKeyToKeyChain call needed) -+ (instancetype)externalDerivationPathWithExtendedPublicKey:(OpaqueKey *)extendedPublicKey withDestinationBlockchainIdentityUniqueId:(UInt256)destinationBlockchainIdentityUniqueId sourceBlockchainIdentityUniqueId:(UInt256)sourceBlockchainIdentityUniqueId onChain:(DSChain *)chain; ++ (instancetype)externalDerivationPathWithExtendedPublicKey:(DMaybeOpaqueKey *)extendedPublicKey + withDestinationIdentityUniqueId:(UInt256)destinationIdentityUniqueId + sourceIdentityUniqueId:(UInt256)sourceIdentityUniqueId + onChain:(DSChain *)chain; //The extended public key will be loaded from disk -+ (instancetype)externalDerivationPathWithExtendedPublicKeyUniqueID:(NSString *)extendedPublicKeyUniqueId withDestinationBlockchainIdentityUniqueId:(UInt256)destinationBlockchainIdentityUniqueId sourceBlockchainIdentityUniqueId:(UInt256)sourceBlockchainIdentityUniqueId onChain:(DSChain *)chain; ++ (instancetype)externalDerivationPathWithExtendedPublicKeyUniqueID:(NSString *)extendedPublicKeyUniqueId + withDestinationIdentityUniqueId:(UInt256)destinationIdentityUniqueId + sourceIdentityUniqueId:(UInt256)sourceIdentityUniqueId + onChain:(DSChain *)chain; // returns the first unused external address @property (nonatomic, readonly, nullable) NSString *receiveAddress; @@ -45,17 +50,6 @@ NS_ASSUME_NONNULL_BEGIN // used external addresses @property (nonatomic, readonly) NSArray *usedReceiveAddresses; -- (NSArray *_Nullable)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError *_Nullable *_Nullable)error; - -- (NSString *_Nullable)privateKeyStringAtIndex:(uint32_t)n fromSeed:(NSData *)seed; -- (NSArray *_Nullable)serializedPrivateKeys:(NSArray *)n fromSeed:(NSData *)seed; -- (NSArray *_Nullable)privateKeys:(NSArray *)n fromSeed:(NSData *)seed; - -- (NSData *_Nullable)publicKeyDataAtIndex:(uint32_t)n; - -// gets an addess at an index one level down based on bip32 -- (NSString *)addressAtIndex:(uint32_t)index; - - (NSString *)receiveAddressAtOffset:(NSUInteger)offset; - (NSIndexPath *_Nullable)indexPathForKnownAddress:(NSString *)address; diff --git a/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.m index 7bbc16d2f..200be4a9b 100644 --- a/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.m +++ b/DashSync/shared/Models/Derivation Paths/DSIncomingFundsDerivationPath.m @@ -17,67 +17,105 @@ #import "DSIncomingFundsDerivationPath.h" #import "DSAccount.h" -#import "DSBlockchainIdentity.h" +#import "DSAddressEntity+CoreDataClass.h" +#import "DSIdentity.h" #import "DSChainManager.h" +#import "DSChain+Params.h" #import "DSDashpayUserEntity+CoreDataClass.h" #import "DSDerivationPath+Protected.h" +#import "DSGapLimit.h" +#import "DSTxOutputEntity+CoreDataClass.h" #import "NSError+Dash.h" -#import "dash_shared_core.h" +#import "NSManagedObject+Sugar.h" +#import "dash_spv_apple_bindings.h" @interface DSIncomingFundsDerivationPath () @property (atomic, strong) NSMutableArray *externalAddresses; -@property (nonatomic, assign) UInt256 contactSourceBlockchainIdentityUniqueId; -@property (nonatomic, assign) UInt256 contactDestinationBlockchainIdentityUniqueId; -@property (nonatomic, assign) BOOL externalDerivationPath; +@property (nonatomic, assign) UInt256 contactSourceIdentityUniqueId; +@property (nonatomic, assign) UInt256 contactDestinationIdentityUniqueId; @end @implementation DSIncomingFundsDerivationPath -+ (instancetype)contactBasedDerivationPathWithDestinationBlockchainIdentityUniqueId:(UInt256)destinationBlockchainIdentityUniqueId sourceBlockchainIdentityUniqueId:(UInt256)sourceBlockchainIdentityUniqueId forAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain { - NSAssert(!uint256_eq(sourceBlockchainIdentityUniqueId, destinationBlockchainIdentityUniqueId), @"source and destination must be different"); - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long((uint64_t) chain_coin_type(chain.chainType)), uint256_from_long(FEATURE_PURPOSE_DASHPAY), uint256_from_long(accountNumber), sourceBlockchainIdentityUniqueId, destinationBlockchainIdentityUniqueId}; ++ (instancetype)contactBasedDerivationPathWithDestinationIdentityUniqueId:(UInt256)destinationIdentityUniqueId + sourceIdentityUniqueId:(UInt256)sourceIdentityUniqueId + forAccount:(DSAccount *)account + onChain:(DSChain *)chain { + NSAssert(!uint256_eq(sourceIdentityUniqueId, destinationIdentityUniqueId), @"source and destination must be different"); + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long((uint64_t) chain.coinType), uint256_from_long(FEATURE_PURPOSE_DASHPAY), uint256_from_long(account.accountNumber), sourceIdentityUniqueId, destinationIdentityUniqueId}; BOOL hardenedIndexes[] = {YES, YES, YES, YES, NO, NO}; //todo full uint256 derivation - DSIncomingFundsDerivationPath *derivationPath = [self derivationPathWithIndexes:indexes hardened:hardenedIndexes length:6 type:DSDerivationPathType_ClearFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ContactBasedFunds onChain:chain]; - - derivationPath.contactSourceBlockchainIdentityUniqueId = sourceBlockchainIdentityUniqueId; - derivationPath.contactDestinationBlockchainIdentityUniqueId = destinationBlockchainIdentityUniqueId; - + DSIncomingFundsDerivationPath *derivationPath = [self derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:6 + type:DSDerivationPathType_ClearFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ContactBasedFunds + onChain:chain]; + + derivationPath.contactSourceIdentityUniqueId = sourceIdentityUniqueId; + derivationPath.contactDestinationIdentityUniqueId = destinationIdentityUniqueId; + derivationPath.account = account; return derivationPath; } -+ (instancetype)externalDerivationPathWithExtendedPublicKey:(OpaqueKey *)extendedPublicKey - withDestinationBlockchainIdentityUniqueId:(UInt256)destinationBlockchainIdentityUniqueId - sourceBlockchainIdentityUniqueId:(UInt256)sourceBlockchainIdentityUniqueId ++ (instancetype)externalDerivationPathWithExtendedPublicKey:(DMaybeOpaqueKey *)extendedPublicKey + withDestinationIdentityUniqueId:(UInt256)destinationIdentityUniqueId + sourceIdentityUniqueId:(UInt256)sourceIdentityUniqueId onChain:(DSChain *)chain { UInt256 indexes[] = {}; BOOL hardenedIndexes[] = {}; - DSIncomingFundsDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes hardened:hardenedIndexes length:0 type:DSDerivationPathType_ViewOnlyFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ContactBasedFundsExternal onChain:chain]; //we are going to assume this is only ecdsa for now + DSIncomingFundsDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes + hardened:hardenedIndexes + length:0 + type:DSDerivationPathType_ViewOnlyFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ContactBasedFundsExternal + onChain:chain]; //we are going to assume this is only ecdsa for now derivationPath.extendedPublicKey = extendedPublicKey; - - derivationPath.contactSourceBlockchainIdentityUniqueId = sourceBlockchainIdentityUniqueId; - derivationPath.contactDestinationBlockchainIdentityUniqueId = destinationBlockchainIdentityUniqueId; - derivationPath.externalDerivationPath = TRUE; + derivationPath.contactSourceIdentityUniqueId = sourceIdentityUniqueId; + derivationPath.contactDestinationIdentityUniqueId = destinationIdentityUniqueId; return derivationPath; } -+ (instancetype)externalDerivationPathWithExtendedPublicKeyUniqueID:(NSString *)extendedPublicKeyUniqueId withDestinationBlockchainIdentityUniqueId:(UInt256)destinationBlockchainIdentityUniqueId sourceBlockchainIdentityUniqueId:(UInt256)sourceBlockchainIdentityUniqueId onChain:(DSChain *)chain { + ++ (instancetype)externalDerivationPathWithExtendedPublicKeyUniqueID:(NSString *)extendedPublicKeyUniqueId + withDestinationIdentityUniqueId:(UInt256)destinationIdentityUniqueId + sourceIdentityUniqueId:(UInt256)sourceIdentityUniqueId + onChain:(DSChain *)chain { UInt256 indexes[] = {}; BOOL hardenedIndexes[] = {}; - DSIncomingFundsDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes hardened:hardenedIndexes length:0 type:DSDerivationPathType_ViewOnlyFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ContactBasedFundsExternal onChain:chain]; //we are going to assume this is only ecdsa for now + DSIncomingFundsDerivationPath *derivationPath = [[self alloc] initWithIndexes:indexes + hardened:hardenedIndexes + length:0 + type:DSDerivationPathType_ViewOnlyFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ContactBasedFundsExternal + onChain:chain]; //we are going to assume this is only ecdsa for now derivationPath.standaloneExtendedPublicKeyUniqueID = extendedPublicKeyUniqueId; - derivationPath.contactSourceBlockchainIdentityUniqueId = sourceBlockchainIdentityUniqueId; - derivationPath.contactDestinationBlockchainIdentityUniqueId = destinationBlockchainIdentityUniqueId; - derivationPath.externalDerivationPath = TRUE; + derivationPath.contactSourceIdentityUniqueId = sourceIdentityUniqueId; + derivationPath.contactDestinationIdentityUniqueId = destinationIdentityUniqueId; return derivationPath; } -- (instancetype)initWithIndexes:(const UInt256[])indexes hardened:(const BOOL[])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type signingAlgorithm:(KeyKind)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { - if (!(self = [super initWithIndexes:indexes hardened:hardenedIndexes length:length type:type signingAlgorithm:signingAlgorithm reference:reference onChain:chain])) return nil; +- (instancetype)initWithIndexes:(const UInt256[])indexes + hardened:(const BOOL[])hardenedIndexes + length:(NSUInteger)length + type:(DSDerivationPathType)type + signingAlgorithm:(DKeyKind *)signingAlgorithm + reference:(DSDerivationPathReference)reference + onChain:(DSChain *)chain { + if (!(self = [super initWithIndexes:indexes + hardened:hardenedIndexes + length:length + type:type + signingAlgorithm:signingAlgorithm + reference:reference + onChain:chain])) return nil; self.externalAddresses = [NSMutableArray array]; @@ -104,18 +142,18 @@ - (void)loadAddressesInContext:(NSManagedObjectContext *)context { if (!self.addressesLoaded) { [context performBlockAndWait:^{ DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:context]; - self.syncBlockHeight = derivationPathEntity.syncBlockHeight; for (DSAddressEntity *e in derivationPathEntity.addresses) { @autoreleasepool { NSMutableArray *a = self.externalAddresses; - while (e.index >= a.count) [a addObject:[NSNull null]]; - if (![DSKeyManager isValidDashAddress:e.address forChain:self.account.wallet.chain]) { -#if DEBUG - DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.account.wallet.chain.name, e.address); -#else - DSLog(@"[%@] address %@ loaded but was not valid on chain", self.account.wallet.chain.name, @""); -#endif /* DEBUG */ + while (e.index >= a.count) + [a addObject:[NSNull null]]; + if (!DIsValidDashAddress(DChar(e.address), self.chain.chainType)) { + #if DEBUG + DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.chain.name, e.address); + #else + DSLog(@"[%@] address %@ loaded but was not valid on chain", self.chain.name, @""); + #endif /* DEBUG */ continue; } a[e.index] = e.address; @@ -126,8 +164,9 @@ - (void)loadAddressesInContext:(NSManagedObjectContext *)context { } } }]; + self.addressesLoaded = TRUE; - [self registerAddressesWithGapLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL inContext:context error:nil]; + [self registerAddressesWithSettings:[DSGapLimit withLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL] inContext:context]; } } @@ -135,21 +174,13 @@ - (NSUInteger)accountNumber { return [self indexAtPosition:[self length] - 3].u64[0] & ~BIP32_HARD; } -- (BOOL)sourceIsLocal { - return !![self.chain blockchainIdentityForUniqueId:self.contactSourceBlockchainIdentityUniqueId]; -} - -- (BOOL)destinationIsLocal { - return !![self.chain blockchainIdentityForUniqueId:self.contactDestinationBlockchainIdentityUniqueId]; -} - // MARK: - Derivation Path Addresses - (BOOL)registerTransactionAddress:(NSString *_Nonnull)address { if ([self containsAddress:address]) { if (![self.mUsedAddresses containsObject:address]) { [self.mUsedAddresses addObject:address]; - [self registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_EXTERNAL error:nil]; + [self registerAddressesWithSettings:[DSGapLimit withLimit:SEQUENCE_GAP_LIMIT_EXTERNAL]]; } return TRUE; } @@ -158,18 +189,20 @@ - (BOOL)registerTransactionAddress:(NSString *_Nonnull)address { - (NSString *)createIdentifierForDerivationPath { - return [NSString stringWithFormat:@"%@-%@-%@", [NSData dataWithUInt256:_contactSourceBlockchainIdentityUniqueId].shortHexString, [NSData dataWithUInt256:_contactDestinationBlockchainIdentityUniqueId].shortHexString, [NSData dataWithUInt256:[[self extendedPublicKeyData] SHA256]].shortHexString]; -} - -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError **)error { - return [self registerAddressesWithGapLimit:gapLimit inContext:self.managedObjectContext error:error]; + return [NSString stringWithFormat:@"%@-%@-%@", + uint256_data(_contactSourceIdentityUniqueId).shortHexString, + uint256_data(_contactDestinationIdentityUniqueId).shortHexString, + [super createIdentifierForDerivationPath] + ]; } // Wallets are composed of chains of addresses. Each chain is traversed until a gap of a certain number of addresses is // found that haven't been used in any transactions. This method returns an array of unused addresses // following the last used address in the chain. The internal chain is used for change addresses and the external chain // for receive addresses. -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit inContext:(NSManagedObjectContext *)context error:(NSError **)error { +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings + inContext:(NSManagedObjectContext *)context { + uintptr_t gapLimit = settings.gapLimit; NSAssert(self.account, @"Account must be set"); if (!self.account.wallet.isTransient) { if (!self.addressesLoaded) { @@ -210,35 +243,19 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit inContext:(NSMan NSUInteger upperLimit = gapLimit; while (a.count < upperLimit) { // generate new addresses up to gapLimit - NSString *address = [self addressAtIndex:n]; + NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:(const NSUInteger[]){n} length:1]]; + NSString *address = [DSKeyManager ecdsaKeyAddressFromPublicKeyData:pubKey forChainType:self.chain.chainType]; if (!address) { DSLog(@"[%@] error generating keys", self.chain.name); - if (error) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Error generating public keys"]; - } return nil; } - __block BOOL isUsed = FALSE; - if (!self.account.wallet.isTransient) { - [context performBlockAndWait:^{ // store new address in core data - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:context]; - DSAddressEntity *e = [DSAddressEntity managedObjectInContext:context]; - e.derivationPath = derivationPathEntity; - NSAssert([DSKeyManager isValidDashAddress:address forChain:self.chain], @"the address is being saved to the wrong derivation path"); - e.address = address; - e.index = n; - e.internal = NO; - e.standalone = NO; - NSArray *outputs = [DSTxOutputEntity objectsInContext:context matching:@"address == %@", address]; - [e addUsedInOutputs:[NSSet setWithArray:outputs]]; - if (outputs.count) isUsed = TRUE; - }]; - } - if (isUsed) { - [self.mUsedAddresses addObject:address]; - upperLimit++; + BOOL isUsed = [self storeNewAddressInContext:address atIndex:n context:context]; + if (isUsed) { + [self.mUsedAddresses addObject:address]; + upperLimit++; + } } [self.mAllAddresses addObject:address]; [self.externalAddresses addObject:address]; @@ -250,12 +267,6 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit inContext:(NSMan } } -// gets an address at an index path -- (NSString *)addressAtIndex:(uint32_t)index { - NSData *pubKey = [self publicKeyDataAtIndex:index]; - return [DSKeyManager ecdsaKeyAddressFromPublicKeyData:pubKey forChainType:self.chain.chainType]; -} - // returns the first unused external address - (NSString *)receiveAddress { return [self receiveAddressInContext:self.managedObjectContext]; @@ -271,8 +282,8 @@ - (NSString *)receiveAddressAtOffset:(NSUInteger)offset { - (NSString *)receiveAddressAtOffset:(NSUInteger)offset inContext:(NSManagedObjectContext *)context { //TODO: limit to 10,000 total addresses and utxos for practical usability with bloom filters - NSString *addr = [self registerAddressesWithGapLimit:offset + 1 inContext:context error:nil].lastObject; - return (addr) ? addr : self.allReceiveAddresses.lastObject; + NSString *addr = [self registerAddressesWithSettings:[DSGapLimit withLimit:offset + 1] inContext:context].lastObject; + return addr ?: self.allReceiveAddresses.lastObject; } // all previously generated external addresses @@ -286,35 +297,6 @@ - (NSArray *)usedReceiveAddresses { return [intersection allObjects]; } -- (NSData *)publicKeyDataAtIndex:(uint32_t)n { - NSUInteger indexes[] = {n}; - return [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndexes:indexes length:1]]; -} - -- (NSString *)privateKeyStringAtIndex:(uint32_t)n fromSeed:(NSData *)seed { - return seed ? [self serializedPrivateKeys:@[@(n)] fromSeed:seed].lastObject : nil; -} - -- (NSArray *)privateKeys:(NSArray *)n fromSeed:(NSData *)seed { - NSMutableArray *mArray = [NSMutableArray array]; - for (NSNumber *index in n) { - NSUInteger indexes[] = {index.unsignedIntValue}; - [mArray addObject:[NSIndexPath indexPathWithIndexes:indexes length:1]]; - } - - return [self privateKeysAtIndexPaths:mArray fromSeed:seed]; -} - -- (NSArray *)serializedPrivateKeys:(NSArray *)n fromSeed:(NSData *)seed { - NSMutableArray *mArray = [NSMutableArray array]; - for (NSNumber *index in n) { - NSUInteger indexes[] = {index.unsignedIntValue}; - [mArray addObject:[NSIndexPath indexPathWithIndexes:indexes length:1]]; - } - - return [self serializedPrivateKeysAtIndexPaths:mArray fromSeed:seed]; -} - - (NSIndexPath *)indexPathForKnownAddress:(NSString *)address { if ([self.allReceiveAddresses containsObject:address]) { NSUInteger indexes[] = {[self.allReceiveAddresses indexOfObject:address]}; @@ -324,12 +306,25 @@ - (NSIndexPath *)indexPathForKnownAddress:(NSString *)address { } -- (DSBlockchainIdentity *)contactSourceBlockchainIdentity { - return [self.chain blockchainIdentityForUniqueId:self.contactSourceBlockchainIdentityUniqueId foundInWallet:nil includeForeignBlockchainIdentities:YES]; +- (BOOL)storeNewAddressInContext:(NSString *)address + atIndex:(uint32_t)n + context:(NSManagedObjectContext *)context { + __block BOOL isUsed = FALSE; + [context performBlockAndWait:^{ // store new address in core data + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:context]; + DSAddressEntity *e = [DSAddressEntity managedObjectInContext:context]; + e.derivationPath = derivationPathEntity; + NSAssert(DIsValidDashAddress(DChar(address), self.chain.chainType), @"the address is being saved to the wrong derivation path"); + e.address = address; + e.index = n; + e.internal = NO; + e.standalone = NO; + NSArray *outputs = [DSTxOutputEntity objectsInContext:context matching:@"address == %@", address]; + [e addUsedInOutputs:[NSSet setWithArray:outputs]]; + if (outputs.count) isUsed = TRUE; + }]; + return isUsed; } -- (DSBlockchainIdentity *)contactDestinationBlockchainIdentity { - return [self.chain blockchainIdentityForUniqueId:self.contactDestinationBlockchainIdentityUniqueId foundInWallet:nil includeForeignBlockchainIdentities:YES]; -} @end diff --git a/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.h index ee41d035a..c4cb997aa 100644 --- a/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.h @@ -14,10 +14,6 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)providerFundsDerivationPathForWallet:(DSWallet *)wallet; -- (NSString *)receiveAddress; - -- (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *)authprompt completion:(TransactionValidityCompletionBlock)completion; - @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.m index efb570a29..be85b3c3e 100644 --- a/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.m +++ b/DashSync/shared/Models/Derivation Paths/DSMasternodeHoldingsDerivationPath.m @@ -5,9 +5,11 @@ // Created by Sam Westrich on 2/10/19. // +#import "DSChain+Params.h" #import "DSMasternodeHoldingsDerivationPath.h" #import "DSDerivationPath+Protected.h" #import "DSDerivationPathFactory.h" +#import "DSGapLimit.h" #import "DSMasternodeManager.h" #import "DSSimpleIndexedDerivationPath+Protected.h" #import "DSWallet+Protected.h" @@ -23,41 +25,19 @@ + (instancetype _Nonnull)providerFundsDerivationPathForWallet:(DSWallet *)wallet } + (instancetype _Nonnull)providerFundsDerivationPathForChain:(DSChain *)chain { - UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long((uint64_t) chain_coin_type(chain.chainType)), uint256_from_long(3), uint256_from_long(0)}; + UInt256 indexes[] = {uint256_from_long(FEATURE_PURPOSE), uint256_from_long((uint64_t) chain.coinType), uint256_from_long(3), uint256_from_long(0)}; BOOL hardenedIndexes[] = {YES, YES, YES, YES}; - return [self derivationPathWithIndexes:indexes hardened:hardenedIndexes length:4 type:DSDerivationPathType_ProtectedFunds signingAlgorithm:KeyKind_ECDSA reference:DSDerivationPathReference_ProviderFunds onChain:chain]; + return [self derivationPathWithIndexes:indexes + hardened:hardenedIndexes + length:4 + type:DSDerivationPathType_ProtectedFunds + signingAlgorithm:DKeyKindECDSA() + reference:DSDerivationPathReference_ProviderFunds + onChain:chain]; } -- (NSString *)receiveAddress { - NSString *addr = [self registerAddressesWithGapLimit:1 error:nil].lastObject; - return (addr) ? addr : self.mOrderedAddresses.lastObject; -} - -- (NSUInteger)defaultGapLimit { - return 5; -} - -// sign any inputs in the given transaction that can be signed using private keys from the wallet -- (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *)authprompt completion:(TransactionValidityCompletionBlock)completion; -{ - if ([transaction inputAddresses].count != 1) { - completion(NO, NO); - return; - } - - NSUInteger index = [self indexOfKnownAddress:[[transaction inputAddresses] firstObject]]; - - @autoreleasepool { // @autoreleasepool ensures sensitive data will be dealocated immediately - self.wallet.secureSeedRequestBlock(authprompt, MASTERNODE_COST, ^void(NSData *_Nullable seed, BOOL cancelled) { - if (!seed) { - if (completion) completion(NO, cancelled); - } else { - OpaqueKey *key = [self privateKeyAtIndex:(uint32_t)index fromSeed:seed]; - BOOL signedSuccessfully = [transaction signWithPrivateKeys:@[[NSValue valueWithPointer:key]]]; - if (completion) completion(signedSuccessfully, NO); - } - }); - } +- (DSGapLimit *)defaultGapSettings { + return [DSGapLimit withLimit:5]; } @end diff --git a/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath+Protected.h b/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath+Protected.h index d16387256..45b2c6d2e 100644 --- a/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath+Protected.h +++ b/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath+Protected.h @@ -14,7 +14,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) NSMutableArray *mOrderedAddresses; -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError *_Nullable *_Nullable)error; +- (DSGapLimit *)defaultGapSettings; + +- (void)loadAddressesInContext:(NSManagedObjectContext *)context; @end diff --git a/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.h b/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.h index 4410191a5..cce1dc3ad 100644 --- a/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.h +++ b/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.h @@ -13,21 +13,10 @@ NS_ASSUME_NONNULL_BEGIN // returns the index of an address in the derivation path as long as it is within the gap limit - (NSUInteger)indexOfKnownAddress:(NSString *)address; +- (NSUInteger)indexOfKnownAddressHash:(UInt160)hash; // returns the index of the first unused Address; -- (NSUInteger)firstUnusedIndex; - -// gets a public key at an index -- (NSData *)publicKeyDataAtIndex:(uint32_t)index; - -// gets public keys to an index as NSData -- (NSArray *)publicKeyDataArrayToIndex:(NSUInteger)index; - -// gets an addess at an index -- (NSString *)addressAtIndex:(uint32_t)index; - -// true if the address at the index was previously used as an input or output in any wallet transaction -- (BOOL)addressIsUsedAtIndex:(uint32_t)index; +- (uint32_t)firstUnusedIndex; // gets addresses to an index, does not use cache and does not add to cache - (NSArray *)addressesToIndex:(NSUInteger)index; @@ -35,17 +24,8 @@ NS_ASSUME_NONNULL_BEGIN // gets addresses to an index, does not use cache and does not add to cache - (NSArray *)addressesToIndex:(NSUInteger)index useCache:(BOOL)useCache addToCache:(BOOL)addToCache; -// gets a private key at an index -- (OpaqueKey *_Nullable)privateKeyAtIndex:(uint32_t)index fromSeed:(NSData *)seed; - -// get private keys for a range or to an index -- (NSArray *)privateKeysToIndex:(NSUInteger)index fromSeed:(NSData *)seed; - (NSArray *)privateKeysForRange:(NSRange)range fromSeed:(NSData *)seed; -// update addresses -- (NSArray *_Nullable)registerAddressesWithDefaultGapLimitWithError:(NSError **)error; -- (NSArray *_Nullable)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError **)error; - @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.m b/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.m index 41254a03c..1e7317bff 100644 --- a/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.m +++ b/DashSync/shared/Models/Derivation Paths/DSSimpleIndexedDerivationPath.m @@ -5,15 +5,33 @@ // Created by Sam Westrich on 2/20/19. // +#import "DSAccount.h" +#import "DSAddressEntity+CoreDataClass.h" +#import "DSGapLimit.h" +#import "DSAuthenticationKeysDerivationPath.h" +#import "DSChain+Params.h" #import "DSDerivationPath+Protected.h" #import "DSKeyManager.h" #import "DSSimpleIndexedDerivationPath+Protected.h" #import "NSError+Dash.h" +#import "NSManagedObject+Sugar.h" @implementation DSSimpleIndexedDerivationPath -- (instancetype _Nullable)initWithIndexes:(const UInt256[_Nullable])indexes hardened:(const BOOL[_Nullable])hardenedIndexes length:(NSUInteger)length type:(DSDerivationPathType)type signingAlgorithm:(KeyKind)signingAlgorithm reference:(DSDerivationPathReference)reference onChain:(DSChain *)chain { - if (!(self = [super initWithIndexes:indexes hardened:hardenedIndexes length:length type:type signingAlgorithm:signingAlgorithm reference:reference onChain:chain])) return nil; +- (instancetype _Nullable)initWithIndexes:(const UInt256[_Nullable])indexes + hardened:(const BOOL[_Nullable])hardenedIndexes + length:(NSUInteger)length + type:(DSDerivationPathType)type + signingAlgorithm:(DKeyKind *)signingAlgorithm + reference:(DSDerivationPathReference)reference + onChain:(DSChain *)chain { + if (!(self = [super initWithIndexes:indexes + hardened:hardenedIndexes + length:length + type:type + signingAlgorithm:signingAlgorithm + reference:reference + onChain:chain])) return nil; self.mOrderedAddresses = [NSMutableArray array]; @@ -23,31 +41,9 @@ - (instancetype _Nullable)initWithIndexes:(const UInt256[_Nullable])indexes hard - (void)loadAddresses { @synchronized(self) { if (!self.addressesLoaded) { - [self.managedObjectContext performBlockAndWait:^{ - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; - self.syncBlockHeight = derivationPathEntity.syncBlockHeight; - NSArray *addresses = [derivationPathEntity.addresses sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]]; - for (DSAddressEntity *e in addresses) { - @autoreleasepool { - while (e.index >= self.mOrderedAddresses.count) [self.mOrderedAddresses addObject:[NSNull null]]; - if (![DSKeyManager isValidDashAddress:e.address forChain:self.wallet.chain]) { -#if DEBUG - DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.account.wallet.chain.name, e.address); -#else - DSLog(@"[%@] address %@ loaded but was not valid on chain", self.account.wallet.chain.name, @""); -#endif /* DEBUG */ - continue; - } - self.mOrderedAddresses[e.index] = e.address; - [self.mAllAddresses addObject:e.address]; - if ([e.usedInInputs count] || [e.usedInOutputs count] || [e.usedInSpecialTransactions count] || [e.usedInSimplifiedMasternodeEntries count]) { - [self.mUsedAddresses addObject:e.address]; - } - } - } - }]; + [self loadAddressesInContext:self.managedObjectContext]; self.addressesLoaded = TRUE; - [self registerAddressesWithGapLimit:10 error:nil]; + [self registerAddressesWithSettings:[self defaultGapSettings]]; } } } @@ -66,26 +62,24 @@ - (BOOL)registerTransactionAddress:(NSString *_Nonnull)address { if ([self containsAddress:address]) { if (![self.mUsedAddresses containsObject:address]) { [self.mUsedAddresses addObject:address]; - [self registerAddressesWithDefaultGapLimitWithError:nil]; + [self registerAddressesWithSettings:[self defaultGapSettings]]; } return TRUE; } return FALSE; } -- (NSUInteger)defaultGapLimit { - return 10; -} - -- (NSArray *)registerAddressesWithDefaultGapLimitWithError:(NSError **)error { - return [self registerAddressesWithGapLimit:[self defaultGapLimit] error:error]; +- (DSGapLimit *)defaultGapSettings { + return [DSGapLimit withLimit:10]; } // Wallets are composed of chains of addresses. Each chain is traversed until a gap of a certain number of addresses is // found that haven't been used in any transactions. This method returns an array of unused addresses // following the last used address in the chain. -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError **)error { - NSAssert(self.type != DSDerivationPathType_MultipleUserAuthentication, @"This should not be called for multiple user authentication. Use '- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit forIdentityIndex:(uint32_t)identityIndex error:(NSError**)error' instead."); +- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)settings + inContext:(NSManagedObjectContext *)context { + NSAssert(self.type != DSDerivationPathType_MultipleUserAuthentication, @"This should not be called for multiple user authentication. Use '- (NSArray *)registerAddressesWithSettings:(DSGapLimit *)gapLimit' with DSGapLimitIdentity."); + uintptr_t gapLimit = settings.gapLimit; NSMutableArray *rArray = [self.mOrderedAddresses mutableCopy]; @@ -118,27 +112,14 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError * if (rArray.count >= gapLimit) return [rArray subarrayWithRange:NSMakeRange(0, gapLimit)]; while (rArray.count < gapLimit) { // generate new addresses up to gapLimit - NSData *pubKey = [self publicKeyDataAtIndex:n]; + NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:n]]; NSString *addr = [DSKeyManager addressWithPublicKeyData:pubKey forChain:self.chain]; if (!addr) { DSLog(@"[%@] error generating keys", self.chain.name); - if (error) { - *error = [NSError errorWithCode:500 localizedDescriptionKey:@"Error generating public keys"]; - } return nil; } - - if (!self.wallet.isTransient) { - [self.managedObjectContext performBlock:^{ // store new address in core data - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; - DSAddressEntity *e = [DSAddressEntity managedObjectInContext:self.managedObjectContext]; - e.derivationPath = derivationPathEntity; - NSAssert([DSKeyManager isValidDashAddress:addr forChain:self.chain], @"the address is being saved to the wrong derivation path"); - e.address = addr; - e.index = n; - e.standalone = NO; - }]; - } + if (!self.wallet.isTransient) + [self storeNewAddressInContext:addr atIndex:n context:self.managedObjectContext]; [self.mAllAddresses addObject:addr]; [rArray addObject:addr]; @@ -150,7 +131,7 @@ - (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit error:(NSError * } } -- (NSUInteger)firstUnusedIndex { +- (uint32_t)firstUnusedIndex { uint32_t i = (uint32_t)self.mOrderedAddresses.count; // keep only the trailing contiguous block of addresses that aren't used @@ -161,15 +142,6 @@ - (NSUInteger)firstUnusedIndex { return i; } -// gets an addess at an index -- (NSString *)addressAtIndex:(uint32_t)index { - return [self addressAtIndexPath:[NSIndexPath indexPathWithIndex:index]]; -} - -- (BOOL)addressIsUsedAtIndex:(uint32_t)index { - return [self addressIsUsedAtIndexPath:[NSIndexPath indexPathWithIndex:index]]; -} - - (NSIndexPath *)indexPathForKnownAddress:(NSString *)address { return [NSIndexPath indexPathWithIndex:[self indexOfKnownAddress:address]]; } @@ -178,22 +150,9 @@ - (NSUInteger)indexOfKnownAddress:(NSString *)address { return [self.mOrderedAddresses indexOfObject:address]; } -// gets a public key at an index -- (NSData *)publicKeyDataAtIndex:(uint32_t)index { - return [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:index]]; -} - -- (OpaqueKey *)privateKeyAtIndex:(uint32_t)index fromSeed:(NSData *)seed { - return [self privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:index] fromSeed:seed]; -} - -- (NSArray *)publicKeyDataArrayToIndex:(NSUInteger)index { - NSMutableArray *mArray = [NSMutableArray array]; - for (int i = 0; i < index; i++) { - NSData *pubKey = [self publicKeyDataAtIndex:i]; - [mArray addObject:pubKey]; - } - return [mArray copy]; +- (NSUInteger)indexOfKnownAddressHash:(UInt160)hash { + NSString *address = [DSKeyManager addressFromHash160:hash forChain:self.chain]; + return [self.mOrderedAddresses indexOfObject:address]; } - (NSArray *)addressesToIndex:(NSUInteger)index { @@ -206,7 +165,7 @@ - (NSArray *)addressesToIndex:(NSUInteger)index useCache:(BOOL)useCache addToCac if (useCache && self.mOrderedAddresses.count > i && self.mOrderedAddresses[i]) { [mArray addObject:self.mOrderedAddresses[i]]; } else { - NSData *pubKey = [self publicKeyDataAtIndex:i]; + NSData *pubKey = [self publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:i]]; NSString *addr = [DSKeyManager addressWithPublicKeyData:pubKey forChain:self.chain]; [mArray addObject:addr]; if (addToCache && self.mOrderedAddresses.count == i) { @@ -220,15 +179,51 @@ - (NSArray *)addressesToIndex:(NSUInteger)index useCache:(BOOL)useCache addToCac - (NSArray *)privateKeysForRange:(NSRange)range fromSeed:(NSData *)seed { NSMutableArray *mArray = [NSMutableArray array]; for (NSUInteger i = range.location; i < (range.location + range.length); i++) { - OpaqueKey *privateKey = [self privateKeyAtIndex:(uint32_t)i fromSeed:seed]; + DMaybeOpaqueKey *privateKey = [self privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:i] fromSeed:seed]; NSValue *privateKeyValue = [NSValue valueWithPointer:privateKey]; [mArray addObject:privateKeyValue]; } return [mArray copy]; } -- (NSArray *)privateKeysToIndex:(NSUInteger)index fromSeed:(NSData *)seed { - return [self privateKeysForRange:NSMakeRange(0, index) fromSeed:seed]; +- (void)loadAddressesInContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; + NSArray *addresses = [derivationPathEntity.addresses sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]]; + for (DSAddressEntity *e in addresses) { + @autoreleasepool { + while (e.index >= self.mOrderedAddresses.count) + [self.mOrderedAddresses addObject:[NSNull null]]; + if (!DIsValidDashAddress(DChar(e.address), self.chain.chainType)) { +#if DEBUG + DSLogPrivate(@"[%@] address %@ loaded but was not valid on chain", self.chain.name, e.address); +#else + DSLog(@"[%@] address %@ loaded but was not valid on chain", self.chain.name, @""); +#endif /* DEBUG */ + continue; + } + self.mOrderedAddresses[e.index] = e.address; + [self.mAllAddresses addObject:e.address]; + if ([e.usedInInputs count] || [e.usedInOutputs count] || [e.usedInSpecialTransactions count] /*|| [e.usedInSimplifiedMasternodeEntries count]*/) { + [self.mUsedAddresses addObject:e.address]; + } + } + } + }]; + } +- (void)storeNewAddressInContext:(NSString *)address + atIndex:(uint32_t)n + context:(NSManagedObjectContext *)context { + [context performBlock:^{ // store new address in core data + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self inContext:self.managedObjectContext]; + DSAddressEntity *e = [DSAddressEntity managedObjectInContext:self.managedObjectContext]; + e.derivationPath = derivationPathEntity; + NSAssert(DIsValidDashAddress(DChar(address), self.chain.chainType), @"the address is being saved to the wrong derivation path"); + e.address = address; + e.index = n; + e.standalone = NO; + }]; +} @end diff --git a/DashSync/shared/Models/Entities/DSAccountEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSAccountEntity+CoreDataClass.m index 9d5ee4f16..474faa315 100644 --- a/DashSync/shared/Models/Entities/DSAccountEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSAccountEntity+CoreDataClass.m @@ -12,7 +12,10 @@ @implementation DSAccountEntity -+ (DSAccountEntity *_Nonnull)accountEntityForWalletUniqueID:(NSString *)walletUniqueID index:(uint32_t)index onChain:(DSChain *)chain inContext:(NSManagedObjectContext *)context { ++ (DSAccountEntity *_Nonnull)accountEntityForWalletUniqueID:(NSString *)walletUniqueID + index:(uint32_t)index + onChain:(DSChain *)chain + inContext:(NSManagedObjectContext *)context { NSParameterAssert(walletUniqueID); NSParameterAssert(chain); NSParameterAssert(context); diff --git a/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataClass.h index 86e3d1477..3a3f8716a 100644 --- a/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataClass.h @@ -25,7 +25,7 @@ #import #import -@class DSDerivationPathEntity, DSTxInputEntity, DSTxOutputEntity, DSSpecialTransactionEntity, DSSimplifiedMasternodeEntryEntity, DSChain, DSChainEntity; +@class DSDerivationPathEntity, DSTxInputEntity, DSTxOutputEntity, DSSpecialTransactionEntity, DSChain, DSChainEntity; NS_ASSUME_NONNULL_BEGIN diff --git a/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.h index 448eaa7dc..d8eee4156 100644 --- a/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.h @@ -23,7 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, retain) DSDerivationPathEntity *derivationPath; @property (nonnull, nonatomic, retain) NSSet *usedInInputs; @property (nonnull, nonatomic, retain) NSSet *usedInOutputs; -@property (nonnull, nonatomic, retain) NSSet *usedInSimplifiedMasternodeEntries; @property (nonnull, nonatomic, retain) NSSet *usedInSpecialTransactions; @end @@ -40,11 +39,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)addUsedInOutputs:(NSSet *)values; - (void)removeUsedInOutputs:(NSSet *)values; -- (void)addUsedInSimplifiedMasternodeEntriesObject:(DSSimplifiedMasternodeEntryEntity *)value; -- (void)removeUsedInSimplifiedMasternodeEntriesObject:(DSSimplifiedMasternodeEntryEntity *)value; -- (void)addUsedInSimplifiedMasternodeEntries:(NSSet *)values; -- (void)removeUsedInSimplifiedMasternodeEntries:(NSSet *)values; - - (void)addUsedInSpecialTransactionsObject:(DSSpecialTransactionEntity *)value; - (void)removeUsedInSpecialTransactionsObject:(DSSpecialTransactionEntity *)value; - (void)addUsedInSpecialTransactions:(NSSet *)values; diff --git a/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.m index 0d903b053..f4050b2ee 100644 --- a/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSAddressEntity+CoreDataProperties.m @@ -22,7 +22,6 @@ @implementation DSAddressEntity (CoreDataProperties) @dynamic derivationPath; @dynamic usedInInputs; @dynamic usedInOutputs; -@dynamic usedInSimplifiedMasternodeEntries; @dynamic usedInSpecialTransactions; @end diff --git a/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataClass.m index 8633ceca6..6abee05a5 100644 --- a/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataClass.m @@ -20,8 +20,12 @@ #import "DSAssetLockTransactionEntity+CoreDataClass.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataClass.h" +#import "DSDerivationPathEntity+CoreDataClass.h" +#import "DSInstantSendLockEntity+CoreDataClass.h" #import "DSKeyManager.h" +#import "DSTransaction+Protected.h" #import "DSTransactionFactory.h" +#import "DSTransactionHashEntity+CoreDataClass.h" #import "DSTransactionOutput.h" #import "DSTxOutputEntity+CoreDataClass.h" #import "NSData+Dash.h" @@ -36,16 +40,25 @@ - (instancetype)setAttributesFromTransaction:(DSTransaction *)transaction { DSAssetLockTransaction *tx = (DSAssetLockTransaction *)transaction; self.specialTransactionVersion = tx.specialTransactionVersion; NSMutableOrderedSet *creditOutputs = [self mutableOrderedSetValueForKey:@"creditOutputs"]; - while (creditOutputs.count < tx.creditOutputs.count) { - [creditOutputs addObject:[DSTxOutputEntity managedObjectInBlockedContext:self.managedObjectContext]]; - } - while (creditOutputs.count > tx.creditOutputs.count) { - - [self removeObjectFromCreditOutputsAtIndex:creditOutputs.count - 1]; + NSMutableOrderedSet *baseOutputs = [self mutableOrderedSetValueForKey:@"outputs"]; // Explicitly fetch `outputs` from base class + [creditOutputs removeAllObjects]; + + for (NSUInteger idx = 0; idx < tx.creditOutputs.count; idx++) { + DSTxOutputEntity *e = [DSTxOutputEntity managedObjectInBlockedContext:self.managedObjectContext]; + e.txHash = uint256_data(transaction.txHash); + e.n = (uint32_t)idx; + DSTransactionOutput *output = tx.creditOutputs[idx]; + e.address = output.address; + e.script = output.outScript; + e.value = output.amount; + e.transaction = self; + [creditOutputs addObject:e]; } - NSUInteger idx = 0; + + // Ensure `creditOutputs` are NOT added to `outputs` for (DSTxOutputEntity *e in creditOutputs) { - [e setAttributesFromTransaction:tx outputIndex:idx++ forTransactionEntity:self]; + if ([baseOutputs containsObject:e]) + [baseOutputs removeObject:e]; // Explicitly remove credit outputs from base transaction outputs } }]; @@ -55,16 +68,21 @@ - (instancetype)setAttributesFromTransaction:(DSTransaction *)transaction { - (DSTransaction *)transactionForChain:(DSChain *)chain { DSAssetLockTransaction *tx = (DSAssetLockTransaction *)[super transactionForChain:chain]; tx.type = DSTransactionType_AssetLock; + tx.version = SPECIAL_TX_VERSION; + [self.managedObjectContext performBlockAndWait:^{ + tx.instantSendLockAwaitingProcessing = [self.instantSendLock instantSendTransactionLockForChain:chain]; tx.specialTransactionVersion = self.specialTransactionVersion; + NSMutableArray *creditOutputs = [NSMutableArray arrayWithCapacity:self.creditOutputs.count]; for (DSTxOutputEntity *e in self.creditOutputs) { NSString *address = e.address; if (!address && e.script) { address = [DSKeyManager addressWithScriptPubKey:e.script forChain:tx.chain]; } DSTransactionOutput *transactionOutput = [DSTransactionOutput transactionOutputWithAmount:e.value address:address outScript:e.script onChain:tx.chain]; - [tx.creditOutputs addObject:transactionOutput]; + [creditOutputs addObject:transactionOutput]; } + tx.creditOutputs = creditOutputs; }]; return tx; diff --git a/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.h index fafef85b5..6c7118238 100644 --- a/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.h @@ -16,6 +16,7 @@ // #import "DSAssetLockTransactionEntity+CoreDataClass.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" NS_ASSUME_NONNULL_BEGIN @@ -24,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN + (NSFetchRequest *)fetchRequest; +@property (nullable, nonatomic, retain) DSBlockchainIdentityEntity *identity; + @property (nonatomic, retain) NSOrderedSet *creditOutputs; @end diff --git a/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.m index 3b8e838d9..5f7e9278f 100644 --- a/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSAssetLockTransactionEntity+CoreDataProperties.m @@ -23,6 +23,7 @@ @implementation DSAssetLockTransactionEntity (CoreDataProperties) return [NSFetchRequest fetchRequestWithEntityName:@"DSAssetLockTransactionEntity"]; } +@dynamic identity; @dynamic creditOutputs; @end diff --git a/DashSync/shared/Models/Entities/DSAssetUnlockTransactionEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSAssetUnlockTransactionEntity+CoreDataClass.m index aa67b7306..ba10aa781 100644 --- a/DashSync/shared/Models/Entities/DSAssetUnlockTransactionEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSAssetUnlockTransactionEntity+CoreDataClass.m @@ -45,6 +45,7 @@ - (instancetype)setAttributesFromTransaction:(DSTransaction *)transaction { - (DSTransaction *)transactionForChain:(DSChain *)chain { DSAssetUnlockTransaction *tx = (DSAssetUnlockTransaction *)[super transactionForChain:chain]; tx.type = DSTransactionType_AssetUnlock; + tx.version = SPECIAL_TX_VERSION; [self.managedObjectContext performBlockAndWait:^{ tx.specialTransactionVersion = self.specialTransactionVersion; tx.index = self.index; diff --git a/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.h index f9ce47430..8eb48ce28 100644 --- a/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.h @@ -9,13 +9,13 @@ #import #import -@class DSBlockchainIdentityKeyPathEntity, DSChainEntity, DSDashpayUserEntity, DSCreditFundingTransactionEntity, DSTransitionEntity, DSBlockchainIdentityUsernameEntity, DSBlockchainIdentity, DSBlockchainInvitationEntity; +@class DSBlockchainIdentityKeyPathEntity, DSChainEntity, DSDashpayUserEntity, DSTransitionEntity, DSBlockchainIdentityUsernameEntity, DSIdentity, DSBlockchainInvitationEntity; NS_ASSUME_NONNULL_BEGIN @interface DSBlockchainIdentityEntity : NSManagedObject -- (DSBlockchainIdentity *)blockchainIdentity; +- (DSIdentity *)identity; + (void)deleteBlockchainIdentitiesOnChainEntity:(DSChainEntity *)chainEntity; diff --git a/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.m index 0816ad7c6..8db507374 100644 --- a/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataClass.m @@ -6,7 +6,7 @@ // // -#import "DSBlockchainIdentity+Protected.h" +#import "DSIdentity+Protected.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSChainEntity+CoreDataClass.h" #import "NSManagedObject+Sugar.h" @@ -15,15 +15,15 @@ @implementation DSBlockchainIdentityEntity + (void)deleteBlockchainIdentitiesOnChainEntity:(DSChainEntity *)chainEntity { [chainEntity.managedObjectContext performBlockAndWait:^{ - NSArray *blockchainIdentitiesToDelete = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@)", chainEntity]; - for (DSBlockchainIdentityEntity *blockchainIdentity in blockchainIdentitiesToDelete) { - [chainEntity.managedObjectContext deleteObject:blockchainIdentity]; + NSArray *identitiesToDelete = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@)", chainEntity]; + for (DSBlockchainIdentityEntity *identity in identitiesToDelete) { + [chainEntity.managedObjectContext deleteObject:identity]; } }]; } -- (DSBlockchainIdentity *)blockchainIdentity { - return [[DSBlockchainIdentity alloc] initWithBlockchainIdentityEntity:self]; +- (DSIdentity *)identity { + return [[DSIdentity alloc] initWithIdentityEntity:self]; } @end diff --git a/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataProperties.h index 15af858a6..e1cfce493 100644 --- a/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSBlockchainIdentityEntity+CoreDataProperties.h @@ -7,7 +7,7 @@ // #import "DSBlockchainIdentityEntity+CoreDataClass.h" - +#import "DSAssetLockTransactionEntity+CoreDataClass.h" NS_ASSUME_NONNULL_BEGIN @@ -20,8 +20,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) uint16_t registrationStatus; @property (nonatomic, assign) uint64_t creditBalance; @property (nullable, nonatomic, retain) NSData *dashpaySyncronizationBlockHash; -@property (nullable, nonatomic, retain) NSSet *topUpFundingTransactions; -@property (nullable, nonatomic, retain) DSCreditFundingTransactionEntity *registrationFundingTransaction; +@property (nullable, nonatomic, retain) NSSet *topUpFundingTransactions; +@property (nullable, nonatomic, retain) DSAssetLockTransactionEntity *registrationFundingTransaction; @property (nullable, nonatomic, retain) NSSet *keyPaths; @property (nullable, nonatomic, retain) DSDashpayUserEntity *matchingDashpayUser; @property (nullable, nonatomic, retain) DSBlockchainInvitationEntity *associatedInvitation; @@ -37,10 +37,10 @@ NS_ASSUME_NONNULL_BEGIN @interface DSBlockchainIdentityEntity (CoreDataGeneratedAccessors) -- (void)addTopUpFundingTransactionsObject:(DSCreditFundingTransactionEntity *)value; -- (void)removeTopUpFundingTransactionsObject:(DSCreditFundingTransactionEntity *)value; -- (void)addTopUpFundingTransactions:(NSSet *)values; -- (void)removeTopUpFundingTransactions:(NSSet *)values; +- (void)addTopUpFundingTransactionsObject:(DSAssetLockTransactionEntity *)value; +- (void)removeTopUpFundingTransactionsObject:(DSAssetLockTransactionEntity *)value; +- (void)addTopUpFundingTransactions:(NSSet *)values; +- (void)removeTopUpFundingTransactions:(NSSet *)values; - (void)addKeyPathsObject:(DSBlockchainIdentityKeyPathEntity *)value; - (void)removeKeyPathsObject:(DSBlockchainIdentityKeyPathEntity *)value; diff --git a/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.h index b7df527df..bb6497e75 100644 --- a/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.h @@ -6,7 +6,7 @@ // // -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" #import "DSBlockchainIdentityKeyPathEntity+CoreDataClass.h" NS_ASSUME_NONNULL_BEGIN @@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) uint16_t keyType; @property (nonatomic, assign) uint16_t keyStatus; @property (nonatomic, assign) uint32_t keyID; +@property (nonatomic, assign) uint8_t securityLevel; +@property (nonatomic, assign) uint8_t purpose; @property (nullable, nonatomic, retain) NSData *publicKeyData; @end diff --git a/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.m index c60206ad2..308801309 100644 --- a/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSBlockchainIdentityKeyPathEntity+CoreDataProperties.m @@ -20,6 +20,8 @@ @implementation DSBlockchainIdentityKeyPathEntity (CoreDataProperties) @dynamic keyType; @dynamic keyStatus; @dynamic keyID; +@dynamic securityLevel; +@dynamic purpose; @dynamic publicKeyData; @end diff --git a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.h index c09518fa4..0a6964f3f 100644 --- a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.h @@ -23,16 +23,17 @@ // THE SOFTWARE. #import "DSChain.h" +#import "DSKeyManager.h" #import #import -@class DSAccountEntity, DSBlockchainIdentityEntity, DSBlockchainInvitationEntity, DSDashpayUserEntity, DSDerivationPathEntity, DSGovernanceObjectHashEntity, DSGovernanceVoteHashEntity, DSMerkleBlockEntity, DSPeerEntity, DSQuorumEntryEntity, DSSimplifiedMasternodeEntryEntity, DSSporkHashEntity, DSTransactionHashEntity, DSChainLockEntity; +@class DSAccountEntity, DSBlockchainIdentityEntity, DSBlockchainInvitationEntity, DSDashpayUserEntity, DSDerivationPathEntity, DSGovernanceObjectHashEntity, DSGovernanceVoteHashEntity, DSMerkleBlockEntity, DSPeerEntity, DSSporkHashEntity, DSTransactionHashEntity, DSChainLockEntity; NS_ASSUME_NONNULL_BEGIN @interface DSChainEntity : NSManagedObject -+ (DSChainEntity *_Nonnull)chainEntityForType:(ChainType)type checkpoints:(NSArray *_Nullable)checkpoints inContext:(NSManagedObjectContext *)context; ++ (DSChainEntity *_Nonnull)chainEntityForType:(DChainType *)type checkpoints:(NSArray *_Nullable)checkpoints inContext:(NSManagedObjectContext *)context; - (instancetype)setAttributesFromChain:(DSChain *_Nonnull)chain; - (DSChain *_Nonnull)chain; diff --git a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.m index d99f69eca..b66d81a63 100644 --- a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataClass.m @@ -22,7 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataProperties.h" #import "DSChainLockEntity+CoreDataProperties.h" @@ -45,7 +46,7 @@ @implementation DSChainEntity @synthesize cachedChain; - (instancetype)setAttributesFromChain:(DSChain *)chain { - self.type = chain_type_index(chain.chainType); + self.type = dash_spv_crypto_network_chain_type_ChainType_index(chain.chainType); self.totalGovernanceObjectsCount = chain.totalGovernanceObjectsCount; return self; } @@ -54,7 +55,7 @@ - (DSChain *)chain { if (self.cachedChain) { return self.cachedChain; } - __block ChainType type; + __block dash_spv_crypto_network_chain_type_ChainType *type; __block NSString *devnetIdentifier; __block uint16_t devnetVersion; __block NSData *data; @@ -68,7 +69,7 @@ - (DSChain *)chain { __block NSArray *lastPersistedChainSyncLocators; [self.managedObjectContext performBlockAndWait:^{ - type = chain_type_from_index(self.type); + type = dash_spv_crypto_network_chain_type_chain_type_from_index(self.type); devnetIdentifier = self.devnetIdentifier; devnetVersion = self.devnetVersion; data = self.checkpoints; @@ -81,17 +82,18 @@ - (DSChain *)chain { lastPersistedChainSyncBlockTimestamp = self.syncBlockTimestamp; }]; DSChain *chain = nil; - if (type.tag == ChainType_MainNet) { + + if (type->tag == dash_spv_crypto_network_chain_type_ChainType_MainNet) { chain = [DSChain mainnet]; - } else if (type.tag == ChainType_TestNet) { + } else if (type->tag == dash_spv_crypto_network_chain_type_ChainType_TestNet) { chain = [DSChain testnet]; - } else if (type.tag == ChainType_DevNet) { + } else if (type->tag == dash_spv_crypto_network_chain_type_ChainType_DevNet) { if ([DSChain devnetWithIdentifier:devnetIdentifier]) { chain = [DSChain devnetWithIdentifier:devnetIdentifier]; } else { NSError *checkpointRetrievalError = nil; NSArray *checkpointArray = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:data error:&checkpointRetrievalError]; - chain = [DSChain recoverKnownDevnetWithIdentifier:devnet_type_for_chain_type(type) withCheckpoints:checkpointRetrievalError ? @[] : checkpointArray performSetup:YES]; + chain = [DSChain recoverKnownDevnetWithIdentifier:dash_spv_crypto_network_chain_type_ChainType_devnet_type(type) withCheckpoints:checkpointRetrievalError ? @[] : checkpointArray performSetup:YES]; } } else { NSAssert(FALSE, @"Unknown ChainType"); @@ -123,10 +125,12 @@ - (DSChain *)chain { return chain; } -+ (DSChainEntity *)chainEntityForType:(ChainType)type checkpoints:(NSArray *)checkpoints inContext:(NSManagedObjectContext *)context { ++ (DSChainEntity *)chainEntityForType:(dash_spv_crypto_network_chain_type_ChainType *)type + checkpoints:(NSArray *)checkpoints + inContext:(NSManagedObjectContext *)context { NSString *devnetIdentifier = [DSKeyManager devnetIdentifierFor:type]; - int16_t devnetVersion = devnet_version_for_chain_type(type); - NSArray *objects = [DSChainEntity objectsForPredicate:[NSPredicate predicateWithFormat:@"type = %d && ((type != %d) || devnetIdentifier = %@)", type, ChainType_DevNet, devnetIdentifier] inContext:context]; + int16_t devnetVersion = dash_spv_crypto_network_chain_type_ChainType_devnet_version(type); + NSArray *objects = [DSChainEntity objectsForPredicate:[NSPredicate predicateWithFormat:@"type = %d && ((type != %d) || devnetIdentifier = %@)", type->tag, dash_spv_crypto_network_chain_type_ChainType_DevNet, devnetIdentifier] inContext:context]; if (objects.count) { NSAssert(objects.count == 1, @"There should only ever be 1 chain for either mainnet, testnet, or a devnet Identifier"); if (objects.count > 1) { @@ -135,7 +139,7 @@ + (DSChainEntity *)chainEntityForType:(ChainType)type checkpoints:(NSArray *)che DSChainEntity *chainEntityToRemove = objects[i]; [context deleteObject:chainEntityToRemove]; [context ds_save]; - DSLog(@"Removing extra chain entity of type %d", type.tag); + DSLog(@"Removing extra chain entity of type %d", type->tag); } } DSChainEntity *chainEntity = [objects objectAtIndex:0]; @@ -156,7 +160,7 @@ + (DSChainEntity *)chainEntityForType:(ChainType)type checkpoints:(NSArray *)che } DSChainEntity *chainEntity = [self managedObjectInBlockedContext:context]; - chainEntity.type = (uint16_t) type.tag; + chainEntity.type = (uint16_t) type->tag; chainEntity.devnetIdentifier = devnetIdentifier; chainEntity.devnetVersion = devnetVersion; if (checkpoints && devnetIdentifier) { diff --git a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.h index 7f5c958da..a68b5b779 100644 --- a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.h @@ -33,8 +33,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, retain) NSSet *derivationPaths; @property (nullable, nonatomic, retain) NSSet *governanceObjectHashes; @property (nullable, nonatomic, retain) NSSet *peers; -@property (nullable, nonatomic, retain) NSSet *quorums; -@property (nullable, nonatomic, retain) NSSet *simplifiedMasternodeEntries; @property (nullable, nonatomic, retain) NSSet *sporks; @property (nullable, nonatomic, retain) NSSet *transactionHashes; @property (nullable, nonatomic, retain) NSSet *votes; @@ -75,16 +73,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)addPeers:(NSSet *)values; - (void)removePeers:(NSSet *)values; -- (void)addQuorumsObject:(DSQuorumEntryEntity *)value; -- (void)removeQuorumsObject:(DSQuorumEntryEntity *)value; -- (void)addQuorums:(NSSet *)values; -- (void)removeQuorums:(NSSet *)values; - -- (void)addSimplifiedMasternodeEntriesObject:(DSSimplifiedMasternodeEntryEntity *)value; -- (void)removeSimplifiedMasternodeEntriesObject:(DSSimplifiedMasternodeEntryEntity *)value; -- (void)addSimplifiedMasternodeEntries:(NSSet *)values; -- (void)removeSimplifiedMasternodeEntries:(NSSet *)values; - - (void)addSporksObject:(DSSporkHashEntity *)value; - (void)removeSporksObject:(DSSporkHashEntity *)value; - (void)addSporks:(NSSet *)values; diff --git a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.m index 7595fe49c..e1fdb564c 100644 --- a/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSChainEntity+CoreDataProperties.m @@ -26,8 +26,6 @@ @implementation DSChainEntity (CoreDataProperties) @dynamic derivationPaths; @dynamic governanceObjectHashes; @dynamic peers; -@dynamic quorums; -@dynamic simplifiedMasternodeEntries; @dynamic sporks; @dynamic transactionHashes; @dynamic votes; diff --git a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.h index 7bb53e8f0..a219e7834 100644 --- a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.h @@ -9,7 +9,7 @@ #import #import -@class DSMerkleBlockEntity, DSQuorumEntryEntity, DSChain, DSChainLock, DSChainEntity; +@class DSMerkleBlockEntity, DSChain, DSChainLock, DSChainEntity; NS_ASSUME_NONNULL_BEGIN diff --git a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.m index 50e4e1a91..9e4717210 100644 --- a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataClass.m @@ -13,27 +13,24 @@ #import "DSChainLockEntity+CoreDataClass.h" #import "DSMerkleBlock.h" #import "DSMerkleBlockEntity+CoreDataClass.h" -#import "DSQuorumEntry.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntry.h" #import "NSData+Dash.h" #import "NSManagedObject+Sugar.h" @implementation DSChainLockEntity -+ (instancetype)chainLockEntityForChainLock:(DSChainLock *)chainLock inContext:(NSManagedObjectContext *)context { - DSMerkleBlockEntity *merkleBlockEntity = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:chainLock.blockHash inContext:context]; ++ (instancetype)chainLockEntityForChainLock:(DSChainLock *)chainLock + inContext:(NSManagedObjectContext *)context { + DSMerkleBlockEntity *merkleBlockEntity = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:chainLock.blockHashData inContext:context]; if (!merkleBlockEntity) { return nil; } DSChainLockEntity *chainLockEntity = [DSChainLockEntity managedObjectInBlockedContext:context]; chainLockEntity.validSignature = chainLock.signatureVerified; - chainLockEntity.signature = [NSData dataWithUInt768:chainLock.signature]; + chainLockEntity.signature = chainLock.signatureData; chainLockEntity.merkleBlock = merkleBlockEntity; - chainLockEntity.quorum = [chainLock.intendedQuorum matchingQuorumEntryEntityInContext:context]; //the quorum might not yet if (chainLock.signatureVerified) { - DSChainEntity *chainEntity = [chainLock.intendedQuorum.chain chainEntityInContext:context]; - if (!chainEntity.lastChainLock || chainEntity.lastChainLock.merkleBlock.height < chainLock.height) { + DSChainEntity *chainEntity = [chainLock.chain chainEntityInContext:context]; + if (!chainEntity.lastChainLock || chainEntity.lastChainLock.merkleBlock.height < DChainLockBlockHeight(chainLock.lock)) { chainEntity.lastChainLock = chainLockEntity; } } @@ -42,9 +39,12 @@ + (instancetype)chainLockEntityForChainLock:(DSChainLock *)chainLock inContext:( } - (DSChainLock *)chainLockForChain:(DSChain *)chain { - DSChainLock *chainLock = [[DSChainLock alloc] initWithBlockHash:self.merkleBlock.blockHash.UInt256 signature:self.signature.UInt768 signatureVerified:TRUE quorumVerified:TRUE onChain:chain]; - - return chainLock; + return [[DSChainLock alloc] initWithBlockHash:self.merkleBlock.blockHash + height:self.merkleBlock.height + signature:self.signature + signatureVerified:TRUE + quorumVerified:TRUE + onChain:chain]; } @end diff --git a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.h index 06b4c5304..bb9f7d4e5 100644 --- a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.h @@ -17,7 +17,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, retain) NSData *signature; @property (assign, nonatomic) BOOL validSignature; -@property (nullable, nonatomic, retain) DSQuorumEntryEntity *quorum; @property (nullable, nonatomic, retain) DSMerkleBlockEntity *merkleBlock; @property (nullable, nonatomic, retain) DSChainEntity *chainIfLastChainLock; diff --git a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.m index 6666d238f..0c45aea98 100644 --- a/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSChainLockEntity+CoreDataProperties.m @@ -16,7 +16,7 @@ @implementation DSChainLockEntity (CoreDataProperties) @dynamic signature; @dynamic validSignature; -@dynamic quorum; +//@dynamic quorum; @dynamic merkleBlock; @dynamic chainIfLastChainLock; diff --git a/DashSync/shared/Models/Entities/DSContactRequest.h b/DashSync/shared/Models/Entities/DSContactRequest.h deleted file mode 100644 index eb0c65be5..000000000 --- a/DashSync/shared/Models/Entities/DSContactRequest.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "BigIntTypes.h" -#import "dash_shared_core.h" -#import "DPTypes.h" -#import - -@class DSBlockchainIdentity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSContactRequest : NSObject - -@property (nonatomic, readonly) UInt256 recipientBlockchainIdentityUniqueId; -@property (nonatomic, readonly) UInt256 senderBlockchainIdentityUniqueId; -@property (nonatomic, readonly) uint32_t recipientKeyIndex; -@property (nonatomic, readonly) uint32_t senderKeyIndex; -@property (nonatomic, readonly) uint32_t accountReference; -@property (nonatomic, readonly) NSData *encryptedAccountLabel; - -@property (nonatomic, readonly) NSTimeInterval createdAt; - -@property (nonatomic, readonly) NSData *encryptedPublicKeyData; - -+ (instancetype)contactRequestFromDictionary:(DSStringValueDictionary *)serverDictionary onBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; - -- (NSData *)decryptedPublicKeyDataWithKey:(OpaqueKey *)key; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSContactRequest.m b/DashSync/shared/Models/Entities/DSContactRequest.m deleted file mode 100644 index da7aabb5c..000000000 --- a/DashSync/shared/Models/Entities/DSContactRequest.m +++ /dev/null @@ -1,103 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSContactRequest.h" -#import "DSBlockchainIdentity+Protected.h" -#import "NSData+Dash.h" -#import "NSData+Encryption.h" -#import "NSString+Bitcoin.h" - -@interface DSContactRequest () - -@property (nonatomic, assign) UInt256 recipientBlockchainIdentityUniqueId; -@property (nonatomic, assign) UInt256 senderBlockchainIdentityUniqueId; -@property (nonatomic, assign) uint32_t recipientKeyIndex; -@property (nonatomic, assign) uint32_t senderKeyIndex; -@property (nonatomic, assign) uint32_t accountReference; -@property (nonatomic, strong) NSData *encryptedAccountLabel; - -@property (nonatomic, assign) NSTimeInterval createdAt; - -@property (nonatomic, strong) NSData *encryptedPublicKeyData; -@property (nonatomic, strong) DSBlockchainIdentity *blockchainIdentity; - -@end - -@implementation DSContactRequest - -- (instancetype)initWithDictionary:(DSStringValueDictionary *)rawContact onBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - NSParameterAssert(rawContact); - NSParameterAssert(blockchainIdentity); - if (!(self = [super init])) return nil; - - NSData *recipientData = rawContact[@"toUserId"]; - NSData *senderData = rawContact[@"$ownerId"]; - NSData *encryptedAccountLabel = rawContact[@"encryptedAccountLabel"]; - NSData *encryptedPublicKeyData = rawContact[@"encryptedPublicKey"]; - NSNumber *accountReference = rawContact[@"accountReference"]; - NSNumber *senderKeyIndex = rawContact[@"senderKeyIndex"]; - NSNumber *recipientKeyIndex = rawContact[@"recipientKeyIndex"]; - NSNumber *createdAt = rawContact[@"$createdAt"]; - if (!recipientData || !senderData || !encryptedPublicKeyData || !senderKeyIndex || !recipientKeyIndex || !createdAt) { - NSAssert(FALSE, @"malformed server response"); - return nil; - } - self.recipientBlockchainIdentityUniqueId = recipientData.UInt256; - self.senderBlockchainIdentityUniqueId = senderData.UInt256; - self.encryptedPublicKeyData = encryptedPublicKeyData; - self.encryptedAccountLabel = encryptedAccountLabel; - self.accountReference = [accountReference unsignedIntValue]; - self.createdAt = [createdAt doubleValue] / 1000.0; - self.recipientKeyIndex = [recipientKeyIndex unsignedIntValue]; - self.senderKeyIndex = [senderKeyIndex unsignedIntValue]; - self.blockchainIdentity = blockchainIdentity; - return self; -} - -+ (instancetype)contactRequestFromDictionary:(DSStringValueDictionary *)serverDictionary onBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - return [[self alloc] initWithDictionary:serverDictionary onBlockchainIdentity:blockchainIdentity]; -} - -- (BOOL)blockchainIdentityIsRecipient { - if (uint256_eq(self.blockchainIdentity.uniqueID, self.recipientBlockchainIdentityUniqueId)) { - //we are the recipient of the friend request - return YES; - } else if (uint256_eq(self.blockchainIdentity.uniqueID, self.senderBlockchainIdentityUniqueId)) { - //we are the sender of the friend request - return NO; - } - NSAssert(NO, @"We should never get here"); - return NO; -} - -- (OpaqueKey *)secretKeyForDecryptionOfType:(KeyKind)type { - uint32_t index = [self blockchainIdentityIsRecipient] ? self.recipientKeyIndex : self.senderKeyIndex; - OpaqueKey *key = [self.blockchainIdentity privateKeyAtIndex:index ofType:type]; - NSAssert(key, @"Key should exist"); - return key; -} - -- (NSData *)decryptedPublicKeyDataWithKey:(OpaqueKey *)key { - NSParameterAssert(key); - return [self.encryptedPublicKeyData decryptWithSecretKey:[self secretKeyForDecryptionOfType:(int16_t) key->tag] fromPublicKey:key]; -} - -- (NSString *)debugDescription { - return [NSString stringWithFormat:@"%@ - from %@/%d to %@/%d", [super debugDescription], uint256_base58(self.senderBlockchainIdentityUniqueId), self.senderKeyIndex, uint256_base58(self.recipientBlockchainIdentityUniqueId), self.recipientKeyIndex]; -} - -@end diff --git a/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.h index 8b98e38b5..e3fbdf9f9 100644 --- a/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.h @@ -8,13 +8,18 @@ #import #import +#import "NSManagedObject+Sugar.h" -@class DSBlockchainIdentityEntity, DSChainEntity; +@class DSBlockchainIdentityEntity, DSChainEntity, DSChain; NS_ASSUME_NONNULL_BEGIN @interface DSContractEntity : NSManagedObject ++ (instancetype)entityWithLocalContractIdentifier:(NSString *)identifier + onChain:(DSChain *)chain + inContext:(NSManagedObjectContext *)context; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.m index 67144ea83..a028ab500 100644 --- a/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSContractEntity+CoreDataClass.m @@ -7,7 +7,14 @@ // #import "DSContractEntity+CoreDataClass.h" +#import "DSChain.h" @implementation DSContractEntity ++ (instancetype)entityWithLocalContractIdentifier:(NSString *)identifier + onChain:(DSChain *)chain + inContext:(NSManagedObjectContext *)context { + return [DSContractEntity anyObjectInContext:context matching:@"localContractIdentifier == %@ && chain == %@", identifier, [chain chainEntityInContext:context]]; +} + @end diff --git a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataClass.h deleted file mode 100644 index d657599ef..000000000 --- a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataClass.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// DSCreditFundingTransactionEntity+CoreDataClass.h -// DashSync -// -// Created by Sam Westrich on 12/31/19. -// -// - -#import "DSSpecialTransactionEntity+CoreDataClass.h" -#import - -@class DSBlockchainIdentityEntity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSCreditFundingTransactionEntity : DSSpecialTransactionEntity - -@end - -NS_ASSUME_NONNULL_END - -#import "DSCreditFundingTransactionEntity+CoreDataProperties.h" diff --git a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataClass.m deleted file mode 100644 index 1a382dbce..000000000 --- a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataClass.m +++ /dev/null @@ -1,48 +0,0 @@ -// -// DSCreditFundingTransactionEntity+CoreDataClass.m -// DashSync -// -// Created by Sam Westrich on 12/31/19. -// -// - -#import "DSAccount.h" -#import "DSBlockchainIdentity+Protected.h" -#import "DSBlockchainIdentityEntity+CoreDataClass.h" -#import "DSCreditFundingTransaction.h" -#import "DSCreditFundingTransactionEntity+CoreDataClass.h" -#import "DSInstantSendLockEntity+CoreDataClass.h" -#import "DSTransaction+Protected.h" -#import "DSTransactionFactory.h" -#import "DSWallet.h" - -@implementation DSCreditFundingTransactionEntity - -- (Class)transactionClass { - return [DSCreditFundingTransaction class]; -} - -- (DSTransaction *)transactionForChain:(DSChain *)chain { - DSCreditFundingTransaction *transaction = (DSCreditFundingTransaction *)[super transactionForChain:chain]; - transaction.type = DSTransactionType_Classic; - [self.managedObjectContext performBlockAndWait:^{ - transaction.instantSendLockAwaitingProcessing = [self.instantSendLock instantSendTransactionLockForChain:chain]; - }]; - - return transaction; -} - -//- (instancetype)setAttributesFromTransaction:(DSTransaction *)tx -//{ -// [self.managedObjectContext performBlockAndWait:^{ -// [super setAttributesFromTransaction:tx]; -// DSCreditFundingTransaction * creditFundingTransaction = (DSCreditFundingTransaction *)tx; -// DSWallet * wallet = tx.account.wallet; -// DSBlockchainIdentity * identity = [wallet blockchainIdentityForUniqueId:creditFundingTransaction.creditBurnIdentityIdentifier]; -// self.blockchainIdentity = identity.blockchainIdentityEntity; -// }]; -// -// return self; -//} - -@end diff --git a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataProperties.h deleted file mode 100644 index 62777b488..000000000 --- a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataProperties.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// DSCreditFundingTransactionEntity+CoreDataProperties.h -// DashSync -// -// Created by Sam Westrich on 12/31/19. -// -// - -#import "DSCreditFundingTransactionEntity+CoreDataClass.h" - - -NS_ASSUME_NONNULL_BEGIN - -@interface DSCreditFundingTransactionEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest; - -@property (nullable, nonatomic, retain) DSBlockchainIdentityEntity *blockchainIdentity; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataProperties.m deleted file mode 100644 index f1c381206..000000000 --- a/DashSync/shared/Models/Entities/DSCreditFundingTransactionEntity+CoreDataProperties.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// DSCreditFundingTransactionEntity+CoreDataProperties.m -// DashSync -// -// Created by Sam Westrich on 12/31/19. -// -// - -#import "DSCreditFundingTransactionEntity+CoreDataProperties.h" - -@implementation DSCreditFundingTransactionEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest { - return [NSFetchRequest fetchRequestWithEntityName:@"DSCreditFundingTransactionEntity"]; -} - -@dynamic blockchainIdentity; - -@end diff --git a/DashSync/shared/Models/Entities/DSDashpayUserEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSDashpayUserEntity+CoreDataClass.h index afe661408..cce63d322 100644 --- a/DashSync/shared/Models/Entities/DSDashpayUserEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSDashpayUserEntity+CoreDataClass.h @@ -27,7 +27,7 @@ typedef NS_ENUM(NSUInteger, DSDashpayUserEntityFriendActivityType) DSDashpayUserEntityFriendActivityType_OutgoingTransactions }; -@class DSAccountEntity, DSFriendRequestEntity, DSTransitionEntity, DSTransientDashpayUser, DSBlockchainIdentity, DSPotentialOneWayFriendship, DSWallet, DSIncomingFundsDerivationPath, DSChainEntity, DSBlockchainIdentityEntity, DPDocument; +@class DSAccountEntity, DSFriendRequestEntity, DSTransitionEntity, DSTransientDashpayUser, DSIdentity, DSPotentialOneWayFriendship, DSWallet, DSIncomingFundsDerivationPath, DSChainEntity, DSBlockchainIdentityEntity; NS_ASSUME_NONNULL_BEGIN @@ -45,7 +45,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSDictionary *)friendsWithActivityForType:(DSDashpayUserEntityFriendActivityType)activityType count:(NSUInteger)count ascending:(BOOL)ascending; -- (NSError *)applyTransientDashpayUser:(DSTransientDashpayUser *)transientDashpayUser save:(BOOL)save; +- (NSError *)applyTransientDashpayUser:(DSTransientDashpayUser *)transientDashpayUser + save:(BOOL)save; - (void)sendAmount:(uint64_t)amount fromAccount:(DSAccount *)account toFriendWithIdentityIdentifier:(UInt256)identityIdentifier requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge diff --git a/DashSync/shared/Models/Entities/DSDerivationPathEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSDerivationPathEntity+CoreDataClass.m index a56c2580b..fee5dd003 100644 --- a/DashSync/shared/Models/Entities/DSDerivationPathEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSDerivationPathEntity+CoreDataClass.m @@ -36,7 +36,8 @@ @implementation DSDerivationPathEntity -+ (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:(DSDerivationPath *)derivationPath inContext:(NSManagedObjectContext *)context { ++ (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:(DSDerivationPath *)derivationPath + inContext:(NSManagedObjectContext *)context { NSAssert(derivationPath.standaloneExtendedPublicKeyUniqueID, @"standaloneExtendedPublicKeyUniqueID must be set"); //DSChain * chain = derivationPath.chain; NSArray *derivationPathEntities; @@ -59,12 +60,15 @@ + (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:( derivationPathEntity.publicKeyIdentifier = derivationPath.standaloneExtendedPublicKeyUniqueID; derivationPathEntity.syncBlockHeight = BIP39_CREATION_TIME; if (derivationPath.account) { - derivationPathEntity.account = [DSAccountEntity accountEntityForWalletUniqueID:derivationPath.account.wallet.uniqueIDString index:derivationPath.account.accountNumber onChain:derivationPath.chain inContext:context]; + derivationPathEntity.account = [DSAccountEntity accountEntityForWalletUniqueID:derivationPath.account.wallet.uniqueIDString + index:derivationPath.account.accountNumber + onChain:derivationPath.chain + inContext:context]; } if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { //NSLog(@"--->creating derivation path entity on path %@ (%@) with no friendship identifier %@", derivationPath, derivationPath.stringRepresentation, [NSThread callStackSymbols]); DSIncomingFundsDerivationPath *incomingFundsDerivationPath = (DSIncomingFundsDerivationPath *)derivationPath; - NSPredicate *predicatee = [NSPredicate predicateWithFormat:@"sourceContact.associatedBlockchainIdentity.uniqueID == %@ && destinationContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(incomingFundsDerivationPath.contactSourceBlockchainIdentityUniqueId), uint256_data(incomingFundsDerivationPath.contactDestinationBlockchainIdentityUniqueId)]; + NSPredicate *predicatee = [NSPredicate predicateWithFormat:@"sourceContact.associatedBlockchainIdentity.uniqueID == %@ && destinationContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(incomingFundsDerivationPath.contactSourceIdentityUniqueId), uint256_data(incomingFundsDerivationPath.contactDestinationIdentityUniqueId)]; DSFriendRequestEntity *friendRequest = [DSFriendRequestEntity anyObjectForPredicate:predicatee inContext:context]; if (friendRequest) { derivationPathEntity.friendRequest = friendRequest; @@ -75,7 +79,9 @@ + (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:( } } -+ (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath associateWithFriendRequest:(DSFriendRequestEntity *)friendRequest inContext:(NSManagedObjectContext *)context { ++ (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath + associateWithFriendRequest:(DSFriendRequestEntity *)friendRequest + inContext:(NSManagedObjectContext *)context { NSAssert(derivationPath.standaloneExtendedPublicKeyUniqueID, @"standaloneExtendedPublicKeyUniqueID must be set"); NSParameterAssert(friendRequest); @@ -96,7 +102,10 @@ + (DSDerivationPathEntity *_Nonnull)derivationPathEntityMatchingDerivationPath:( derivationPathEntity.publicKeyIdentifier = derivationPath.standaloneExtendedPublicKeyUniqueID; derivationPathEntity.syncBlockHeight = BIP39_CREATION_TIME; if (derivationPath.account) { - derivationPathEntity.account = [DSAccountEntity accountEntityForWalletUniqueID:derivationPath.account.wallet.uniqueIDString index:derivationPath.account.accountNumber onChain:derivationPath.chain inContext:context]; + derivationPathEntity.account = [DSAccountEntity accountEntityForWalletUniqueID:derivationPath.account.wallet.uniqueIDString + index:derivationPath.account.accountNumber + onChain:derivationPath.chain + inContext:context]; } derivationPathEntity.friendRequest = friendRequest; diff --git a/DashSync/shared/Models/Entities/DSFriendRequestEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSFriendRequestEntity+CoreDataClass.m index 0975c5589..be3a071b4 100644 --- a/DashSync/shared/Models/Entities/DSFriendRequestEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSFriendRequestEntity+CoreDataClass.m @@ -88,8 +88,9 @@ - (void)sendAmount:(uint64_t)amount fromAccount:(DSAccount *)account requestingA NSAssert([paymentRequest isValidAsNonDashpayPaymentRequest], @"Payment request must be valid"); + // TODO: MOCK_DASHPAY mixed only? [account.wallet.chain.chainManager.transactionManager confirmPaymentRequest:paymentRequest - usingUserBlockchainIdentity:nil + usingUserIdentity:nil fromAccount:account acceptInternalAddress:NO acceptReusingAddress:YES diff --git a/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataClass.m index b510e933e..958bdd255 100644 --- a/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataClass.m @@ -12,7 +12,6 @@ #import "DSGovernanceVote.h" #import "DSGovernanceVoteEntity+CoreDataClass.h" #import "DSGovernanceVoteHashEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataClass.h" #import "NSData+DSHash.h" #import "NSManagedObject+Sugar.h" @@ -29,10 +28,10 @@ - (void)setAttributesFromGovernanceVote:(DSGovernanceVote *)governanceVote forHa self.masternodeHash = masternodeHashData; self.masternodeIndex = (uint32_t)governanceVote.masternodeUTXO.n; /// WTF? old fields ? - NSArray *matchingMasternodeEntities = [DSSimplifiedMasternodeEntryEntity objectsInContext:self.managedObjectContext matching:@"utxoHash == %@ && utxoIndex == %@", masternodeHashData, @(governanceVote.masternodeUTXO.n)]; - if ([matchingMasternodeEntities count]) { - self.masternode = [matchingMasternodeEntities firstObject]; - } +// NSArray *matchingMasternodeEntities = [DSSimplifiedMasternodeEntryEntity objectsInContext:self.managedObjectContext matching:@"utxoHash == %@ && utxoIndex == %@", masternodeHashData, @(governanceVote.masternodeUTXO.n)]; +// if ([matchingMasternodeEntities count]) { +// self.masternode = [matchingMasternodeEntities firstObject]; +// } }]; } diff --git a/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.h index 7de0390f8..1929089d4 100644 --- a/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, retain) NSData *masternodeHash; @property (nonatomic, assign) uint32_t masternodeIndex; @property (nullable, nonatomic, retain) DSGovernanceVoteHashEntity *governanceVoteHash; -@property (nullable, nonatomic, retain) DSSimplifiedMasternodeEntry *masternode; +//@property (nullable, nonatomic, retain) DSSimplifiedMasternodeEntry *masternode; @end diff --git a/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.m index 8817d7225..db5010d16 100644 --- a/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSGovernanceVoteEntity+CoreDataProperties.m @@ -20,7 +20,7 @@ @implementation DSGovernanceVoteEntity (CoreDataProperties) @dynamic signature; @dynamic masternodeHash; @dynamic governanceVoteHash; -@dynamic masternode; +//@dynamic masternode; @dynamic parentHash; @dynamic masternodeIndex; diff --git a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.h index 82a46fab5..07a4b007b 100644 --- a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.h @@ -9,7 +9,7 @@ #import #import -@class DSQuorumEntryEntity, DSTransactionEntity, DSInstantSendTransactionLock, DSChain; +@class DSTransactionEntity, DSInstantSendTransactionLock, DSChain; NS_ASSUME_NONNULL_BEGIN diff --git a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.m index 14de18afa..f5674ff39 100644 --- a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataClass.m @@ -10,9 +10,6 @@ #import "DSChainEntity+CoreDataClass.h" #import "DSInstantSendLockEntity+CoreDataClass.h" #import "DSInstantSendTransactionLock.h" -#import "DSQuorumEntry.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntry.h" #import "DSTransactionEntity+CoreDataClass.h" #import "DSTransactionHashEntity+CoreDataClass.m" #import "DSTxInputEntity+CoreDataClass.h" @@ -22,28 +19,26 @@ @implementation DSInstantSendLockEntity + (DSInstantSendLockEntity *)instantSendLockEntityFromInstantSendLock:(DSInstantSendTransactionLock *)instantSendTransactionLock inContext:(NSManagedObjectContext *)context { - DSTransactionEntity *transactionEntity = [DSTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", uint256_data(instantSendTransactionLock.transactionHash)]; + DSTransactionEntity *transactionEntity = [DSTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", instantSendTransactionLock.transactionHashData]; if (transactionEntity) { DSInstantSendLockEntity *entity = [DSInstantSendLockEntity managedObjectInContext:context]; entity.validSignature = instantSendTransactionLock.signatureVerified; - entity.signature = [NSData dataWithUInt768:instantSendTransactionLock.signature]; - + entity.signature = instantSendTransactionLock.signatureData; + entity.cycleHash = instantSendTransactionLock.cycleHashData; NSAssert(transactionEntity, @"transaction must exist"); entity.transaction = transactionEntity; - entity.quorum = [instantSendTransactionLock.intendedQuorum matchingQuorumEntryEntityInContext:context]; //the quorum might not yet } - return nil; } - (instancetype)setAttributesFromInstantSendTransactionLock:(DSInstantSendTransactionLock *)instantSendTransactionLock { [self.managedObjectContext performBlockAndWait:^{ self.validSignature = instantSendTransactionLock.signatureVerified; - self.signature = [NSData dataWithUInt768:instantSendTransactionLock.signature]; - DSTransactionEntity *transactionEntity = [DSTransactionEntity anyObjectInContext:self.managedObjectContext matching:@"transactionHash.txHash == %@", uint256_data(instantSendTransactionLock.transactionHash)]; + self.signature = instantSendTransactionLock.signatureData; + self.cycleHash = instantSendTransactionLock.cycleHashData; + DSTransactionEntity *transactionEntity = [DSTransactionEntity anyObjectInContext:self.managedObjectContext matching:@"transactionHash.txHash == %@", instantSendTransactionLock.transactionHashData]; NSAssert(transactionEntity, @"transaction must exist"); self.transaction = transactionEntity; - self.quorum = [instantSendTransactionLock.intendedQuorum matchingQuorumEntryEntityInContext:self.managedObjectContext]; //the quorum might not yet }]; return self; @@ -54,9 +49,14 @@ - (DSInstantSendTransactionLock *)instantSendTransactionLockForChain:(DSChain *) for (DSTxInputEntity *input in self.transaction.inputs) { [inputOutpoints addObject:dsutxo_data(input.outpoint)]; } - DSInstantSendTransactionLock *instantSendTransactionLock = [[DSInstantSendTransactionLock alloc] initWithTransactionHash:self.transaction.transactionHash.txHash.UInt256 withInputOutpoints:inputOutpoints signature:self.signature.UInt768 signatureVerified:TRUE quorumVerified:TRUE onChain:chain]; - - return instantSendTransactionLock; + return [[DSInstantSendTransactionLock alloc] initWithTransactionHash:self.transaction.transactionHash.txHash + withInputOutpoints:inputOutpoints + version:self.version + signature:self.signature + cycleHash:self.cycleHash + signatureVerified:TRUE + quorumVerified:TRUE + onChain:chain]; } @end diff --git a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.h index 054159485..9b260c482 100644 --- a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.h @@ -16,9 +16,10 @@ NS_ASSUME_NONNULL_BEGIN + (NSFetchRequest *)fetchRequest; @property (nullable, nonatomic, retain) NSData *signature; +@property (nullable, nonatomic, retain) NSData *cycleHash; +@property (assign, nonatomic) uint8_t version; @property (assign, nonatomic) BOOL validSignature; @property (nullable, nonatomic, retain) DSTransactionEntity *transaction; -@property (nullable, nonatomic, retain) DSQuorumEntryEntity *quorum; @end diff --git a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.m index a70eb95ad..a2dd118d9 100644 --- a/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSInstantSendLockEntity+CoreDataProperties.m @@ -15,8 +15,9 @@ @implementation DSInstantSendLockEntity (CoreDataProperties) } @dynamic signature; +@dynamic cycleHash; @dynamic validSignature; @dynamic transaction; -@dynamic quorum; +@dynamic version; @end diff --git a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.h index fa0e36e1a..fe25c7c32 100644 --- a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.h @@ -9,7 +9,7 @@ #import #import -@class DSProviderRegistrationTransactionEntity, DSProviderUpdateRegistrarTransactionEntity, DSProviderUpdateRevocationTransactionEntity, DSProviderUpdateServiceTransactionEntity, DSSimplifiedMasternodeEntryEntity, DSLocalMasternode, DSChainEntity; +@class DSProviderRegistrationTransactionEntity, DSProviderUpdateRegistrarTransactionEntity, DSProviderUpdateRevocationTransactionEntity, DSProviderUpdateServiceTransactionEntity, DSLocalMasternode, DSChainEntity; NS_ASSUME_NONNULL_BEGIN @@ -23,6 +23,9 @@ NS_ASSUME_NONNULL_BEGIN + (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity; ++ (void)loadLocalMasternodesInContext:(NSManagedObjectContext *)context + onChainEntity:(DSChainEntity *)chainEntity; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.m index f169b4709..f19de7c6a 100644 --- a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataClass.m @@ -21,7 +21,6 @@ #import "DSProviderUpdateRevocationTransactionEntity+CoreDataProperties.h" #import "DSProviderUpdateServiceTransaction.h" #import "DSProviderUpdateServiceTransactionEntity+CoreDataProperties.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h" #import "DSTransactionHashEntity+CoreDataClass.h" #import "DSWallet.h" #import "NSData+Dash.h" @@ -64,8 +63,6 @@ - (void)setAttributesFromLocalMasternode:(DSLocalMasternode *)localMasternode { self.holdingKeysIndex = localMasternode.holdingWalletIndex; DSProviderRegistrationTransactionEntity *providerRegistrationTransactionEntity = [DSProviderRegistrationTransactionEntity anyObjectInContext:self.managedObjectContext matching:@"transactionHash.txHash == %@", uint256_data(localMasternode.providerRegistrationTransaction.txHash)]; self.providerRegistrationTransaction = providerRegistrationTransactionEntity; - DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity = [DSSimplifiedMasternodeEntryEntity anyObjectInContext:self.managedObjectContext matching:@"providerRegistrationTransactionHash == %@", uint256_data(localMasternode.providerRegistrationTransaction.txHash)]; - self.simplifiedMasternodeEntry = simplifiedMasternodeEntryEntity; for (DSProviderUpdateServiceTransaction *providerUpdateServiceTransaction in localMasternode.providerUpdateServiceTransactions) { DSProviderUpdateServiceTransactionEntity *providerUpdateServiceTransactionEntity = [DSProviderUpdateServiceTransactionEntity anyObjectInContext:self.managedObjectContext matching:@"transactionHash.txHash == %@", uint256_data(providerUpdateServiceTransaction.txHash)]; @@ -105,4 +102,14 @@ + (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity { } } ++ (void)loadLocalMasternodesInContext:(NSManagedObjectContext *)context + onChainEntity:(DSChainEntity *)chainEntity { + NSFetchRequest *fetchRequest = [[DSLocalMasternodeEntity fetchRequest] copy]; + [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"providerRegistrationTransaction.transactionHash.chain == %@", chainEntity]]; + NSArray *localMasternodeEntities = [DSLocalMasternodeEntity fetchObjects:fetchRequest inContext:context]; + for (DSLocalMasternodeEntity *localMasternodeEntity in localMasternodeEntities) { + [localMasternodeEntity loadLocalMasternode]; // lazy loaded into the list + } +} + @end diff --git a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.h index fb58181cf..5219aae47 100644 --- a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.h @@ -24,7 +24,6 @@ NS_ASSUME_NONNULL_BEGIN @property (assign, nonatomic) uint32_t votingKeysIndex; @property (assign, nonatomic) uint32_t holdingKeysIndex; @property (nullable, nonatomic, retain) DSProviderRegistrationTransactionEntity *providerRegistrationTransaction; -@property (nullable, nonatomic, retain) DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntry; @property (nullable, nonatomic, retain) NSOrderedSet *providerUpdateRegistrarTransactions; @property (nullable, nonatomic, retain) NSOrderedSet *providerUpdateServiceTransactions; @property (nullable, nonatomic, retain) NSOrderedSet *providerUpdateRevocationTransactions; diff --git a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.m index e12ab277e..e5a3c47c0 100644 --- a/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSLocalMasternodeEntity+CoreDataProperties.m @@ -23,7 +23,7 @@ @implementation DSLocalMasternodeEntity (CoreDataProperties) @dynamic votingKeysIndex; @dynamic votingKeysWalletUniqueId; @dynamic providerRegistrationTransaction; -@dynamic simplifiedMasternodeEntry; +//@dynamic simplifiedMasternodeEntry; @dynamic providerUpdateRegistrarTransactions; @dynamic providerUpdateServiceTransactions; @dynamic providerUpdateRevocationTransactions; diff --git a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataClass.h deleted file mode 100644 index ea4882528..000000000 --- a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataClass.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// DSMasternodeListEntity+CoreDataClass.h -// DashSync -// -// Created by Sam Westrich on 5/23/19. -// -// - -#import "BigIntTypes.h" -#import -#import - -@class DSMerkleBlockEntity, DSQuorumEntryEntity, DSSimplifiedMasternodeEntryEntity, DSMasternodeList, DSSimplifiedMasternodeEntry, DSQuorumEntry, DSChainEntity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSMasternodeListEntity : NSManagedObject - -- (DSMasternodeList *)masternodeListWithSimplifiedMasternodeEntryPool:(NSDictionary *)simplifiedMasternodeEntries quorumEntryPool:(NSDictionary *)quorumEntries; - -- (DSMasternodeList *)masternodeListWithSimplifiedMasternodeEntryPool:(NSDictionary *)simplifiedMasternodeEntries quorumEntryPool:(NSDictionary *)quorumEntries withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity; - -@end - -NS_ASSUME_NONNULL_END - -#import "DSMasternodeListEntity+CoreDataProperties.h" diff --git a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataClass.m deleted file mode 100644 index c9dcadd50..000000000 --- a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataClass.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// DSMasternodeListEntity+CoreDataClass.m -// DashSync -// -// Created by Sam Westrich on 5/23/19. -// -// - -#import "BigIntTypes.h" -#import "DSChainEntity+CoreDataClass.h" -#import "DSMasternodeList.h" -#import "DSMasternodeListEntity+CoreDataClass.h" -#import "DSMerkleBlockEntity+CoreDataClass.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataClass.h" -#import "NSData+Dash.h" -#import "NSManagedObject+Sugar.h" - -@implementation DSMasternodeListEntity - -- (DSMasternodeList *)masternodeListWithSimplifiedMasternodeEntryPool:(NSDictionary *)simplifiedMasternodeEntries quorumEntryPool:(NSDictionary *)quorumEntries { - return [self masternodeListWithSimplifiedMasternodeEntryPool:simplifiedMasternodeEntries quorumEntryPool:quorumEntries withBlockHeightLookup:nil]; -} - -- (DSMasternodeList *)masternodeListWithSimplifiedMasternodeEntryPool:(NSDictionary *)simplifiedMasternodeEntries quorumEntryPool:(NSDictionary *)quorumEntries withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - - /// TODO: it's a BS to collect this stuff into arrays and then to recollect it into dictionaries in the next step... - NSMutableArray *masternodeEntriesArray = [NSMutableArray array]; - - for (DSSimplifiedMasternodeEntryEntity *masternodeEntity in self.masternodes) { - DSSimplifiedMasternodeEntry *masternodeEntry = [simplifiedMasternodeEntries objectForKey:masternodeEntity.providerRegistrationTransactionHash]; - if (!masternodeEntry) { - masternodeEntry = [masternodeEntity simplifiedMasternodeEntryWithBlockHeightLookup:blockHeightLookup]; - } - [masternodeEntriesArray addObject:masternodeEntry]; - } - NSMutableArray *quorumEntriesArray = [NSMutableArray array]; - for (DSQuorumEntryEntity *quorumEntity in self.quorums) { - DSQuorumEntry *quorumEntry = [[quorumEntries objectForKey:@(quorumEntity.llmqType)] objectForKey:quorumEntity.quorumHashData]; - if (!quorumEntry) { - quorumEntry = quorumEntity.quorumEntry; - } - [quorumEntriesArray addObject:quorumEntry]; - } - return [DSMasternodeList masternodeListWithSimplifiedMasternodeEntries:masternodeEntriesArray quorumEntries:quorumEntriesArray atBlockHash:self.block.blockHash.UInt256 atBlockHeight:self.block.height withMasternodeMerkleRootHash:self.masternodeListMerkleRoot.UInt256 withQuorumMerkleRootHash:self.quorumListMerkleRoot.UInt256 onChain:self.block.chain.chain]; -} - -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity { - NSArray *masternodeLists = [self objectsInContext:chainEntity.managedObjectContext matching:@"(block.chain == %@)", chainEntity]; - for (DSMasternodeListEntity *masternodeList in masternodeLists) { - DSLog(@"MasternodeListEntity.deleteAllOnChainEntity: %@", masternodeList); - [chainEntity.managedObjectContext deleteObject:masternodeList]; - } -} - -@end diff --git a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataProperties.h deleted file mode 100644 index c0b56698a..000000000 --- a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataProperties.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// DSMasternodeListEntity+CoreDataProperties.h -// DashSync -// -// Created by Sam Westrich on 5/23/19. -// -// - -#import "DSMasternodeListEntity+CoreDataClass.h" - - -NS_ASSUME_NONNULL_BEGIN - -@interface DSMasternodeListEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest; - -@property (nullable, nonatomic, retain) DSMerkleBlockEntity *block; -@property (nullable, nonatomic, retain) NSSet *masternodes; -@property (nullable, nonatomic, retain) NSSet *quorums; -@property (nullable, nonatomic, retain) NSData *masternodeListMerkleRoot; -@property (nullable, nonatomic, retain) NSData *quorumListMerkleRoot; - -@end - -@interface DSMasternodeListEntity (CoreDataGeneratedAccessors) - -- (void)addMasternodesObject:(DSSimplifiedMasternodeEntryEntity *)value; -- (void)removeMasternodesObject:(DSSimplifiedMasternodeEntryEntity *)value; -- (void)addMasternodes:(NSSet *)values; -- (void)removeMasternodes:(NSSet *)values; - -- (void)addQuorumsObject:(DSQuorumEntryEntity *)value; -- (void)removeQuorumsObject:(DSQuorumEntryEntity *)value; -- (void)addQuorums:(NSSet *)values; -- (void)removeQuorums:(NSSet *)values; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataProperties.m deleted file mode 100644 index 5c631f48b..000000000 --- a/DashSync/shared/Models/Entities/DSMasternodeListEntity+CoreDataProperties.m +++ /dev/null @@ -1,23 +0,0 @@ -// -// DSMasternodeListEntity+CoreDataProperties.m -// DashSync -// -// Created by Sam Westrich on 5/23/19. -// -// - -#import "DSMasternodeListEntity+CoreDataProperties.h" - -@implementation DSMasternodeListEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest { - return [NSFetchRequest fetchRequestWithEntityName:@"DSMasternodeListEntity"]; -} - -@dynamic block; -@dynamic masternodes; -@dynamic quorums; -@dynamic masternodeListMerkleRoot; -@dynamic quorumListMerkleRoot; - -@end diff --git a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.h index f82e7854a..1291c7a3e 100644 --- a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.h @@ -26,7 +26,7 @@ #import #import -@class DSChainEntity, DSBlock, DSChain, DSMerkleBlock, DSMasternodeListEntity, DSQuorumEntryEntity, DSQuorumSnapshotEntity, DSChainLockEntity; +@class DSChainEntity, DSBlock, DSChain, DSMerkleBlock, DSChainLockEntity; NS_ASSUME_NONNULL_BEGIN @@ -40,13 +40,18 @@ NS_ASSUME_NONNULL_BEGIN + (DSMerkleBlockEntity *)blockWithHash:(UInt256)hash onChainEntity:(DSChainEntity *)chainEntity; + (void)deleteBlocksOnChainEntity:(DSChainEntity *)chainEntity; -+ (instancetype)merkleBlockEntityForBlockHash:(UInt256)blockHash inContext:(NSManagedObjectContext *)context; -+ (instancetype)merkleBlockEntityForBlockHashFromCheckpoint:(UInt256)blockHash chain:(DSChain *)chain inContext:(NSManagedObjectContext *)context; -+ (instancetype)createMerkleBlockEntityForBlockHash:(UInt256)blockHash ++ (instancetype)merkleBlockEntityForBlockHash:(NSData *)blockHash inContext:(NSManagedObjectContext *)context; ++ (instancetype)merkleBlockEntityForBlockHashFromCheckpoint:(UInt256)blockHash + chain:(DSChain *)chain + inContext:(NSManagedObjectContext *)context; ++ (instancetype)createMerkleBlockEntityForBlockHash:(NSData *)blockHash blockHeight:(uint32_t)blockHeight chainEntity:(DSChainEntity *)chainEntity inContext:(NSManagedObjectContext *)context; ++ (BOOL)hasBlocksWithHash:(UInt256)blockHash + inContext:(NSManagedObjectContext *)context; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.m index 80e367643..2432f262c 100644 --- a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataClass.m @@ -21,8 +21,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#import "DSChain+Checkpoint.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataClass.h" +#import "DSChainLock.h" #import "DSChainLockEntity+CoreDataClass.h" #import "DSCheckpoint.h" #import "DSMerkleBlock.h" @@ -147,9 +149,8 @@ + (void)deleteBlocksOnChainEntity:(DSChainEntity *)chainEntity { }]; } -+ (instancetype)merkleBlockEntityForBlockHash:(UInt256)blockHash inContext:(NSManagedObjectContext *)context { - DSMerkleBlockEntity *merkleBlockEntity = [DSMerkleBlockEntity anyObjectInContext:context matching:@"blockHash == %@", uint256_data(blockHash)]; - return merkleBlockEntity; ++ (instancetype)merkleBlockEntityForBlockHash:(NSData *)blockHash inContext:(NSManagedObjectContext *)context { + return [DSMerkleBlockEntity anyObjectInContext:context matching:@"blockHash == %@", blockHash]; } + (instancetype)merkleBlockEntityForBlockHashFromCheckpoint:(UInt256)blockHash chain:(DSChain *)chain inContext:(NSManagedObjectContext *)context { @@ -162,15 +163,24 @@ + (instancetype)merkleBlockEntityForBlockHashFromCheckpoint:(UInt256)blockHash c return nil; } -+ (instancetype)createMerkleBlockEntityForBlockHash:(UInt256)blockHash ++ (instancetype)createMerkleBlockEntityForBlockHash:(NSData *)blockHash blockHeight:(uint32_t)blockHeight chainEntity:(DSChainEntity *)chainEntity inContext:(NSManagedObjectContext *)context { DSMerkleBlockEntity *merkleBlockEntity = [DSMerkleBlockEntity managedObjectInBlockedContext:context]; - merkleBlockEntity.blockHash = uint256_data(blockHash); + merkleBlockEntity.blockHash = blockHash; merkleBlockEntity.height = blockHeight; merkleBlockEntity.chain = chainEntity; return merkleBlockEntity; } ++ (BOOL)hasBlocksWithHash:(UInt256)blockHash + inContext:(NSManagedObjectContext *)context { + __block BOOL hasBlock = NO; + [context performBlockAndWait:^{ + hasBlock = !![DSMerkleBlockEntity countObjectsInContext:context matching:@"blockHash == %@", uint256_data(blockHash)]; + }]; + return hasBlock; +} + @end diff --git a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.h index 8a871d43a..39a20455e 100644 --- a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.h @@ -29,18 +29,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, retain) NSData *chainWork; @property (nullable, nonatomic, retain) DSChainEntity *chain; @property (nullable, nonatomic, retain) DSChainLockEntity *chainLock; -@property (nullable, nonatomic, retain) DSMasternodeListEntity *masternodeList; -@property (nullable, nonatomic, retain) NSSet *usedByQuorums; -@property (nullable, nonatomic, retain) DSQuorumSnapshotEntity *quorumSnapshot; - -@end - -@interface DSMerkleBlockEntity (CoreDataGeneratedAccessors) - -- (void)addQuorumsObject:(DSQuorumEntryEntity *)value; -- (void)removeQuorumsObject:(DSQuorumEntryEntity *)value; -- (void)addQuorums:(NSSet *)values; -- (void)removeQuorums:(NSSet *)values; @end diff --git a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.m index 9f08510ac..b5a32fcf7 100644 --- a/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSMerkleBlockEntity+CoreDataProperties.m @@ -26,9 +26,6 @@ @implementation DSMerkleBlockEntity (CoreDataProperties) @dynamic totalTransactions; @dynamic version; @dynamic chain; -@dynamic masternodeList; -@dynamic usedByQuorums; -@dynamic quorumSnapshot; @dynamic chainLock; @dynamic chainWork; diff --git a/DashSync/shared/Models/Entities/DSPeerEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSPeerEntity+CoreDataClass.m index d9d23deff..f0a07ae26 100644 --- a/DashSync/shared/Models/Entities/DSPeerEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSPeerEntity+CoreDataClass.m @@ -23,6 +23,7 @@ // THE SOFTWARE. #import "DSChain.h" +#import "DSChain+Params.h" #import "DSChainEntity+CoreDataClass.h" #import "DSPeer.h" #import "DSPeerEntity+CoreDataClass.h" diff --git a/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataClass.h index 7f589b56c..25302d08d 100644 --- a/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataClass.h +++ b/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataClass.h @@ -9,7 +9,6 @@ #import "DSSpecialTransactionEntity+CoreDataClass.h" #import -@class DSQuorumEntryEntity; NS_ASSUME_NONNULL_BEGIN diff --git a/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.h index b5b47db90..510b4128a 100644 --- a/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.h +++ b/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.h @@ -16,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN + (NSFetchRequest *)fetchRequest; @property (nullable, nonatomic, copy) NSNumber *quorumCommitmentHeight; -@property (nullable, nonatomic, retain) DSQuorumEntryEntity *entry; @end diff --git a/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.m index ee6aa5a98..45d27e4be 100644 --- a/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.m +++ b/DashSync/shared/Models/Entities/DSQuorumCommitmentTransactionEntity+CoreDataProperties.m @@ -15,6 +15,5 @@ @implementation DSQuorumCommitmentTransactionEntity (CoreDataProperties) } @dynamic quorumCommitmentHeight; -@dynamic entry; @end diff --git a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataClass.h deleted file mode 100644 index fb3c6084e..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataClass.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// DSQuorumEntryEntity+CoreDataClass.h -// DashSync -// -// Created by Sam Westrich on 4/25/19. -// -// - -#import "BigIntTypes.h" -#import -#import - -@class DSChainEntity, DSInstantSendLockEntity, DSMasternodeListEntity, DSMerkleBlockEntity, DSQuorumCommitmentTransactionEntity, DSChain, DSQuorumEntry, DSChainLockEntity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQuorumEntryEntity : NSManagedObject - -@property (nonatomic, assign) UInt256 commitmentHash; -@property (nonatomic, assign) UInt256 quorumHash; -@property (nonatomic, assign) UInt384 quorumPublicKey; -@property (nonatomic, assign) UInt768 quorumThresholdSignature; -@property (nonatomic, assign) UInt256 quorumVerificationVectorHash; -@property (nonatomic, assign) UInt768 allCommitmentAggregatedSignature; -@property (nonatomic, readonly) DSQuorumEntry *quorumEntry; - -+ (instancetype _Nullable)quorumEntryEntityFromPotentialQuorumEntry:(DSQuorumEntry *)potentialQuorumEntry inContext:(NSManagedObjectContext *)context; -+ (instancetype _Nullable)quorumEntryEntityFromPotentialQuorumEntryForMerging:(DSQuorumEntry *)potentialQuorumEntry inContext:(NSManagedObjectContext *)context; - -- (void)setAttributesFromPotentialQuorumEntry:(DSQuorumEntry *)potentialQuorumEntry onBlock:(DSMerkleBlockEntity *_Nullable)block; - -+ (void)deleteHavingQuorumHashes:(NSArray *)quorumHashes onChainEntity:(DSChainEntity *)chainEntity; -+ (DSQuorumEntryEntity *_Nullable)quorumEntryForHash:(NSData *)quorumEntryHash onChainEntity:(DSChainEntity *)chainEntity; - -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity; - -- (UInt256)orderingHashForRequestID:(UInt256)requestID; - -@end - -NS_ASSUME_NONNULL_END - -#import "DSQuorumEntryEntity+CoreDataProperties.h" diff --git a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataClass.m deleted file mode 100644 index 2d3e88129..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataClass.m +++ /dev/null @@ -1,172 +0,0 @@ -// -// DSQuorumEntryEntity+CoreDataClass.m -// DashSync -// -// Created by Sam Westrich on 4/25/19. -// -// - -#import "DSChain+Protected.h" -#import "DSChainEntity+CoreDataClass.h" -#import "DSMerkleBlockEntity+CoreDataClass.h" -#import "DSQuorumEntry.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "NSData+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" - -@implementation DSQuorumEntryEntity - - -+ (instancetype)quorumEntryEntityFromPotentialQuorumEntry:(DSQuorumEntry *)potentialQuorumEntry inContext:(NSManagedObjectContext *)context { - DSMerkleBlockEntity *block = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:potentialQuorumEntry.quorumHash inContext:context]; - DSQuorumEntryEntity *quorumEntryEntity = nil; - if (block) { - quorumEntryEntity = [[block.usedByQuorums filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"quorumHashData == %@ && llmqType == %@ ", uint256_data(potentialQuorumEntry.quorumHash), @((int16_t)potentialQuorumEntry.llmqType)]] anyObject]; - } else { - quorumEntryEntity = [DSQuorumEntryEntity anyObjectInContext:context matching:@"quorumHashData == %@ && llmqType == %@ ", uint256_data(potentialQuorumEntry.quorumHash), @((int16_t)potentialQuorumEntry.llmqType)]; - } - if (!quorumEntryEntity) { - if (potentialQuorumEntry.saved) { //it was deleted in the meantime, and should be ignored - return nil; - } else { - quorumEntryEntity = [DSQuorumEntryEntity managedObjectInBlockedContext:context]; - [quorumEntryEntity setAttributesFromPotentialQuorumEntry:potentialQuorumEntry onBlock:block]; - } - } else { - [quorumEntryEntity updateAttributesFromPotentialQuorumEntry:potentialQuorumEntry onBlock:block]; - } - return quorumEntryEntity; -} - -+ (instancetype)quorumEntryEntityFromPotentialQuorumEntryForMerging:(DSQuorumEntry *)potentialQuorumEntry inContext:(NSManagedObjectContext *)context { - DSMerkleBlockEntity *block = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:potentialQuorumEntry.quorumHash inContext:context]; - DSQuorumEntryEntity *quorumEntryEntity = nil; - if (block) { - quorumEntryEntity = [[block.usedByQuorums filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"quorumHashData == %@ && llmqType == %@ ", uint256_data(potentialQuorumEntry.quorumHash), @((int16_t)potentialQuorumEntry.llmqType)]] anyObject]; - } else { - quorumEntryEntity = [DSQuorumEntryEntity anyObjectInContext:context matching:@"quorumHashData == %@ && llmqType == %@ ", uint256_data(potentialQuorumEntry.quorumHash), @((int16_t)potentialQuorumEntry.llmqType)]; - } - if (!quorumEntryEntity) { - if (potentialQuorumEntry.saved && potentialQuorumEntry.verified) { - return nil; - } else { - quorumEntryEntity = [DSQuorumEntryEntity managedObjectInBlockedContext:context]; - [quorumEntryEntity setAttributesFromPotentialQuorumEntry:potentialQuorumEntry onBlock:block]; - } - } else { - [quorumEntryEntity updateAttributesFromPotentialQuorumEntry:potentialQuorumEntry onBlock:block]; - } - return quorumEntryEntity; -} - -- (void)setAttributesFromPotentialQuorumEntry:(DSQuorumEntry *)potentialQuorumEntry onBlock:(DSMerkleBlockEntity *)block { - self.verified = (block != nil) && potentialQuorumEntry.verified; - self.block = block; - self.quorumHash = potentialQuorumEntry.quorumHash; - self.quorumPublicKey = potentialQuorumEntry.quorumPublicKey; - self.quorumThresholdSignature = potentialQuorumEntry.quorumThresholdSignature; - self.quorumVerificationVectorHash = potentialQuorumEntry.quorumVerificationVectorHash; - self.signersCount = potentialQuorumEntry.signersCount; - self.signersBitset = potentialQuorumEntry.signersBitset; - self.validMembersCount = potentialQuorumEntry.validMembersCount; - self.validMembersBitset = potentialQuorumEntry.validMembersBitset; - self.llmqType = (int16_t)potentialQuorumEntry.llmqType; - self.version = potentialQuorumEntry.version; - self.allCommitmentAggregatedSignature = potentialQuorumEntry.allCommitmentAggregatedSignature; - self.commitmentHash = potentialQuorumEntry.quorumEntryHash; - self.quorumIndex = potentialQuorumEntry.quorumIndex; - self.chain = [potentialQuorumEntry.chain chainEntityInContext:self.managedObjectContext]; - potentialQuorumEntry.saved = TRUE; -} - -- (void)updateAttributesFromPotentialQuorumEntry:(DSQuorumEntry *)potentialQuorumEntry onBlock:(DSMerkleBlockEntity *)block { - if (!self.verified) { - self.verified = (block != nil) && potentialQuorumEntry.verified; - } - if (!self.block) { - self.block = block; - } -} - -- (UInt256)quorumHash { - return self.quorumHashData.UInt256; -} - -- (void)setQuorumHash:(UInt256)quorumHash { - self.quorumHashData = [NSData dataWithUInt256:quorumHash]; -} - -- (UInt384)quorumPublicKey { - return self.quorumPublicKeyData.UInt384; -} - -- (void)setQuorumPublicKey:(UInt384)quorumPublicKey { - self.quorumPublicKeyData = [NSData dataWithUInt384:quorumPublicKey]; -} - -- (UInt768)quorumThresholdSignature { - return self.quorumThresholdSignatureData.UInt768; -} - -- (void)setQuorumThresholdSignature:(UInt768)quorumThresholdSignature { - self.quorumThresholdSignatureData = [NSData dataWithUInt768:quorumThresholdSignature]; -} - -- (UInt768)allCommitmentAggregatedSignature { - return self.allCommitmentAggregatedSignatureData.UInt768; -} - -- (void)setAllCommitmentAggregatedSignature:(UInt768)allCommitmentAggregatedSignature { - self.allCommitmentAggregatedSignatureData = [NSData dataWithUInt768:allCommitmentAggregatedSignature]; -} - -- (UInt256)quorumVerificationVectorHash { - return self.quorumVerificationVectorHashData.UInt256; -} - -- (void)setQuorumVerificationVectorHash:(UInt256)quorumVerificationVectorHash { - self.quorumVerificationVectorHashData = [NSData dataWithUInt256:quorumVerificationVectorHash]; -} - -- (UInt256)commitmentHash { - return self.commitmentHashData.UInt256; -} - -- (void)setCommitmentHash:(UInt256)commitmentHash { - self.commitmentHashData = [NSData dataWithUInt256:commitmentHash]; -} - -+ (void)deleteHavingQuorumHashes:(NSArray *)quorumHashes onChainEntity:(DSChainEntity *)chainEntity { - NSArray *hashesToDelete = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@) && (quorumHashData IN %@)", chainEntity, quorumHashes]; - for (DSQuorumEntryEntity *quorumEntryEntity in hashesToDelete) { - [chainEntity.managedObjectContext deleteObject:quorumEntryEntity]; - } -} - -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity { - NSArray *hashesToDelete = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@)", chainEntity]; - for (DSQuorumEntryEntity *quorumEntryEntity in hashesToDelete) { - [chainEntity.managedObjectContext deleteObject:quorumEntryEntity]; - } -} - -+ (DSQuorumEntryEntity *)quorumEntryForHash:(NSData *)quorumEntryHash onChainEntity:(DSChainEntity *)chainEntity { - /// Seems to be unused or quorumEntryHash must be changed into commitmentHash ? - NSArray *objects = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@) && (commitmentHash == %@)", chainEntity, quorumEntryHash]; - return [objects firstObject]; -} - -- (UInt256)orderingHashForRequestID:(UInt256)requestID { - NSMutableData *data = [NSMutableData data]; - [data appendVarInt:1]; - [data appendUInt256:self.quorumHash]; - [data appendUInt256:requestID]; - return [data SHA256_2]; -} - -- (DSQuorumEntry *)quorumEntry { - return [[DSQuorumEntry alloc] initWithVersion:self.version type:self.llmqType quorumHash:self.quorumHash quorumIndex:self.quorumIndex signersCount:self.signersCount signersBitset:self.signersBitset validMembersCount:self.validMembersCount validMembersBitset:self.validMembersBitset quorumPublicKey:self.quorumPublicKey quorumVerificationVectorHash:self.quorumVerificationVectorHash quorumThresholdSignature:self.quorumThresholdSignature allCommitmentAggregatedSignature:self.allCommitmentAggregatedSignature quorumEntryHash:self.commitmentHash onChain:self.chain.chain]; -} - -@end diff --git a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataProperties.h deleted file mode 100644 index 1d1c08ae0..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataProperties.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// DSQuorumEntryEntity+CoreDataProperties.h -// Dash-PLCrashReporter -// -// Created by Sam Westrich on 6/14/19. -// -// - -#import "DSQuorumEntry.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQuorumEntryEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest; - -@property (nonatomic, assign) int16_t llmqType; -@property (nonatomic, assign) int16_t version; -@property (nonatomic, assign) int32_t validMembersCount; -@property (nonatomic, assign) int32_t signersCount; -@property (nonatomic, assign) int32_t quorumIndex; -@property (nonatomic, assign) BOOL verified; -@property (nullable, nonatomic, retain) NSData *allCommitmentAggregatedSignatureData; -@property (nullable, nonatomic, retain) NSData *commitmentHashData; -@property (nullable, nonatomic, retain) NSData *quorumHashData; -@property (nullable, nonatomic, retain) NSData *quorumPublicKeyData; -@property (nullable, nonatomic, retain) NSData *quorumThresholdSignatureData; -@property (nullable, nonatomic, retain) NSData *quorumVerificationVectorHashData; -@property (nullable, nonatomic, retain) NSData *signersBitset; -@property (nullable, nonatomic, retain) NSData *validMembersBitset; -@property (nullable, nonatomic, retain) DSMerkleBlockEntity *block; -@property (nullable, nonatomic, retain) DSChainEntity *chain; -@property (nullable, nonatomic, retain) DSQuorumCommitmentTransactionEntity *commitmentTransaction; -@property (nullable, nonatomic, retain) NSSet *instantSendLocks; -@property (nullable, nonatomic, retain) NSSet *chainLocks; -@property (nullable, nonatomic, retain) NSSet *referencedByMasternodeLists; - -@end - -@interface DSQuorumEntryEntity (CoreDataGeneratedAccessors) - -- (void)addInstantSendLocksObject:(DSInstantSendLockEntity *)value; -- (void)removeInstantSendLocksObject:(DSInstantSendLockEntity *)value; -- (void)addInstantSendLocks:(NSSet *)values; -- (void)removeInstantSendLocks:(NSSet *)values; - -- (void)addChainLocksObject:(DSChainLockEntity *)value; -- (void)removeChainLocksObject:(DSChainLockEntity *)value; -- (void)addChainLocks:(NSSet *)values; -- (void)removeChainLocks:(NSSet *)values; - -- (void)addUsedByMasternodeListsObject:(DSMasternodeListEntity *)value; -- (void)removeUsedByMasternodeListsObject:(DSMasternodeListEntity *)value; -- (void)addUsedByMasternodeLists:(NSSet *)values; -- (void)removeUsedByMasternodeLists:(NSSet *)values; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataProperties.m deleted file mode 100644 index c3895a734..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumEntryEntity+CoreDataProperties.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// DSQuorumEntryEntity+CoreDataProperties.m -// Dash-PLCrashReporter -// -// Created by Sam Westrich on 6/14/19. -// -// - -#import "DSQuorumEntryEntity+CoreDataProperties.h" - -@implementation DSQuorumEntryEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest { - return [NSFetchRequest fetchRequestWithEntityName:@"DSQuorumEntryEntity"]; -} - -@dynamic allCommitmentAggregatedSignatureData; -@dynamic commitmentHashData; -@dynamic llmqType; -@dynamic quorumHashData; -@dynamic quorumPublicKeyData; -@dynamic quorumThresholdSignatureData; -@dynamic quorumVerificationVectorHashData; -@dynamic signersBitset; -@dynamic signersCount; -@dynamic validMembersBitset; -@dynamic validMembersCount; -@dynamic verified; -@dynamic version; -@dynamic block; -@dynamic chain; -@dynamic commitmentTransaction; -@dynamic instantSendLocks; -@dynamic chainLocks; -@dynamic referencedByMasternodeLists; -@dynamic quorumIndex; - -@end diff --git a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataClass.h deleted file mode 100644 index 48b3cf581..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataClass.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 -#import "DSChainEntity+CoreDataProperties.h" -#import "DSMerkleBlockEntity+CoreDataProperties.h" -#import "DSQuorumSnapshot.h" -#import "dash_shared_core.h" - -@class DSChainEntity, DSMerkleBlockEntity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQuorumSnapshotEntity : NSManagedObject - -+ (instancetype)quorumSnapshotEntityFromPotentialQuorumSnapshot:(DSQuorumSnapshot *)potentialQuorumSnapshot inContext:(NSManagedObjectContext *)context; -+ (instancetype)quorumSnapshotEntityForMerkleBlockEntity:(DSMerkleBlockEntity *)blockEntity quorumSnapshot:(DSQuorumSnapshot *)quorumSnapshot inContext:(NSManagedObjectContext *)context; -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity; - -- (void)updateAttributesFromPotentialQuorumSnapshot:(DSQuorumSnapshot *)quorumSnapshot onBlock:(DSMerkleBlockEntity *) block; - -@end - -NS_ASSUME_NONNULL_END - -#import "DSQuorumSnapshotEntity+CoreDataProperties.h" diff --git a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataClass.m deleted file mode 100644 index 69b06285b..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataClass.m +++ /dev/null @@ -1,74 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSQuorumSnapshotEntity+CoreDataClass.h" -#import "NSManagedObject+Sugar.h" -#import "NSData+Dash.h" -@implementation DSQuorumSnapshotEntity - - -+ (instancetype)quorumSnapshotEntityFromPotentialQuorumSnapshot:(DSQuorumSnapshot *)potentialQuorumSnapshot inContext:(NSManagedObjectContext *)context { - UInt256 quorumSnapshotBlockHash = potentialQuorumSnapshot.blockHash; - DSMerkleBlockEntity *block = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:quorumSnapshotBlockHash inContext:context]; - DSQuorumSnapshotEntity *quorumSnapshotEntity = nil; - if (block) { - quorumSnapshotEntity = block.quorumSnapshot; - } - if (!quorumSnapshotEntity) { - quorumSnapshotEntity = [DSQuorumSnapshotEntity managedObjectInBlockedContext:context]; - [quorumSnapshotEntity updateAttributesFromPotentialQuorumSnapshot:potentialQuorumSnapshot onBlock:block]; - } else { - [quorumSnapshotEntity updateAttributesFromPotentialQuorumSnapshot:potentialQuorumSnapshot onBlock:block]; - } - - return quorumSnapshotEntity; -} - -+ (instancetype)quorumSnapshotEntityForMerkleBlockEntity:(DSMerkleBlockEntity *)blockEntity quorumSnapshot:(DSQuorumSnapshot *)quorumSnapshot inContext:(NSManagedObjectContext *)context { - NSArray *objects = [DSQuorumSnapshotEntity objectsForPredicate:[NSPredicate predicateWithFormat:@"block = %@", blockEntity] inContext:context]; - DSQuorumSnapshotEntity *entity = NULL; - if (objects.count) { - NSAssert(objects.count == 1, @"There should only ever be 1 quorum snapshot for either mainnet, testnet, or a devnet Identifier"); - entity = objects[0]; - - } else { - entity = [self managedObjectInBlockedContext:context]; - } - [entity updateAttributesFromPotentialQuorumSnapshot:quorumSnapshot onBlock:blockEntity]; - return entity; -} - -- (void)updateAttributesFromPotentialQuorumSnapshot:(DSQuorumSnapshot *)quorumSnapshot onBlock:(DSMerkleBlockEntity *) block { - self.block = block; - NSError *error = nil; - NSData *archivedSkipList = [NSKeyedArchiver archivedDataWithRootObject:quorumSnapshot.skipList requiringSecureCoding:YES error:&error]; - NSAssert(error == nil, @"There should not be an error when decrypting skipList"); - if (!error) { - self.skipList = archivedSkipList; - } - self.memberList = quorumSnapshot.memberList; - self.skipListMode = quorumSnapshot.skipListMode; -} - -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity { - NSArray *quorumSnapshots = [self objectsInContext:chainEntity.managedObjectContext matching:@"(block.chain == %@)", chainEntity]; - for (DSQuorumSnapshotEntity *quorumSnapshot in quorumSnapshots) { - [chainEntity.managedObjectContext deleteObject:quorumSnapshot]; - } -} - -@end diff --git a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataProperties.h deleted file mode 100644 index e4f381eb8..000000000 --- a/DashSync/shared/Models/Entities/DSQuorumSnapshotEntity+CoreDataProperties.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSQuorumSnapshotEntity+CoreDataClass.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQuorumSnapshotEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest; - -@property (nullable, nonatomic, retain) DSMerkleBlockEntity *block; -@property (nullable, nonatomic, retain) NSData *memberList; -@property (nullable, retain) NSData *skipList; -@property (nonatomic) int32_t skipListMode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataClass.h b/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataClass.h deleted file mode 100644 index c1c080717..000000000 --- a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataClass.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// DSSimplifiedMasternodeEntryEntity+CoreDataClass.h -// DashSync -// -// Created by Sam Westrich on 7/19/18. -// -// - -#import "BigIntTypes.h" -#import -#import - -@class DSAddressEntity, DSChainEntity, DSGovernanceVoteEntity, DSLocalMasternodeEntity, DSMasternodeListEntity, DSTransactionLockVoteEntity, DSSimplifiedMasternodeEntry, DSMasternodeList; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSSimplifiedMasternodeEntryEntity : NSManagedObject - -- (void)updateAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *_Nonnull)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight; -- (void)updateAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *_Nonnull)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight knownOperatorAddresses:(NSDictionary *_Nullable)knownOperatorAddresses knownVotingAddresses:(NSDictionary *_Nullable)knownVotingAddresses localMasternodes:(NSDictionary *_Nullable)localMasternodes; -- (void)setAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight knownOperatorAddresses:(NSDictionary *_Nullable)knownOperatorAddresses knownVotingAddresses:(NSDictionary *_Nullable)knownVotingAddresses localMasternodes:(NSDictionary *_Nullable)localMasternodes onChainEntity:(DSChainEntity *_Nullable)chainEntity; -- (void)setAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *_Nonnull)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight onChainEntity:(DSChainEntity *_Nullable)chainEntity; -+ (void)deleteHavingProviderTransactionHashes:(NSArray *)providerTransactionHashes onChainEntity:(DSChainEntity *_Nonnull)chainEntity; -+ (DSSimplifiedMasternodeEntryEntity *_Nullable)simplifiedMasternodeEntryForHash:(NSData *)simplifiedMasternodeEntryHash onChainEntity:(DSChainEntity *_Nonnull)chainEntity; -+ (DSSimplifiedMasternodeEntryEntity *)simplifiedMasternodeEntryForProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash onChainEntity:(DSChainEntity *_Nonnull)chainEntity; - -- (DSSimplifiedMasternodeEntry *_Nullable)simplifiedMasternodeEntry; -- (DSSimplifiedMasternodeEntry *_Nullable)simplifiedMasternodeEntryWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity; - -@end - -NS_ASSUME_NONNULL_END - -#import "DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h" diff --git a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataClass.m deleted file mode 100644 index 2ffc58237..000000000 --- a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataClass.m +++ /dev/null @@ -1,303 +0,0 @@ -// -// DSSimplifiedMasternodeEntryEntity+CoreDataClass.m -// DashSync -// -// Created by Sam Westrich on 7/19/18. -// -// - -#import "DSAddressEntity+CoreDataClass.h" -#import "DSChain+Protected.h" -#import "DSChainEntity+CoreDataProperties.h" -#import "NSDictionary+Dash.h" -#import "DSKeyManager.h" -#import "DSLocalMasternodeEntity+CoreDataClass.h" -#import "DSMerkleBlock.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataClass.h" -#import "NSData+Dash.h" -#import "NSManagedObject+Sugar.h" -#include - -#define LOG_SMNE_CHANGES 0 - -#if LOG_SMNE_CHANGES -#define DSDSMNELog(s, ...) DSLog(s, ##__VA_ARGS__) -#else -#define DSDSMNELog(s, ...) -#endif - -//DSLog(s, ##__VA_ARGS__) - -@implementation DSSimplifiedMasternodeEntryEntity - -- (void)updateAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight { - [self updateAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight knownOperatorAddresses:nil knownVotingAddresses:nil localMasternodes:nil]; -} - -- (void)updateAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight knownOperatorAddresses:(NSDictionary *)knownOperatorAddresses knownVotingAddresses:(NSDictionary *)knownVotingAddresses localMasternodes:(NSDictionary *)localMasternodes { - if (self.updateHeight < blockHeight) { - //NSAssert(simplifiedMasternodeEntry.updateHeight == blockHeight, @"the block height should be the same as the entry update height"); - self.updateHeight = blockHeight; - //we should only update if the data received is the most recent - if (!uint128_eq(self.ipv6Address.UInt128, simplifiedMasternodeEntry.address)) { - self.ipv6Address = uint128_data(simplifiedMasternodeEntry.address); - uint32_t address32 = CFSwapInt32BigToHost(simplifiedMasternodeEntry.address.u32[3]); - if (self.address != address32) { - self.address = address32; -#if LOG_SMNE_CHANGES - char s[INET6_ADDRSTRLEN]; -#endif - DSDSMNELog(@"changing address to %@", @(inet_ntop(AF_INET, &address32, s, sizeof(s)))); - } - } - NSData *confirmedHashData = uint256_data(simplifiedMasternodeEntry.confirmedHash); - if (![self.confirmedHash isEqualToData:confirmedHashData]) { - NSAssert(self.confirmedHash == nil || uint256_is_zero(self.confirmedHash.UInt256), @"If this changes the previous should be empty"); - //this should only happen once at confirmation - self.confirmedHash = confirmedHashData; - self.knownConfirmedAtHeight = blockHeight; - DSDSMNELog(@"changing confirmedHashData to %@", confirmedHashData.hexString); - } - if (self.port != simplifiedMasternodeEntry.port) { - self.port = simplifiedMasternodeEntry.port; - DSDSMNELog(@"changing port to %u", simplifiedMasternodeEntry.port); - } - NSData *keyIDVotingData = [NSData dataWithUInt160:simplifiedMasternodeEntry.keyIDVoting]; - if (![self.keyIDVoting isEqualToData:keyIDVotingData]) { - self.keyIDVoting = keyIDVotingData; - DSDSMNELog(@"changing keyIDVotingData to %@", keyIDVotingData.hexString); - } - NSData *operatorPublicKeyData = [NSData dataWithUInt384:simplifiedMasternodeEntry.operatorPublicKey]; - if (![self.operatorBLSPublicKey isEqualToData:operatorPublicKeyData]) { - self.operatorBLSPublicKey = operatorPublicKeyData; - self.operatorPublicKeyVersion = simplifiedMasternodeEntry.operatorPublicKeyVersion; - DSDSMNELog(@"changing operatorBLSPublicKey to %@", operatorPublicKeyData.hexString); - } - - if (self.type != simplifiedMasternodeEntry.type) { - self.type = simplifiedMasternodeEntry.type; - DSDSMNELog(@"changing type to %d", simplifiedMasternodeEntry.type); - } - NSData *platformNodeIDData = uint160_data(simplifiedMasternodeEntry.platformNodeID); - if (![self.platformNodeID isEqualToData:platformNodeIDData]) { - self.platformNodeID = platformNodeIDData; - DSDSMNELog(@"changing platformNodeID to %d", platformNodeIDData.hexString); - } - if (self.platformHTTPPort != simplifiedMasternodeEntry.platformHTTPPort) { - self.platformHTTPPort = simplifiedMasternodeEntry.platformHTTPPort; - DSDSMNELog(@"changing platformHTTPPort to %d", simplifiedMasternodeEntry.platformHTTPPort); - } - - if (self.isValid != simplifiedMasternodeEntry.isValid) { - self.isValid = simplifiedMasternodeEntry.isValid; - DSDSMNELog(@"changing isValid to %@", simplifiedMasternodeEntry.isValid ? @"TRUE" : @"FALSE"); - } - self.simplifiedMasternodeEntryHash = [NSData dataWithUInt256:simplifiedMasternodeEntry.simplifiedMasternodeEntryHash]; - [self mergePreviousFieldsUsingSimplifiedMasternodeEntrysPreviousFields:simplifiedMasternodeEntry atBlockHeight:blockHeight]; - DSLocalMasternodeEntity *localMasternode = localMasternodes - ? [localMasternodes objectForKey:uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash)] - : [DSLocalMasternodeEntity anyObjectInContext:self.managedObjectContext matching:@"providerRegistrationTransaction.transactionHash.txHash == %@", uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash)]; - self.localMasternode = localMasternode; - NSString *operatorAddress = [DSKeyManager addressWithPublicKeyData:self.operatorBLSPublicKey forChain:simplifiedMasternodeEntry.chain]; - NSString *votingAddress = [DSKeyManager addressFromHash160:self.keyIDVoting.UInt160 forChain:simplifiedMasternodeEntry.chain]; - DSAddressEntity *operatorAddressEntity = knownOperatorAddresses - ? [knownOperatorAddresses objectForKey:operatorAddress] - : [DSAddressEntity findAddressMatching:operatorAddress onChain:simplifiedMasternodeEntry.chain inContext:self.managedObjectContext]; - if (operatorAddressEntity) { - [self addAddressesObject:operatorAddressEntity]; - } - DSAddressEntity *votingAddressEntity = knownVotingAddresses - ? [knownVotingAddresses objectForKey:operatorAddress] - : [DSAddressEntity findAddressMatching:votingAddress onChain:simplifiedMasternodeEntry.chain inContext:self.managedObjectContext]; - if (votingAddressEntity) { - [self addAddressesObject:votingAddressEntity]; - } - } else if (blockHeight < self.updateHeight) { - [self mergePreviousFieldsUsingSimplifiedMasternodeEntrysPreviousFields:simplifiedMasternodeEntry atBlockHeight:blockHeight]; - } -} - -- (void)mergePreviousFieldsUsingSimplifiedMasternodeEntrysPreviousFields:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight { - //we should not update current values but we should merge some fields - //currentPrevious means the current set of previous values - //oldPrevious means the old set of previous values - - //SimplifiedMasternodeEntryHashes - NSDictionary *oldPreviousSimplifiedMasternodeEntryHashesDictionary = [self blockHashDictionaryFromBlockDictionary:simplifiedMasternodeEntry.previousSimplifiedMasternodeEntryHashes]; - if (oldPreviousSimplifiedMasternodeEntryHashesDictionary && oldPreviousSimplifiedMasternodeEntryHashesDictionary.count) { - self.previousSimplifiedMasternodeEntryHashes = [NSDictionary mergeDictionary:self.previousSimplifiedMasternodeEntryHashes withDictionary:oldPreviousSimplifiedMasternodeEntryHashesDictionary]; - } - - //OperatorBLSPublicKeys - NSDictionary *oldPreviousOperatorBLSPublicKeysDictionary = [self blockHashDictionaryFromBlockDictionary:simplifiedMasternodeEntry.previousOperatorPublicKeys]; - if (oldPreviousOperatorBLSPublicKeysDictionary && oldPreviousOperatorBLSPublicKeysDictionary.count) { - self.previousOperatorBLSPublicKeys = [NSDictionary mergeDictionary:self.previousOperatorBLSPublicKeys withDictionary:oldPreviousOperatorBLSPublicKeysDictionary]; - } - - //MasternodeValidity - NSDictionary *oldPreviousValidityDictionary = [self blockHashDictionaryFromBlockDictionary:simplifiedMasternodeEntry.previousValidity]; - if (oldPreviousValidityDictionary && oldPreviousValidityDictionary.count) { - self.previousValidity = [NSDictionary mergeDictionary:self.previousValidity withDictionary:oldPreviousValidityDictionary]; - } - - if (uint256_is_not_zero(self.confirmedHash.UInt256) && uint256_is_not_zero(simplifiedMasternodeEntry.confirmedHash) && (self.knownConfirmedAtHeight > blockHeight)) { - //we now know it was confirmed earlier so update to earlier - self.knownConfirmedAtHeight = blockHeight; - } -} - -- (void)setAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight onChainEntity:(DSChainEntity *)chainEntity { - [self setAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:blockHeight knownOperatorAddresses:nil knownVotingAddresses:nil localMasternodes:nil onChainEntity:chainEntity]; -} - -- (void)setAttributesFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry atBlockHeight:(uint32_t)blockHeight knownOperatorAddresses:(NSDictionary *)knownOperatorAddresses knownVotingAddresses:(NSDictionary *)knownVotingAddresses localMasternodes:(NSDictionary *)localMasternodes onChainEntity:(DSChainEntity *)chainEntity { - NSParameterAssert(simplifiedMasternodeEntry); - self.providerRegistrationTransactionHash = [NSData dataWithUInt256:simplifiedMasternodeEntry.providerRegistrationTransactionHash]; - self.confirmedHash = [NSData dataWithUInt256:simplifiedMasternodeEntry.confirmedHash]; - if (uint256_is_not_zero(simplifiedMasternodeEntry.confirmedHash)) { - self.knownConfirmedAtHeight = blockHeight; - } - self.ipv6Address = uint128_data(simplifiedMasternodeEntry.address); - self.address = CFSwapInt32BigToHost(simplifiedMasternodeEntry.address.u32[3]); - self.port = simplifiedMasternodeEntry.port; - self.keyIDVoting = [NSData dataWithUInt160:simplifiedMasternodeEntry.keyIDVoting]; - self.operatorBLSPublicKey = [NSData dataWithUInt384:simplifiedMasternodeEntry.operatorPublicKey]; - self.operatorPublicKeyVersion = simplifiedMasternodeEntry.operatorPublicKeyVersion; - self.type = simplifiedMasternodeEntry.type; - self.platformNodeID = [NSData dataWithUInt160:simplifiedMasternodeEntry.platformNodeID]; - self.platformHTTPPort = simplifiedMasternodeEntry.platformHTTPPort; - self.isValid = simplifiedMasternodeEntry.isValid; - self.simplifiedMasternodeEntryHash = [NSData dataWithUInt256:simplifiedMasternodeEntry.simplifiedMasternodeEntryHash]; - self.updateHeight = blockHeight; - - if (simplifiedMasternodeEntry.updateHeight != blockHeight) { - DSLog(@"• setAttributesFromSimplifiedMasternodeEntry: list.height %u != entry.height %u", blockHeight, simplifiedMasternodeEntry.updateHeight); - } - // TODO: make sure we're doing -// NSAssert(simplifiedMasternodeEntry.updateHeight == blockHeight, ([NSString stringWithFormat:@"the block height (%i) for %@ should be the same as the entry update height (%i)", blockHeight, uint256_hex(simplifiedMasternodeEntry.providerRegistrationTransactionHash), simplifiedMasternodeEntry.updateHeight])); - if (!chainEntity) { - self.chain = [simplifiedMasternodeEntry.chain chainEntityInContext:self.managedObjectContext]; - } else { - self.chain = chainEntity; - } - DSLocalMasternodeEntity *localMasternode = localMasternodes - ? [localMasternodes objectForKey:uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash)] - : [DSLocalMasternodeEntity anyObjectInContext:chainEntity.managedObjectContext matching:@"providerRegistrationTransaction.transactionHash.txHash == %@", uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash)]; - self.localMasternode = localMasternode; - NSString *operatorAddress = [DSKeyManager addressWithPublicKeyData:self.operatorBLSPublicKey forChain:simplifiedMasternodeEntry.chain]; - NSString *votingAddress = [DSKeyManager addressFromHash160:self.keyIDVoting.UInt160 forChain:simplifiedMasternodeEntry.chain]; - // TODO: check do we have to do the same for platform node addresses - DSAddressEntity *operatorAddressEntity = knownOperatorAddresses - ? [knownOperatorAddresses objectForKey:operatorAddress] - : [DSAddressEntity findAddressMatching:operatorAddress onChain:simplifiedMasternodeEntry.chain inContext:self.managedObjectContext]; - if (operatorAddressEntity) { - [self addAddressesObject:operatorAddressEntity]; - } - DSAddressEntity *votingAddressEntity = knownVotingAddresses - ? [knownVotingAddresses objectForKey:votingAddress] - : [DSAddressEntity findAddressMatching:votingAddress onChain:simplifiedMasternodeEntry.chain inContext:self.managedObjectContext]; - if (votingAddressEntity) { - [self addAddressesObject:votingAddressEntity]; - } -} - -+ (void)deleteHavingProviderTransactionHashes:(NSArray *)providerTransactionHashes onChainEntity:(DSChainEntity *)chainEntity { - NSArray *hashesToDelete = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@) && (providerRegistrationTransactionHash IN %@)", chainEntity, providerTransactionHashes]; - for (DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity in hashesToDelete) { - DSLog(@"deleteHavingProviderTransactionHashes: %@", simplifiedMasternodeEntryEntity.providerRegistrationTransactionHash.hexString); - [chainEntity.managedObjectContext deleteObject:simplifiedMasternodeEntryEntity]; - } -} - -+ (void)deleteAllOnChainEntity:(DSChainEntity *)chainEntity { - NSArray *hashesToDelete = [self objectsInContext:chainEntity.managedObjectContext matching:@"(chain == %@)", chainEntity]; - for (DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity in hashesToDelete) { - [chainEntity.managedObjectContext deleteObject:simplifiedMasternodeEntryEntity]; - } -} - -+ (DSSimplifiedMasternodeEntryEntity *)simplifiedMasternodeEntryForProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash onChainEntity:(DSChainEntity *)chainEntity { - return [self anyObjectInContext:chainEntity.managedObjectContext matching:@"(providerRegistrationTransactionHash == %@) && (chain == %@)", providerRegistrationTransactionHash, chainEntity]; -} - -+ (DSSimplifiedMasternodeEntryEntity *)simplifiedMasternodeEntryForHash:(NSData *)simplifiedMasternodeEntryHash onChainEntity:(DSChainEntity *)chainEntity { - return [self anyObjectInContext:chainEntity.managedObjectContext matching:@"(simplifiedMasternodeEntryHash == %@) && (chain == %@)", simplifiedMasternodeEntryHash, chainEntity]; -} - -- (NSDictionary *)blockDictionaryFromBlockHashDictionary:(NSDictionary *)blockHashDictionary { - return [self blockDictionaryFromBlockHashDictionary:blockHashDictionary blockHeightLookup:nil]; -} - -- (NSDictionary *)blockDictionaryFromBlockHashDictionary:(NSDictionary *)blockHashDictionary blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; - DSChain *chain = self.chain.chain; - for (NSData *blockHash in blockHashDictionary) { - UInt256 hash = *(UInt256 *)(blockHash.bytes); - DSBlock *block = [chain blockForBlockHash:hash]; - if (block) { - rDictionary[[NSData dataWithBlockHash:hash height:block.height]] = blockHashDictionary[blockHash]; - } else if (blockHeightLookup) { - uint32_t blockHeight = blockHeightLookup(blockHash.UInt256); - if (blockHeight && blockHeight != UINT32_MAX) { - rDictionary[[NSData dataWithBlockHash:hash height:blockHeight]] = blockHashDictionary[blockHash]; - } - } - } - return rDictionary; -} - -- (NSDictionary *)blockHashDictionaryFromBlockDictionary:(NSDictionary *)blockHashDictionary { - NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; - for (NSData *blockInfo in blockHashDictionary) { - UInt256 blockInfoHash = [blockInfo UInt256AtOffset:0]; - NSData *blockHash = uint256_data(blockInfoHash); - if (blockHash) { - rDictionary[blockHash] = blockHashDictionary[blockInfo]; - } - } - return rDictionary; -} - -- (DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry { - return [self simplifiedMasternodeEntryWithBlockHeightLookup:nil]; -} - -- (DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntryWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = [DSSimplifiedMasternodeEntry simplifiedMasternodeEntryWithProviderRegistrationTransactionHash:[self.providerRegistrationTransactionHash UInt256] confirmedHash:[self.confirmedHash UInt256] address:self.ipv6Address.UInt128 port:self.port operatorBLSPublicKey:[self.operatorBLSPublicKey UInt384] operatorPublicKeyVersion:self.operatorPublicKeyVersion previousOperatorBLSPublicKeys:[self blockDictionaryFromBlockHashDictionary:self.previousOperatorBLSPublicKeys blockHeightLookup:blockHeightLookup] keyIDVoting:[self.keyIDVoting UInt160] isValid:self.isValid type:self.type platformHTTPPort:self.platformHTTPPort platformNodeID:[self.platformNodeID UInt160] previousValidity:[self blockDictionaryFromBlockHashDictionary:self.previousValidity blockHeightLookup:blockHeightLookup] knownConfirmedAtHeight:self.knownConfirmedAtHeight updateHeight:self.updateHeight simplifiedMasternodeEntryHash:[self.simplifiedMasternodeEntryHash UInt256] previousSimplifiedMasternodeEntryHashes:[self blockDictionaryFromBlockHashDictionary:self.previousSimplifiedMasternodeEntryHashes blockHeightLookup:blockHeightLookup] onChain:self.chain.chain]; - return simplifiedMasternodeEntry; -} - - -- (NSString *)debugDescription { - NSMutableString *str = [NSMutableString string]; - [str appendFormat:@"---------------------- \n"]; - [str appendFormat:@"pro_reg_tx_hash: %@ \n", self.providerRegistrationTransactionHash.hexString]; - [str appendFormat:@"confirmed_hash: %@ \n", self.confirmedHash.hexString]; - [str appendFormat:@"address: %@ \n", self.ipv6Address.hexString]; - [str appendFormat:@"port: %u \n", self.port]; - [str appendFormat:@"operator_public_key: %@ \n", self.operatorBLSPublicKey.hexString]; - [str appendFormat:@"operator_public_key_version: %u \n", self.operatorPublicKeyVersion]; - for (NSData *hash in self.previousOperatorBLSPublicKeys) { - [str appendFormat:@"prev_operator_public_key [%@]: %@ \n", hash.hexString, ((NSData *) self.previousOperatorBLSPublicKeys[hash]).hexString]; - } - [str appendFormat:@"key_id_voting: %@ \n", self.keyIDVoting.hexString]; - [str appendFormat:@"is_valid: %u \n", self.isValid]; - [str appendFormat:@"type: %u \n", self.type]; - [str appendFormat:@"platform_http_port: %u \n", self.platformHTTPPort]; - [str appendFormat:@"platform_node_id: %@ \n", self.platformNodeID.hexString]; - for (NSData *hash in self.previousValidity) { - [str appendFormat:@"prev_validity [%@]: %u \n", hash.hexString, ((NSNumber *) self.previousValidity[hash]).boolValue]; - } - [str appendFormat:@"known_confirmed_at_height: %u \n", self.knownConfirmedAtHeight]; - [str appendFormat:@"update_height: %u \n", self.updateHeight]; - [str appendFormat:@"entry_hash: %@ \n", self.simplifiedMasternodeEntryHash.hexString]; - for (NSData *hash in self.previousSimplifiedMasternodeEntryHashes) { - [str appendFormat:@"prev_entry_hash [%@]: %@ \n", hash.hexString, ((NSData *) self.previousSimplifiedMasternodeEntryHashes[hash]).hexString]; - } - [str appendFormat:@"---------------------- \n"]; - return [[super debugDescription] stringByAppendingString:str]; -} - - -@end diff --git a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h b/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h deleted file mode 100644 index 64ed9c1a8..000000000 --- a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h -// DashSync -// -// Created by Sam Westrich on 6/19/19. -// -// - -#import "DSSimplifiedMasternodeEntryEntity+CoreDataClass.h" - - -NS_ASSUME_NONNULL_BEGIN - -@interface DSSimplifiedMasternodeEntryEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest; - -@property (nonatomic, assign) uint64_t address; //it's really on 32 bits but unsigned -@property (nonatomic, assign) uint64_t platformPing; -@property (nonatomic, assign) uint16_t port; -@property (nonatomic, assign) uint32_t updateHeight; -@property (nonatomic, assign) uint32_t knownConfirmedAtHeight; -@property (nonatomic, assign) BOOL isValid; -@property (nonatomic, assign) uint16_t type; -@property (nonatomic, assign) uint16_t platformHTTPPort; -@property (nullable, nonatomic, retain) NSData *platformNodeID; -@property (nullable, nonatomic, retain) NSString *coreVersion; -@property (nonatomic, assign) uint64_t coreProtocol; -@property (nullable, nonatomic, retain) NSDate *coreLastConnectionDate; -@property (nullable, nonatomic, retain) NSString *platformVersion; -@property (nullable, nonatomic, retain) NSDate *platformPingDate; -@property (nullable, nonatomic, retain) NSData *confirmedHash; -@property (nullable, nonatomic, retain) NSData *ipv6Address; -@property (nullable, nonatomic, retain) NSData *keyIDVoting; -@property (nullable, nonatomic, retain) NSData *operatorBLSPublicKey; -@property (nonatomic, assign) uint16_t operatorPublicKeyVersion; -@property (nullable, nonatomic, retain) NSDictionary *previousOperatorBLSPublicKeys; -@property (nullable, nonatomic, retain) NSDictionary *previousValidity; -@property (nullable, nonatomic, retain) NSData *providerRegistrationTransactionHash; -@property (nullable, nonatomic, retain) NSData *simplifiedMasternodeEntryHash; -@property (nullable, nonatomic, retain) NSDictionary *previousSimplifiedMasternodeEntryHashes; -@property (nullable, nonatomic, retain) NSOrderedSet *addresses; -@property (nullable, nonatomic, retain) DSChainEntity *chain; -@property (nullable, nonatomic, retain) NSSet *governanceVotes; -@property (nullable, nonatomic, retain) DSLocalMasternodeEntity *localMasternode; -@property (nullable, nonatomic, retain) NSSet *masternodeLists; - -@end - -@interface DSSimplifiedMasternodeEntryEntity (CoreDataGeneratedAccessors) - -- (void)insertObject:(DSAddressEntity *)value inAddressesAtIndex:(NSUInteger)idx; -- (void)removeObjectFromAddressesAtIndex:(NSUInteger)idx; -- (void)insertAddresses:(NSArray *)value atIndexes:(NSIndexSet *)indexes; -- (void)removeAddressesAtIndexes:(NSIndexSet *)indexes; -- (void)replaceObjectInAddressesAtIndex:(NSUInteger)idx withObject:(DSAddressEntity *)value; -- (void)replaceAddressesAtIndexes:(NSIndexSet *)indexes withAddresses:(NSArray *)values; -- (void)addAddressesObject:(DSAddressEntity *)value; -- (void)removeAddressesObject:(DSAddressEntity *)value; -- (void)addAddresses:(NSOrderedSet *)values; -- (void)removeAddresses:(NSOrderedSet *)values; - -- (void)addGovernanceVotesObject:(DSGovernanceVoteEntity *)value; -- (void)removeGovernanceVotesObject:(DSGovernanceVoteEntity *)value; -- (void)addGovernanceVotes:(NSSet *)values; -- (void)removeGovernanceVotes:(NSSet *)values; - -- (void)addMasternodeListsObject:(DSMasternodeListEntity *)value; -- (void)removeMasternodeListsObject:(DSMasternodeListEntity *)value; -- (void)addMasternodeLists:(NSSet *)values; -- (void)removeMasternodeLists:(NSSet *)values; - -- (void)addTransactionLockVotesObject:(DSTransactionLockVoteEntity *)value; -- (void)removeTransactionLockVotesObject:(DSTransactionLockVoteEntity *)value; -- (void)addTransactionLockVotes:(NSSet *)values; -- (void)removeTransactionLockVotes:(NSSet *)values; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataProperties.m b/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataProperties.m deleted file mode 100644 index 2958437fb..000000000 --- a/DashSync/shared/Models/Entities/DSSimplifiedMasternodeEntryEntity+CoreDataProperties.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// DSSimplifiedMasternodeEntryEntity+CoreDataProperties.m -// DashSync -// -// Created by Sam Westrich on 6/19/19. -// -// - -#import "DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h" - -@implementation DSSimplifiedMasternodeEntryEntity (CoreDataProperties) - -+ (NSFetchRequest *)fetchRequest { - return [NSFetchRequest fetchRequestWithEntityName:@"DSSimplifiedMasternodeEntryEntity"]; -} - -@dynamic address; -@dynamic confirmedHash; -@dynamic isValid; -@dynamic keyIDVoting; -@dynamic operatorBLSPublicKey; -@dynamic operatorPublicKeyVersion; -@dynamic port; -@dynamic previousOperatorBLSPublicKeys; -@dynamic previousValidity; -@dynamic providerRegistrationTransactionHash; -@dynamic simplifiedMasternodeEntryHash; -@dynamic addresses; -@dynamic chain; -@dynamic governanceVotes; -@dynamic localMasternode; -@dynamic masternodeLists; -@dynamic previousSimplifiedMasternodeEntryHashes; -@dynamic ipv6Address; -@dynamic updateHeight; -@dynamic knownConfirmedAtHeight; -@dynamic platformPingDate; -@dynamic platformPing; -@dynamic coreVersion; -@dynamic coreProtocol; -@dynamic coreLastConnectionDate; -@dynamic platformVersion; -@dynamic type; -@dynamic platformHTTPPort; -@dynamic platformNodeID; - -@end diff --git a/DashSync/shared/Models/Entities/DSTransactionEntity+CoreDataClass.m b/DashSync/shared/Models/Entities/DSTransactionEntity+CoreDataClass.m index b65805d62..80ce83953 100644 --- a/DashSync/shared/Models/Entities/DSTransactionEntity+CoreDataClass.m +++ b/DashSync/shared/Models/Entities/DSTransactionEntity+CoreDataClass.m @@ -24,6 +24,7 @@ #import "DSAddressEntity+CoreDataClass.h" #import "DSChain+Protected.h" +#import "DSChain+Transaction.h" #import "DSChainEntity+CoreDataClass.h" #import "DSInstantSendLockEntity+CoreDataClass.h" #import "DSInstantSendTransactionLock.h" diff --git a/DashSync/shared/Models/Governance/DSGovernanceObject.m b/DashSync/shared/Models/Governance/DSGovernanceObject.m index 0d0968390..23397aed8 100644 --- a/DashSync/shared/Models/Governance/DSGovernanceObject.m +++ b/DashSync/shared/Models/Governance/DSGovernanceObject.m @@ -7,6 +7,7 @@ #import "DSGovernanceObject.h" #import "DSAccount.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataProperties.h" #import "DSChainManager.h" @@ -19,7 +20,6 @@ #import "DSOptionsManager.h" #import "DSPeer.h" #import "DSPeerManager.h" -#import "DSSimplifiedMasternodeEntry.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" #import "NSManagedObject+Sugar.h" @@ -299,7 +299,10 @@ - (void)loadGovernanceVotes:(NSUInteger)count { if (!_knownGovernanceVoteHashesForExistingGovernanceVotes) _knownGovernanceVoteHashesForExistingGovernanceVotes = [NSMutableOrderedSet orderedSet]; for (DSGovernanceVoteEntity *governanceVoteEntity in governanceVoteEntities) { DSGovernanceVote *governanceVote = [governanceVoteEntity governanceVote]; - DSLog(@"%@ : %@ -> %d/%d", self.identifier, [NSData dataWithUInt256:governanceVote.masternode.simplifiedMasternodeEntryHash].shortHexString, governanceVote.outcome, governanceVote.signal); + u256 *entry_hash = dashcore_hash_types_Sha256dHash_inner(governanceVote.masternode->entry_hash); + NSString *entryHashString = u256_hex(entry_hash); + u256_dtor(entry_hash); + DSLog(@"%@ : %@ -> %d/%d", self.identifier, entryHashString, governanceVote.outcome, governanceVote.signal); [_knownGovernanceVoteHashesForExistingGovernanceVotes addObject:[NSData dataWithUInt256:governanceVote.governanceVoteHash]]; [_governanceVotes addObject:governanceVote]; } diff --git a/DashSync/shared/Models/Governance/DSGovernanceVote.h b/DashSync/shared/Models/Governance/DSGovernanceVote.h index 275039c80..dca9263a2 100644 --- a/DashSync/shared/Models/Governance/DSGovernanceVote.h +++ b/DashSync/shared/Models/Governance/DSGovernanceVote.h @@ -5,8 +5,9 @@ // Created by Sam Westrich on 6/12/18. // -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSChain.h" +#import "DSKeyManager.h" #import NS_ASSUME_NONNULL_BEGIN @@ -33,7 +34,7 @@ typedef NS_ENUM(uint32_t, DSGovernanceVoteOutcome) @interface DSGovernanceVote : NSObject @property (nullable, nonatomic, strong) DSGovernanceObject *governanceObject; -@property (nonatomic, readonly) DSSimplifiedMasternodeEntry *masternode; +@property (nonatomic, readonly) DMasternodeEntry *masternode; @property (nonatomic, readonly) DSGovernanceVoteOutcome outcome; @property (nonatomic, readonly) DSGovernanceVoteSignal signal; @property (nonatomic, readonly) NSTimeInterval createdAt; @@ -45,7 +46,7 @@ typedef NS_ENUM(uint32_t, DSGovernanceVoteOutcome) + (DSGovernanceVote *_Nullable)governanceVoteFromMessage:(NSData *)message onChain:(DSChain *)chain; - (instancetype)initWithParentHash:(UInt256)parentHash forMasternodeUTXO:(DSUTXO)masternodeUTXO voteOutcome:(DSGovernanceVoteOutcome)voteOutcome voteSignal:(DSGovernanceVoteSignal)voteSignal createdAt:(NSTimeInterval)createdAt signature:(NSData *_Nullable)signature onChain:(DSChain *)chain; -- (void)signWithKey:(OpaqueKey *)key; +- (void)signWithKey:(DOpaqueKey *)key; - (NSData *)dataMessage; - (BOOL)isValid; diff --git a/DashSync/shared/Models/Governance/DSGovernanceVote.m b/DashSync/shared/Models/Governance/DSGovernanceVote.m index 2ba347b9c..93f8f5dda 100644 --- a/DashSync/shared/Models/Governance/DSGovernanceVote.m +++ b/DashSync/shared/Models/Governance/DSGovernanceVote.m @@ -5,14 +5,13 @@ // Created by Sam Westrich on 6/12/18. // +#import "dash_spv_apple_bindings.h" #import "DSGovernanceVote.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSChainsManager.h" -#import "DSKeyManager.h" #import "DSMasternodeManager.h" #import "DSPeerManager.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataClass.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" #import "NSManagedObject+Sugar.h" @@ -20,7 +19,7 @@ @interface DSGovernanceVote () -@property (nonatomic, strong) DSSimplifiedMasternodeEntry *masternode; +@property (nonatomic, assign) DMasternodeEntry *masternode; @property (nonatomic, assign) DSGovernanceVoteOutcome outcome; @property (nonatomic, assign) DSGovernanceVoteSignal signal; @property (nonatomic, assign) NSTimeInterval createdAt; @@ -138,18 +137,12 @@ - (instancetype)initWithParentHash:(UInt256)parentHash forMasternodeUTXO:(DSUTXO return self; } -- (DSSimplifiedMasternodeEntry *)masternode { - if (!_masternode) { - /// WTF? old fields ? - self.masternode = [DSSimplifiedMasternodeEntryEntity anyObjectInContext:[NSManagedObjectContext chainContext] matching:@"utxoHash = %@ && utxoIndex = %@", [NSData dataWithUInt256:(UInt256)self.masternodeUTXO.hash], @(self.masternodeUTXO.n)].simplifiedMasternodeEntry; - } - return _masternode; -} - -- (void)signWithKey:(OpaqueKey *)key { +- (void)signWithKey:(DOpaqueKey *)key { NSParameterAssert(key); // ECDSA - self.signature = [DSKeyManager NSDataFrom:key_ecdsa_sign(key->ecdsa, self.governanceVoteHash.u8, 32)]; + Slice_u8 *slice = slice_u256_ctor_u(self.governanceVoteHash); + Vec_u8 *result = DECDSAKeySign(key->ecdsa, slice); + self.signature = [DSKeyManager NSDataFrom:result]; } - (BOOL)isValid { diff --git a/DashSync/shared/Models/Identity/DSBlockchainIdentity+Protected.h b/DashSync/shared/Models/Identity/DSBlockchainIdentity+Protected.h deleted file mode 100644 index d77540f5a..000000000 --- a/DashSync/shared/Models/Identity/DSBlockchainIdentity+Protected.h +++ /dev/null @@ -1,91 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSBlockchainIdentity.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSBlockchainIdentityEntity; - -@interface DSBlockchainIdentity () - -@property (nonatomic, readonly) DSBlockchainIdentityEntity *blockchainIdentityEntity; -@property (nullable, nonatomic, strong) DSTransientDashpayUser *transientDashpayUser; -@property (nonatomic, weak) DSBlockchainInvitation *associatedInvitation; -@property (nonatomic, assign) OpaqueKey *registrationFundingPrivateKey; -@property (nonatomic, assign) BOOL isLocal; -@property (nonatomic, assign) UInt256 registrationCreditFundingTransactionHash; - - -- (DSBlockchainIdentityEntity *)blockchainIdentityEntityInContext:(NSManagedObjectContext *)context; - -- (instancetype)initWithBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity; - -//This one is called for a local identity that is being recreated from the network -- (instancetype)initAtIndex:(uint32_t)index withUniqueId:(UInt256)uniqueId inWallet:(DSWallet *)wallet withBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity; - -//This one is called from an identity that was created locally by creating a credit funding transaction -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet withBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity; - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet withBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity associatedToInvitation:(DSBlockchainInvitation *)invitation; - -- (instancetype)initWithUniqueId:(UInt256)uniqueId isTransient:(BOOL)isTransient onChain:(DSChain *)chain; - -- (instancetype)initAtIndex:(uint32_t)index inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withUniqueId:(UInt256)uniqueId inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withIdentityDictionary:(NSDictionary *)identityDictionary version:(uint32_t)version inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction withUsernameDictionary:(NSDictionary *_Nullable)usernameDictionary inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction withUsernameDictionary:(NSDictionary *_Nullable)usernameDictionary havingCredits:(uint64_t)credits registrationStatus:(DSBlockchainIdentityRegistrationStatus)registrationStatus inWallet:(DSWallet *)wallet; - -- (void)addUsername:(NSString *)username inDomain:(NSString *)domain status:(DSBlockchainIdentityUsernameStatus)status save:(BOOL)save registerOnNetwork:(BOOL)registerOnNetwork; - -- (void)addKey:(OpaqueKey *)key atIndex:(uint32_t)index ofType:(KeyKind)type withStatus:(DSBlockchainIdentityKeyStatus)status save:(BOOL)save; -- (void)addKey:(OpaqueKey *)key atIndexPath:(NSIndexPath *)indexPath ofType:(KeyKind)type withStatus:(DSBlockchainIdentityKeyStatus)status save:(BOOL)save; -- (BOOL)registerKeyWithStatus:(DSBlockchainIdentityKeyStatus)status atIndexPath:(NSIndexPath *)indexPath ofType:(KeyKind)type; -- (OpaqueKey *_Nullable)privateKeyAtIndex:(uint32_t)index ofType:(KeyKind)type; -- (OpaqueKey *_Nullable)privateKeyAtIndex:(uint32_t)index ofType:(KeyKind)type forSeed:(NSData *)seed; -- (void)deletePersistentObjectAndSave:(BOOL)save inContext:(NSManagedObjectContext *)context; - -- (void)saveInitial; - -- (void)saveInitialInContext:(NSManagedObjectContext *)context; - -- (void)registerInWalletForBlockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId; - -- (void)registrationTransitionWithCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationTransition *_Nullable blockchainIdentityRegistrationTransition, NSError *_Nullable error))completion; - -- (void)createFundingPrivateKeyWithSeed:(NSData *)seed isForInvitation:(BOOL)isForInvitation completion:(void (^_Nullable)(BOOL success))completion; - -- (void)applyProfileChanges:(DSTransientDashpayUser *)transientDashpayUser inContext:(NSManagedObjectContext *)context saveContext:(BOOL)saveContext completion:(void (^_Nullable)(BOOL success, NSError *_Nullable error))completion onCompletionQueue:(dispatch_queue_t)completionQueue; - -- (void)setInvitationUniqueId:(UInt256)uniqueId; - -- (void)setInvitationRegistrationCreditFundingTransaction:(DSCreditFundingTransaction *)creditFundingTransaction; - -//-(void)topupTransitionForForFundingTransaction:(DSTransaction*)fundingTransaction completion:(void (^ _Nullable)(DSBlockchainIdentityTopupTransition * blockchainIdentityTopupTransition))completion; -// -//-(void)updateTransitionUsingNewIndex:(uint32_t)index completion:(void (^ _Nullable)(DSBlockchainIdentityUpdateTransition * blockchainIdentityUpdateTransition))completion; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSBlockchainIdentity.h b/DashSync/shared/Models/Identity/DSBlockchainIdentity.h deleted file mode 100644 index 6f62e921c..000000000 --- a/DashSync/shared/Models/Identity/DSBlockchainIdentity.h +++ /dev/null @@ -1,409 +0,0 @@ -// -// DSBlockchainIdentity.h -// DashSync -// -// Created by Sam Westrich on 7/26/18. -// - -#import "BigIntTypes.h" -#import "DSDAPIClient.h" -#import "DSDerivationPath.h" -#import "DSKeyManager.h" -#import - -NS_ASSUME_NONNULL_BEGIN -@class DSWallet, DSBlockchainIdentityRegistrationTransition, DSBlockchainIdentityTopupTransition, DSBlockchainIdentityUpdateTransition, DSBlockchainIdentityCloseTransition, DSAccount, DSChain, DSTransition, DSDashpayUserEntity, DSPotentialOneWayFriendship, DSTransaction, DSFriendRequestEntity, DSPotentialContact, DSCreditFundingTransaction, DSDocumentTransition, DPDocumentFactory, DSTransientDashpayUser, DSBlockchainInvitation, DSAuthenticationKeysDerivationPath, UIImage; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityRegistrationStep) -{ - DSBlockchainIdentityRegistrationStep_None = 0, - DSBlockchainIdentityRegistrationStep_FundingTransactionCreation = 1, - DSBlockchainIdentityRegistrationStep_FundingTransactionAccepted = 2, - DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence = 4, - DSBlockchainIdentityRegistrationStep_ProofAvailable = 8, - DSBlockchainIdentityRegistrationStep_L1Steps = DSBlockchainIdentityRegistrationStep_FundingTransactionCreation | DSBlockchainIdentityRegistrationStep_FundingTransactionAccepted | DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence | DSBlockchainIdentityRegistrationStep_ProofAvailable, - DSBlockchainIdentityRegistrationStep_Identity = 16, - DSBlockchainIdentityRegistrationStep_RegistrationSteps = DSBlockchainIdentityRegistrationStep_L1Steps | DSBlockchainIdentityRegistrationStep_Identity, - DSBlockchainIdentityRegistrationStep_Username = 32, - DSBlockchainIdentityRegistrationStep_RegistrationStepsWithUsername = DSBlockchainIdentityRegistrationStep_RegistrationSteps | DSBlockchainIdentityRegistrationStep_Username, - DSBlockchainIdentityRegistrationStep_Profile = 64, - DSBlockchainIdentityRegistrationStep_RegistrationStepsWithUsernameAndDashpayProfile = DSBlockchainIdentityRegistrationStep_RegistrationStepsWithUsername | DSBlockchainIdentityRegistrationStep_Profile, - DSBlockchainIdentityRegistrationStep_All = DSBlockchainIdentityRegistrationStep_RegistrationStepsWithUsernameAndDashpayProfile, - DSBlockchainIdentityRegistrationStep_Cancelled = 1 << 30 -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityMonitorOptions) -{ - DSBlockchainIdentityMonitorOptions_None = 0, - DSBlockchainIdentityMonitorOptions_AcceptNotFoundAsNotAnError = 1, -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityQueryStep) -{ - DSBlockchainIdentityQueryStep_None = DSBlockchainIdentityRegistrationStep_None, //0 - DSBlockchainIdentityQueryStep_Identity = DSBlockchainIdentityRegistrationStep_Identity, //16 - DSBlockchainIdentityQueryStep_Username = DSBlockchainIdentityRegistrationStep_Username, //32 - DSBlockchainIdentityQueryStep_Profile = DSBlockchainIdentityRegistrationStep_Profile, //64 - DSBlockchainIdentityQueryStep_IncomingContactRequests = 128, - DSBlockchainIdentityQueryStep_OutgoingContactRequests = 256, - DSBlockchainIdentityQueryStep_ContactRequests = DSBlockchainIdentityQueryStep_IncomingContactRequests | DSBlockchainIdentityQueryStep_OutgoingContactRequests, - DSBlockchainIdentityQueryStep_AllForForeignBlockchainIdentity = DSBlockchainIdentityQueryStep_Identity | DSBlockchainIdentityQueryStep_Username | DSBlockchainIdentityQueryStep_Profile, - DSBlockchainIdentityQueryStep_AllForLocalBlockchainIdentity = DSBlockchainIdentityQueryStep_Identity | DSBlockchainIdentityQueryStep_Username | DSBlockchainIdentityQueryStep_Profile | DSBlockchainIdentityQueryStep_ContactRequests, - DSBlockchainIdentityQueryStep_NoIdentity = 1 << 28, - DSBlockchainIdentityQueryStep_BadQuery = 1 << 29, - DSBlockchainIdentityQueryStep_Cancelled = 1 << 30 -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityRegistrationStatus) -{ - DSBlockchainIdentityRegistrationStatus_Unknown = 0, - DSBlockchainIdentityRegistrationStatus_Registered = 1, - DSBlockchainIdentityRegistrationStatus_Registering = 2, - DSBlockchainIdentityRegistrationStatus_NotRegistered = 3, //sent to DAPI, not yet confirmed -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityUsernameStatus) -{ - DSBlockchainIdentityUsernameStatus_NotPresent = 0, - DSBlockchainIdentityUsernameStatus_Initial = 1, - DSBlockchainIdentityUsernameStatus_PreorderRegistrationPending = 2, - DSBlockchainIdentityUsernameStatus_Preordered = 3, - DSBlockchainIdentityUsernameStatus_RegistrationPending = 4, //sent to DAPI, not yet confirmed - DSBlockchainIdentityUsernameStatus_Confirmed = 5, - DSBlockchainIdentityUsernameStatus_TakenOnNetwork = 6, -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityFriendshipStatus) -{ - DSBlockchainIdentityFriendshipStatus_Unknown = NSUIntegerMax, - DSBlockchainIdentityFriendshipStatus_None = 0, - DSBlockchainIdentityFriendshipStatus_Outgoing = 1, - DSBlockchainIdentityFriendshipStatus_Incoming = 2, - DSBlockchainIdentityFriendshipStatus_Friends = DSBlockchainIdentityFriendshipStatus_Outgoing | DSBlockchainIdentityFriendshipStatus_Incoming, -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityRetryDelayType) -{ - DSBlockchainIdentityRetryDelayType_Linear = 0, - DSBlockchainIdentityRetryDelayType_SlowingDown20Percent = 1, - DSBlockchainIdentityRetryDelayType_SlowingDown50Percent = 2, -}; - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityKeyStatus) -{ - DSBlockchainIdentityKeyStatus_Unknown = 0, - DSBlockchainIdentityKeyStatus_Registered = 1, - DSBlockchainIdentityKeyStatus_Registering = 2, - DSBlockchainIdentityKeyStatus_NotRegistered = 3, - DSBlockchainIdentityKeyStatus_Revoked = 4, -}; - -#define BLOCKCHAIN_USERNAME_STATUS @"BLOCKCHAIN_USERNAME_STATUS" -#define BLOCKCHAIN_USERNAME_PROPER @"BLOCKCHAIN_USERNAME_PROPER" -#define BLOCKCHAIN_USERNAME_DOMAIN @"BLOCKCHAIN_USERNAME_DOMAIN" -#define BLOCKCHAIN_USERNAME_SALT @"BLOCKCHAIN_USERNAME_SALT" - -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityDidUpdateNotification; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityDidUpdateUsernameStatusNotification; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityKey; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUsernameKey; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUsernameDomainKey; - -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUpdateEvents; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUpdateEventKeyUpdate; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUpdateEventRegistration; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUpdateEventCreditBalance; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUpdateEventType; -FOUNDATION_EXPORT NSString *const DSBlockchainIdentityUpdateEventDashpaySyncronizationBlockHash; - -@interface DSBlockchainIdentity : NSObject - -/*! @brief This is the unique identifier representing the blockchain identity. It is derived from the credit funding transaction credit burn UTXO (as of dpp v10). Returned as a 256 bit number */ -@property (nonatomic, readonly) UInt256 uniqueID; - -/*! @brief This is the unique identifier representing the blockchain identity. It is derived from the credit funding transaction credit burn UTXO (as of dpp v10). Returned as a base 58 string of a 256 bit number */ -@property (nonatomic, readonly) NSString *uniqueIdString; - -/*! @brief This is the unique identifier representing the blockchain identity. It is derived from the credit funding transaction credit burn UTXO (as of dpp v10). Returned as a NSData of a 256 bit number */ -@property (nonatomic, readonly) NSData *uniqueIDData; - -/*! @brief This is the outpoint of the registration credit funding transaction. It is used to determine the unique ID by double SHA256 its value. Returned as a UTXO { .hash , .n } */ -@property (nonatomic, readonly) DSUTXO lockedOutpoint; - -/*! @brief This is the outpoint of the registration credit funding transaction. It is used to determine the unique ID by double SHA256 its value. Returned as an NSData of a UTXO { .hash , .n } */ -@property (nonatomic, readonly) NSData *lockedOutpointData; - -/*! @brief This is if the blockchain identity is present in wallets or not. If this is false then the blockchain identity is known for example from being a dashpay friend. */ -@property (nonatomic, readonly) BOOL isLocal; - -/*! @brief This is if the blockchain identity is made for being an invitation. All invitations should be marked as non local as well. */ -@property (nonatomic, readonly) BOOL isOutgoingInvitation; - -/*! @brief This is if the blockchain identity is made from an invitation we received. */ -@property (nonatomic, readonly) BOOL isFromIncomingInvitation; - -/*! @brief This is TRUE if the blockchain identity is an effemeral identity returned when searching. */ -@property (nonatomic, readonly) BOOL isTransient; - -/*! @brief This is TRUE only if the blockchain identity is contained within a wallet. It could be in a cleanup phase where it was removed from the wallet but still being help in memory by callbacks. */ -@property (nonatomic, readonly) BOOL isActive; - -/*! @brief This references transient Dashpay user info if on a transient blockchain identity. */ -@property (nonatomic, readonly) DSTransientDashpayUser *transientDashpayUser; - -/*! @brief This is the bitwise steps that the identity has already performed in registration. */ -@property (nonatomic, readonly) DSBlockchainIdentityRegistrationStep stepsCompleted; - -/*! @brief This is the wallet holding the blockchain identity. There should always be a wallet associated to a blockchain identity if the blockchain identity is local, but never if it is not. */ -@property (nonatomic, weak, readonly) DSWallet *wallet; - -/*! @brief This is invitation that is identity originated from. */ -@property (nonatomic, weak, readonly) DSBlockchainInvitation *associatedInvitation; - -/*! @brief This is the index of the blockchain identity in the wallet. The index is the top derivation used to derive an extended set of keys for the identity. No two local blockchain identities should be allowed to have the same index in a wallet. For example m/.../.../.../index/key */ -@property (nonatomic, readonly) uint32_t index; - -/*! @brief Related to DPNS. This is the list of usernames that are associated to the identity in the domain "dash". These usernames however might not yet be registered or might be invalid. This can be used in tandem with the statusOfUsername: method */ -@property (nonatomic, readonly) NSArray *dashpayUsernames; - -/*! @brief Related to DPNS. This is the list of usernames with their .dash domain that are associated to the identity in the domain "dash". These usernames however might not yet be registered or might be invalid. This can be used in tandem with the statusOfUsername: method */ -@property (nonatomic, readonly) NSArray *dashpayUsernameFullPaths; - -/*! @brief Related to DPNS. This is current and most likely username associated to the identity. It is not necessarily registered yet on L2 however so its state should be determined with the statusOfUsername: method - @discussion There are situations where this is nil as it is not yet known or if no username has yet been set. */ -@property (nullable, nonatomic, readonly) NSString *currentDashpayUsername; - -/*! @brief Related to registering the identity. This is the address used to fund the registration of the identity. Dash sent to this address in the special credit funding transaction will be converted to L2 credits */ -@property (nonatomic, readonly) NSString *registrationFundingAddress; - -/*! @brief The known balance in credits of the identity */ -@property (nonatomic, readonly) uint64_t creditBalance; - -/*! @brief The number of registered active keys that the blockchain identity has */ -@property (nonatomic, readonly) uint32_t activeKeyCount; - -/*! @brief The number of all keys that the blockchain identity has, registered, in registration, or inactive */ -@property (nonatomic, readonly) uint32_t totalKeyCount; - -/*! @brief This is the transaction on L1 that has an output that is used to fund the creation of this blockchain identity. - @discussion There are situations where this is nil as it is not yet known ; if the blockchain identity is being retrieved from L2 or if we are resyncing the chain. */ -@property (nullable, nonatomic, readonly) DSCreditFundingTransaction *registrationCreditFundingTransaction; - -/*! @brief This is the hash of the transaction on L1 that has an output that is used to fund the creation of this blockchain identity. - @discussion There are situations where this is nil as it is not yet known ; if the blockchain identity is being retrieved from L2 or if we are resyncing the chain. */ -@property (nonatomic, readonly) UInt256 registrationCreditFundingTransactionHash; - -/*! @brief In our system a contact is a vue on a blockchain identity for Dashpay. A blockchain identity is therefore represented by a contact that will have relationships in the system. This is in the default backgroundContext. */ -@property (nonatomic, readonly) DSDashpayUserEntity *matchingDashpayUserInViewContext; - -/*! @brief This is the status of the registration of the identity. It starts off in an initial status, and ends in a confirmed status */ -@property (nonatomic, readonly) DSBlockchainIdentityRegistrationStatus registrationStatus; - -/*! @brief This is the localized status of the registration of the identity returned as a string. It starts off in an initial status, and ends in a confirmed status */ -@property (nonatomic, readonly) NSString *localizedRegistrationStatusString; - -/*! @brief This is a convenience method that checks to see if registrationStatus is confirmed */ -@property (nonatomic, readonly, getter=isRegistered) BOOL registered; - -/*! @brief This is a convenience factory to quickly make dashpay documents */ -@property (nonatomic, readonly) DPDocumentFactory *dashpayDocumentFactory; - -/*! @brief This is a convenience factory to quickly make dpns documents */ -@property (nonatomic, readonly) DPDocumentFactory *dpnsDocumentFactory; - -/*! @brief DashpaySyncronizationBlock represents the last L1 block height for which Dashpay would be synchronized, if this isn't at the end of the chain then we need to query L2 to make sure we don't need to update our bloom filter */ -@property (nonatomic, readonly) uint32_t dashpaySyncronizationBlockHeight; - -/*! @brief DashpaySyncronizationBlock represents the last L1 block hash for which Dashpay would be synchronized */ -@property (nonatomic, readonly) UInt256 dashpaySyncronizationBlockHash; - -// MARK: - Contracts - -- (void)fetchAndUpdateContract:(DPContract *)contract; - -// MARK: - Helpers - -- (DSDashpayUserEntity *)matchingDashpayUserInContext:(NSManagedObjectContext *)context; - -// MARK: - Identity - -- (void)registerOnNetwork:(DSBlockchainIdentityRegistrationStep)steps withFundingAccount:(DSAccount *)account forTopupAmount:(uint64_t)topupDuffAmount pinPrompt:(NSString *)prompt stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion; - -- (void)continueRegisteringOnNetwork:(DSBlockchainIdentityRegistrationStep)steps withFundingAccount:(DSAccount *)fundingAccount forTopupAmount:(uint64_t)topupDuffAmount pinPrompt:(NSString *)prompt stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion; - -- (void)continueRegisteringIdentityOnNetwork:(DSBlockchainIdentityRegistrationStep)steps stepsCompleted:(DSBlockchainIdentityRegistrationStep)stepsAlreadyCompleted stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion; - -- (void)fundingTransactionForTopupAmount:(uint64_t)topupAmount toAddress:(NSString *)address fundedByAccount:(DSAccount *)fundingAccount completion:(void (^_Nullable)(DSCreditFundingTransaction *fundingTransaction))completion; - -- (void)fetchIdentityNetworkStateInformationWithCompletion:(void (^)(BOOL success, BOOL found, NSError *error))completion; - -- (void)fetchAllNetworkStateInformationWithCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion; - -- (void)fetchNeededNetworkStateInformationWithCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion; - -- (void)signStateTransition:(DSTransition *)transition completion:(void (^_Nullable)(BOOL success))completion; - -- (void)signStateTransition:(DSTransition *)transition forKeyIndex:(uint32_t)keyIndex ofType:(KeyKind)signingAlgorithm completion:(void (^_Nullable)(BOOL success))completion; - -- (void)signMessageDigest:(UInt256)digest forKeyIndex:(uint32_t)keyIndex ofType:(KeyKind)signingAlgorithm completion:(void (^_Nullable)(BOOL success, NSData *signature))completion; - -- (BOOL)verifySignature:(NSData *)signature forKeyIndex:(uint32_t)keyIndex ofType:(KeyKind)signingAlgorithm forMessageDigest:(UInt256)messageDigest; - -- (BOOL)verifySignature:(NSData *)signature ofType:(KeyKind)signingAlgorithm forMessageDigest:(UInt256)messageDigest; - -- (void)createFundingPrivateKeyWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion; - -- (void)createFundingPrivateKeyForInvitationWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion; - -- (void)createAndPublishRegistrationTransitionWithCompletion:(void (^_Nullable)(NSDictionary *_Nullable successInfo, NSError *_Nullable error))completion; - - -- (void)encryptData:(NSData *)data withKeyAtIndex:(uint32_t)index forRecipientKey:(OpaqueKey *)recipientKey completion:(void (^_Nullable)(NSData *encryptedData))completion; - -/*! @brief Register the blockchain identity to its wallet. This should only be done once on the creation of the blockchain identity. -*/ -- (void)registerInWallet; - -/*! @brief Unregister the blockchain identity from the wallet. This should only be used if the blockchain identity is not yet registered or if a progressive wallet wipe is happening. - @discussion When a blockchain identity is registered on the network it is automatically retrieved from the L1 chain on resync. If a client wallet wishes to change their default blockchain identity in a wallet it should be done by marking the default blockchain identity index in the wallet. Clients should not try to delete a registered blockchain identity from a wallet. - */ -- (BOOL)unregisterLocally; - -/*! @brief Register the blockchain identity to its wallet from a credit funding registration transaction. This should only be done once on the creation of the blockchain identity. - @param fundingTransaction The funding transaction used to initially fund the blockchain identity. -*/ -- (void)registerInWalletForRegistrationFundingTransaction:(DSCreditFundingTransaction *)fundingTransaction; - -// MARK: - Keys - -/*! @brief Register the blockchain identity to its wallet from a credit funding registration transaction. This should only be done once on the creation of the blockchain identity. -*/ - -- (void)generateBlockchainIdentityExtendedPublicKeysWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL registered))completion; - -- (BOOL)setExternalFundingPrivateKey:(OpaqueKey *)privateKey; - -- (BOOL)hasBlockchainIdentityExtendedPublicKeys; - -- (DSBlockchainIdentityKeyStatus)statusOfKeyAtIndex:(NSUInteger)index; - -- (KeyKind)typeOfKeyAtIndex:(NSUInteger)index; - -- (OpaqueKey *_Nullable)keyAtIndex:(NSUInteger)index; - -- (uint32_t)keyCountForKeyType:(KeyKind)keyType; - -+ (NSString *)localizedStatusOfKeyForBlockchainIdentityKeyStatus:(DSBlockchainIdentityKeyStatus)status; - -- (NSString *)localizedStatusOfKeyAtIndex:(NSUInteger)index; - -- (OpaqueKey *_Nullable)createNewKeyOfType:(KeyKind)type saveKey:(BOOL)saveKey returnIndex:(uint32_t *)rIndex; - -- (OpaqueKey *_Nullable)keyOfType:(KeyKind)type atIndex:(uint32_t)rIndex; - -+ (DSAuthenticationKeysDerivationPath *_Nullable)derivationPathForType:(KeyKind)type forWallet:(DSWallet *)wallet; - -+ (OpaqueKey *_Nullable)keyFromKeyDictionary:(NSDictionary *)dictionary rType:(uint32_t *)rType rIndex:(uint32_t *)rIndex; - -+ (OpaqueKey *_Nullable)firstKeyInIdentityDictionary:(NSDictionary *)identityDictionary; - -// MARK: - Dashpay - -/*! @brief This is a helper to easily get the avatar path of the matching dashpay user. */ -@property (nonatomic, readonly, nullable) NSString *avatarPath; - -/*! @brief This is a helper to easily get the avatar fingerprint of the matching dashpay user. */ -@property (nonatomic, readonly) NSData *avatarFingerprint; - -/*! @brief This is a helper to easily get the avatar hash of the matching dashpay user. */ -@property (nonatomic, readonly, nullable) NSData *avatarHash; - -/*! @brief This is a helper to easily get the display name of the matching dashpay user. */ -@property (nonatomic, readonly, nullable) NSString *displayName; - -/*! @brief This is a helper to easily get the public message of the matching dashpay user. */ -@property (nonatomic, readonly, nullable) NSString *publicMessage; - -/*! @brief This is a helper to easily get the last time the profile was updated of the matching dashpay user. */ -@property (nonatomic, assign) uint64_t dashpayProfileUpdatedAt; - -/*! @brief This is a helper to easily get the creation time of the profile of the matching dashpay user. */ -@property (nonatomic, assign) uint64_t dashpayProfileCreatedAt; - -- (void)sendNewFriendRequestToBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion; - -- (void)sendNewFriendRequestToPotentialContact:(DSPotentialContact *)potentialContact completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -- (void)sendNewFriendRequestMatchingPotentialFriendship:(DSPotentialOneWayFriendship *)potentialFriendship completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - - -- (void)acceptFriendRequestFromBlockchainIdentity:(DSBlockchainIdentity *)otherBlockchainIdentity completion:(void (^)(BOOL success, NSArray *errors))completion; - -- (void)acceptFriendRequest:(DSFriendRequestEntity *)friendRequest completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -- (BOOL)activePrivateKeysAreLoadedWithFetchingError:(NSError **)error; - -- (void)fetchContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -- (void)fetchOutgoingContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -- (void)fetchIncomingContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion; - -- (void)fetchProfileWithCompletion:(void (^_Nullable)(BOOL success, NSError *error))completion; - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName; - -- (void)updateDashpayProfileWithPublicMessage:(NSString *)publicMessage; - -- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString; - -- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString avatarHash:(NSData *)avatarHash avatarFingerprint:(NSData *)avatarFingerprint; - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage; - -#if TARGET_OS_IOS - -- (void)updateDashpayProfileWithAvatarImage:(UIImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString; - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarImage:(UIImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString; - -#else - -- (void)updateDashpayProfileWithAvatarImage:(NSImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString; - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarImage:(NSImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString; - - -#endif - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarURLString:(NSString *)avatarURLString; - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarURLString:(NSString *)avatarURLString avatarHash:(NSData *)avatarHash avatarFingerprint:(NSData *)avatarFingerprint; - -- (void)signedProfileDocumentTransitionInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSTransition *transition, BOOL cancelled, NSError *error))completion; - -- (void)signAndPublishProfileWithCompletion:(void (^)(BOOL success, BOOL cancelled, NSError *error))completion; - -- (BOOL)verifyKeysForWallet:(DSWallet *)wallet; - -// MARK: - Dashpay Friendship Helpers - -- (DSBlockchainIdentityFriendshipStatus)friendshipStatusForRelationshipWithBlockchainIdentity:(DSBlockchainIdentity *)otherBlockchainIdentity; - -// MARK: - DPNS - -- (void)addDashpayUsername:(NSString *)username save:(BOOL)save; - -- (void)addUsername:(NSString *)username inDomain:(NSString *)domain save:(BOOL)save; - -- (DSBlockchainIdentityUsernameStatus)statusOfUsername:(NSString *)username inDomain:(NSString *)domain; - -- (DSBlockchainIdentityUsernameStatus)statusOfDashpayUsername:(NSString *)username; - -- (void)registerUsernamesWithCompletion:(void (^_Nullable)(BOOL success, NSError *error))completion; - -- (void)fetchUsernamesWithCompletion:(void (^_Nullable)(BOOL success, NSError *error))completion; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSBlockchainIdentity.m b/DashSync/shared/Models/Identity/DSBlockchainIdentity.m deleted file mode 100644 index f62ffd288..000000000 --- a/DashSync/shared/Models/Identity/DSBlockchainIdentity.m +++ /dev/null @@ -1,4849 +0,0 @@ -// -// DSBlockchainIdentity.m -// DashSync -// -// Created by Sam Westrich on 7/26/18. -// - -#import "BigIntTypes.h" -#import "dash_shared_core.h" -#import "DPContract+Protected.h" -#import "DPDocumentFactory.h" -#import "DSAccount.h" -#import "DSAccountEntity+CoreDataClass.h" -#import "DSAuthenticationKeysDerivationPath.h" -#import "DSAuthenticationManager.h" -#import "DSBlockchainIdentity+Protected.h" -#import "DSBlockchainIdentityCloseTransition.h" -#import "DSBlockchainIdentityEntity+CoreDataClass.h" -#import "DSBlockchainIdentityKeyPathEntity+CoreDataClass.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSBlockchainIdentityTopupTransition.h" -#import "DSBlockchainIdentityUpdateTransition.h" -#import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" -#import "DSBlockchainInvitation+Protected.h" -#import "DSBlockchainInvitationEntity+CoreDataClass.h" -#import "DSChain+Protected.h" -#import "DSChainEntity+CoreDataClass.h" -#import "DSChainManager.h" -#import "DSContactRequest.h" -#import "DSContractTransition.h" -#import "DSCreditFundingDerivationPath.h" -#import "DSCreditFundingTransaction.h" -#import "DSCreditFundingTransactionEntity+CoreDataClass.h" -#import "DSDAPIPlatformNetworkService.h" -#import "DSDashPlatform.h" -#import "DSDashpayUserEntity+CoreDataClass.h" -#import "DSDerivationPath.h" -#import "DSDerivationPathFactory.h" -#import "DSDocumentTransition.h" -#import "DSFriendRequestEntity+CoreDataClass.h" -#import "DSIdentitiesManager+Protected.h" -#import "DSIncomingFundsDerivationPath.h" -#import "DSMerkleBlock.h" -#import "DSOptionsManager.h" -#import "DSPeerManager.h" -#import "DSPotentialContact.h" -#import "DSPotentialOneWayFriendship.h" -#import "DSPriceManager.h" -#import "DSSpecialTransactionsWalletHolder.h" -#import "DSTransaction+Protected.h" -#import "DSTransactionHashEntity+CoreDataClass.h" -#import "DSTransactionManager+Protected.h" -#import "DSTransientDashpayUser.h" -#import "DSTransition+Protected.h" -#import "DSWallet.h" -#import "NSCoder+Dash.h" -#import "NSData+Dash.h" -#import "NSData+Encryption.h" -#import "NSError+Dash.h" -#import "NSIndexPath+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" -#import -#import -#import "dash_shared_core.h" - -#define BLOCKCHAIN_USER_UNIQUE_IDENTIFIER_KEY @"BLOCKCHAIN_USER_UNIQUE_IDENTIFIER_KEY" -#define DEFAULT_SIGNING_ALGORITHM KeyKind_ECDSA -#define DEFAULT_FETCH_IDENTITY_RETRY_COUNT 5 -#define DEFAULT_FETCH_USERNAMES_RETRY_COUNT 5 -#define DEFAULT_FETCH_PROFILE_RETRY_COUNT 5 - -typedef NS_ENUM(NSUInteger, DSBlockchainIdentityKeyDictionary) -{ - DSBlockchainIdentityKeyDictionary_Key = 0, - DSBlockchainIdentityKeyDictionary_KeyType = 1, - DSBlockchainIdentityKeyDictionary_KeyStatus = 2, -}; - -@interface DSBlockchainIdentity () - -@property (nonatomic, weak) DSWallet *wallet; -@property (nonatomic, strong) NSMutableDictionary *usernameStatuses; -@property (nonatomic, assign) UInt256 uniqueID; -@property (nonatomic, assign) BOOL isOutgoingInvitation; -@property (nonatomic, assign) BOOL isFromIncomingInvitation; -@property (nonatomic, assign) BOOL isTransient; -@property (nonatomic, assign) DSUTXO lockedOutpoint; -@property (nonatomic, assign) uint32_t index; -@property (nonatomic, assign) DSBlockchainIdentityRegistrationStatus registrationStatus; -@property (nonatomic, assign) uint64_t creditBalance; - -@property (nonatomic, assign) uint32_t keysCreated; -@property (nonatomic, strong) NSMutableDictionary *keyInfoDictionaries; -@property (nonatomic, assign) uint32_t currentMainKeyIndex; -@property (nonatomic, assign) KeyKind currentMainKeyType; - -@property (nonatomic, strong) NSMutableDictionary *usernameSalts; -@property (nonatomic, strong) NSMutableDictionary *usernameDomains; - -@property (nonatomic, readonly) DSDAPIClient *DAPIClient; -@property (nonatomic, readonly) DSDAPIPlatformNetworkService *DAPINetworkService; - -@property (nonatomic, strong) DPDocumentFactory *dashpayDocumentFactory; -@property (nonatomic, strong) DPDocumentFactory *dpnsDocumentFactory; - -@property (nonatomic, strong) DSDashpayUserEntity *matchingDashpayUserInViewContext; - -@property (nonatomic, strong) DSDashpayUserEntity *matchingDashpayUserInPlatformContext; - -@property (nonatomic, strong) DSChain *chain; - -@property (nonatomic, assign) OpaqueKey *internalRegistrationFundingPrivateKey; - -@property (nonatomic, assign) UInt256 dashpaySyncronizationBlockHash; - -@property (nonatomic, readonly) DSIdentitiesManager *identitiesManager; - -@property (nonatomic, readonly) NSManagedObjectContext *platformContext; - -@property (nonatomic, strong) DSCreditFundingTransaction *registrationCreditFundingTransaction; - -@property (nonatomic, strong) dispatch_queue_t identityQueue; - - -@property (nonatomic, assign) uint64_t lastCheckedUsernamesTimestamp; -@property (nonatomic, assign) uint64_t lastCheckedProfileTimestamp; -@property (nonatomic, assign) uint64_t lastCheckedIncomingContactsTimestamp; -@property (nonatomic, assign) uint64_t lastCheckedOutgoingContactsTimestamp; - -@end - -@implementation DSBlockchainIdentity - -- (void)dealloc { - if (_internalRegistrationFundingPrivateKey != NULL) { - processor_destroy_opaque_key(_internalRegistrationFundingPrivateKey); - } -} -// MARK: - Initialization - -- (instancetype)initWithUniqueId:(UInt256)uniqueId isTransient:(BOOL)isTransient onChain:(DSChain *)chain { - //this is the initialization of a non local blockchain identity - if (!(self = [super init])) return nil; - NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); - _uniqueID = uniqueId; - _isLocal = FALSE; - _isTransient = isTransient; - _keysCreated = 0; - _currentMainKeyIndex = 0; - _currentMainKeyType = KeyKind_ECDSA; - self.usernameStatuses = [NSMutableDictionary dictionary]; - self.usernameDomains = [NSMutableDictionary dictionary]; - self.keyInfoDictionaries = [NSMutableDictionary dictionary]; - _registrationStatus = DSBlockchainIdentityRegistrationStatus_Registered; - self.chain = chain; - return self; -} - -- (instancetype)initWithUniqueId:(UInt256)uniqueId isTransient:(BOOL)isTransient withCredits:(uint32_t)credits onChain:(DSChain *)chain { - //this is the initialization of a non local blockchain identity - if (!(self = [self initWithUniqueId:uniqueId isTransient:isTransient onChain:chain])) return nil; - _creditBalance = credits; - return self; -} - -- (void)applyIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity { - for (DSBlockchainIdentityUsernameEntity *usernameEntity in blockchainIdentityEntity.usernames) { - NSData *salt = usernameEntity.salt; - if (salt) { - [self.usernameStatuses setObject:@{BLOCKCHAIN_USERNAME_PROPER: usernameEntity.stringValue, BLOCKCHAIN_USERNAME_DOMAIN: usernameEntity.domain ? usernameEntity.domain : @"", BLOCKCHAIN_USERNAME_STATUS: @(usernameEntity.status), BLOCKCHAIN_USERNAME_SALT: usernameEntity.salt} forKey:[self fullPathForUsername:usernameEntity.stringValue inDomain:usernameEntity.domain]]; - [self.usernameSalts setObject:usernameEntity.salt forKey:usernameEntity.stringValue]; - } else { - [self.usernameStatuses setObject:@{BLOCKCHAIN_USERNAME_PROPER: usernameEntity.stringValue, BLOCKCHAIN_USERNAME_DOMAIN: usernameEntity.domain ? usernameEntity.domain : @"", BLOCKCHAIN_USERNAME_STATUS: @(usernameEntity.status)} forKey:[self fullPathForUsername:usernameEntity.stringValue inDomain:usernameEntity.domain]]; - } - } - _creditBalance = blockchainIdentityEntity.creditBalance; - _registrationStatus = blockchainIdentityEntity.registrationStatus; - - _lastCheckedProfileTimestamp = blockchainIdentityEntity.lastCheckedProfileTimestamp; - _lastCheckedUsernamesTimestamp = blockchainIdentityEntity.lastCheckedUsernamesTimestamp; - _lastCheckedIncomingContactsTimestamp = blockchainIdentityEntity.lastCheckedIncomingContactsTimestamp; - _lastCheckedOutgoingContactsTimestamp = blockchainIdentityEntity.lastCheckedOutgoingContactsTimestamp; - - self.dashpaySyncronizationBlockHash = blockchainIdentityEntity.dashpaySyncronizationBlockHash.UInt256; - for (DSBlockchainIdentityKeyPathEntity *keyPath in blockchainIdentityEntity.keyPaths) { - KeyKind keyType = (KeyKind) keyPath.keyType; - NSIndexPath *keyIndexPath = (NSIndexPath *)[keyPath path]; - if (keyIndexPath) { - NSIndexPath *nonHardenedKeyIndexPath = [keyIndexPath softenAllItems]; - BOOL success = [self registerKeyWithStatus:keyPath.keyStatus atIndexPath:nonHardenedKeyIndexPath ofType:keyType]; - if (!success) { - OpaqueKey *key = [DSKeyManager keyWithPublicKeyData:keyPath.publicKeyData ofType:keyType]; - [self registerKey:key withStatus:keyPath.keyStatus atIndex:keyPath.keyID ofType:keyType]; - } - } else { - OpaqueKey *key = [DSKeyManager keyWithPublicKeyData:keyPath.publicKeyData ofType:keyType]; - [self registerKey:key withStatus:keyPath.keyStatus atIndex:keyPath.keyID ofType:keyType]; - } - } - if (self.isLocal || self.isOutgoingInvitation) { - if (blockchainIdentityEntity.registrationFundingTransaction) { - self.registrationCreditFundingTransactionHash = blockchainIdentityEntity.registrationFundingTransaction.transactionHash.txHash.UInt256; - } else { - NSData *transactionHashData = uint256_data(uint256_reverse(self.lockedOutpoint.hash)); - DSTransactionEntity *creditRegitrationTransactionEntity = [DSTransactionEntity anyObjectInContext:blockchainIdentityEntity.managedObjectContext matching:@"transactionHash.txHash == %@", transactionHashData]; - if (creditRegitrationTransactionEntity) { - self.registrationCreditFundingTransactionHash = creditRegitrationTransactionEntity.transactionHash.txHash.UInt256; - DSCreditFundingTransaction *registrationCreditFundingTransaction = (DSCreditFundingTransaction *)[creditRegitrationTransactionEntity transactionForChain:self.chain]; - BOOL correctIndex; - if (self.isOutgoingInvitation) { - correctIndex = [registrationCreditFundingTransaction checkInvitationDerivationPathIndexForWallet:self.wallet isIndex:self.index]; - } else { - correctIndex = [registrationCreditFundingTransaction checkDerivationPathIndexForWallet:self.wallet isIndex:self.index]; - } - if (!correctIndex) { - NSAssert(FALSE, @"We should implement this"); - } - } - } - } -} - -- (instancetype)initWithBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity { - if (!(self = [self initWithUniqueId:blockchainIdentityEntity.uniqueID.UInt256 isTransient:FALSE onChain:blockchainIdentityEntity.chain.chain])) return nil; - [self applyIdentityEntity:blockchainIdentityEntity]; - - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet withBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity { - if (!(self = [self initAtIndex:index withLockedOutpoint:lockedOutpoint inWallet:wallet])) return nil; - [self applyIdentityEntity:blockchainIdentityEntity]; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withUniqueId:(UInt256)uniqueId inWallet:(DSWallet *)wallet withBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity { - if (!(self = [self initAtIndex:index withUniqueId:uniqueId inWallet:wallet])) return nil; - [self applyIdentityEntity:blockchainIdentityEntity]; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet withBlockchainIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity associatedToInvitation:(DSBlockchainInvitation *)invitation { - if (!(self = [self initAtIndex:index withLockedOutpoint:lockedOutpoint inWallet:wallet])) return nil; - [self setAssociatedInvitation:invitation]; - [self applyIdentityEntity:blockchainIdentityEntity]; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index inWallet:(DSWallet *)wallet { - //this is the creation of a new blockchain identity - NSParameterAssert(wallet); - - if (!(self = [super init])) return nil; - self.wallet = wallet; - self.isLocal = YES; - self.isOutgoingInvitation = NO; - self.isTransient = FALSE; - self.keysCreated = 0; - self.currentMainKeyIndex = 0; - self.currentMainKeyType = KeyKind_ECDSA; - self.index = index; - self.usernameStatuses = [NSMutableDictionary dictionary]; - self.keyInfoDictionaries = [NSMutableDictionary dictionary]; - self.registrationStatus = DSBlockchainIdentityRegistrationStatus_Unknown; - self.usernameSalts = [NSMutableDictionary dictionary]; - self.chain = wallet.chain; - return self; -} - -- (void)setAssociatedInvitation:(DSBlockchainInvitation *)associatedInvitation { - _associatedInvitation = associatedInvitation; - // It was created locally, we are sending the invite - if (associatedInvitation.createdLocally) { - self.isOutgoingInvitation = TRUE; - self.isFromIncomingInvitation = FALSE; - self.isLocal = FALSE; - } else { - // It was created on another device, we are receiving the invite - self.isOutgoingInvitation = FALSE; - self.isFromIncomingInvitation = TRUE; - self.isLocal = TRUE; - } -} - -- (instancetype)initAtIndex:(uint32_t)index withUniqueId:(UInt256)uniqueId inWallet:(DSWallet *)wallet { - if (!(self = [self initAtIndex:index inWallet:wallet])) return nil; - self.uniqueID = uniqueId; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet { - if (!(self = [self initAtIndex:index inWallet:wallet])) return nil; - NSAssert(dsutxo_hash_is_not_zero(lockedOutpoint), @"utxo must not be nil"); - self.lockedOutpoint = lockedOutpoint; - self.uniqueID = [dsutxo_data(lockedOutpoint) SHA256_2]; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction inWallet:(DSWallet *)wallet { - NSParameterAssert(wallet); - if (![transaction isCreditFundingTransaction]) return nil; - NSAssert(index != UINT32_MAX, @"index must be found"); - if (!(self = [self initAtIndex:index withLockedOutpoint:transaction.lockedOutpoint inWallet:wallet])) return nil; - - self.registrationCreditFundingTransaction = transaction; - - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction withUsernameDictionary:(NSDictionary *)usernameDictionary inWallet:(DSWallet *)wallet { - NSAssert(index != UINT32_MAX, @"index must be found"); - if (!(self = [self initAtIndex:index withFundingTransaction:transaction inWallet:wallet])) return nil; - - if (usernameDictionary) { - NSMutableDictionary *usernameSalts = [NSMutableDictionary dictionary]; - for (NSString *username in usernameDictionary) { - NSDictionary *subDictionary = usernameDictionary[username]; - NSData *salt = subDictionary[BLOCKCHAIN_USERNAME_SALT]; - if (salt) { - usernameSalts[username] = salt; - } - } - self.usernameStatuses = [usernameDictionary mutableCopy]; - self.usernameSalts = usernameSalts; - } - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction withUsernameDictionary:(NSDictionary *_Nullable)usernameDictionary havingCredits:(uint64_t)credits registrationStatus:(DSBlockchainIdentityRegistrationStatus)registrationStatus inWallet:(DSWallet *)wallet { - if (!(self = [self initAtIndex:index withFundingTransaction:transaction withUsernameDictionary:usernameDictionary inWallet:wallet])) return nil; - - self.creditBalance = credits; - self.registrationStatus = registrationStatus; - - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withIdentityDictionary:(NSDictionary *)identityDictionary version:(uint32_t)version inWallet:(DSWallet *)wallet { - NSParameterAssert(wallet); - - if (!(self = [super init])) return nil; - self.wallet = wallet; - self.isLocal = YES; - self.isOutgoingInvitation = NO; - self.isTransient = FALSE; - self.keysCreated = 0; - self.currentMainKeyIndex = 0; - self.currentMainKeyType = KeyKind_ECDSA; - NSData *identityIdData = [identityDictionary objectForKey:@"id"]; - self.uniqueID = identityIdData.UInt256; - self.usernameStatuses = [NSMutableDictionary dictionary]; - self.keyInfoDictionaries = [NSMutableDictionary dictionary]; - self.registrationStatus = DSBlockchainIdentityRegistrationStatus_Registered; - self.usernameSalts = [NSMutableDictionary dictionary]; - self.chain = wallet.chain; - self.index = index; - - [self applyIdentityDictionary:identityDictionary version:version save:NO inContext:nil]; - - return self; -} - -- (dispatch_queue_t)identityQueue { - if (_identityQueue) return _identityQueue; - _identityQueue = self.chain.chainManager.identitiesManager.identityQueue; - return _identityQueue; -} - -// MARK: - Full Registration agglomerate - -- (DSBlockchainIdentityRegistrationStep)stepsCompleted { - DSBlockchainIdentityRegistrationStep stepsCompleted = DSBlockchainIdentityRegistrationStep_None; - if (self.isRegistered) { - stepsCompleted = DSBlockchainIdentityRegistrationStep_RegistrationSteps; - if ([self usernameFullPathsWithStatus:DSBlockchainIdentityUsernameStatus_Confirmed].count) { - stepsCompleted |= DSBlockchainIdentityRegistrationStep_Username; - } - } else if (self.registrationCreditFundingTransaction) { - stepsCompleted |= DSBlockchainIdentityRegistrationStep_FundingTransactionCreation; - DSAccount *account = [self.chain firstAccountThatCanContainTransaction:self.registrationCreditFundingTransaction]; - if (self.registrationCreditFundingTransaction.blockHeight != TX_UNCONFIRMED || [account transactionIsVerified:self.registrationCreditFundingTransaction]) { - stepsCompleted |= DSBlockchainIdentityRegistrationStep_FundingTransactionAccepted; - } - if ([self isRegisteredInWallet]) { - stepsCompleted |= DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence; - } - if (self.registrationCreditFundingTransaction.instantSendLockAwaitingProcessing) { - stepsCompleted |= DSBlockchainIdentityRegistrationStep_ProofAvailable; - } - } - - return stepsCompleted; -} - -- (void)continueRegisteringProfileOnNetwork:(DSBlockchainIdentityRegistrationStep)steps stepsCompleted:(DSBlockchainIdentityRegistrationStep)stepsAlreadyCompleted stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion { - __block DSBlockchainIdentityRegistrationStep stepsCompleted = stepsAlreadyCompleted; - - if (!(steps & DSBlockchainIdentityRegistrationStep_Profile)) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } - return; - } - //todo:we need to still do profile - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } -} - - -- (void)continueRegisteringUsernamesOnNetwork:(DSBlockchainIdentityRegistrationStep)steps stepsCompleted:(DSBlockchainIdentityRegistrationStep)stepsAlreadyCompleted stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion { - __block DSBlockchainIdentityRegistrationStep stepsCompleted = stepsAlreadyCompleted; - - if (!(steps & DSBlockchainIdentityRegistrationStep_Username)) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } - return; - } - - [self registerUsernamesWithCompletion:^(BOOL success, NSError *_Nonnull error) { - if (!success) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, error); - }); - } - return; - } - if (stepCompletion) { - dispatch_async(dispatch_get_main_queue(), ^{ - stepCompletion(DSBlockchainIdentityRegistrationStep_Username); - }); - } - stepsCompleted |= DSBlockchainIdentityRegistrationStep_Username; - - [self continueRegisteringProfileOnNetwork:steps stepsCompleted:stepsCompleted stepCompletion:stepCompletion completion:completion]; - }]; -} - -- (void)continueRegisteringIdentityOnNetwork:(DSBlockchainIdentityRegistrationStep)steps stepsCompleted:(DSBlockchainIdentityRegistrationStep)stepsAlreadyCompleted stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion { - __block DSBlockchainIdentityRegistrationStep stepsCompleted = stepsAlreadyCompleted; - if (!(steps & DSBlockchainIdentityRegistrationStep_Identity)) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } - return; - } - - - [self createAndPublishRegistrationTransitionWithCompletion:^(NSDictionary *_Nullable successInfo, NSError *_Nullable error) { - if (error) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, error); - }); - } - return; - } - if (stepCompletion) { - dispatch_async(dispatch_get_main_queue(), ^{ - stepCompletion(DSBlockchainIdentityRegistrationStep_Identity); - }); - } - stepsCompleted |= DSBlockchainIdentityRegistrationStep_Identity; - - [self continueRegisteringUsernamesOnNetwork:steps stepsCompleted:stepsCompleted stepCompletion:stepCompletion completion:completion]; - }]; -} - -- (void)continueRegisteringOnNetwork:(DSBlockchainIdentityRegistrationStep)steps withFundingAccount:(DSAccount *)fundingAccount forTopupAmount:(uint64_t)topupDuffAmount pinPrompt:(NSString *)prompt stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion { - [self continueRegisteringOnNetwork:steps withFundingAccount:fundingAccount forTopupAmount:topupDuffAmount pinPrompt:prompt inContext:self.platformContext stepCompletion:stepCompletion completion:completion]; -} - -- (void)continueRegisteringOnNetwork:(DSBlockchainIdentityRegistrationStep)steps withFundingAccount:(DSAccount *)fundingAccount forTopupAmount:(uint64_t)topupDuffAmount pinPrompt:(NSString *)prompt inContext:(NSManagedObjectContext *)context stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion { - if (!self.registrationCreditFundingTransaction) { - [self registerOnNetwork:steps withFundingAccount:fundingAccount forTopupAmount:topupDuffAmount pinPrompt:prompt stepCompletion:stepCompletion completion:completion]; - } else if (self.registrationStatus != DSBlockchainIdentityRegistrationStatus_Registered) { - [self continueRegisteringIdentityOnNetwork:steps stepsCompleted:DSBlockchainIdentityRegistrationStep_L1Steps stepCompletion:stepCompletion completion:completion]; - } else if ([self.unregisteredUsernameFullPaths count]) { - [self continueRegisteringUsernamesOnNetwork:steps stepsCompleted:DSBlockchainIdentityRegistrationStep_L1Steps | DSBlockchainIdentityRegistrationStep_Identity stepCompletion:stepCompletion completion:completion]; - } else if ([self matchingDashpayUserInContext:context].remoteProfileDocumentRevision < 1) { - [self continueRegisteringProfileOnNetwork:steps stepsCompleted:DSBlockchainIdentityRegistrationStep_L1Steps | DSBlockchainIdentityRegistrationStep_Identity stepCompletion:stepCompletion completion:completion]; - } -} - - -- (void)registerOnNetwork:(DSBlockchainIdentityRegistrationStep)steps withFundingAccount:(DSAccount *)fundingAccount forTopupAmount:(uint64_t)topupDuffAmount pinPrompt:(NSString *)prompt stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion { - __block DSBlockchainIdentityRegistrationStep stepsCompleted = DSBlockchainIdentityRegistrationStep_None; - if (![self hasBlockchainIdentityExtendedPublicKeys]) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity extended public keys need to be registered before you can register a blockchain identity."]); - }); - } - return; - } - if (!(steps & DSBlockchainIdentityRegistrationStep_FundingTransactionCreation)) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } - return; - } - NSString *creditFundingRegistrationAddress = [self registrationFundingAddress]; - [self fundingTransactionForTopupAmount:topupDuffAmount - toAddress:creditFundingRegistrationAddress - fundedByAccount:fundingAccount - completion:^(DSCreditFundingTransaction *_Nonnull fundingTransaction) { - if (!fundingTransaction) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, [NSError errorWithCode:500 localizedDescriptionKey:@"Funding transaction could not be created"]); - }); - } - return; - } - [fundingAccount signTransaction:fundingTransaction - withPrompt:prompt - completion:^(BOOL signedTransaction, BOOL cancelled) { - if (!signedTransaction) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (cancelled) { - stepsCompleted |= DSBlockchainIdentityRegistrationStep_Cancelled; - } - completion(stepsCompleted, cancelled ? nil : [NSError errorWithCode:500 localizedDescriptionKey:@"Transaction could not be signed"]); - }); - } - return; - } - if (stepCompletion) { - dispatch_async(dispatch_get_main_queue(), ^{ - stepCompletion(DSBlockchainIdentityRegistrationStep_FundingTransactionCreation); - }); - } - stepsCompleted |= DSBlockchainIdentityRegistrationStep_FundingTransactionCreation; - - //In wallet registration occurs now - - if (!(steps & DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence)) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } - return; - } - if (self.isOutgoingInvitation) { - [self.associatedInvitation registerInWalletForRegistrationFundingTransaction:fundingTransaction]; - } else { - [self registerInWalletForRegistrationFundingTransaction:fundingTransaction]; - } - - if (stepCompletion) { - dispatch_async(dispatch_get_main_queue(), ^{ - stepCompletion(DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence); - }); - } - stepsCompleted |= DSBlockchainIdentityRegistrationStep_LocalInWalletPersistence; - - if (!(steps & DSBlockchainIdentityRegistrationStep_FundingTransactionAccepted)) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, nil); - }); - } - return; - } - - dispatch_semaphore_t sem = dispatch_semaphore_create(0); - __block BOOL transactionSuccessfullyPublished = FALSE; - __block DSInstantSendTransactionLock *instantSendLock = nil; - - __block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:DSTransactionManagerTransactionStatusDidChangeNotification - object:nil - queue:nil - usingBlock:^(NSNotification *note) { - DSTransaction *tx = [note.userInfo objectForKey:DSTransactionManagerNotificationTransactionKey]; - if ([tx isEqual:fundingTransaction]) { - NSDictionary *changes = [note.userInfo objectForKey:DSTransactionManagerNotificationTransactionChangesKey]; - if (changes) { - NSNumber *accepted = changes[DSTransactionManagerNotificationTransactionAcceptedStatusKey]; - NSNumber *lockVerified = changes[DSTransactionManagerNotificationInstantSendTransactionLockVerifiedKey]; - DSInstantSendTransactionLock *lock = changes[DSTransactionManagerNotificationInstantSendTransactionLockKey]; - if ([lockVerified boolValue] && lock != nil) { - instantSendLock = lock; - transactionSuccessfullyPublished = TRUE; - dispatch_semaphore_signal(sem); - } else if ([accepted boolValue]) { - transactionSuccessfullyPublished = TRUE; - } - } - } - }]; - - - [self.chain.chainManager.transactionManager publishTransaction:fundingTransaction - completion:^(NSError *_Nullable error) { - if (error) { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, error); - }); - } - return; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 25 * NSEC_PER_SEC)); - - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - - if (!transactionSuccessfullyPublished) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, [NSError errorWithCode:500 localizedDescriptionKey:@"Timeout while waiting for funding transaction to be accepted by network"]); - }); - } - return; - } - - if (stepCompletion) { - dispatch_async(dispatch_get_main_queue(), ^{ - stepCompletion(DSBlockchainIdentityRegistrationStep_FundingTransactionAccepted); - }); - } - stepsCompleted |= DSBlockchainIdentityRegistrationStep_FundingTransactionAccepted; - - if (!instantSendLock) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(stepsCompleted, [NSError errorWithCode:500 localizedDescriptionKey:@"Timeout while waiting for funding transaction to aquire an instant send lock"]); - }); - } - return; - } - - - if (stepCompletion) { - dispatch_async(dispatch_get_main_queue(), ^{ - stepCompletion(DSBlockchainIdentityRegistrationStep_ProofAvailable); - }); - } - stepsCompleted |= DSBlockchainIdentityRegistrationStep_ProofAvailable; - - - [self continueRegisteringIdentityOnNetwork:steps stepsCompleted:stepsCompleted stepCompletion:stepCompletion completion:completion]; - }); - }]; - }]; - }]; -} - -// MARK: - Local Registration and Generation - -- (BOOL)hasBlockchainIdentityExtendedPublicKeys { - NSAssert(_isLocal || _isOutgoingInvitation, @"This should not be performed on a non local blockchain identity (but can be done for an invitation)"); - if (!_isLocal && !_isOutgoingInvitation) return FALSE; - if (_isLocal) { - DSAuthenticationKeysDerivationPath *derivationPathBLS = [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:self.wallet]; - DSAuthenticationKeysDerivationPath *derivationPathECDSA = [[DSDerivationPathFactory sharedInstance] blockchainIdentityECDSAKeysDerivationPathForWallet:self.wallet]; - DSCreditFundingDerivationPath *derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:self.wallet]; - DSCreditFundingDerivationPath *derivationPathTopupFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityTopupFundingDerivationPathForWallet:self.wallet]; - if ([derivationPathBLS hasExtendedPublicKey] && [derivationPathECDSA hasExtendedPublicKey] && [derivationPathRegistrationFunding hasExtendedPublicKey] && [derivationPathTopupFunding hasExtendedPublicKey]) { - return YES; - } else { - return NO; - } - } - if (_isOutgoingInvitation) { - DSCreditFundingDerivationPath *derivationPathInvitationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:self.wallet]; - return [derivationPathInvitationFunding hasExtendedPublicKey]; - } - return NO; -} - -- (void)generateBlockchainIdentityExtendedPublicKeysWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL registered))completion { - NSAssert(_isLocal || _isOutgoingInvitation, @"This should not be performed on a non local blockchain identity (but can be done for an invitation)"); - if (!_isLocal && !_isOutgoingInvitation) return; - if ([self hasBlockchainIdentityExtendedPublicKeys]) { - if (completion) { - completion(YES); - } - return; - } - [[DSAuthenticationManager sharedInstance] seedWithPrompt:prompt - forWallet:self.wallet - forAmount:0 - forceAuthentication:NO - completion:^(NSData *_Nullable seed, BOOL cancelled) { - if (!seed) { - completion(NO); - return; - } - if (self->_isLocal) { - DSAuthenticationKeysDerivationPath *derivationPathBLS = [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:self.wallet]; - DSAuthenticationKeysDerivationPath *derivationPathECDSA = [[DSDerivationPathFactory sharedInstance] blockchainIdentityECDSAKeysDerivationPathForWallet:self.wallet]; - - [derivationPathBLS generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; - [derivationPathECDSA generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; - if (!self->_isFromIncomingInvitation) { - DSCreditFundingDerivationPath *derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:self.wallet]; - DSCreditFundingDerivationPath *derivationPathTopupFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityTopupFundingDerivationPathForWallet:self.wallet]; - [derivationPathRegistrationFunding generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; - [derivationPathTopupFunding generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; - } - } - if (self->_isOutgoingInvitation) { - DSCreditFundingDerivationPath *derivationPathInvitationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:self.wallet]; - [derivationPathInvitationFunding generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; - } - completion(YES); - }]; -} - -- (void)registerInWalletForRegistrationFundingTransaction:(DSCreditFundingTransaction *)fundingTransaction { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return; - self.registrationCreditFundingTransactionHash = fundingTransaction.txHash; - self.lockedOutpoint = fundingTransaction.lockedOutpoint; - [self registerInWalletForBlockchainIdentityUniqueId:fundingTransaction.creditBurnIdentityIdentifier]; - - //we need to also set the address of the funding transaction to being used so future identities past the initial gap limit are found - [fundingTransaction markAddressAsUsedInWallet:self.wallet]; -} - -- (void)registerInWalletForBlockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return; - self.uniqueID = blockchainIdentityUniqueId; - [self registerInWallet]; -} - -- (BOOL)isRegisteredInWallet { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return FALSE; - if (!self.wallet) return FALSE; - return [self.wallet containsBlockchainIdentity:self]; -} - -- (void)registerInWallet { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return; - [self.wallet registerBlockchainIdentity:self]; - [self saveInitial]; -} - -- (BOOL)unregisterLocally { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return FALSE; - if (self.isRegistered) return FALSE; //if it is already registered we can not unregister it from the wallet - [self.wallet unregisterBlockchainIdentity:self]; - [self deletePersistentObjectAndSave:YES inContext:self.platformContext]; - return TRUE; -} - -- (void)setInvitationUniqueId:(UInt256)uniqueId { - NSAssert(_isOutgoingInvitation, @"This can only be done on an invitation"); - if (!_isOutgoingInvitation) return; - self.uniqueID = uniqueId; -} - -- (void)setInvitationRegistrationCreditFundingTransaction:(DSCreditFundingTransaction *)creditFundingTransaction { - NSParameterAssert(creditFundingTransaction); - NSAssert(_isOutgoingInvitation, @"This can only be done on an invitation"); - if (!_isOutgoingInvitation) return; - self.registrationCreditFundingTransaction = creditFundingTransaction; - self.lockedOutpoint = creditFundingTransaction.lockedOutpoint; -} - -// MARK: - Read Only Property Helpers - -- (BOOL)isActive { - if (self.isLocal) { - if (!self.wallet) return NO; - return self.wallet.blockchainIdentities[self.uniqueIDData] != nil; - } else { - return [self.chain.chainManager.identitiesManager foreignBlockchainIdentityWithUniqueId:self.uniqueID] != nil; - } -} - -- (DSDashpayUserEntity *)matchingDashpayUserInViewContext { - if (!_matchingDashpayUserInViewContext) { - _matchingDashpayUserInViewContext = [self matchingDashpayUserInContext:[NSManagedObjectContext viewContext]]; - } - return _matchingDashpayUserInViewContext; -} - -- (DSDashpayUserEntity *)matchingDashpayUserInPlatformContext { - if (!_matchingDashpayUserInPlatformContext) { - _matchingDashpayUserInPlatformContext = [self matchingDashpayUserInContext:[NSManagedObjectContext platformContext]]; - } - return _matchingDashpayUserInPlatformContext; -} - -- (DSDashpayUserEntity *)matchingDashpayUserInContext:(NSManagedObjectContext *)context { - if (_matchingDashpayUserInViewContext || _matchingDashpayUserInPlatformContext) { - if (context == [_matchingDashpayUserInPlatformContext managedObjectContext]) return _matchingDashpayUserInPlatformContext; - if (context == [_matchingDashpayUserInViewContext managedObjectContext]) return _matchingDashpayUserInViewContext; - if (_matchingDashpayUserInPlatformContext) { - __block NSManagedObjectID *managedId; - [[NSManagedObjectContext platformContext] performBlockAndWait:^{ - managedId = _matchingDashpayUserInPlatformContext.objectID; - }]; - return [context objectWithID:managedId]; - } else { - __block NSManagedObjectID *managedId; - [[NSManagedObjectContext viewContext] performBlockAndWait:^{ - managedId = _matchingDashpayUserInViewContext.objectID; - }]; - return [context objectWithID:managedId]; - } - } else { - __block DSDashpayUserEntity *dashpayUserEntity = nil; - [context performBlockAndWait:^{ - dashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentity.uniqueID == %@", uint256_data(self.uniqueID)]; - }]; - return dashpayUserEntity; - } -} - -- (DSCreditFundingTransaction *)registrationCreditFundingTransaction { - if (!_registrationCreditFundingTransaction) { - _registrationCreditFundingTransaction = (DSCreditFundingTransaction *)[self.chain transactionForHash:self.registrationCreditFundingTransactionHash]; - } - return _registrationCreditFundingTransaction; -} - -- (NSData *)uniqueIDData { - return uint256_data(self.uniqueID); -} - -- (NSData *)lockedOutpointData { - return dsutxo_data(self.lockedOutpoint); -} - -- (NSString *)currentDashpayUsername { - return [self.dashpayUsernames firstObject]; -} - - -- (NSArray *)derivationPaths { - if (!_isLocal) return nil; - return [[DSDerivationPathFactory sharedInstance] unloadedSpecializedDerivationPathsForWallet:self.wallet]; -} - -- (NSString *)uniqueIdString { - return [uint256_data(self.uniqueID) base58String]; -} - - -- (dispatch_queue_t)networkingQueue { - return self.chain.networkingQueue; -} - -- (NSManagedObjectContext *)platformContext { - // NSAssert(![NSThread isMainThread], @"We should not be on main thread"); - return [NSManagedObjectContext platformContext]; -} - -- (DSIdentitiesManager *)identitiesManager { - return self.chain.chainManager.identitiesManager; -} - -// ECDSA -- (OpaqueKey *)registrationFundingPrivateKey { - return self.internalRegistrationFundingPrivateKey; -} - -// MARK: Dashpay helpers - -- (NSString *)avatarPath { - if (self.transientDashpayUser) { - return self.transientDashpayUser.avatarPath; - } else { - return self.matchingDashpayUserInViewContext.avatarPath; - } -} - -- (NSData *)avatarFingerprint { - if (self.transientDashpayUser) { - return self.transientDashpayUser.avatarFingerprint; - } else { - return self.matchingDashpayUserInViewContext.avatarFingerprint; - } -} - -- (NSData *)avatarHash { - if (self.transientDashpayUser) { - return self.transientDashpayUser.avatarHash; - } else { - return self.matchingDashpayUserInViewContext.avatarHash; - } -} - -- (NSString *)displayName { - if (self.transientDashpayUser) { - return self.transientDashpayUser.displayName; - } else { - return self.matchingDashpayUserInViewContext.displayName; - } -} - -- (NSString *)publicMessage { - if (self.transientDashpayUser) { - return self.transientDashpayUser.publicMessage; - } else { - return self.matchingDashpayUserInViewContext.publicMessage; - } -} - -- (uint64_t)dashpayProfileUpdatedAt { - if (self.transientDashpayUser) { - return self.transientDashpayUser.updatedAt; - } else { - return self.matchingDashpayUserInViewContext.updatedAt; - } -} - -- (uint64_t)dashpayProfileCreatedAt { - if (self.transientDashpayUser) { - return self.transientDashpayUser.createdAt; - } else { - return self.matchingDashpayUserInViewContext.createdAt; - } -} - -// MARK: - Keys - -- (void)createFundingPrivateKeyWithSeed:(NSData *)seed isForInvitation:(BOOL)isForInvitation completion:(void (^_Nullable)(BOOL success))completion { - DSCreditFundingDerivationPath *derivationPathRegistrationFunding; - if (isForInvitation) { - derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:self.wallet]; - } else { - derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:self.wallet]; - } - - self.internalRegistrationFundingPrivateKey = [derivationPathRegistrationFunding privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.index] fromSeed:seed]; - if (self.internalRegistrationFundingPrivateKey) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(YES); - }); - } - } else { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO); - }); - } - } -} - -- (BOOL)setExternalFundingPrivateKey:(OpaqueKey *)privateKey { - if (!self.isFromIncomingInvitation) { - return FALSE; - } - self.internalRegistrationFundingPrivateKey = privateKey; - if (self.internalRegistrationFundingPrivateKey) { - return TRUE; - } else { - return FALSE; - } -} - -- (void)createFundingPrivateKeyForInvitationWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion { - [self createFundingPrivateKeyWithPrompt:prompt isForInvitation:YES completion:completion]; -} - -- (void)createFundingPrivateKeyWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion { - [self createFundingPrivateKeyWithPrompt:prompt isForInvitation:NO completion:completion]; -} - -- (void)createFundingPrivateKeyWithPrompt:(NSString *)prompt isForInvitation:(BOOL)isForInvitation completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion { - dispatch_async(dispatch_get_main_queue(), ^{ - [[DSAuthenticationManager sharedInstance] seedWithPrompt:prompt - forWallet:self.wallet - forAmount:0 - forceAuthentication:NO - completion:^(NSData *_Nullable seed, BOOL cancelled) { - if (!seed) { - if (completion) { - completion(NO, cancelled); - } - return; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self createFundingPrivateKeyWithSeed:seed - isForInvitation:isForInvitation - completion:^(BOOL success) { - if (completion) { - completion(success, NO); - } - }]; - }); - }]; - }); -} - -- (BOOL)activePrivateKeysAreLoadedWithFetchingError:(NSError **)error { - BOOL loaded = TRUE; - for (NSNumber *index in self.keyInfoDictionaries) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[index]; - DSBlockchainIdentityKeyStatus status = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyStatus)] unsignedIntValue]; - KeyKind keyType = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyType)] unsignedIntValue]; - if (status == DSBlockchainIdentityKeyStatus_Registered) { - loaded &= [self hasPrivateKeyAtIndex:[index unsignedIntValue] ofType:keyType error:error]; - if (*error) return FALSE; - } - } - return loaded; -} - -- (uint32_t)activeKeyCount { - uint32_t rActiveKeys = 0; - for (NSNumber *index in self.keyInfoDictionaries) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[index]; - DSBlockchainIdentityKeyStatus status = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyStatus)] unsignedIntValue]; - if (status == DSBlockchainIdentityKeyStatus_Registered) rActiveKeys++; - } - return rActiveKeys; -} - -- (uint32_t)totalKeyCount { - return (uint32_t)self.keyInfoDictionaries.count; -} - -- (uint32_t)keyCountForKeyType:(KeyKind)keyType { - uint32_t keyCount = 0; - for (NSNumber *index in self.keyInfoDictionaries) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[index]; - KeyKind type = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyType)] unsignedIntValue]; - if (type == keyType) keyCount++; - } - return keyCount; -} - -- (NSArray *)activeKeysForKeyType:(KeyKind)keyType { - NSMutableArray *activeKeys = [NSMutableArray array]; - for (NSNumber *index in self.keyInfoDictionaries) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[index]; - KeyKind type = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyType)] unsignedIntValue]; - if (type == keyType) { - [activeKeys addObject:keyDictionary[@(DSBlockchainIdentityKeyDictionary_Key)]]; - } - } - return [activeKeys copy]; -} - -- (BOOL)verifyKeysForWallet:(DSWallet *)wallet { - DSWallet *originalWallet = self.wallet; - self.wallet = wallet; - for (uint32_t index = 0; index < self.keyInfoDictionaries.count; index++) { - KeyKind keyType = [self typeOfKeyAtIndex:index]; - OpaqueKey *key = [self keyAtIndex:index]; - if (!key) { - self.wallet = originalWallet; - return FALSE; - } - if (keyType != (int16_t) key->tag) { - self.wallet = originalWallet; - return FALSE; - } - OpaqueKey *derivedKey = [self publicKeyAtIndex:index ofType:keyType]; - if (![DSKeyManager keysPublicKeyDataIsEqual:derivedKey key2:key]) { - self.wallet = originalWallet; - return FALSE; - } - } - return TRUE; -} - -- (DSBlockchainIdentityKeyStatus)statusOfKeyAtIndex:(NSUInteger)index { - return [[[self.keyInfoDictionaries objectForKey:@(index)] objectForKey:@(DSBlockchainIdentityKeyDictionary_KeyStatus)] unsignedIntValue]; -} - -- (KeyKind)typeOfKeyAtIndex:(NSUInteger)index { - return [[[self.keyInfoDictionaries objectForKey:@(index)] objectForKey:@(DSBlockchainIdentityKeyDictionary_KeyType)] unsignedIntValue]; -} - -- (OpaqueKey *_Nullable)keyAtIndex:(NSUInteger)index { - NSValue *keyValue = (NSValue *)[[self.keyInfoDictionaries objectForKey:@(index)] objectForKey:@(DSBlockchainIdentityKeyDictionary_Key)]; - return keyValue.pointerValue; -} - -- (NSString *)localizedStatusOfKeyAtIndex:(NSUInteger)index { - DSBlockchainIdentityKeyStatus status = [self statusOfKeyAtIndex:index]; - return [[self class] localizedStatusOfKeyForBlockchainIdentityKeyStatus:status]; -} - -+ (NSString *)localizedStatusOfKeyForBlockchainIdentityKeyStatus:(DSBlockchainIdentityKeyStatus)status { - switch (status) { - case DSBlockchainIdentityKeyStatus_Unknown: - return DSLocalizedString(@"Unknown", @"Status of Key or Username is Unknown"); - case DSBlockchainIdentityKeyStatus_Registered: - return DSLocalizedString(@"Registered", @"Status of Key or Username is Registered"); - case DSBlockchainIdentityKeyStatus_Registering: - return DSLocalizedString(@"Registering", @"Status of Key or Username is Registering"); - case DSBlockchainIdentityKeyStatus_NotRegistered: - return DSLocalizedString(@"Not Registered", @"Status of Key or Username is Not Registered"); - case DSBlockchainIdentityKeyStatus_Revoked: - return DSLocalizedString(@"Revoked", @"Status of Key or Username is Revoked"); - default: - return @""; - } -} - -+ (DSAuthenticationKeysDerivationPath *)derivationPathForType:(KeyKind)type forWallet:(DSWallet *)wallet { - // TODO: ed25519 + bls basic - if (type == KeyKind_ECDSA) { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityECDSAKeysDerivationPathForWallet:wallet]; - } else if (type == KeyKind_BLS || type == KeyKind_BLSBasic) { - return [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:wallet]; - } - return nil; -} - -- (DSAuthenticationKeysDerivationPath *)derivationPathForType:(KeyKind)type { - if (!_isLocal) return nil; - return [DSBlockchainIdentity derivationPathForType:type forWallet:self.wallet]; -} - -- (BOOL)hasPrivateKeyAtIndex:(uint32_t)index ofType:(KeyKind)type error:(NSError **)error { - if (!_isLocal) return NO; - const NSUInteger indexes[] = {_index | BIP32_HARD, index | BIP32_HARD}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - return hasKeychainData([self identifierForKeyAtPath:indexPath fromDerivationPath:derivationPath], error); -} - -- (OpaqueKey *)privateKeyAtIndex:(uint32_t)index ofType:(KeyKind)type { - if (!_isLocal) return nil; - const NSUInteger indexes[] = {_index | BIP32_HARD, index | BIP32_HARD}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - NSError *error = nil; - NSData *keySecret = getKeychainData([self identifierForKeyAtPath:indexPath fromDerivationPath:derivationPath], &error); - - NSAssert(keySecret, @"This should be present"); - - if (!keySecret || error) return nil; - return [DSKeyManager keyWithPrivateKeyData:keySecret ofType:type]; -} - -- (OpaqueKey *)derivePrivateKeyAtIdentityKeyIndex:(uint32_t)index ofType:(KeyKind)type { - if (!_isLocal) return nil; - const NSUInteger indexes[] = {_index, index}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - return [self derivePrivateKeyAtIndexPath:indexPath ofType:type]; -} - -- (OpaqueKey *)derivePrivateKeyAtIndexPath:(NSIndexPath *)indexPath ofType:(KeyKind)type { - if (!_isLocal) return nil; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - return [derivationPath privateKeyAtIndexPath:[indexPath hardenAllItems]]; -} - -- (OpaqueKey *)privateKeyAtIndex:(uint32_t)index ofType:(KeyKind)type forSeed:(NSData *)seed { - if (!_isLocal) return nil; - const NSUInteger indexes[] = {_index | BIP32_HARD, index | BIP32_HARD}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - return [derivationPath privateKeyAtIndexPath:indexPath fromSeed:seed]; -} - -- (OpaqueKey *)publicKeyAtIndex:(uint32_t)index ofType:(KeyKind)type { - if (!_isLocal) return nil; - const NSUInteger indexes[] = {_index | BIP32_HARD, index | BIP32_HARD}; - NSIndexPath *hardenedIndexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - return [derivationPath publicKeyAtIndexPath:hardenedIndexPath]; -} - -- (OpaqueKey *)createNewKeyOfType:(KeyKind)type saveKey:(BOOL)saveKey returnIndex:(uint32_t *)rIndex { - return [self createNewKeyOfType:type saveKey:saveKey returnIndex:rIndex inContext:[NSManagedObjectContext viewContext]]; -} - -- (OpaqueKey *)createNewKeyOfType:(KeyKind)type saveKey:(BOOL)saveKey returnIndex:(uint32_t *)rIndex inContext:(NSManagedObjectContext *)context { - if (!_isLocal) return nil; - uint32_t keyIndex = self.keysCreated; - const NSUInteger indexes[] = {_index | BIP32_HARD, keyIndex | BIP32_HARD}; - NSIndexPath *hardenedIndexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - OpaqueKey *publicKey = [derivationPath publicKeyAtIndexPath:hardenedIndexPath]; - NSAssert([derivationPath hasExtendedPrivateKey], @"The derivation path should have an extended private key"); - OpaqueKey *privateKey = [derivationPath privateKeyAtIndexPath:hardenedIndexPath]; - NSAssert(privateKey, @"The private key should have been derived"); - NSAssert([DSKeyManager keysPublicKeyDataIsEqual:publicKey key2:privateKey], @"These should be equal"); - self.keysCreated++; - if (rIndex) { - *rIndex = keyIndex; - } - NSValue *publicKeyValue = [NSValue valueWithPointer:publicKey]; - NSDictionary *keyDictionary = @{@(DSBlockchainIdentityKeyDictionary_Key): publicKeyValue, @(DSBlockchainIdentityKeyDictionary_KeyType): @(type), @(DSBlockchainIdentityKeyDictionary_KeyStatus): @(DSBlockchainIdentityKeyStatus_Registering)}; - [self.keyInfoDictionaries setObject:keyDictionary forKey:@(keyIndex)]; - if (saveKey) { - [self saveNewKey:publicKey atPath:hardenedIndexPath withStatus:DSBlockchainIdentityKeyStatus_Registering fromDerivationPath:derivationPath inContext:context]; - } - return publicKey; -} - -- (uint32_t)firstIndexOfKeyOfType:(KeyKind)type createIfNotPresent:(BOOL)createIfNotPresent saveKey:(BOOL)saveKey { - for (NSNumber *indexNumber in self.keyInfoDictionaries) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[indexNumber]; - KeyKind keyTypeAtIndex = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyType)] unsignedIntValue]; - if (keyTypeAtIndex == type) { - return [indexNumber unsignedIntValue]; - } - } - if (_isLocal && createIfNotPresent) { - uint32_t rIndex; - [self createNewKeyOfType:type saveKey:saveKey returnIndex:&rIndex]; - return rIndex; - } else { - return UINT32_MAX; - } -} - -- (OpaqueKey *)keyOfType:(KeyKind)type atIndex:(uint32_t)index { - if (!_isLocal) return nil; - const NSUInteger indexes[] = {_index | BIP32_HARD, index | BIP32_HARD}; - NSIndexPath *hardenedIndexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - - OpaqueKey *key = [derivationPath publicKeyAtIndexPath:hardenedIndexPath]; - return key; -} - -- (void)addKey:(OpaqueKey *)key atIndex:(uint32_t)index ofType:(KeyKind)type withStatus:(DSBlockchainIdentityKeyStatus)status save:(BOOL)save { - [self addKey:key atIndex:index ofType:type withStatus:status save:save inContext:self.platformContext]; -} - -- (void)addKey:(OpaqueKey *)key atIndex:(uint32_t)index ofType:(KeyKind)type withStatus:(DSBlockchainIdentityKeyStatus)status save:(BOOL)save inContext:(NSManagedObjectContext *)context { - if (self.isLocal) { - const NSUInteger indexes[] = {_index, index}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - [self addKey:key atIndexPath:indexPath ofType:type withStatus:status save:save inContext:context]; - } else { - if (self.keyInfoDictionaries[@(index)]) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[@(index)]; - NSValue *keyToCheckInDictionary = keyDictionary[@(DSBlockchainIdentityKeyDictionary_Key)]; - DSBlockchainIdentityKeyStatus keyToCheckInDictionaryStatus = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyStatus)] unsignedIntegerValue]; - if ([DSKeyManager keysPublicKeyDataIsEqual:keyToCheckInDictionary.pointerValue key2:key]) { - if (save && status != keyToCheckInDictionaryStatus) { - [self updateStatus:status forKeyWithIndexID:index inContext:context]; - } - } else { - NSAssert(FALSE, @"these should really match up"); - DSLog(@"these should really match up"); - return; - } - } else { - self.keysCreated = MAX(self.keysCreated, index + 1); - if (save) { - [self saveNewRemoteIdentityKey:key forKeyWithIndexID:index withStatus:status inContext:context]; - } - } - NSDictionary *keyDictionary = @{@(DSBlockchainIdentityKeyDictionary_Key): [NSValue valueWithPointer:key], @(DSBlockchainIdentityKeyDictionary_KeyType): @(type), @(DSBlockchainIdentityKeyDictionary_KeyStatus): @(status)}; - [self.keyInfoDictionaries setObject:keyDictionary forKey:@(index)]; - } -} - -- (void)addKey:(OpaqueKey *)key atIndexPath:(NSIndexPath *)indexPath ofType:(KeyKind)type withStatus:(DSBlockchainIdentityKeyStatus)status save:(BOOL)save { - [self addKey:key atIndexPath:indexPath ofType:type withStatus:status save:save inContext:self.platformContext]; -} - -- (void)addKey:(OpaqueKey *)key atIndexPath:(NSIndexPath *)indexPath ofType:(KeyKind)type withStatus:(DSBlockchainIdentityKeyStatus)status save:(BOOL)save inContext:(NSManagedObjectContext *_Nullable)context { - NSAssert(self.isLocal, @"This should only be called on local blockchain identities"); - if (!self.isLocal) return; - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - //derivationPath will be nil if not local - - OpaqueKey *keyToCheck = [derivationPath publicKeyAtIndexPath:[indexPath hardenAllItems]]; - NSAssert(keyToCheck != nil, @"This key should be found"); - if ([DSKeyManager keysPublicKeyDataIsEqual:keyToCheck key2:key]) { //if it isn't local we shouldn't verify - uint32_t index = (uint32_t)[indexPath indexAtPosition:[indexPath length] - 1]; - if (self.keyInfoDictionaries[@(index)]) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[@(index)]; - NSValue *keyToCheckInDictionaryValue = keyDictionary[@(DSBlockchainIdentityKeyDictionary_Key)]; - if ([DSKeyManager keysPublicKeyDataIsEqual:keyToCheckInDictionaryValue.pointerValue key2:key]) { - if (save) { - [self updateStatus:status forKeyAtPath:indexPath fromDerivationPath:derivationPath inContext:context]; - } - } else { - NSAssert(FALSE, @"these should really match up"); - DSLog(@"these should really match up"); - return; - } - } else { - self.keysCreated = MAX(self.keysCreated, index + 1); - if (save) { - [self saveNewKey:key atPath:indexPath withStatus:status fromDerivationPath:derivationPath inContext:context]; - } - } - NSDictionary *keyDictionary = @{@(DSBlockchainIdentityKeyDictionary_Key): [NSValue valueWithPointer:key], @(DSBlockchainIdentityKeyDictionary_KeyType): @(type), @(DSBlockchainIdentityKeyDictionary_KeyStatus): @(status)}; - [self.keyInfoDictionaries setObject:keyDictionary forKey:@(index)]; - } else { - DSLog(@"these should really match up"); - } -} - -- (BOOL)registerKeyWithStatus:(DSBlockchainIdentityKeyStatus)status atIndexPath:(NSIndexPath *)indexPath ofType:(KeyKind)type { - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; - OpaqueKey *key = [derivationPath publicKeyAtIndexPath:[indexPath hardenAllItems]]; - if (!key) return FALSE; - uint32_t index = (uint32_t)[indexPath indexAtPosition:[indexPath length] - 1]; - self.keysCreated = MAX(self.keysCreated, index + 1); - NSDictionary *keyDictionary = @{@(DSBlockchainIdentityKeyDictionary_Key): [NSValue valueWithPointer:key], @(DSBlockchainIdentityKeyDictionary_KeyType): @(type), @(DSBlockchainIdentityKeyDictionary_KeyStatus): @(status)}; - [self.keyInfoDictionaries setObject:keyDictionary forKey:@(index)]; - return TRUE; -} - -- (void)registerKey:(OpaqueKey *)key withStatus:(DSBlockchainIdentityKeyStatus)status atIndex:(uint32_t)index ofType:(KeyKind)type { - self.keysCreated = MAX(self.keysCreated, index + 1); - NSDictionary *keyDictionary = @{@(DSBlockchainIdentityKeyDictionary_Key): [NSValue valueWithPointer:key], @(DSBlockchainIdentityKeyDictionary_KeyType): @(type), @(DSBlockchainIdentityKeyDictionary_KeyStatus): @(status)}; - [self.keyInfoDictionaries setObject:keyDictionary forKey:@(index)]; -} - -// MARK: From Remote/Network -// TODO: make sure we determine 'legacy' correctly here -+ (OpaqueKey *)keyFromKeyDictionary:(NSDictionary *)dictionary rType:(uint32_t *)rType rIndex:(uint32_t *)rIndex { - NSData *keyData = dictionary[@"data"]; - NSNumber *keyId = dictionary[@"id"]; - NSNumber *type = dictionary[@"type"]; - if (keyData && keyId && type) { - OpaqueKey *key = [DSKeyManager keyWithPublicKeyData:keyData ofType:type.intValue]; - *rIndex = [keyId unsignedIntValue]; - *rType = [type unsignedIntValue]; - return key; - } - return nil; -} - -- (void)addKeyFromKeyDictionary:(NSDictionary *)dictionary save:(BOOL)save inContext:(NSManagedObjectContext *_Nullable)context { - uint32_t index = 0; - uint32_t type = 0; - OpaqueKey *key = [DSBlockchainIdentity keyFromKeyDictionary:dictionary rType:&type rIndex:&index]; - if (key) { - [self addKey:key atIndex:index ofType:type withStatus:DSBlockchainIdentityKeyStatus_Registered save:save inContext:context]; - } -} - -// MARK: - Funding - -- (NSString *)registrationFundingAddress { - if (self.registrationCreditFundingTransaction) { - return [DSKeyManager addressFromHash160:self.registrationCreditFundingTransaction.creditBurnPublicKeyHash forChain:self.chain]; - } else { - DSCreditFundingDerivationPath *derivationPathRegistrationFunding; - if (self.isOutgoingInvitation) { - derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:self.wallet]; - } else { - derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:self.wallet]; - } - - return [derivationPathRegistrationFunding addressAtIndex:self.index]; - } -} - -- (void)fundingTransactionForTopupAmount:(uint64_t)topupAmount toAddress:(NSString *)address fundedByAccount:(DSAccount *)fundingAccount completion:(void (^_Nullable)(DSCreditFundingTransaction *fundingTransaction))completion { - DSCreditFundingTransaction *fundingTransaction = [fundingAccount creditFundingTransactionFor:topupAmount to:address withFee:YES]; - completion(fundingTransaction); -} - -// MARK: - Registration - -// MARK: Helpers - -- (BOOL)isRegistered { - return self.registrationStatus == DSBlockchainIdentityRegistrationStatus_Registered; -} - -- (NSString *)localizedRegistrationStatusString { - switch (self.registrationStatus) { - case DSBlockchainIdentityRegistrationStatus_Registered: - return DSLocalizedString(@"Registered", @"The Dash Identity is registered"); - case DSBlockchainIdentityRegistrationStatus_Unknown: - return DSLocalizedString(@"Unknown", @"It is Unknown if the Dash Identity is registered"); - case DSBlockchainIdentityRegistrationStatus_Registering: - return DSLocalizedString(@"Registering", @"The Dash Identity is being registered"); - case DSBlockchainIdentityRegistrationStatus_NotRegistered: - return DSLocalizedString(@"Not Registered", @"The Dash Identity is not registered"); - default: - break; - } - return @""; -} - -- (void)applyIdentityDictionary:(NSDictionary *)identityDictionary version:(uint32_t)version save:(BOOL)save inContext:(NSManagedObjectContext *_Nullable)context { - if (identityDictionary[@"balance"]) { - uint64_t creditBalance = (uint64_t)[identityDictionary[@"balance"] longLongValue]; - _creditBalance = creditBalance; - } - if (identityDictionary[@"publicKeys"]) { - for (NSDictionary *dictionary in identityDictionary[@"publicKeys"]) { - [self addKeyFromKeyDictionary:dictionary save:save inContext:context]; - } - } -} - -+ (OpaqueKey *)firstKeyInIdentityDictionary:(NSDictionary *)identityDictionary { - if (identityDictionary[@"publicKeys"]) { - for (NSDictionary *dictionary in identityDictionary[@"publicKeys"]) { - uint32_t index = 0; - uint32_t type = 0; - OpaqueKey *key = [DSBlockchainIdentity keyFromKeyDictionary:dictionary rType:&type rIndex:&index]; - if (index == 0) { - return key; - } - } - } - return nil; -} - -// MARK: Transition - -- (void)registrationTransitionSignedByPrivateKey:(OpaqueKey *)privateKey registeringPublicKeys:(NSDictionary *)publicKeys usingCreditFundingTransaction:(DSCreditFundingTransaction *)creditFundingTransaction completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationTransition *blockchainIdentityRegistrationTransition))completion { - DSBlockchainIdentityRegistrationTransition *blockchainIdentityRegistrationTransition = [[DSBlockchainIdentityRegistrationTransition alloc] initWithVersion:1 registeringPublicKeys:publicKeys usingCreditFundingTransaction:creditFundingTransaction onChain:self.chain]; - [blockchainIdentityRegistrationTransition signWithKey:privateKey atIndex:UINT32_MAX fromIdentity:self]; - if (completion) { - completion(blockchainIdentityRegistrationTransition); - } -} - -- (void)registrationTransitionWithCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationTransition *_Nullable blockchainIdentityRegistrationTransaction, NSError *_Nullable error))completion { - if (!self.internalRegistrationFundingPrivateKey) { - if (completion) { - completion(nil, [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity funding private key should be first created with createFundingPrivateKeyWithCompletion"]); - } - return; - } - - uint32_t index = [self firstIndexOfKeyOfType:KeyKind_ECDSA createIfNotPresent:YES saveKey:!self.wallet.isTransient]; - - OpaqueKey *publicKey = [self keyAtIndex:index]; - - NSAssert((index & ~(BIP32_HARD)) == 0, @"The index should be 0 here"); - - NSAssert(self.registrationCreditFundingTransaction, @"The registration credit funding transaction must be known"); - - if (!self.registrationCreditFundingTransaction.instantSendLockAwaitingProcessing && self.registrationCreditFundingTransaction.blockHeight == BLOCK_UNKNOWN_HEIGHT) { - if (completion) { - completion(nil, [NSError errorWithCode:500 localizedDescriptionKey:@"The registration credit funding transaction has not been mined yet and has no instant send lock"]); - } - return; - } - - [self registrationTransitionSignedByPrivateKey:self.internalRegistrationFundingPrivateKey - registeringPublicKeys:@{@(index): [NSValue valueWithPointer:publicKey]} - usingCreditFundingTransaction:self.registrationCreditFundingTransaction - completion:^(DSBlockchainIdentityRegistrationTransition *blockchainIdentityRegistrationTransaction) { - if (completion) { - completion(blockchainIdentityRegistrationTransaction, nil); - } - }]; -} - -// MARK: Registering - -- (void)createAndPublishRegistrationTransitionWithCompletion:(void (^)(NSDictionary *, NSError *))completion { - [self registrationTransitionWithCompletion:^(DSBlockchainIdentityRegistrationTransition *blockchainIdentityRegistrationTransition, NSError *registrationTransitionError) { - if (blockchainIdentityRegistrationTransition) { - [self.DAPIClient publishTransition:blockchainIdentityRegistrationTransition - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull successDictionary, BOOL added) { - [self monitorForBlockchainIdentityWithRetryCount:5 - retryAbsentCount:5 - delay:4 - retryDelayType:DSBlockchainIdentityRetryDelayType_Linear - options:DSBlockchainIdentityMonitorOptions_None - inContext:self.platformContext - completion:^(BOOL success, BOOL found, NSError *error) { - if (completion) { - completion(successDictionary, error); - } - }]; - } - failure:^(NSError *_Nonnull error) { - if (error) { - [self monitorForBlockchainIdentityWithRetryCount:1 - retryAbsentCount:1 - delay:4 - retryDelayType:DSBlockchainIdentityRetryDelayType_Linear - options:DSBlockchainIdentityMonitorOptions_None - inContext:self.platformContext - completion:^(BOOL success, BOOL found, NSError *error) { - if (completion) { - completion(nil, found ? nil : error); - } - }]; - } else { - if (completion) { - completion(nil, [NSError errorWithCode:501 localizedDescriptionKey:@"Unable to register registration transition"]); - } - } - }]; - } else { - if (completion) { - NSError *error = [NSError errorWithCode:501 localizedDescriptionKey:@"Unable to create registration transition"]; - completion(nil, registrationTransitionError ? registrationTransitionError : error); - } - } - }]; -} - -// MARK: Retrieval - -- (void)fetchIdentityNetworkStateInformationWithCompletion:(void (^)(BOOL success, BOOL found, NSError *error))completion { - dispatch_async(self.identityQueue, ^{ - [self fetchIdentityNetworkStateInformationInContext:self.platformContext withCompletion:completion]; - }); -} - -- (void)fetchIdentityNetworkStateInformationInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, BOOL found, NSError *error))completion { - //a local identity might not have been published yet - //todo retryabsentcount should be 0 if it can be proved to be absent - [self monitorForBlockchainIdentityWithRetryCount:DEFAULT_FETCH_IDENTITY_RETRY_COUNT retryAbsentCount:DEFAULT_FETCH_IDENTITY_RETRY_COUNT delay:3 retryDelayType:DSBlockchainIdentityRetryDelayType_SlowingDown50Percent options:self.isLocal ? DSBlockchainIdentityMonitorOptions_AcceptNotFoundAsNotAnError : DSBlockchainIdentityMonitorOptions_None inContext:context completion:completion]; -} - -- (void)fetchAllNetworkStateInformationWithCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion { - dispatch_async(self.identityQueue, ^{ - [self fetchAllNetworkStateInformationInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; - }); -} - -- (void)fetchAllNetworkStateInformationInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - dispatch_async(self.identityQueue, ^{ - DSBlockchainIdentityQueryStep query = DSBlockchainIdentityQueryStep_None; - if ([DSOptionsManager sharedInstance].syncType & DSSyncType_BlockchainIdentities) { - query |= DSBlockchainIdentityQueryStep_Identity; - } - if ([DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) { - query |= DSBlockchainIdentityQueryStep_Username; - } - if ([DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - query |= DSBlockchainIdentityQueryStep_Profile; - if (self.isLocal) { - query |= DSBlockchainIdentityQueryStep_ContactRequests; - } - } - [self fetchNetworkStateInformation:query - inContext:context - withCompletion:completion - onCompletionQueue:completionQueue]; - }); -} - -- (void)fetchL3NetworkStateInformation:(DSBlockchainIdentityQueryStep)queryStep withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion { - [self fetchL3NetworkStateInformation:queryStep inContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)fetchL3NetworkStateInformation:(DSBlockchainIdentityQueryStep)queryStep inContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - if (!(queryStep & DSBlockchainIdentityQueryStep_Identity) && (!self.activeKeyCount)) { - //We need to fetch keys if we want to query other information - if (completion) { - completion(DSBlockchainIdentityQueryStep_BadQuery, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Attempt to query DAPs for blockchain identity with no active keys"]]); - } - return; - } - - __block DSBlockchainIdentityQueryStep failureStep = DSBlockchainIdentityQueryStep_None; - __block NSMutableArray *groupedErrors = [NSMutableArray array]; - dispatch_group_t dispatchGroup = dispatch_group_create(); - if (queryStep & DSBlockchainIdentityQueryStep_Username) { - dispatch_group_enter(dispatchGroup); - [self fetchUsernamesInContext:context - withCompletion:^(BOOL success, NSError *error) { - failureStep |= success & DSBlockchainIdentityQueryStep_Username; - if (error) { - [groupedErrors addObject:error]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } - - if (queryStep & DSBlockchainIdentityQueryStep_Profile) { - dispatch_group_enter(dispatchGroup); - [self fetchProfileInContext:context - withCompletion:^(BOOL success, NSError *error) { - failureStep |= success & DSBlockchainIdentityQueryStep_Profile; - if (error) { - [groupedErrors addObject:error]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } - - if (queryStep & DSBlockchainIdentityQueryStep_OutgoingContactRequests) { - dispatch_group_enter(dispatchGroup); - [self fetchOutgoingContactRequestsInContext:context - withCompletion:^(BOOL success, NSArray *errors) { - failureStep |= success & DSBlockchainIdentityQueryStep_OutgoingContactRequests; - if ([errors count]) { - [groupedErrors addObjectsFromArray:errors]; - dispatch_group_leave(dispatchGroup); - } else { - if (queryStep & DSBlockchainIdentityQueryStep_IncomingContactRequests) { - [self fetchIncomingContactRequestsInContext:context - withCompletion:^(BOOL success, NSArray *errors) { - failureStep |= success & DSBlockchainIdentityQueryStep_IncomingContactRequests; - if ([errors count]) { - [groupedErrors addObjectsFromArray:errors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } else { - dispatch_group_leave(dispatchGroup); - } - } - } - onCompletionQueue:self.identityQueue]; - } else if (queryStep & DSBlockchainIdentityQueryStep_IncomingContactRequests) { - dispatch_group_enter(dispatchGroup); - [self fetchIncomingContactRequestsInContext:context - withCompletion:^(BOOL success, NSArray *errors) { - failureStep |= success & DSBlockchainIdentityQueryStep_IncomingContactRequests; - if ([errors count]) { - [groupedErrors addObjectsFromArray:errors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } - - __weak typeof(self) weakSelf = self; - if (completion) { - dispatch_group_notify(dispatchGroup, self.identityQueue, ^{ -#if DEBUG - DSLogPrivate(@"Completed fetching of blockchain identity information for user %@ (query %lu - failures %lu)", - self.currentDashpayUsername ? self.currentDashpayUsername : self.uniqueIdString, - (unsigned long)queryStep, - failureStep); -#else - DSLog(@"Completed fetching of blockchain identity information for user %@ (query %lu - failures %lu)", - @"", - (unsigned long)queryStep, - failureStep); -#endif /* DEBUG */ - if (!(failureStep & DSBlockchainIdentityQueryStep_ContactRequests)) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - //todo This needs to be eventually set with the blockchain returned by platform. - strongSelf.dashpaySyncronizationBlockHash = strongSelf.chain.lastTerminalBlock.blockHash; - } - dispatch_async(completionQueue, ^{ - completion(failureStep, [groupedErrors copy]); - }); - }); - } -} - -- (void)fetchNetworkStateInformation:(DSBlockchainIdentityQueryStep)querySteps withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion { - [self fetchNetworkStateInformation:querySteps inContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)fetchNetworkStateInformation:(DSBlockchainIdentityQueryStep)querySteps inContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - if (querySteps & DSBlockchainIdentityQueryStep_Identity) { - [self fetchIdentityNetworkStateInformationWithCompletion:^(BOOL success, BOOL found, NSError *error) { - if (!success) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(DSBlockchainIdentityQueryStep_Identity, error ? @[error] : @[]); - }); - } - return; - } - if (!found) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(DSBlockchainIdentityQueryStep_NoIdentity, @[]); - }); - } - return; - } - [self fetchL3NetworkStateInformation:querySteps - inContext:context - withCompletion:completion - onCompletionQueue:completionQueue]; - }]; - } else { - NSAssert([self blockchainIdentityEntityInContext:context], @"Blockchain identity entity should be known"); - [self fetchL3NetworkStateInformation:querySteps inContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } -} - -- (void)fetchIfNeededNetworkStateInformation:(DSBlockchainIdentityQueryStep)querySteps inContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - dispatch_async(self.identityQueue, ^{ - if (!self.activeKeyCount) { - if (self.isLocal) { - [self fetchNetworkStateInformation:querySteps inContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } else { - DSBlockchainIdentityQueryStep stepsNeeded = DSBlockchainIdentityQueryStep_None; - if ([DSOptionsManager sharedInstance].syncType & DSSyncType_BlockchainIdentities) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Identity; - } - if (![self.dashpayUsernameFullPaths count] && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Username; - } - if ((self.lastCheckedProfileTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Profile; - } - if (stepsNeeded == DSBlockchainIdentityQueryStep_None) { - if (completion) { - completion(DSBlockchainIdentityQueryStep_None, @[]); - } - } else { - [self fetchNetworkStateInformation:stepsNeeded & querySteps inContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } - } - } else { - DSBlockchainIdentityQueryStep stepsNeeded = DSBlockchainIdentityQueryStep_None; - if (![self.dashpayUsernameFullPaths count] && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Username; - } - __block uint64_t createdAt; - [context performBlockAndWait:^{ - createdAt = [[self matchingDashpayUserInContext:context] createdAt]; - }]; - if (!createdAt && (self.lastCheckedProfileTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Profile; - } - if (self.isLocal && (self.lastCheckedIncomingContactsTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_IncomingContactRequests; - } - if (self.isLocal && (self.lastCheckedOutgoingContactsTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_OutgoingContactRequests; - } - if (stepsNeeded == DSBlockchainIdentityQueryStep_None) { - if (completion) { - completion(DSBlockchainIdentityQueryStep_None, @[]); - } - } else { - [self fetchNetworkStateInformation:stepsNeeded & querySteps inContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } - } - }); -} - -- (void)fetchNeededNetworkStateInformationWithCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion { - [self fetchNeededNetworkStateInformationInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)fetchNeededNetworkStateInformationInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSBlockchainIdentityQueryStep failureStep, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - dispatch_async(self.identityQueue, ^{ - if (!self.activeKeyCount) { - if (self.isLocal) { - [self fetchAllNetworkStateInformationWithCompletion:completion]; - } else { - DSBlockchainIdentityQueryStep stepsNeeded = DSBlockchainIdentityQueryStep_None; - if ([DSOptionsManager sharedInstance].syncType & DSSyncType_BlockchainIdentities) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Identity; - } - if (![self.dashpayUsernameFullPaths count] && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Username; - } - if ((self.lastCheckedProfileTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Profile; - } - if (stepsNeeded == DSBlockchainIdentityQueryStep_None) { - if (completion) { - completion(DSBlockchainIdentityQueryStep_None, @[]); - } - } else { - [self fetchNetworkStateInformation:stepsNeeded inContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } - } - } else { - DSBlockchainIdentityQueryStep stepsNeeded = DSBlockchainIdentityQueryStep_None; - if (![self.dashpayUsernameFullPaths count] && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Username; - } - if (![[self matchingDashpayUserInContext:context] createdAt] && (self.lastCheckedProfileTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_Profile; - } - if (self.isLocal && (self.lastCheckedIncomingContactsTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_IncomingContactRequests; - } - if (self.isLocal && (self.lastCheckedOutgoingContactsTimestamp < [[NSDate date] timeIntervalSince1970] - HOUR_TIME_INTERVAL) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { - stepsNeeded |= DSBlockchainIdentityQueryStep_OutgoingContactRequests; - } - if (stepsNeeded == DSBlockchainIdentityQueryStep_None) { - if (completion) { - completion(DSBlockchainIdentityQueryStep_None, @[]); - } - } else { - [self fetchNetworkStateInformation:stepsNeeded inContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } - } - }); -} - -// MARK: - Platform Helpers - -- (DPDocumentFactory *)dashpayDocumentFactory { - if (!_dashpayDocumentFactory) { - DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - NSAssert(contract, @"Contract must be defined"); - self.dashpayDocumentFactory = [[DPDocumentFactory alloc] initWithBlockchainIdentity:self contract:contract onChain:self.chain]; - } - return _dashpayDocumentFactory; -} - -- (DPDocumentFactory *)dpnsDocumentFactory { - if (!_dpnsDocumentFactory) { - DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - NSAssert(contract, @"Contract must be defined"); - self.dpnsDocumentFactory = [[DPDocumentFactory alloc] initWithBlockchainIdentity:self contract:contract onChain:self.chain]; - } - return _dpnsDocumentFactory; -} - -- (DSDAPIClient *)DAPIClient { - return self.chain.chainManager.DAPIClient; -} - -- (DSDAPIPlatformNetworkService *)DAPINetworkService { - return self.DAPIClient.DAPIPlatformNetworkService; -} - -// MARK: - Signing and Encryption - -- (void)signStateTransition:(DSTransition *)transition forKeyIndex:(uint32_t)keyIndex ofType:(KeyKind)signingAlgorithm completion:(void (^_Nullable)(BOOL success))completion { - NSParameterAssert(transition); - - OpaqueKey *privateKey = [self privateKeyAtIndex:keyIndex ofType:signingAlgorithm]; - NSAssert(privateKey, @"The private key should exist"); - NSAssert([DSKeyManager keysPublicKeyDataIsEqual:privateKey key2:[self publicKeyAtIndex:keyIndex ofType:signingAlgorithm]], @"These should be equal"); - // NSLog(@"%@",uint160_hex(self.blockchainIdentityRegistrationTransition.pubkeyHash)); - // NSAssert(uint160_eq(privateKey.publicKeyData.hash160,self.blockchainIdentityRegistrationTransition.pubkeyHash),@"Keys aren't ok"); - [transition signWithKey:privateKey atIndex:keyIndex fromIdentity:self]; - if (completion) { - completion(YES); - } -} - -- (void)signStateTransition:(DSTransition *)transition completion:(void (^_Nullable)(BOOL success))completion { - if (!self.keysCreated) { - uint32_t index; - [self createNewKeyOfType:DEFAULT_SIGNING_ALGORITHM saveKey:!self.wallet.isTransient returnIndex:&index]; - } - return [self signStateTransition:transition forKeyIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType completion:completion]; -} - -- (void)signMessageDigest:(UInt256)digest forKeyIndex:(uint32_t)keyIndex ofType:(KeyKind)signingAlgorithm completion:(void (^_Nullable)(BOOL success, NSData *signature))completion { - NSParameterAssert(completion); - OpaqueKey *privateKey = [self privateKeyAtIndex:keyIndex ofType:signingAlgorithm]; - NSAssert(privateKey, @"The private key should exist"); - NSAssert([DSKeyManager keysPublicKeyDataIsEqual:privateKey key2:[self publicKeyAtIndex:keyIndex ofType:signingAlgorithm]], @"These should be equal"); - NSParameterAssert(privateKey); - DSLogPrivate(@"Signing %@ with key %@", uint256_hex(digest), [DSKeyManager publicKeyData:privateKey].hexString); - NSData *signature = [DSKeyManager signMesasageDigest:privateKey digest:digest]; - completion(!signature.isZeroBytes, signature); -} - -- (BOOL)verifySignature:(NSData *)signature ofType:(KeyKind)signingAlgorithm forMessageDigest:(UInt256)messageDigest { - for (NSValue *publicKey in [self activeKeysForKeyType:signingAlgorithm]) { - BOOL verified = key_verify_message_digest(publicKey.pointerValue, messageDigest.u8, signature.bytes, signature.length); - if (verified) { - return TRUE; - } - } - return FALSE; -} - -- (BOOL)verifySignature:(NSData *)signature forKeyIndex:(uint32_t)keyIndex ofType:(KeyKind)signingAlgorithm forMessageDigest:(UInt256)messageDigest { - OpaqueKey *publicKey = [self publicKeyAtIndex:keyIndex ofType:signingAlgorithm]; - BOOL verified = [DSKeyManager verifyMessageDigest:publicKey digest:messageDigest signature:signature]; - // TODO: check we need to destroy here - processor_destroy_opaque_key(publicKey); - return verified; -} - -- (void)encryptData:(NSData *)data withKeyAtIndex:(uint32_t)index forRecipientKey:(OpaqueKey *)recipientPublicKey completion:(void (^_Nullable)(NSData *encryptedData))completion { - NSParameterAssert(data); - NSParameterAssert(recipientPublicKey); - OpaqueKey *privateKey = [self privateKeyAtIndex:index ofType:(int16_t) recipientPublicKey->tag]; - NSData *encryptedData = [data encryptWithSecretKey:privateKey forPublicKey:recipientPublicKey]; - // TODO: destroy opqaque pointer here? - processor_destroy_opaque_key(privateKey); - if (completion) { - completion(encryptedData); - } -} - -- (void)decryptData:(NSData *)encryptedData withKeyAtIndex:(uint32_t)index fromSenderKey:(OpaqueKey *)senderPublicKey completion:(void (^_Nullable)(NSData *decryptedData))completion { - OpaqueKey *privateKey = [self privateKeyAtIndex:index ofType:(KeyKind)senderPublicKey->tag]; - // TODO: destroy pointers here? - NSData *data = [encryptedData decryptWithSecretKey:privateKey fromPublicKey:senderPublicKey]; - if (completion) { - completion(data); - } -} - -// MARK: - Contracts - -- (void)fetchAndUpdateContract:(DPContract *)contract { - return [self fetchAndUpdateContract:contract inContext:self.platformContext]; -} - -- (void)fetchAndUpdateContract:(DPContract *)contract inContext:(NSManagedObjectContext *)context { - __weak typeof(contract) weakContract = contract; - __weak typeof(self) weakSelf = self; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //this is so we don't get DAPINetworkService immediately - BOOL isDPNSEmpty = [contract.name isEqual:@"DPNS"] && uint256_is_zero(self.chain.dpnsContractID); - BOOL isDashpayEmpty = [contract.name isEqual:@"DashPay"] && uint256_is_zero(self.chain.dashpayContractID); - BOOL isOtherContract = !([contract.name isEqual:@"DashPay"] || [contract.name isEqual:@"DPNS"]); - if (((isDPNSEmpty || isDashpayEmpty || isOtherContract) && uint256_is_zero(contract.registeredBlockchainIdentityUniqueID)) || contract.contractState == DPContractState_NotRegistered) { - [contract registerCreator:self inContext:context]; - __block DSContractTransition *transition = [contract contractRegistrationTransitionForIdentity:self]; - [self signStateTransition:transition - completion:^(BOOL success) { - if (success) { - [self.DAPIClient publishTransition:transition - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull successDictionary, BOOL added) { - __strong typeof(weakContract) strongContract = weakContract; - if (!strongContract) { - return; - } - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [strongContract setContractState:DPContractState_Registering - inContext:context]; - [strongSelf monitorForContract:strongContract - withRetryCount:2 - inContext:context - completion:^(BOOL success, NSError *error){ - - }]; - } - failure:^(NSError *_Nonnull error) { - //maybe it was already registered - __strong typeof(weakContract) strongContract = weakContract; - if (!strongContract) { - return; - } - [strongContract setContractState:DPContractState_Unknown - inContext:context]; - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [strongSelf monitorForContract:strongContract - withRetryCount:2 - inContext:context - completion:^(BOOL success, NSError *error){ - - }]; - }]; - } - }]; - - } else if (contract.contractState == DPContractState_Registered || contract.contractState == DPContractState_Registering) { - DSLog(@"Fetching contract for verification %@", contract.base58ContractId); - [self.DAPINetworkService fetchContractForId:uint256_data(contract.contractId) - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull contractDictionary) { - __strong typeof(weakContract) strongContract = weakContract; - if (!weakContract) { - return; - } - if (!contractDictionary[@"documents"]) { - [strongContract setContractState:DPContractState_NotRegistered inContext:context]; - return; - } - if (strongContract.contractState == DPContractState_Registered) { - NSSet *set1 = [NSSet setWithArray:[contractDictionary[@"documents"] allKeys]]; - NSSet *set2 = [NSSet setWithArray:[strongContract.documents allKeys]]; - - if (![set1 isEqualToSet:set2]) { - [strongContract setContractState:DPContractState_NotRegistered inContext:context]; - } - DSLog(@"Contract dictionary is %@", contractDictionary); - } - } - failure:^(NSError *_Nonnull error) { - NSString *debugDescription1 = [error.userInfo objectForKey:@"NSDebugDescription"]; - NSError *jsonError; - NSData *objectData = [debugDescription1 dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary *debugDescription = [NSJSONSerialization JSONObjectWithData:objectData options:0 error:&jsonError]; - //NSDictionary * debugDescription = - __unused NSString *errorMessage = debugDescription[@"grpc_message"]; //!OCLINT - if (TRUE) { //[errorMessage isEqualToString:@"Invalid argument: Contract not found"]) { - __strong typeof(weakContract) strongContract = weakContract; - if (!strongContract) { - return; - } - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [strongContract setContractState:DPContractState_NotRegistered - inContext:context]; - } - }]; - } - }); -} - -- (void)fetchAndUpdateContractWithBase58Identifier:(NSString *)base58Identifier { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //this is so we don't get DAPINetworkService immediately - [self.DAPINetworkService fetchContractForId:base58Identifier.base58ToData - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull contract) { - //[DPContract contr] - } - failure:^(NSError *_Nonnull error){ - - }]; - }); -} - -// MARK: - DPNS - -// MARK: Usernames - -- (void)addDashpayUsername:(NSString *)username save:(BOOL)save { - [self addUsername:username inDomain:[self dashpayDomainName] status:DSBlockchainIdentityUsernameStatus_Initial save:save registerOnNetwork:YES]; -} - -- (void)addUsername:(NSString *)username inDomain:(NSString *)domain save:(BOOL)save { - [self addUsername:username inDomain:domain status:DSBlockchainIdentityUsernameStatus_Initial save:save registerOnNetwork:YES]; -} - -- (void)addUsername:(NSString *)username inDomain:(NSString *)domain status:(DSBlockchainIdentityUsernameStatus)status save:(BOOL)save registerOnNetwork:(BOOL)registerOnNetwork { - [self.usernameStatuses setObject:@{BLOCKCHAIN_USERNAME_STATUS: @(DSBlockchainIdentityUsernameStatus_Initial), BLOCKCHAIN_USERNAME_PROPER: username, BLOCKCHAIN_USERNAME_DOMAIN: domain} forKey:[self fullPathForUsername:username inDomain:domain]]; - if (save) { - dispatch_async(self.identityQueue, ^{ - [self saveNewUsername:username inDomain:domain status:DSBlockchainIdentityUsernameStatus_Initial inContext:self.platformContext]; - if (registerOnNetwork && self.registered && status != DSBlockchainIdentityUsernameStatus_Confirmed) { - [self registerUsernamesWithCompletion:^(BOOL success, NSError *_Nonnull error){ - - }]; - } - }); - } -} - -- (DSBlockchainIdentityUsernameStatus)statusOfUsername:(NSString *)username inDomain:(NSString *)domain { - return [self statusOfUsernameFullPath:[self fullPathForUsername:username inDomain:domain]]; -} - -- (DSBlockchainIdentityUsernameStatus)statusOfDashpayUsername:(NSString *)username { - return [self statusOfUsernameFullPath:[self fullPathForUsername:username inDomain:[self dashpayDomainName]]]; -} - -- (DSBlockchainIdentityUsernameStatus)statusOfUsernameFullPath:(NSString *)usernameFullPath { - return [[[self.usernameStatuses objectForKey:usernameFullPath] objectForKey:BLOCKCHAIN_USERNAME_STATUS] unsignedIntegerValue]; -} - -- (NSString *)usernameOfUsernameFullPath:(NSString *)usernameFullPath { - return [[self.usernameStatuses objectForKey:usernameFullPath] objectForKey:BLOCKCHAIN_USERNAME_PROPER]; -} - -- (NSString *)domainOfUsernameFullPath:(NSString *)usernameFullPath { - return [[self.usernameStatuses objectForKey:usernameFullPath] objectForKey:BLOCKCHAIN_USERNAME_DOMAIN]; -} - -- (NSString *)fullPathForUsername:(NSString *)username inDomain:(NSString *)domain { - NSString *fullPath = [[username lowercaseString] stringByAppendingFormat:@".%@", [domain lowercaseString]]; - return fullPath; -} - -- (NSArray *)dashpayUsernameFullPaths { - return [self.usernameStatuses allKeys]; -} - -- (NSArray *)dashpayUsernames { - NSMutableArray *usernameArray = [NSMutableArray array]; - for (NSString *usernameFullPath in self.usernameStatuses) { - [usernameArray addObject:[self usernameOfUsernameFullPath:usernameFullPath]]; - } - return [usernameArray copy]; -} - -- (NSArray *)unregisteredUsernameFullPaths { - return [self usernameFullPathsWithStatus:DSBlockchainIdentityUsernameStatus_Initial]; -} - -- (NSArray *)usernameFullPathsWithStatus:(DSBlockchainIdentityUsernameStatus)usernameStatus { - NSMutableArray *unregisteredUsernames = [NSMutableArray array]; - for (NSString *username in self.usernameStatuses) { - NSDictionary *usernameInfo = self.usernameStatuses[username]; - DSBlockchainIdentityUsernameStatus status = [usernameInfo[BLOCKCHAIN_USERNAME_STATUS] unsignedIntegerValue]; - if (status == usernameStatus) { - [unregisteredUsernames addObject:username]; - } - } - return [unregisteredUsernames copy]; -} - -- (NSArray *)preorderedUsernameFullPaths { - NSMutableArray *unregisteredUsernames = [NSMutableArray array]; - for (NSString *username in self.usernameStatuses) { - NSDictionary *usernameInfo = self.usernameStatuses[username]; - DSBlockchainIdentityUsernameStatus status = [usernameInfo[BLOCKCHAIN_USERNAME_STATUS] unsignedIntegerValue]; - if (status == DSBlockchainIdentityUsernameStatus_Preordered) { - [unregisteredUsernames addObject:username]; - } - } - return [unregisteredUsernames copy]; -} - -// MARK: Username Helpers - -- (NSData *)saltForUsernameFullPath:(NSString *)usernameFullPath saveSalt:(BOOL)saveSalt inContext:(NSManagedObjectContext *)context { - NSData *salt; - if ([self statusOfUsernameFullPath:usernameFullPath] == DSBlockchainIdentityUsernameStatus_Initial || !(salt = [self.usernameSalts objectForKey:usernameFullPath])) { - UInt256 random256 = uint256_random; - salt = uint256_data(random256); - [self.usernameSalts setObject:salt forKey:usernameFullPath]; - if (saveSalt) { - [self saveUsername:[self usernameOfUsernameFullPath:usernameFullPath] inDomain:[self domainOfUsernameFullPath:usernameFullPath] status:[self statusOfUsernameFullPath:usernameFullPath] salt:salt commitSave:YES inContext:context]; - } - } else { - salt = [self.usernameSalts objectForKey:usernameFullPath]; - } - return salt; -} - -- (NSMutableDictionary *)saltedDomainHashesForUsernameFullPaths:(NSArray *)usernameFullPaths inContext:(NSManagedObjectContext *)context { - NSMutableDictionary *mSaltedDomainHashes = [NSMutableDictionary dictionary]; - for (NSString *unregisteredUsernameFullPath in usernameFullPaths) { - NSMutableData *saltedDomain = [NSMutableData data]; - NSData *salt = [self saltForUsernameFullPath:unregisteredUsernameFullPath saveSalt:YES inContext:context]; - NSData *usernameDomainData = [unregisteredUsernameFullPath dataUsingEncoding:NSUTF8StringEncoding]; - [saltedDomain appendData:salt]; - [saltedDomain appendData:usernameDomainData]; - mSaltedDomainHashes[unregisteredUsernameFullPath] = uint256_data([saltedDomain SHA256_2]); - [self.usernameSalts setObject:salt forKey:unregisteredUsernameFullPath]; - } - return [mSaltedDomainHashes copy]; -} - -- (NSString *)dashpayDomainName { - return @"dash"; -} - -// MARK: Documents - -- (NSArray *)preorderDocumentsForUnregisteredUsernameFullPaths:(NSArray *)unregisteredUsernameFullPaths usingEntropyData:(NSData *)entropyData inContext:(NSManagedObjectContext *)context error:(NSError **)error { - NSMutableArray *usernamePreorderDocuments = [NSMutableArray array]; - for (NSData *saltedDomainHashData in [[self saltedDomainHashesForUsernameFullPaths:unregisteredUsernameFullPaths inContext:context] allValues]) { - DSStringValueDictionary *dataDictionary = @{ - @"saltedDomainHash": saltedDomainHashData - }; - DPDocument *document = [self.dpnsDocumentFactory documentOnTable:@"preorder" withDataDictionary:dataDictionary usingEntropy:entropyData error:error]; - if (*error) { - return nil; - } - [usernamePreorderDocuments addObject:document]; - } - return usernamePreorderDocuments; -} - -- (NSArray *)domainDocumentsForUnregisteredUsernameFullPaths:(NSArray *)unregisteredUsernameFullPaths usingEntropyData:(NSData *)entropyData inContext:(NSManagedObjectContext *)context error:(NSError **)error { - NSMutableArray *usernameDomainDocuments = [NSMutableArray array]; - for (NSString *usernameFullPath in [self saltedDomainHashesForUsernameFullPaths:unregisteredUsernameFullPaths inContext:context]) { - NSString *username = [self usernameOfUsernameFullPath:usernameFullPath]; - NSString *domain = [self domainOfUsernameFullPath:usernameFullPath]; - DSStringValueDictionary *dataDictionary = @{ - @"label": username, - @"normalizedLabel": [username lowercaseString], - @"normalizedParentDomainName": domain, - @"preorderSalt": [self.usernameSalts objectForKey:usernameFullPath], - @"records": @{@"dashUniqueIdentityId": uint256_data(self.uniqueID)}, - @"subdomainRules": @{@"allowSubdomains": @NO} - }; - DPDocument *document = [self.dpnsDocumentFactory documentOnTable:@"domain" withDataDictionary:dataDictionary usingEntropy:entropyData error:error]; - if (*error) { - return nil; - } - [usernameDomainDocuments addObject:document]; - } - return usernameDomainDocuments; -} - -// MARK: Transitions - -- (DSDocumentTransition *)preorderTransitionForUnregisteredUsernameFullPaths:(NSArray *)unregisteredUsernameFullPaths inContext:(NSManagedObjectContext *)context error:(NSError **)error { - NSData *entropyData = uint256_random_data; - NSArray *usernamePreorderDocuments = [self preorderDocumentsForUnregisteredUsernameFullPaths:unregisteredUsernameFullPaths usingEntropyData:entropyData inContext:context error:error]; - if (![usernamePreorderDocuments count]) return nil; - DSDocumentTransition *transition = [[DSDocumentTransition alloc] initForDocuments:usernamePreorderDocuments withTransitionVersion:1 blockchainIdentityUniqueId:self.uniqueID onChain:self.chain]; - return transition; -} - -- (DSDocumentTransition *)domainTransitionForUnregisteredUsernameFullPaths:(NSArray *)unregisteredUsernameFullPaths inContext:(NSManagedObjectContext *)context error:(NSError **)error { - NSData *entropyData = uint256_random_data; - NSArray *usernamePreorderDocuments = [self domainDocumentsForUnregisteredUsernameFullPaths:unregisteredUsernameFullPaths usingEntropyData:entropyData inContext:context error:error]; - if (![usernamePreorderDocuments count]) return nil; - DSDocumentTransition *transition = [[DSDocumentTransition alloc] initForDocuments:usernamePreorderDocuments withTransitionVersion:1 blockchainIdentityUniqueId:self.uniqueID onChain:self.chain]; - return transition; -} - -// MARK: Registering - -- (void)registerUsernamesWithCompletion:(void (^_Nullable)(BOOL success, NSError *error))completion { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_Initial inContext:self.platformContext completion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)registerUsernamesAtStage:(DSBlockchainIdentityUsernameStatus)blockchainIdentityUsernameStatus inContext:(NSManagedObjectContext *)context completion:(void (^_Nullable)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - DSLog(@"registerUsernamesAtStage %lu", (unsigned long)blockchainIdentityUsernameStatus); - switch (blockchainIdentityUsernameStatus) { - case DSBlockchainIdentityUsernameStatus_Initial: { - NSArray *usernameFullPaths = [self usernameFullPathsWithStatus:DSBlockchainIdentityUsernameStatus_Initial]; - if (usernameFullPaths.count) { - [self registerPreorderedSaltedDomainHashesForUsernameFullPaths:usernameFullPaths - inContext:context - completion:^(BOOL success, NSError *error) { - if (success) { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_PreorderRegistrationPending inContext:context completion:completion onCompletionQueue:completionQueue]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - } - } - onCompletionQueue:self.identityQueue]; - } else { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_PreorderRegistrationPending inContext:context completion:completion onCompletionQueue:completionQueue]; - } - break; - } - case DSBlockchainIdentityUsernameStatus_PreorderRegistrationPending: { - NSArray *usernameFullPaths = [self usernameFullPathsWithStatus:DSBlockchainIdentityUsernameStatus_PreorderRegistrationPending]; - NSDictionary *saltedDomainHashes = [self saltedDomainHashesForUsernameFullPaths:usernameFullPaths inContext:context]; - if (saltedDomainHashes.count) { - [self monitorForDPNSPreorderSaltedDomainHashes:saltedDomainHashes - withRetryCount:4 - inContext:context - completion:^(BOOL allFound, NSError *error) { - if (!error) { - if (!allFound) { - //todo: This needs to be done per username and not for all usernames - [self setAndSaveUsernameFullPaths:usernameFullPaths toStatus:DSBlockchainIdentityUsernameStatus_Initial inContext:context]; - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_Initial inContext:context completion:completion onCompletionQueue:completionQueue]; - } else { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_Preordered inContext:context completion:completion onCompletionQueue:completionQueue]; - } - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - } - } - onCompletionQueue:self.identityQueue]; - } else { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_Preordered inContext:context completion:completion onCompletionQueue:completionQueue]; - } - break; - } - case DSBlockchainIdentityUsernameStatus_Preordered: { - NSArray *usernameFullPaths = [self usernameFullPathsWithStatus:DSBlockchainIdentityUsernameStatus_Preordered]; - if (usernameFullPaths.count) { - [self registerUsernameDomainsForUsernameFullPaths:usernameFullPaths - inContext:context - completion:^(BOOL success, NSError *error) { - if (success) { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_RegistrationPending inContext:context completion:completion onCompletionQueue:completionQueue]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - } - } - onCompletionQueue:self.identityQueue]; - } else { - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_RegistrationPending inContext:context completion:completion onCompletionQueue:completionQueue]; - } - break; - } - case DSBlockchainIdentityUsernameStatus_RegistrationPending: { - NSArray *usernameFullPaths = [self usernameFullPathsWithStatus:DSBlockchainIdentityUsernameStatus_RegistrationPending]; - if (usernameFullPaths.count) { - [self monitorForDPNSUsernameFullPaths:usernameFullPaths - withRetryCount:5 - inContext:context - completion:^(BOOL allFound, NSError *error) { - if (!error) { - if (!allFound) { - //todo: This needs to be done per username and not for all usernames - [self setAndSaveUsernameFullPaths:usernameFullPaths toStatus:DSBlockchainIdentityUsernameStatus_Preordered inContext:context]; - [self registerUsernamesAtStage:DSBlockchainIdentityUsernameStatus_Preordered inContext:context completion:completion onCompletionQueue:completionQueue]; - } else { - //all were found - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - } - - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - } - } - onCompletionQueue:completionQueue]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - } - break; - } - default: - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil); - }); - } - break; - } -} - -//Preorder stage -- (void)registerPreorderedSaltedDomainHashesForUsernameFullPaths:(NSArray *)usernameFullPaths inContext:(NSManagedObjectContext *)context completion:(void (^_Nullable)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSError *error = nil; - DSDocumentTransition *transition = [self preorderTransitionForUnregisteredUsernameFullPaths:usernameFullPaths inContext:context error:&error]; - if (error || !transition) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - return; - } - [self signStateTransition:transition - completion:^(BOOL success) { - if (success) { - //let's start by putting the usernames in an undetermined state - [self setAndSaveUsernameFullPaths:usernameFullPaths toStatus:DSBlockchainIdentityUsernameStatus_PreorderRegistrationPending inContext:context]; - [self.DAPIClient publishTransition:transition - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull successDictionary, BOOL added) { - [self setAndSaveUsernameFullPaths:usernameFullPaths toStatus:DSBlockchainIdentityUsernameStatus_Preordered inContext:context]; - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - } - failure:^(NSError *_Nonnull error) { - DSLogPrivate(@"%@", error); - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - }]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:501 localizedDescriptionKey:@"Unable to sign transition"]); - }); - } - } - }]; -} - -- (void)registerUsernameDomainsForUsernameFullPaths:(NSArray *)usernameFullPaths inContext:(NSManagedObjectContext *)context completion:(void (^_Nullable)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSError *error = nil; - DSDocumentTransition *transition = [self domainTransitionForUnregisteredUsernameFullPaths:usernameFullPaths inContext:context error:&error]; - if (error || !transition) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - return; - } - [self signStateTransition:transition - completion:^(BOOL success) { - if (success) { - [self setAndSaveUsernameFullPaths:usernameFullPaths toStatus:DSBlockchainIdentityUsernameStatus_RegistrationPending inContext:context]; - [self.DAPIClient publishTransition:transition - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull successDictionary, BOOL added) { - [self setAndSaveUsernameFullPaths:usernameFullPaths toStatus:DSBlockchainIdentityUsernameStatus_Confirmed inContext:context]; - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - } - failure:^(NSError *_Nonnull error) { - DSLogPrivate(@"%@", error); - - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - }]; - } - }]; -} - -// MARK: Retrieval - -- (void)fetchUsernamesWithCompletion:(void (^)(BOOL, NSError *_Nonnull))completion { - [self fetchUsernamesInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)fetchUsernamesInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self fetchUsernamesInContext:context retryCount:DEFAULT_FETCH_USERNAMES_RETRY_COUNT withCompletion:completion onCompletionQueue:completionQueue]; -} - -- (void)fetchUsernamesInContext:(NSManagedObjectContext *)context retryCount:(uint32_t)retryCount withCompletion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self internalFetchUsernamesInContext:context - withCompletion:^(BOOL success, NSError *error) { - if (!success && retryCount > 0) { - [self fetchUsernamesInContext:context retryCount:retryCount - 1 withCompletion:completion onCompletionQueue:completionQueue]; - } else if (completion) { - completion(success, error); - } - } - onCompletionQueue:completionQueue]; -} - -- (void)internalFetchUsernamesInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - __weak typeof(self) weakSelf = self; - DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; - if (contract.contractState != DPContractState_Registered) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"DPNS Contract is not yet registered on network"]); - }); - } - return; - } - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService getDPNSDocumentsForIdentityWithUserId:self.uniqueIDData - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - }); - } - return; - } - if (![documents count]) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - return; - } - //todo verify return is true - for (NSDictionary *nameDictionary in documents) { - NSString *username = nameDictionary[@"label"]; - NSString *lowercaseUsername = nameDictionary[@"normalizedLabel"]; - NSString *domain = nameDictionary[@"normalizedParentDomainName"]; - if (username && lowercaseUsername && domain) { - NSMutableDictionary *usernameStatusDictionary = [[self.usernameStatuses objectForKey:[self fullPathForUsername:lowercaseUsername inDomain:domain]] mutableCopy]; - BOOL isNew = FALSE; - if (!usernameStatusDictionary) { - usernameStatusDictionary = [NSMutableDictionary dictionary]; - isNew = TRUE; - usernameStatusDictionary[BLOCKCHAIN_USERNAME_DOMAIN] = domain; - usernameStatusDictionary[BLOCKCHAIN_USERNAME_PROPER] = username; - } - usernameStatusDictionary[BLOCKCHAIN_USERNAME_STATUS] = @(DSBlockchainIdentityUsernameStatus_Confirmed); - [self.usernameStatuses setObject:[usernameStatusDictionary copy] forKey:[self fullPathForUsername:username inDomain:domain]]; - if (isNew) { - [self saveNewUsername:username inDomain:domain status:DSBlockchainIdentityUsernameStatus_Confirmed inContext:context]; - } else { - [self saveUsername:username inDomain:domain status:DSBlockchainIdentityUsernameStatus_Confirmed salt:nil commitSave:YES inContext:context]; - } - } - } - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - [self fetchUsernamesInContext:context withCompletion:completion onCompletionQueue:completionQueue]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - } - }]; -} - - -// MARK: - Monitoring - -- (void)updateCreditBalance { - __weak typeof(self) weakSelf = self; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //this is so we don't get DAPINetworkService immediately - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService getIdentityById:self.uniqueIDData - completionQueue:self.identityQueue - success:^(NSDictionary *_Nullable profileDictionary) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - dispatch_async(self.identityQueue, ^{ - uint64_t creditBalance = (uint64_t)[profileDictionary[@"balance"] longLongValue]; - strongSelf.creditBalance = creditBalance; - }); - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - } - }]; - }); -} - -- (void)monitorForBlockchainIdentityWithRetryCount:(uint32_t)retryCount retryAbsentCount:(uint32_t)retryAbsentCount delay:(NSTimeInterval)delay retryDelayType:(DSBlockchainIdentityRetryDelayType)retryDelayType options:(DSBlockchainIdentityMonitorOptions)options inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, BOOL found, NSError *error))completion { - __weak typeof(self) weakSelf = self; - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService getIdentityById:self.uniqueIDData - completionQueue:self.identityQueue - success:^(NSDictionary *_Nullable versionedIdentityDictionary) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - if (!versionedIdentityDictionary) { - if (completion) { - if (options & DSBlockchainIdentityMonitorOptions_AcceptNotFoundAsNotAnError) { - completion(YES, NO, nil); - return; - } else { - completion(NO, NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no identity when one was expected"]); - return; - } - } - } - - if (![versionedIdentityDictionary respondsToSelector:@selector(objectForKey:)]) { - completion(YES, NO, nil); - return; - } - - NSNumber *version = [versionedIdentityDictionary objectForKey:@(DSPlatformStoredMessage_Version)]; - NSDictionary *identityDictionary = [versionedIdentityDictionary objectForKey:@(DSPlatformStoredMessage_Item)]; - if (!identityDictionary) { - if (completion) { - if (options & DSBlockchainIdentityMonitorOptions_AcceptNotFoundAsNotAnError) { - completion(YES, NO, nil); - } else { - completion(NO, NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no identity when one was expected"]); - } - } - } else { - if (identityDictionary.count) { - [strongSelf applyIdentityDictionary:identityDictionary version:[version intValue] save:!self.isTransient inContext:context]; - strongSelf.registrationStatus = DSBlockchainIdentityRegistrationStatus_Registered; - [self saveInContext:context]; - } - - if (completion) { - completion(YES, YES, nil); - } - } - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - } - uint32_t nextRetryAbsentCount = retryAbsentCount; - if ([[error localizedDescription] isEqualToString:@"Identity not found"]) { - if (!retryAbsentCount) { - if (completion) { - if (options & DSBlockchainIdentityMonitorOptions_AcceptNotFoundAsNotAnError) { - completion(YES, NO, nil); - } else { - completion(NO, NO, error); - } - } - return; - } - nextRetryAbsentCount--; - } - if (retryCount > 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - NSTimeInterval nextDelay = delay; - switch (retryDelayType) { - case DSBlockchainIdentityRetryDelayType_SlowingDown20Percent: - nextDelay = delay * 1.2; - break; - case DSBlockchainIdentityRetryDelayType_SlowingDown50Percent: - nextDelay = delay * 1.5; - break; - - default: - break; - } - [self monitorForBlockchainIdentityWithRetryCount:retryCount - 1 - retryAbsentCount:nextRetryAbsentCount - delay:nextDelay - retryDelayType:retryDelayType - options:options - inContext:context - completion:completion]; - }); - } else { - completion(NO, NO, error); - } - }]; -} - -- (void)monitorForDPNSUsernameFullPaths:(NSArray *)usernameFullPaths withRetryCount:(uint32_t)retryCount inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL allFound, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSMutableDictionary *domains = [NSMutableDictionary dictionary]; - for (NSString *usernameFullPath in usernameFullPaths) { - NSArray *components = [usernameFullPath componentsSeparatedByString:@"."]; - NSString *domain = @""; - NSString *name = components[0]; - if (components.count > 1) { - NSArray *domainComponents = [components subarrayWithRange:NSMakeRange(1, components.count - 1)]; - domain = [domainComponents componentsJoinedByString:@"."]; - } - if (!domains[domain]) { - domains[domain] = [NSMutableArray array]; - } - - [domains[domain] addObject:name]; - } - __block BOOL finished = FALSE; - __block NSUInteger countAllFound = 0; - __block NSUInteger countReturned = 0; - for (NSString *domain in domains) { - [self monitorForDPNSUsernames:domains[domain] - inDomain:domain - withRetryCount:retryCount - inContext:context - completion:^(BOOL allFound, NSError *error) { - if (finished) return; - if (error && !finished) { - finished = TRUE; - if (completion) { - completion(NO, error); - } - return; - } - if (allFound) { - countAllFound++; - } - countReturned++; - if (countReturned == domains.count) { - finished = TRUE; - if (completion) { - completion(countAllFound == domains.count, nil); - } - } - } - onCompletionQueue:completionQueue]; //we can use completion queue directly here - } -} - -- (void)monitorForDPNSUsernames:(NSArray *)usernames inDomain:(NSString *)domain withRetryCount:(uint32_t)retryCount inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL allFound, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - __weak typeof(self) weakSelf = self; - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService getDPNSDocumentsForUsernames:usernames - inDomain:domain - completionQueue:self.identityQueue - success:^(id _Nonnull domainDocumentArray) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - if ([domainDocumentArray isKindOfClass:[NSArray class]]) { - NSMutableArray *usernamesLeft = [usernames mutableCopy]; - for (NSString *username in usernames) { - for (NSDictionary *domainDocument in domainDocumentArray) { - NSString *normalizedLabel = domainDocument[@"normalizedLabel"]; - NSString *label = domainDocument[@"label"]; - NSString *normalizedParentDomainName = domainDocument[@"normalizedParentDomainName"]; - if ([normalizedLabel isEqualToString:[username lowercaseString]]) { - NSMutableDictionary *usernameStatusDictionary = [[self.usernameStatuses objectForKey:username] mutableCopy]; - if (!usernameStatusDictionary) { - usernameStatusDictionary = [NSMutableDictionary dictionary]; - usernameStatusDictionary[BLOCKCHAIN_USERNAME_DOMAIN] = normalizedParentDomainName; - usernameStatusDictionary[BLOCKCHAIN_USERNAME_PROPER] = label; - } - usernameStatusDictionary[BLOCKCHAIN_USERNAME_STATUS] = @(DSBlockchainIdentityUsernameStatus_Confirmed); - [self.usernameStatuses setObject:[usernameStatusDictionary copy] forKey:[self fullPathForUsername:username inDomain:[self dashpayDomainName]]]; - [strongSelf saveUsername:username inDomain:normalizedParentDomainName status:DSBlockchainIdentityUsernameStatus_Confirmed salt:nil commitSave:YES inContext:context]; - [usernamesLeft removeObject:username]; - } - } - } - if ([usernamesLeft count] && retryCount > 0) { - [strongSelf monitorForDPNSUsernames:usernamesLeft inDomain:domain withRetryCount:retryCount - 1 inContext:context completion:completion onCompletionQueue:completionQueue]; - } else if ([usernamesLeft count]) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(FALSE, nil); - }); - } - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(TRUE, nil); - }); - } - } - } else if (retryCount > 0) { - [strongSelf monitorForDPNSUsernames:usernames inDomain:domain withRetryCount:retryCount - 1 inContext:context completion:completion onCompletionQueue:completionQueue]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:501 localizedDescriptionKey:@"Malformed platform response"]); - }); - } - } - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - } - if (retryCount > 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), self.identityQueue, ^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - [strongSelf monitorForDPNSUsernames:usernames - inDomain:domain - withRetryCount:retryCount - 1 - inContext:context - completion:completion - onCompletionQueue:completionQueue]; - }); - } else { - dispatch_async(completionQueue, ^{ - completion(FALSE, error); - }); - } - }]; -} - -- (void)monitorForDPNSPreorderSaltedDomainHashes:(NSDictionary *)saltedDomainHashes withRetryCount:(uint32_t)retryCount inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL allFound, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - __weak typeof(self) weakSelf = self; - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService getDPNSDocumentsForPreorderSaltedDomainHashes:[saltedDomainHashes allValues] - completionQueue:self.identityQueue - success:^(id _Nonnull preorderDocumentArray) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - }); - } - return; - } - if ([preorderDocumentArray isKindOfClass:[NSArray class]]) { - NSMutableArray *usernamesLeft = [[saltedDomainHashes allKeys] mutableCopy]; - for (NSString *usernameFullPath in saltedDomainHashes) { - NSData *saltedDomainHashData = saltedDomainHashes[usernameFullPath]; - for (NSDictionary *preorderDocument in preorderDocumentArray) { - if ([preorderDocument[@"saltedDomainHash"] isEqualToData:saltedDomainHashData]) { - NSMutableDictionary *usernameStatusDictionary = [[self.usernameStatuses objectForKey:usernameFullPath] mutableCopy]; - if (!usernameStatusDictionary) { - usernameStatusDictionary = [NSMutableDictionary dictionary]; - } - usernameStatusDictionary[BLOCKCHAIN_USERNAME_STATUS] = @(DSBlockchainIdentityUsernameStatus_Preordered); - [self.usernameStatuses setObject:[usernameStatusDictionary copy] forKey:usernameFullPath]; - [strongSelf saveUsernameFullPath:usernameFullPath status:DSBlockchainIdentityUsernameStatus_Preordered salt:nil commitSave:YES inContext:context]; - [usernamesLeft removeObject:usernameFullPath]; - } - } - } - if ([usernamesLeft count] && retryCount > 0) { - NSDictionary *saltedDomainHashesLeft = [saltedDomainHashes dictionaryWithValuesForKeys:usernamesLeft]; - [strongSelf monitorForDPNSPreorderSaltedDomainHashes:saltedDomainHashesLeft withRetryCount:retryCount - 1 inContext:context completion:completion onCompletionQueue:completionQueue]; - } else if ([usernamesLeft count]) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(FALSE, nil); - }); - } - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(TRUE, nil); - }); - } - } - } else if (retryCount > 0) { - [strongSelf monitorForDPNSPreorderSaltedDomainHashes:saltedDomainHashes withRetryCount:retryCount - 1 inContext:context completion:completion onCompletionQueue:completionQueue]; - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:501 localizedDescriptionKey:@"Malformed platform response"]); - }); - } - } - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - } - if (retryCount > 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), self.identityQueue, ^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - }); - } - return; - } - [strongSelf monitorForDPNSPreorderSaltedDomainHashes:saltedDomainHashes - withRetryCount:retryCount - 1 - inContext:context - completion:completion - onCompletionQueue:completionQueue]; - }); - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(FALSE, error); - }); - } - } - }]; -} - -- (void)monitorForContract:(DPContract *)contract withRetryCount:(uint32_t)retryCount inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, NSError *error))completion { - __weak typeof(self) weakSelf = self; - NSParameterAssert(contract); - if (!contract) return; - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService fetchContractForId:uint256_data(contract.contractId) - completionQueue:self.identityQueue - success:^(id _Nonnull contractDictionary) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - } - return; - } - DSLog(@"Contract dictionary is %@", contractDictionary); - if ([contractDictionary isKindOfClass:[NSDictionary class]] && [contractDictionary[@"$id"] isEqualToData:uint256_data(contract.contractId)]) { - [contract setContractState:DPContractState_Registered inContext:context]; - if (completion) { - completion(TRUE, nil); - } - } else if (retryCount > 0) { - [strongSelf monitorForContract:contract withRetryCount:retryCount - 1 inContext:context completion:completion]; - } else { - if (completion) { - completion(NO, [NSError errorWithCode:501 localizedDescriptionKey:@"Malformed platform response"]); - } - } - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - } - if (retryCount > 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - } - return; - } - [strongSelf monitorForContract:contract - withRetryCount:retryCount - 1 - inContext:context - completion:completion]; - }); - } else { - if (completion) { - completion(FALSE, error); - } - } - }]; -} - -//-(void)registerContract:(DPContract*)contract { -// __weak typeof(self) weakSelf = self; -// [self.DAPINetworkService getUserById:self.uniqueIdString success:^(NSDictionary * _Nonnull profileDictionary) { -// __strong typeof(weakSelf) strongSelf = weakSelf; -// if (!strongSelf) { -// return; -// } -// uint64_t creditBalance = (uint64_t)[profileDictionary[@"credits"] longLongValue]; -// strongSelf.creditBalance = creditBalance; -// strongSelf.registrationStatus = DSBlockchainIdentityRegistrationStatus_Registered; -// [self save]; -// } failure:^(NSError * _Nonnull error) { -// if (retryCount > 0) { -// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ -// [self monitorForBlockchainIdentityWithRetryCount:retryCount - 1]; -// }); -// } -// }]; -//} - -// MARK: - Dashpay - -// MARK: Helpers - -- (BOOL)isDashpayReady { - if (self.activeKeyCount == 0) { - return NO; - } - if (!self.isRegistered) { - return NO; - } - return YES; -} - -- (DPDocument *)matchingDashpayUserProfileDocumentInContext:(NSManagedObjectContext *)context { - //The revision must be at least at 1, otherwise nothing was ever done - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - if (matchingDashpayUser && matchingDashpayUser.localProfileDocumentRevision) { - __block DSMutableStringValueDictionary *dataDictionary = nil; - - __block NSData *entropyData = nil; - __block NSData *documentIdentifier = nil; - [context performBlockAndWait:^{ - dataDictionary = [@{ - @"$updatedAt": @(matchingDashpayUser.updatedAt), - } mutableCopy]; - if (matchingDashpayUser.createdAt == matchingDashpayUser.updatedAt) { - dataDictionary[@"$createdAt"] = @(matchingDashpayUser.createdAt); - } else { - dataDictionary[@"$revision"] = @(matchingDashpayUser.localProfileDocumentRevision); - } - if (matchingDashpayUser.publicMessage) { - dataDictionary[@"publicMessage"] = matchingDashpayUser.publicMessage; - } - if (matchingDashpayUser.avatarPath) { - dataDictionary[@"avatarUrl"] = matchingDashpayUser.avatarPath; - } - if (matchingDashpayUser.avatarFingerprint) { - dataDictionary[@"avatarFingerprint"] = matchingDashpayUser.avatarFingerprint; - } - if (matchingDashpayUser.avatarHash) { - dataDictionary[@"avatarHash"] = matchingDashpayUser.avatarHash; - } - if (matchingDashpayUser.displayName) { - dataDictionary[@"displayName"] = matchingDashpayUser.displayName; - } - entropyData = matchingDashpayUser.originalEntropyData; - documentIdentifier = matchingDashpayUser.documentIdentifier; - }]; - NSError *error = nil; - if (documentIdentifier == nil) { - NSAssert(entropyData, @"Entropy string must be present"); - DPDocument *document = [self.dashpayDocumentFactory documentOnTable:@"profile" withDataDictionary:dataDictionary usingEntropy:entropyData error:&error]; - return document; - } else { - DPDocument *document = [self.dashpayDocumentFactory documentOnTable:@"profile" withDataDictionary:dataDictionary usingDocumentIdentifier:documentIdentifier error:&error]; - return document; - } - } else { - return nil; - } -} - - -- (DSBlockchainIdentityFriendshipStatus)friendshipStatusForRelationshipWithBlockchainIdentity:(DSBlockchainIdentity *)otherBlockchainIdentity { - if (!self.matchingDashpayUserInViewContext) return DSBlockchainIdentityFriendshipStatus_Unknown; - __block BOOL isIncoming; - __block BOOL isOutgoing; - [self.matchingDashpayUserInViewContext.managedObjectContext performBlockAndWait:^{ - isIncoming = ([self.matchingDashpayUserInViewContext.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(otherBlockchainIdentity.uniqueID)]].count > 0); - isOutgoing = ([self.matchingDashpayUserInViewContext.outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(otherBlockchainIdentity.uniqueID)]].count > 0); - }]; - return ((isIncoming << 1) | isOutgoing); -} - - -// MARK: Sending a Friend Request - - -- (void)setDashpaySyncronizationBlockHash:(UInt256)dashpaySyncronizationBlockHash { - _dashpaySyncronizationBlockHash = dashpaySyncronizationBlockHash; - if (uint256_is_zero(_dashpaySyncronizationBlockHash)) { - _dashpaySyncronizationBlockHeight = 0; - } else { - _dashpaySyncronizationBlockHeight = [self.chain heightForBlockHash:_dashpaySyncronizationBlockHash]; - if (_dashpaySyncronizationBlockHeight == UINT32_MAX) { - _dashpaySyncronizationBlockHeight = 0; - } - } -} - -// MARK: Sending a Friend Request - -- (void)sendNewFriendRequestToBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion { - [self sendNewFriendRequestToBlockchainIdentity:blockchainIdentity inContext:self.platformContext completion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)sendNewFriendRequestToBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - if (blockchainIdentity.isTransient) { - blockchainIdentity.isTransient = FALSE; - [self.identitiesManager registerForeignBlockchainIdentity:blockchainIdentity]; - if (blockchainIdentity.transientDashpayUser) { - [blockchainIdentity applyProfileChanges:blockchainIdentity.transientDashpayUser - inContext:context - saveContext:YES - completion:^(BOOL success, NSError *_Nullable error) { - if (success && !error) { - DSDashpayUserEntity *dashpayUser = [blockchainIdentity matchingDashpayUserInContext:context]; - if (blockchainIdentity.transientDashpayUser.revision == dashpayUser.remoteProfileDocumentRevision) { - blockchainIdentity.transientDashpayUser = nil; - } - } - } - onCompletionQueue:self.identityQueue]; - } - } - [blockchainIdentity fetchNeededNetworkStateInformationInContext:context - withCompletion:^(DSBlockchainIdentityQueryStep failureStep, NSArray *_Nullable errors) { - if (failureStep && failureStep != DSBlockchainIdentityQueryStep_Profile) { //if profile fails we can still continue on - completion(NO, errors); - return; - } - if (![blockchainIdentity isDashpayReady]) { - dispatch_async(completionQueue, ^{ - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"User has actions to complete before being able to use Dashpay"]]); - }); - - return; - } - uint32_t destinationKeyIndex = [blockchainIdentity firstIndexOfKeyOfType:self.currentMainKeyType createIfNotPresent:NO saveKey:NO]; - uint32_t sourceKeyIndex = [self firstIndexOfKeyOfType:self.currentMainKeyType createIfNotPresent:NO saveKey:NO]; - - - if (sourceKeyIndex == UINT32_MAX) { //not found - //to do register a new key - NSAssert(FALSE, @"we shouldn't be getting here"); - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Internal key handling error"]]); - }); - } - return; - } - DSAccount *account = [self.wallet accountWithNumber:0]; - DSPotentialOneWayFriendship *potentialFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationBlockchainIdentity:blockchainIdentity destinationKeyIndex:destinationKeyIndex sourceBlockchainIdentity:self sourceKeyIndex:sourceKeyIndex account:account]; - - [potentialFriendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { - if (!success) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Internal key handling error"]]); - }); - } - return; - } - [potentialFriendship encryptExtendedPublicKeyWithCompletion:^(BOOL success) { - if (!success) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Internal key handling error"]]); - }); - } - return; - } - [self sendNewFriendRequestMatchingPotentialFriendship:potentialFriendship - inContext:context - completion:completion - onCompletionQueue:completionQueue]; - }]; - }]; - } - onCompletionQueue:self.identityQueue]; -} - -- (void)sendNewFriendRequestToPotentialContact:(DSPotentialContact *)potentialContact completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return; - __weak typeof(self) weakSelf = self; - DSDAPIPlatformNetworkService *dapiNetworkService = self.DAPINetworkService; - [dapiNetworkService getIdentityByName:potentialContact.username - inDomain:[self dashpayDomainName] - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull blockchainIdentityVersionedDictionary) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO, @[[NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]]); - } - return; - } - NSNumber *_Nonnull version = blockchainIdentityVersionedDictionary[@(DSPlatformStoredMessage_Version)]; - NSDictionary *_Nonnull blockchainIdentityDictionary = blockchainIdentityVersionedDictionary[@(DSPlatformStoredMessage_Item)]; - NSData *identityIdData = nil; - if (!blockchainIdentityDictionary || !(identityIdData = blockchainIdentityDictionary[@"id"])) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Malformed platform response"]]); - }); - } - return; - } - - UInt256 blockchainIdentityContactUniqueId = identityIdData.UInt256; - - NSAssert(uint256_is_not_zero(blockchainIdentityContactUniqueId), @"blockchainIdentityContactUniqueId should not be null"); - - DSBlockchainIdentityEntity *potentialContactBlockchainIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:self.platformContext matching:@"uniqueID == %@", uint256_data(blockchainIdentityContactUniqueId)]; - - DSBlockchainIdentity *potentialContactBlockchainIdentity = nil; - - if (potentialContactBlockchainIdentityEntity) { - potentialContactBlockchainIdentity = [self.chain blockchainIdentityForUniqueId:blockchainIdentityContactUniqueId]; - if (!potentialContactBlockchainIdentity) { - potentialContactBlockchainIdentity = [[DSBlockchainIdentity alloc] initWithBlockchainIdentityEntity:potentialContactBlockchainIdentityEntity]; - } - } else { - potentialContactBlockchainIdentity = [self.identitiesManager foreignBlockchainIdentityWithUniqueId:blockchainIdentityContactUniqueId createIfMissing:YES inContext:self.platformContext]; - } - [potentialContactBlockchainIdentity applyIdentityDictionary:blockchainIdentityDictionary - version:[version intValue] - save:YES - inContext:self.platformContext]; - [potentialContactBlockchainIdentity saveInContext:self.platformContext]; - - [self sendNewFriendRequestToBlockchainIdentity:potentialContactBlockchainIdentity completion:completion]; - } - failure:^(NSError *_Nonnull error) { - if (error.code == 12) { //UNIMPLEMENTED, this would mean that we are connecting to an old node - [self.DAPIClient removeDAPINodeByAddress:dapiNetworkService.ipAddress]; - } - DSLogPrivate(@"%@", error); - - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, @[error]); - }); - } - }]; -} - -- (void)sendNewFriendRequestMatchingPotentialFriendship:(DSPotentialOneWayFriendship *)potentialFriendship completion:(void (^)(BOOL success, NSArray *errors))completion { - [self sendNewFriendRequestMatchingPotentialFriendship:potentialFriendship inContext:self.platformContext completion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)sendNewFriendRequestMatchingPotentialFriendship:(DSPotentialOneWayFriendship *)potentialFriendship inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) return; - DSDashpayUserEntity *destinationDashpayUser = [potentialFriendship.destinationBlockchainIdentity matchingDashpayUserInContext:context]; - if (!destinationDashpayUser) { - NSAssert([potentialFriendship.destinationBlockchainIdentity matchingDashpayUserInContext:context], @"There must be a destination contact if the destination blockchain identity is not known"); - return; - } - - __weak typeof(self) weakSelf = self; - DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - NSData *entropyData = uint256_random_data; - DPDocument *document = [potentialFriendship contactRequestDocumentWithEntropy:entropyData]; - [self.DAPIClient sendDocument:document - forIdentity:self - contract:contract - completion:^(NSError *_Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, @[[NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]]); - }); - } - return; - } - - BOOL success = error == nil; - - if (!success) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, @[error]); - }); - return; - } - - [context performBlockAndWait:^{ - [self addFriendship:potentialFriendship - inContext:context - completion:^(BOOL success, NSError *error){ - - }]; - // [self addFriendshipFromSourceBlockchainIdentity:potentialFriendship.sourceBlockchainIdentity sourceKeyIndex:potentialFriendship.so toRecipientBlockchainIdentity:<#(DSBlockchainIdentity *)#> recipientKeyIndex:<#(uint32_t)#> inContext:<#(NSManagedObjectContext *)#>] - // DSFriendRequestEntity * friendRequest = [potentialFriendship outgoingFriendRequestForDashpayUserEntity:potentialFriendship.destinationBlockchainIdentity.matchingDashpayUser]; - // [strongSelf.matchingDashpayUser addOutgoingRequestsObject:friendRequest]; - // - // if ([[friendRequest.destinationContact.outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact == %@",strongSelf.matchingDashpayUser]] count]) { - // [strongSelf.matchingDashpayUser addFriendsObject:friendRequest.destinationContact]; - // } - // [potentialFriendship storeExtendedPublicKeyAssociatedWithFriendRequest:friendRequest]; - // [DSFriendRequestEntity saveContext]; - // if (completion) { - // dispatch_async(dispatch_get_main_queue(), ^{ - // completion(success,error); - // }); - // } - }]; - - [self fetchOutgoingContactRequestsInContext:context - withCompletion:^(BOOL success, NSArray *_Nonnull errors) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(success, errors); - }); - } - } - onCompletionQueue:completionQueue]; - }]; -} - -- (void)acceptFriendRequestFromBlockchainIdentity:(DSBlockchainIdentity *)otherBlockchainIdentity completion:(void (^)(BOOL success, NSArray *errors))completion { - [self acceptFriendRequestFromBlockchainIdentity:otherBlockchainIdentity inContext:self.platformContext completion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)acceptFriendRequestFromBlockchainIdentity:(DSBlockchainIdentity *)otherBlockchainIdentity inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) { - if (completion) { - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Accepting a friend request should only happen from a local blockchain identity"]]); - } - return; - } - - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - DSFriendRequestEntity *friendRequest = [[matchingDashpayUser.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(otherBlockchainIdentity.uniqueID)]] anyObject]; - if (!friendRequest) { - if (completion) { - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"You can only accept a friend request from blockchain identity who has sent you one, and none were found"]]); - } - } else { - [self acceptFriendRequest:friendRequest completion:completion onCompletionQueue:completionQueue]; - } - }]; -} - -- (void)acceptFriendRequest:(DSFriendRequestEntity *)friendRequest completion:(void (^)(BOOL success, NSArray *errors))completion { - [self acceptFriendRequest:friendRequest completion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)acceptFriendRequest:(DSFriendRequestEntity *)friendRequest completion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); - if (!_isLocal) { - if (completion) { - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Accepting a friend request should only happen from a local blockchain identity"]]); - } - return; - } - DSAccount *account = [self.wallet accountWithNumber:0]; - DSDashpayUserEntity *otherDashpayUser = friendRequest.sourceContact; - DSBlockchainIdentity *otherBlockchainIdentity = [self.chain blockchainIdentityForUniqueId:otherDashpayUser.associatedBlockchainIdentity.uniqueID.UInt256]; - - if (!otherBlockchainIdentity) { - otherBlockchainIdentity = [[DSBlockchainIdentity alloc] initWithBlockchainIdentityEntity:otherDashpayUser.associatedBlockchainIdentity]; - } - // DSPotentialContact *contact = [[DSPotentialContact alloc] initWithUsername:friendRequest.sourceContact.username avatarPath:friendRequest.sourceContact.avatarPath - // publicMessage:friendRequest.sourceContact.publicMessage]; - // [contact setAssociatedBlockchainIdentityUniqueId:friendRequest.sourceContact.associatedBlockchainIdentity.uniqueID.UInt256]; - // DSKey * friendsEncyptionKey = [otherBlockchainIdentity keyOfType:friendRequest.sourceEncryptionPublicKeyIndex atIndex:friendRequest.sourceEncryptionPublicKeyIndex]; - //[DSKey keyWithPublicKeyData:friendRequest.sourceContact.encryptionPublicKey forKeyType:friendRequest.sourceContact.encryptionPublicKeyType onChain:self.chain]; - // [contact addPublicKey:friendsEncyptionKey atIndex:friendRequest.sourceContact.encryptionPublicKeyIndex]; - // uint32_t sourceKeyIndex = [self firstIndexOfKeyOfType:friendRequest.sourceContact.encryptionPublicKeyType createIfNotPresent:NO]; - // if (sourceKeyIndex == UINT32_MAX) { //not found - // //to do register a new key - // NSAssert(FALSE, @"we shouldn't be getting here"); - // return; - // } - DSPotentialOneWayFriendship *potentialFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationBlockchainIdentity:otherBlockchainIdentity destinationKeyIndex:friendRequest.sourceKeyIndex sourceBlockchainIdentity:self sourceKeyIndex:friendRequest.destinationKeyIndex account:account]; - [potentialFriendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { - if (success) { - [potentialFriendship encryptExtendedPublicKeyWithCompletion:^(BOOL success) { - if (!success) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, @[[NSError errorWithCode:501 localizedDescriptionKey:@"Internal key handling error"]]); - }); - } - return; - } - [self sendNewFriendRequestMatchingPotentialFriendship:potentialFriendship - inContext:friendRequest.managedObjectContext - completion:completion - onCompletionQueue:completionQueue]; - }]; - } else { - if (completion) { - completion(NO, @[[NSError errorWithCode:500 localizedDescriptionKey:@"Could not create friendship derivation path"]]); - } - } - }]; -} - -// MARK: Profile - -- (DSDocumentTransition *)profileDocumentTransitionInContext:(NSManagedObjectContext *)context { - DPDocument *profileDocument = [self matchingDashpayUserProfileDocumentInContext:context]; - if (!profileDocument) return nil; - DSDocumentTransition *transition = [[DSDocumentTransition alloc] initForDocuments:@[profileDocument] withTransitionVersion:1 blockchainIdentityUniqueId:self.uniqueID onChain:self.chain]; - return transition; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName { - [self updateDashpayProfileWithDisplayName:displayName inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.displayName = displayName; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - -- (void)updateDashpayProfileWithPublicMessage:(NSString *)publicMessage { - [self updateDashpayProfileWithPublicMessage:publicMessage inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithPublicMessage:(NSString *)publicMessage inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.publicMessage = publicMessage; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - -- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString { - [self updateDashpayProfileWithAvatarURLString:avatarURLString inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.avatarPath = avatarURLString; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage { - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.displayName = displayName; - matchingDashpayUser.publicMessage = publicMessage; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarURLString:(NSString *)avatarURLString { - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage avatarURLString:avatarURLString inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarURLString:(NSString *)avatarURLString inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.displayName = displayName; - matchingDashpayUser.publicMessage = publicMessage; - matchingDashpayUser.avatarPath = avatarURLString; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - -#if TARGET_OS_IOS - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarImage:(UIImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString { - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage avatarImage:avatarImage avatarData:data avatarURLString:avatarURLString inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarImage:(UIImage *)avatarImage avatarData:(NSData *)avatarData avatarURLString:(NSString *)avatarURLString inContext:(NSManagedObjectContext *)context { - NSData *avatarHash = uint256_data(avatarData.SHA256); - uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage avatarURLString:avatarURLString avatarHash:avatarHash avatarFingerprint:[NSData dataWithUInt64:fingerprint] inContext:context]; -} - -- (void)updateDashpayProfileWithAvatarImage:(UIImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString { - [self updateDashpayProfileWithAvatarImage:avatarImage avatarData:data avatarURLString:avatarURLString inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithAvatarImage:(UIImage *)avatarImage avatarData:(NSData *)avatarData avatarURLString:(NSString *)avatarURLString inContext:(NSManagedObjectContext *)context { - NSData *avatarHash = uint256_data(avatarData.SHA256); - uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; - [self updateDashpayProfileWithAvatarURLString:avatarURLString avatarHash:avatarHash avatarFingerprint:[NSData dataWithUInt64:fingerprint] inContext:context]; -} - -#else - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarImage:(NSImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString { - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage avatarImage:avatarImage avatarData:data avatarURLString:avatarURLString inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarImage:(NSImage *)avatarImage avatarData:(NSData *)avatarData avatarURLString:(NSString *)avatarURLString inContext:(NSManagedObjectContext *)context { - NSData *avatarHash = uint256_data(avatarData.SHA256); - uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage avatarURLString:avatarURLString avatarHash:avatarHash avatarFingerprint:[NSData dataWithUInt64:fingerprint] inContext:context]; -} - -- (void)updateDashpayProfileWithAvatarImage:(NSImage *)avatarImage avatarData:(NSData *)data avatarURLString:(NSString *)avatarURLString { - [self updateDashpayProfileWithAvatarImage:avatarImage avatarData:data avatarURLString:avatarURLString inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithAvatarImage:(NSImage *)avatarImage avatarData:(NSData *)avatarData avatarURLString:(NSString *)avatarURLString inContext:(NSManagedObjectContext *)context { - NSData *avatarHash = uint256_data(avatarData.SHA256); - uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; - [self updateDashpayProfileWithAvatarURLString:avatarURLString avatarHash:avatarHash avatarFingerprint:[NSData dataWithUInt64:fingerprint] inContext:context]; -} - -#endif - - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarURLString:(NSString *)avatarURLString avatarHash:(NSData *)avatarHash avatarFingerprint:(NSData *)avatarFingerprint { - [self updateDashpayProfileWithDisplayName:displayName publicMessage:publicMessage avatarURLString:avatarURLString avatarHash:avatarHash avatarFingerprint:avatarFingerprint inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName publicMessage:(NSString *)publicMessage avatarURLString:(NSString *)avatarURLString avatarHash:(NSData *)avatarHash avatarFingerprint:(NSData *)avatarFingerprint inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.displayName = displayName; - matchingDashpayUser.publicMessage = publicMessage; - matchingDashpayUser.avatarPath = avatarURLString; - matchingDashpayUser.avatarFingerprint = avatarFingerprint; - matchingDashpayUser.avatarHash = avatarHash; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - -- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString avatarHash:(NSData *)avatarHash avatarFingerprint:(NSData *)avatarFingerprint { - [self updateDashpayProfileWithAvatarURLString:avatarURLString avatarHash:avatarHash avatarFingerprint:avatarFingerprint inContext:self.platformContext]; -} - -- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString avatarHash:(NSData *)avatarHash avatarFingerprint:(NSData *)avatarFingerprint inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - matchingDashpayUser.avatarPath = avatarURLString; - matchingDashpayUser.avatarFingerprint = avatarFingerprint; - matchingDashpayUser.avatarHash = avatarHash; - if (!matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; - if (!matchingDashpayUser.originalEntropyData) { - matchingDashpayUser.originalEntropyData = uint256_random_data; - } - } - matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; - matchingDashpayUser.localProfileDocumentRevision++; - [context ds_save]; - }]; -} - -- (void)signedProfileDocumentTransitionInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(DSTransition *transition, BOOL cancelled, NSError *error))completion { - __weak typeof(self) weakSelf = self; - DSDocumentTransition *transition = [self profileDocumentTransitionInContext:context]; - if (!transition) { - if (completion) { - completion(nil, NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Transition had nothing to update"]); - } - return; - } - [self signStateTransition:transition - completion:^(BOOL success) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(nil, NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - } - return; - } - if (success) { - completion(transition, NO, nil); - } - }]; -} - -- (void)signAndPublishProfileWithCompletion:(void (^)(BOOL success, BOOL cancelled, NSError *error))completion { - [self signAndPublishProfileInContext:self.platformContext withCompletion:completion]; -} - -- (void)signAndPublishProfileInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, BOOL cancelled, NSError *error))completion { - __weak typeof(self) weakSelf = self; - __block uint32_t profileDocumentRevision; - [context performBlockAndWait:^{ - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; - if (matchingDashpayUser.localProfileDocumentRevision > matchingDashpayUser.remoteProfileDocumentRevision) { - matchingDashpayUser.localProfileDocumentRevision = matchingDashpayUser.remoteProfileDocumentRevision + 1; - } - profileDocumentRevision = matchingDashpayUser.localProfileDocumentRevision; - [context ds_save]; - }]; - [self signedProfileDocumentTransitionInContext:context - withCompletion:^(DSTransition *transition, BOOL cancelled, NSError *error) { - if (!transition) { - if (completion) { - completion(NO, cancelled, error); - } - return; - } - [self.DAPIClient publishTransition:transition - completionQueue:self.identityQueue - success:^(NSDictionary *_Nonnull successDictionary, BOOL added) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO, NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - } - return; - } - [context performBlockAndWait:^{ - [self matchingDashpayUserInContext:context].remoteProfileDocumentRevision = profileDocumentRevision; - [context ds_save]; - }]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(YES, NO, nil); - }); - } - } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, NO, error); - }); - } - }]; - }]; -} - -// - -// MARK: Fetching - -- (void)fetchProfileWithCompletion:(void (^)(BOOL success, NSError *error))completion { - dispatch_async(self.identityQueue, ^{ - [self fetchProfileInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; - }); -} - -- (void)fetchProfileInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self fetchProfileInContext:context retryCount:DEFAULT_FETCH_PROFILE_RETRY_COUNT withCompletion:completion onCompletionQueue:completionQueue]; -} - -- (void)fetchProfileInContext:(NSManagedObjectContext *)context retryCount:(uint32_t)retryCount withCompletion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self internalFetchProfileInContext:context - withCompletion:^(BOOL success, NSError *error) { - if (!success && retryCount > 0) { - [self fetchUsernamesInContext:context retryCount:retryCount - 1 withCompletion:completion onCompletionQueue:completionQueue]; - } else if (completion) { - completion(success, error); - } - } - onCompletionQueue:completionQueue]; -} - -- (void)internalFetchProfileInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - if ([dashpayContract contractState] != DPContractState_Registered) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Dashpay Contract is not yet registered on network"]); - }); - } - return; - } - - [self.identitiesManager fetchProfileForBlockchainIdentity:self - withCompletion:^(BOOL success, DSTransientDashpayUser *_Nullable dashpayUserInfo, NSError *_Nullable error) { - if (!success || error || dashpayUserInfo == nil) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(success, error); - }); - } - return; - } - [self applyProfileChanges:dashpayUserInfo - inContext:context - saveContext:YES - completion:^(BOOL success, NSError *_Nullable error) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(success, error); - }); - } - } - onCompletionQueue:self.identityQueue]; - } - onCompletionQueue:self.identityQueue]; -} - -- (void)fetchContactRequests:(void (^)(BOOL success, NSArray *errors))completion { - dispatch_async(self.identityQueue, ^{ - [self fetchContactRequestsInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; - }); -} - -#define DEFAULT_CONTACT_REQUEST_FETCH_RETRIES 5 - -- (void)fetchContactRequestsInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - __weak typeof(self) weakSelf = self; - [self fetchIncomingContactRequestsInContext:context - withCompletion:^(BOOL success, NSArray *errors) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO, @[[NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]]); - } - return; - } - if (!success) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(success, errors); - }); - } - return; - } - - [strongSelf fetchOutgoingContactRequestsInContext:context - withCompletion:completion - onCompletionQueue:completionQueue]; - } - onCompletionQueue:self.identityQueue]; -} - -- (void)fetchIncomingContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion { - [self fetchIncomingContactRequestsInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)fetchIncomingContactRequestsInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self fetchIncomingContactRequestsInContext:context startAfter:nil retriesLeft:DEFAULT_CONTACT_REQUEST_FETCH_RETRIES withCompletion:completion onCompletionQueue:completionQueue]; -} - -- (void)fetchIncomingContactRequestsInContext:(NSManagedObjectContext *)context startAfter:(NSData*_Nullable)startAfter retriesLeft:(int32_t)retriesLeft withCompletion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self internalFetchIncomingContactRequestsInContext:context - startAfter:startAfter - withCompletion:^(BOOL success, NSData*_Nullable hasMoreStartAfter, NSArray *errors) { - if (!success && retriesLeft > 0) { - [self fetchIncomingContactRequestsInContext:context startAfter:startAfter retriesLeft:retriesLeft - 1 withCompletion:completion onCompletionQueue:completionQueue]; - } else if (success && hasMoreStartAfter) { - [self fetchIncomingContactRequestsInContext:context startAfter:hasMoreStartAfter retriesLeft:DEFAULT_CONTACT_REQUEST_FETCH_RETRIES withCompletion:completion onCompletionQueue:completionQueue]; - } else if (completion) { - completion(success, errors); - } - } - onCompletionQueue:completionQueue]; -} - -- (void)internalFetchIncomingContactRequestsInContext:(NSManagedObjectContext *)context startAfter:(NSData*_Nullable)startAfter withCompletion:(void (^)(BOOL success, NSData*_Nullable hasMoreStartAfter, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - if (dashpayContract.contractState != DPContractState_Registered) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[[NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"]]); - }); - } - return; - } - NSError *error = nil; - if (![self activePrivateKeysAreLoadedWithFetchingError:&error]) { - //The blockchain identity hasn't been intialized on the device, ask the user to activate the blockchain user, this action allows private keys to be cached on the blockchain identity level - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[error ? error : [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity hasn't yet been locally activated"]]); - }); - } - return; - } - __weak typeof(self) weakSelf = self; - [self.DAPINetworkService getDashpayIncomingContactRequestsForUserId:self.uniqueIDData - since:self.lastCheckedIncomingContactsTimestamp ? (self.lastCheckedIncomingContactsTimestamp - HOUR_TIME_INTERVAL) : 0 - startAfter:startAfter - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - //todo chance the since parameter - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[[NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]]); - }); - } - return; - } - - dispatch_async(self.identityQueue, ^{ - [strongSelf handleContactRequestObjects:documents - context:context - completion:^(BOOL success, NSArray *errors) { - BOOL hasMore = documents.count == DAPI_DOCUMENT_RESPONSE_COUNT_LIMIT; - if (!hasMore) { - [self.platformContext performBlockAndWait:^{ - self.lastCheckedIncomingContactsTimestamp = [[NSDate date] timeIntervalSince1970]; - }]; - } - if (completion) { - NSData * hasMoreStartAfter = documents.lastObject[@"$id"]; - dispatch_async(completionQueue, ^{ - completion(success, hasMoreStartAfter, errors); - }); - } - } - onCompletionQueue:self.identityQueue]; - }); - } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[error]); - }); - } - }]; -} - -- (void)fetchOutgoingContactRequests:(void (^)(BOOL success, NSArray *errors))completion { - [self fetchOutgoingContactRequestsInContext:self.platformContext withCompletion:completion onCompletionQueue:dispatch_get_main_queue()]; -} - -- (void)fetchOutgoingContactRequestsInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self fetchOutgoingContactRequestsInContext:context startAfter:nil retriesLeft:DEFAULT_CONTACT_REQUEST_FETCH_RETRIES withCompletion:completion onCompletionQueue:completionQueue]; -} - -- (void)fetchOutgoingContactRequestsInContext:(NSManagedObjectContext *)context startAfter:(NSData*_Nullable)startAfter retriesLeft:(int32_t)retriesLeft withCompletion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - [self internalFetchOutgoingContactRequestsInContext:context - startAfter:startAfter - withCompletion:^(BOOL success, NSData*_Nullable hasMoreStartAfter, NSArray *errors) { - if (!success && retriesLeft > 0) { - [self fetchOutgoingContactRequestsInContext:context startAfter:startAfter retriesLeft:retriesLeft - 1 withCompletion:completion onCompletionQueue:completionQueue]; - } else if (success && hasMoreStartAfter) { - [self fetchOutgoingContactRequestsInContext:context startAfter:hasMoreStartAfter retriesLeft:DEFAULT_CONTACT_REQUEST_FETCH_RETRIES withCompletion:completion onCompletionQueue:completionQueue]; - } else if (completion) { - completion(success, errors); - } - } - onCompletionQueue:completionQueue]; -} - -- (void)internalFetchOutgoingContactRequestsInContext:(NSManagedObjectContext *)context startAfter:(NSData*_Nullable)startAfter withCompletion:(void (^)(BOOL success, NSData*_Nullable hasMoreStartAfter, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; - if (dashpayContract.contractState != DPContractState_Registered) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[[NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"]]); - }); - } - return; - } - NSError *error = nil; - if (![self activePrivateKeysAreLoadedWithFetchingError:&error]) { - //The blockchain identity hasn't been intialized on the device, ask the user to activate the blockchain user, this action allows private keys to be cached on the blockchain identity level - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[error ? error : [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity hasn't yet been locally activated"]]); - }); - } - return; - } - __weak typeof(self) weakSelf = self; - [self.DAPINetworkService getDashpayOutgoingContactRequestsForUserId:self.uniqueIDData - since:self.lastCheckedOutgoingContactsTimestamp ? (self.lastCheckedOutgoingContactsTimestamp - HOUR_TIME_INTERVAL) : 0 - startAfter:startAfter - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - //todo chance the since parameter - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[[NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]]); - }); - } - return; - } - - dispatch_async(self.identityQueue, ^{ - [strongSelf handleContactRequestObjects:documents - context:context - completion:^(BOOL success, NSArray *errors) { - BOOL hasMore = documents.count == DAPI_DOCUMENT_RESPONSE_COUNT_LIMIT; - - if (!hasMore) { - [self.platformContext performBlockAndWait:^{ - self.lastCheckedOutgoingContactsTimestamp = [[NSDate date] timeIntervalSince1970]; - }]; - } - __block NSData * hasMoreStartAfter = success?documents.lastObject[@"$id"]:nil; - dispatch_async(completionQueue, ^{ - - completion(success, hasMoreStartAfter, errors); - }); - } - onCompletionQueue:self.identityQueue]; - }); - } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, @[error]); - }); - } - }]; -} - -// MARK: Response Processing - -- (void)applyProfileChanges:(DSTransientDashpayUser *)transientDashpayUser inContext:(NSManagedObjectContext *)context saveContext:(BOOL)saveContext completion:(void (^)(BOOL success, NSError *error))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - if (![self isActive]) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Identity no longer active in wallet"]); - }); - } - return; - } - __weak typeof(self) weakSelf = self; - dispatch_async(self.identityQueue, ^{ - [context performBlockAndWait:^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - } - return; - } - if (![self isActive]) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, [NSError errorWithCode:500 localizedDescriptionKey:@"Identity no longer active in wallet"]); - }); - } - return; - } - DSDashpayUserEntity *contact = [[self blockchainIdentityEntityInContext:context] matchingDashpayUser]; - if (!contact) { - NSAssert(FALSE, @"It is weird to get here"); - contact = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentity.uniqueID == %@", self.uniqueIDData]; - } - if (!contact || transientDashpayUser.updatedAt > contact.updatedAt) { - if (!contact) { - contact = [DSDashpayUserEntity managedObjectInBlockedContext:context]; - contact.chain = [strongSelf.wallet.chain chainEntityInContext:context]; - contact.associatedBlockchainIdentity = [strongSelf blockchainIdentityEntityInContext:context]; - } - - NSError *error = [contact applyTransientDashpayUser:transientDashpayUser save:saveContext]; - if (error) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, error); - }); - } - return; - } - } - - [self.platformContext performBlockAndWait:^{ - self.lastCheckedProfileTimestamp = [[NSDate date] timeIntervalSince1970]; - //[self saveInContext:self.platformContext]; - }]; - - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil); - }); - } - }]; - }); -} - -/// Handle an array of contact requests. This method will split contact requests into either incoming contact requests or outgoing contact requests and then call methods for handling them if applicable. -/// @param rawContactRequests A dictionary of rawContactRequests, these are returned by the network. -/// @param context The managed object context in which to process results. -/// @param completion Completion callback with success boolean. -- (void)handleContactRequestObjects:(NSArray *)rawContactRequests context:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, NSArray *errors))completion onCompletionQueue:(dispatch_queue_t)completionQueue { - NSAssert(completionQueue == self.identityQueue, @"we should be on identity queue"); - __block NSMutableArray *incomingNewRequests = [NSMutableArray array]; - __block NSMutableArray *outgoingNewRequests = [NSMutableArray array]; - __block NSMutableArray *rErrors = [NSMutableArray array]; - [context performBlockAndWait:^{ - for (NSDictionary *rawContact in rawContactRequests) { - DSContactRequest *contactRequest = [DSContactRequest contactRequestFromDictionary:rawContact onBlockchainIdentity:self]; - - if (uint256_eq(contactRequest.recipientBlockchainIdentityUniqueId, self.uniqueID)) { - //we are the recipient, this is an incoming request - DSFriendRequestEntity *friendRequest = [DSFriendRequestEntity anyObjectInContext:context matching:@"destinationContact == %@ && sourceContact.associatedBlockchainIdentity.uniqueID == %@", [self matchingDashpayUserInContext:context], uint256_data(contactRequest.senderBlockchainIdentityUniqueId)]; - if (!friendRequest) { - [incomingNewRequests addObject:contactRequest]; - } - } else if (uint256_eq(contactRequest.senderBlockchainIdentityUniqueId, self.uniqueID)) { - //we are the sender, this is an outgoing request - BOOL isNew = ![DSFriendRequestEntity countObjectsInContext:context matching:@"sourceContact == %@ && destinationContact.associatedBlockchainIdentity.uniqueID == %@", [self matchingDashpayUserInContext:context], [NSData dataWithUInt256:contactRequest.recipientBlockchainIdentityUniqueId]]; - if (isNew) { - [outgoingNewRequests addObject:contactRequest]; - } - } else { - //we should not have received this - NSAssert(FALSE, @"the contact request needs to be either outgoing or incoming"); - } - } - }]; - - - __block BOOL succeeded = YES; - dispatch_group_t dispatchGroup = dispatch_group_create(); - - if ([incomingNewRequests count]) { - dispatch_group_enter(dispatchGroup); - [self handleIncomingRequests:incomingNewRequests - context:context - completion:^(BOOL success, NSArray *errors) { - if (!success) { - succeeded = NO; - [rErrors addObjectsFromArray:errors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:completionQueue]; - } - if ([outgoingNewRequests count]) { - dispatch_group_enter(dispatchGroup); - [self handleOutgoingRequests:outgoingNewRequests - context:context - completion:^(BOOL success, NSArray *errors) { - if (!success) { - succeeded = NO; - [rErrors addObjectsFromArray:errors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:completionQueue]; - } - - dispatch_group_notify(dispatchGroup, completionQueue, ^{ - if (completion) { - completion(succeeded, [rErrors copy]); - } - }); -} - -- (void)handleIncomingRequests:(NSArray *)incomingRequests - context:(NSManagedObjectContext *)context - completion:(void (^)(BOOL success, NSArray *errors))completion - onCompletionQueue:(dispatch_queue_t)completionQueue { - if (!self.isActive) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, @[[NSError errorWithCode:410 localizedDescriptionKey:@"Identity no longer active in wallet"]]); - }); - } - return; - } - [context performBlockAndWait:^{ - __block BOOL succeeded = YES; - __block NSMutableArray *errors = [NSMutableArray array]; - dispatch_group_t dispatchGroup = dispatch_group_create(); - - for (DSContactRequest *contactRequest in incomingRequests) { - DSBlockchainIdentityEntity *externalBlockchainIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", uint256_data(contactRequest.senderBlockchainIdentityUniqueId)]; - if (!externalBlockchainIdentityEntity) { - //no externalBlockchainIdentity exists yet, which means no dashpay user - dispatch_group_enter(dispatchGroup); - DSBlockchainIdentity *senderBlockchainIdentity = [self.identitiesManager foreignBlockchainIdentityWithUniqueId:contactRequest.senderBlockchainIdentityUniqueId createIfMissing:YES inContext:context]; - - [senderBlockchainIdentity fetchNeededNetworkStateInformationInContext:self.platformContext - withCompletion:^(DSBlockchainIdentityQueryStep failureStep, NSArray *_Nullable networkErrors) { - if (!failureStep) { - OpaqueKey *senderPublicKey = [senderBlockchainIdentity keyAtIndex:contactRequest.senderKeyIndex]; - NSData *extendedPublicKeyData = [contactRequest decryptedPublicKeyDataWithKey:senderPublicKey]; - OpaqueKey *extendedPublicKey = [DSKeyManager keyWithExtendedPublicKeyData:extendedPublicKeyData ofType:KeyKind_ECDSA]; - if (!extendedPublicKey) { - succeeded = FALSE; - [errors addObject:[NSError errorWithCode:500 localizedDescriptionKey:@"Incorrect key format after contact request decryption"]]; - } else { - DSDashpayUserEntity *senderDashpayUserEntity = [senderBlockchainIdentity blockchainIdentityEntityInContext:context].matchingDashpayUser; - NSAssert(senderDashpayUserEntity, @"The sender should exist"); - [self addIncomingRequestFromContact:senderDashpayUserEntity - forExtendedPublicKey:extendedPublicKey - atTimestamp:contactRequest.createdAt]; - } - } else { - [errors addObjectsFromArray:networkErrors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - - } else { - if ([self.chain blockchainIdentityForUniqueId:externalBlockchainIdentityEntity.uniqueID.UInt256]) { - //it's also local (aka both contacts are local to this device), we should store the extended public key for the destination - DSBlockchainIdentity *sourceBlockchainIdentity = [self.chain blockchainIdentityForUniqueId:externalBlockchainIdentityEntity.uniqueID.UInt256]; - - DSAccount *account = [sourceBlockchainIdentity.wallet accountWithNumber:0]; - - DSPotentialOneWayFriendship *potentialFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationBlockchainIdentity:self destinationKeyIndex:contactRequest.recipientKeyIndex sourceBlockchainIdentity:sourceBlockchainIdentity sourceKeyIndex:contactRequest.senderKeyIndex account:account]; - - if (![DSFriendRequestEntity existingFriendRequestEntityWithSourceIdentifier:sourceBlockchainIdentity.uniqueID destinationIdentifier:self.uniqueID onAccountIndex:account.accountNumber inContext:context]) { - dispatch_group_enter(dispatchGroup); - [potentialFriendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { - if (success) { - DSDashpayUserEntity *matchingDashpayUserInContext = [self matchingDashpayUserInContext:context]; - DSFriendRequestEntity *friendRequest = [potentialFriendship outgoingFriendRequestForDashpayUserEntity:matchingDashpayUserInContext atTimestamp:contactRequest.createdAt]; - [potentialFriendship storeExtendedPublicKeyAssociatedWithFriendRequest:friendRequest]; - [matchingDashpayUserInContext addIncomingRequestsObject:friendRequest]; - - if ([[friendRequest.sourceContact.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUserInContext]] count]) { - [matchingDashpayUserInContext addFriendsObject:friendRequest.sourceContact]; - } - - [account addIncomingDerivationPath:incomingFundsDerivationPath - forFriendshipIdentifier:friendRequest.friendshipIdentifier - inContext:context]; - [context ds_save]; - [self.chain.chainManager.transactionManager updateTransactionsBloomFilter]; - } else { - succeeded = FALSE; - [errors addObject:[NSError errorWithCode:500 localizedDescriptionKey:@"Could not create friendship derivation path"]]; - } - dispatch_group_leave(dispatchGroup); - }]; - } - - } else { - DSBlockchainIdentity *sourceBlockchainIdentity = [[DSBlockchainIdentity alloc] initWithBlockchainIdentityEntity:externalBlockchainIdentityEntity]; - NSAssert(sourceBlockchainIdentity, @"This should not be null"); - if ([sourceBlockchainIdentity activeKeyCount] > 0 && [sourceBlockchainIdentity keyAtIndex:contactRequest.senderKeyIndex]) { - //the contact already existed, and has an encryption public key set, create the incoming friend request, add a friendship if an outgoing friend request also exists - OpaqueKey *key = [sourceBlockchainIdentity keyAtIndex:contactRequest.senderKeyIndex]; - NSData *decryptedExtendedPublicKeyData = [contactRequest decryptedPublicKeyDataWithKey:key]; - NSAssert(decryptedExtendedPublicKeyData, @"Data should be decrypted"); - OpaqueKey *extendedPublicKey = [DSKeyManager keyWithExtendedPublicKeyData:decryptedExtendedPublicKeyData ofType:KeyKind_ECDSA]; - if (!extendedPublicKey) { - succeeded = FALSE; - [errors addObject:[NSError errorWithCode:500 localizedDescriptionKey:@"Contact request extended public key is incorrectly encrypted."]]; - return; - } - [self addIncomingRequestFromContact:externalBlockchainIdentityEntity.matchingDashpayUser - forExtendedPublicKey:extendedPublicKey - atTimestamp:contactRequest.createdAt]; - - DSDashpayUserEntity *matchingDashpayUserInContext = [self matchingDashpayUserInContext:context]; - if ([[externalBlockchainIdentityEntity.matchingDashpayUser.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUserInContext]] count]) { - [matchingDashpayUserInContext addFriendsObject:[externalBlockchainIdentityEntity matchingDashpayUser]]; - [context ds_save]; - } - - } else { - //the blockchain identity is already known, but needs to updated to get the right key, create the incoming friend request, add a friendship if an outgoing friend request also exists - dispatch_group_enter(dispatchGroup); - [sourceBlockchainIdentity fetchNeededNetworkStateInformationInContext:context - withCompletion:^(DSBlockchainIdentityQueryStep failureStep, NSArray *networkStateInformationErrors) { - if (!failureStep) { - [context performBlockAndWait:^{ - OpaqueKey *key = [sourceBlockchainIdentity keyAtIndex:contactRequest.senderKeyIndex]; - NSAssert(key, @"key should be known"); - NSData *decryptedExtendedPublicKeyData = [contactRequest decryptedPublicKeyDataWithKey:key]; - NSAssert(decryptedExtendedPublicKeyData, @"Data should be decrypted"); - OpaqueKey *extendedPublicKey = [DSKeyManager keyWithExtendedPublicKeyData:decryptedExtendedPublicKeyData ofType:KeyKind_ECDSA]; - NSAssert(extendedPublicKey, @"A key should be recovered"); - [self addIncomingRequestFromContact:externalBlockchainIdentityEntity.matchingDashpayUser - forExtendedPublicKey:extendedPublicKey - atTimestamp:contactRequest.createdAt]; - DSDashpayUserEntity *matchingDashpayUserInContext = [self matchingDashpayUserInContext:context]; - if ([[externalBlockchainIdentityEntity.matchingDashpayUser.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUserInContext]] count]) { - [matchingDashpayUserInContext addFriendsObject:externalBlockchainIdentityEntity.matchingDashpayUser]; - [context ds_save]; - } - }]; - } else { - succeeded = FALSE; - [errors addObjectsFromArray:networkStateInformationErrors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } - } - } - } - - dispatch_group_notify(dispatchGroup, completionQueue, ^{ - if (completion) { - completion(succeeded, [errors copy]); - } - }); - }]; -} - -- (void)addFriendship:(DSPotentialOneWayFriendship *)friendship inContext:(NSManagedObjectContext *)context completion:(void (^)(BOOL success, NSError *error))completion { - //DSFriendRequestEntity * friendRequestEntity = [friendship outgoingFriendRequestForDashpayUserEntity:friendship.destinationBlockchainIdentity.matchingDashpayUser]; - DSFriendRequestEntity *friendRequestEntity = [DSFriendRequestEntity managedObjectInBlockedContext:context]; - friendRequestEntity.sourceContact = [friendship.sourceBlockchainIdentity matchingDashpayUserInContext:context]; - friendRequestEntity.destinationContact = [friendship.destinationBlockchainIdentity matchingDashpayUserInContext:context]; - friendRequestEntity.timestamp = friendship.createdAt; - NSAssert(friendRequestEntity.sourceContact != friendRequestEntity.destinationContact, @"This must be different contacts"); - - DSAccountEntity *accountEntity = [DSAccountEntity accountEntityForWalletUniqueID:self.wallet.uniqueIDString index:0 onChain:self.chain inContext:context]; - - friendRequestEntity.account = accountEntity; - - [friendRequestEntity finalizeWithFriendshipIdentifier]; - - [friendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { - if (!success) { - return; - } - friendRequestEntity.derivationPath = [friendship storeExtendedPublicKeyAssociatedWithFriendRequest:friendRequestEntity inContext:context]; - - DSAccount *account = [self.wallet accountWithNumber:0]; - if (friendship.destinationBlockchainIdentity.isLocal) { //the destination is also local - NSAssert(friendship.destinationBlockchainIdentity.wallet, @"Wallet should be known"); - DSAccount *recipientAccount = [friendship.destinationBlockchainIdentity.wallet accountWithNumber:0]; - NSAssert(recipientAccount, @"Recipient Wallet should exist"); - [recipientAccount addIncomingDerivationPath:incomingFundsDerivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:context]; - if (recipientAccount != account) { - [account addOutgoingDerivationPath:incomingFundsDerivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:context]; - } - } else { - //todo update outgoing derivation paths to incoming derivation paths as blockchain users come in - [account addIncomingDerivationPath:incomingFundsDerivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:context]; - } - - NSAssert(friendRequestEntity.derivationPath, @"derivation path must be present"); - - DSDashpayUserEntity *dashpayUserInChildContext = [self matchingDashpayUserInContext:context]; - - [dashpayUserInChildContext addOutgoingRequestsObject:friendRequestEntity]; - - if ([[[friendship.destinationBlockchainIdentity matchingDashpayUserInContext:context].outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact == %@", dashpayUserInChildContext]] count]) { - [dashpayUserInChildContext addFriendsObject:[friendship.destinationBlockchainIdentity matchingDashpayUserInContext:context]]; - } - NSError *savingError = [context ds_save]; - [self.chain.chainManager.transactionManager updateTransactionsBloomFilter]; - if (completion) { - completion(savingError ? NO : YES, savingError); - } - }]; -} - -- (void)addFriendshipFromSourceBlockchainIdentity:(DSBlockchainIdentity *)sourceBlockchainIdentity sourceKeyIndex:(uint32_t)sourceKeyIndex toRecipientBlockchainIdentity:(DSBlockchainIdentity *)recipientBlockchainIdentity recipientKeyIndex:(uint32_t)recipientKeyIndex atTimestamp:(NSTimeInterval)timestamp inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSAccount *account = [self.wallet accountWithNumber:0]; - - DSPotentialOneWayFriendship *realFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationBlockchainIdentity:recipientBlockchainIdentity destinationKeyIndex:recipientKeyIndex sourceBlockchainIdentity:self sourceKeyIndex:sourceKeyIndex account:account createdAt:timestamp]; - - if (![DSFriendRequestEntity existingFriendRequestEntityWithSourceIdentifier:self.uniqueID destinationIdentifier:recipientBlockchainIdentity.uniqueID onAccountIndex:account.accountNumber inContext:context]) { - //it was probably added already - //this could happen when have 2 blockchain identities in same wallet - //Identity A gets outgoing contacts - //Which are the same as Identity B incoming contacts, no need to add the friendships twice - [self addFriendship:realFriendship inContext:context completion:nil]; - } - }]; -} - -- (void)handleOutgoingRequests:(NSArray *)outgoingRequests - context:(NSManagedObjectContext *)context - completion:(void (^)(BOOL success, NSArray *errors))completion - onCompletionQueue:(dispatch_queue_t)completionQueue { - if (!self.isActive) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, @[[NSError errorWithCode:410 localizedDescriptionKey:@"Identity no longer active in wallet"]]); - }); - } - return; - } - [context performBlockAndWait:^{ - __block NSMutableArray *errors = [NSMutableArray array]; - - __block BOOL succeeded = YES; - dispatch_group_t dispatchGroup = dispatch_group_create(); - - for (DSContactRequest *contactRequest in outgoingRequests) { - DSBlockchainIdentityEntity *recipientBlockchainIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", uint256_data(contactRequest.recipientBlockchainIdentityUniqueId)]; - if (!recipientBlockchainIdentityEntity) { - //no contact exists yet - dispatch_group_enter(dispatchGroup); - DSBlockchainIdentity *recipientBlockchainIdentity = [self.identitiesManager foreignBlockchainIdentityWithUniqueId:contactRequest.recipientBlockchainIdentityUniqueId createIfMissing:YES inContext:context]; - NSAssert([recipientBlockchainIdentity blockchainIdentityEntityInContext:context], @"Entity should now exist"); - [recipientBlockchainIdentity fetchNeededNetworkStateInformationInContext:context - withCompletion:^(DSBlockchainIdentityQueryStep failureStep, NSArray *_Nullable networkErrors) { - if (!failureStep) { - [self addFriendshipFromSourceBlockchainIdentity:self sourceKeyIndex:contactRequest.senderKeyIndex toRecipientBlockchainIdentity:recipientBlockchainIdentity recipientKeyIndex:contactRequest.recipientKeyIndex atTimestamp:contactRequest.createdAt inContext:context]; - } else { - succeeded = FALSE; - [errors addObjectsFromArray:networkErrors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } else { - //the recipient blockchain identity is already known, meaning they had made a friend request to us before, and on another device we had accepted - //or the recipient blockchain identity is also local to the device - - DSWallet *recipientWallet = nil; - DSBlockchainIdentity *recipientBlockchainIdentity = [self.chain blockchainIdentityForUniqueId:recipientBlockchainIdentityEntity.uniqueID.UInt256 foundInWallet:&recipientWallet]; - BOOL isLocal = TRUE; - if (!recipientBlockchainIdentity) { - //this is not local - recipientBlockchainIdentity = [[DSBlockchainIdentity alloc] initWithBlockchainIdentityEntity:recipientBlockchainIdentityEntity]; - isLocal = FALSE; - } - - dispatch_group_enter(dispatchGroup); - [recipientBlockchainIdentity fetchIfNeededNetworkStateInformation:DSBlockchainIdentityQueryStep_Profile & DSBlockchainIdentityQueryStep_Username & DSBlockchainIdentityQueryStep_Identity - inContext:context - withCompletion:^(DSBlockchainIdentityQueryStep failureStep, NSArray *_Nullable networkErrors) { - if (!failureStep) { - [self addFriendshipFromSourceBlockchainIdentity:self sourceKeyIndex:contactRequest.senderKeyIndex toRecipientBlockchainIdentity:recipientBlockchainIdentity recipientKeyIndex:contactRequest.recipientKeyIndex atTimestamp:contactRequest.createdAt inContext:context]; - } else { - succeeded = FALSE; - [errors addObjectsFromArray:networkErrors]; - } - dispatch_group_leave(dispatchGroup); - } - onCompletionQueue:self.identityQueue]; - } - } - - dispatch_group_notify(dispatchGroup, completionQueue, ^{ - if (completion) { - completion(succeeded, [errors copy]); - } - }); - }]; -} - -- (void)addIncomingRequestFromContact:(DSDashpayUserEntity *)dashpayUserEntity - forExtendedPublicKey:(OpaqueKey *)extendedPublicKey - atTimestamp:(NSTimeInterval)timestamp { - NSManagedObjectContext *context = dashpayUserEntity.managedObjectContext; - DSFriendRequestEntity *friendRequestEntity = [DSFriendRequestEntity managedObjectInBlockedContext:context]; - friendRequestEntity.sourceContact = dashpayUserEntity; - friendRequestEntity.destinationContact = [self matchingDashpayUserInContext:dashpayUserEntity.managedObjectContext]; - friendRequestEntity.timestamp = timestamp; - NSAssert(friendRequestEntity.sourceContact != friendRequestEntity.destinationContact, @"This must be different contacts"); - - DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity managedObjectInBlockedContext:context]; - derivationPathEntity.chain = [self.chain chainEntityInContext:context]; - - friendRequestEntity.derivationPath = derivationPathEntity; - - NSAssert(friendRequestEntity.derivationPath, @"There must be a derivation path"); - - DSAccount *account = [self.wallet accountWithNumber:0]; - - DSAccountEntity *accountEntity = [DSAccountEntity accountEntityForWalletUniqueID:self.wallet.uniqueIDString index:account.accountNumber onChain:self.chain inContext:dashpayUserEntity.managedObjectContext]; - - derivationPathEntity.account = accountEntity; - - friendRequestEntity.account = accountEntity; - - [friendRequestEntity finalizeWithFriendshipIdentifier]; - - //NSLog(@"->created derivation path entity %@ %@", friendRequestEntity.friendshipIdentifier.hexString, [NSThread callStackSymbols]); - - DSIncomingFundsDerivationPath *derivationPath = [DSIncomingFundsDerivationPath externalDerivationPathWithExtendedPublicKey:extendedPublicKey withDestinationBlockchainIdentityUniqueId:[self matchingDashpayUserInContext:dashpayUserEntity.managedObjectContext].associatedBlockchainIdentity.uniqueID.UInt256 sourceBlockchainIdentityUniqueId:dashpayUserEntity.associatedBlockchainIdentity.uniqueID.UInt256 onChain:self.chain]; - - derivationPathEntity.publicKeyIdentifier = derivationPath.standaloneExtendedPublicKeyUniqueID; - - [derivationPath storeExternalDerivationPathExtendedPublicKeyToKeyChain]; - - //incoming request uses an outgoing derivation path - [account addOutgoingDerivationPath:derivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:dashpayUserEntity.managedObjectContext]; - - DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:dashpayUserEntity.managedObjectContext]; - [matchingDashpayUser addIncomingRequestsObject:friendRequestEntity]; - - if ([[friendRequestEntity.sourceContact.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUser]] count]) { - [matchingDashpayUser addFriendsObject:friendRequestEntity.sourceContact]; - } - - [context ds_save]; - [self.chain.chainManager.transactionManager updateTransactionsBloomFilter]; -} - -// MARK: - Persistence - -// MARK: Saving - -- (void)saveInitial { - [self saveInitialInContext:self.platformContext]; -} - -- (DSBlockchainIdentityEntity *)initialEntityInContext:(NSManagedObjectContext *)context { - DSChainEntity *chainEntity = [self.chain chainEntityInContext:context]; - - DSBlockchainIdentityEntity *entity = [DSBlockchainIdentityEntity managedObjectInBlockedContext:context]; - entity.uniqueID = uint256_data(self.uniqueID); - entity.isLocal = self.isLocal; - entity.registrationStatus = self.registrationStatus; - if (self.isLocal) { - NSData *transactionHash = uint256_data(self.registrationCreditFundingTransaction.txHash); - DSCreditFundingTransactionEntity *transactionEntity = (DSCreditFundingTransactionEntity *)[DSTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", transactionHash]; - entity.registrationFundingTransaction = transactionEntity; - } - entity.chain = chainEntity; - for (NSString *usernameFullPath in self.usernameStatuses) { - DSBlockchainIdentityUsernameEntity *usernameEntity = [DSBlockchainIdentityUsernameEntity managedObjectInBlockedContext:context]; - usernameEntity.status = [self statusOfUsernameFullPath:usernameFullPath]; - usernameEntity.stringValue = [self usernameOfUsernameFullPath:usernameFullPath]; - usernameEntity.domain = [self domainOfUsernameFullPath:usernameFullPath]; - usernameEntity.blockchainIdentity = entity; - [entity addUsernamesObject:usernameEntity]; - [entity setDashpayUsername:usernameEntity]; - } - - for (NSNumber *index in self.keyInfoDictionaries) { - NSDictionary *keyDictionary = self.keyInfoDictionaries[index]; - DSBlockchainIdentityKeyStatus status = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyStatus)] unsignedIntValue]; - KeyKind keyType = [keyDictionary[@(DSBlockchainIdentityKeyDictionary_KeyType)] unsignedIntValue]; - NSValue *key = keyDictionary[@(DSBlockchainIdentityKeyDictionary_Key)]; - DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:keyType]; - const NSUInteger indexes[] = {_index, index.unsignedIntegerValue}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - [self createNewKey:key.pointerValue forIdentityEntity:entity atPath:indexPath withStatus:status fromDerivationPath:derivationPath inContext:context]; - } - - DSDashpayUserEntity *dashpayUserEntity = [DSDashpayUserEntity managedObjectInBlockedContext:context]; - dashpayUserEntity.chain = chainEntity; - entity.matchingDashpayUser = dashpayUserEntity; - - if (self.isOutgoingInvitation) { - DSBlockchainInvitationEntity *blockchainInvitationEntity = [DSBlockchainInvitationEntity managedObjectInBlockedContext:context]; - blockchainInvitationEntity.chain = chainEntity; - entity.associatedInvitation = blockchainInvitationEntity; - } - - return entity; -} - -- (void)saveInitialInContext:(NSManagedObjectContext *)context { - if (self.isTransient) return; - //no need for active check, in fact it will cause an infinite loop - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *entity = [self initialEntityInContext:context]; - DSDashpayUserEntity *dashpayUserEntity = entity.matchingDashpayUser; - - [context ds_saveInBlockAndWait]; - [[NSManagedObjectContext viewContext] performBlockAndWait:^{ - self.matchingDashpayUserInViewContext = [[NSManagedObjectContext viewContext] objectWithID:dashpayUserEntity.objectID]; - }]; - [[NSManagedObjectContext platformContext] performBlockAndWait:^{ - self.matchingDashpayUserInPlatformContext = [[NSManagedObjectContext platformContext] objectWithID:dashpayUserEntity.objectID]; - }]; - if ([self isLocal]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainIdentityKey: self}]; - }); - } - }]; -} - -- (void)saveInContext:(NSManagedObjectContext *)context { - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - BOOL changeOccured = NO; - NSMutableArray *updateEvents = [NSMutableArray array]; - DSBlockchainIdentityEntity *entity = [self blockchainIdentityEntityInContext:context]; - if (entity.creditBalance != self.creditBalance) { - entity.creditBalance = self.creditBalance; - changeOccured = YES; - [updateEvents addObject:DSBlockchainIdentityUpdateEventCreditBalance]; - } - if (entity.registrationStatus != self.registrationStatus) { - entity.registrationStatus = self.registrationStatus; - changeOccured = YES; - [updateEvents addObject:DSBlockchainIdentityUpdateEventRegistration]; - } - - if (!uint256_eq(entity.dashpaySyncronizationBlockHash.UInt256, self.dashpaySyncronizationBlockHash)) { - entity.dashpaySyncronizationBlockHash = uint256_data(self.dashpaySyncronizationBlockHash); - changeOccured = YES; - [updateEvents addObject:DSBlockchainIdentityUpdateEventDashpaySyncronizationBlockHash]; - } - - if (entity.lastCheckedUsernamesTimestamp != self.lastCheckedUsernamesTimestamp) { - entity.lastCheckedUsernamesTimestamp = self.lastCheckedUsernamesTimestamp; - changeOccured = YES; - } - - if (entity.lastCheckedProfileTimestamp != self.lastCheckedProfileTimestamp) { - entity.lastCheckedProfileTimestamp = self.lastCheckedProfileTimestamp; - changeOccured = YES; - } - - if (entity.lastCheckedIncomingContactsTimestamp != self.lastCheckedIncomingContactsTimestamp) { - entity.lastCheckedIncomingContactsTimestamp = self.lastCheckedIncomingContactsTimestamp; - changeOccured = YES; - } - - if (entity.lastCheckedOutgoingContactsTimestamp != self.lastCheckedOutgoingContactsTimestamp) { - entity.lastCheckedOutgoingContactsTimestamp = self.lastCheckedOutgoingContactsTimestamp; - changeOccured = YES; - } - - if (changeOccured) { - [context ds_save]; - if (updateEvents.count) { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainIdentityKey: self, DSBlockchainIdentityUpdateEvents: updateEvents}]; - }); - } - } - }]; -} - -- (NSString *)identifierForKeyAtPath:(NSIndexPath *)path fromDerivationPath:(DSDerivationPath *)derivationPath { - NSIndexPath *softenedPath = [path softenAllItems]; - return [NSString stringWithFormat:@"%@-%@-%@", self.uniqueIdString, derivationPath.standaloneExtendedPublicKeyUniqueID, [softenedPath indexPathString]]; -} - -- (BOOL)createNewKey:(OpaqueKey *)key forIdentityEntity:(DSBlockchainIdentityEntity *)blockchainIdentityEntity atPath:(NSIndexPath *)path withStatus:(DSBlockchainIdentityKeyStatus)status fromDerivationPath:(DSDerivationPath *)derivationPath inContext:(NSManagedObjectContext *)context { - NSAssert(blockchainIdentityEntity, @"Entity should be present"); - DSDerivationPathEntity *derivationPathEntity = [derivationPath derivationPathEntityInContext:context]; - NSUInteger count = [DSBlockchainIdentityKeyPathEntity countObjectsInContext:context matching:@"blockchainIdentity == %@ && derivationPath == %@ && path == %@", blockchainIdentityEntity, derivationPathEntity, path]; - if (!count) { - DSBlockchainIdentityKeyPathEntity *blockchainIdentityKeyPathEntity = [DSBlockchainIdentityKeyPathEntity managedObjectInBlockedContext:context]; - blockchainIdentityKeyPathEntity.derivationPath = derivationPathEntity; - blockchainIdentityKeyPathEntity.keyType = key->tag; - blockchainIdentityKeyPathEntity.keyStatus = status; - NSData *privateKeyData = [DSKeyManager privateKeyData:key]; - if (privateKeyData) { - setKeychainData(privateKeyData, [self identifierForKeyAtPath:path fromDerivationPath:derivationPath], YES); -#if DEBUG - DSLogPrivate(@"Saving key at %@ for user %@", [self identifierForKeyAtPath:path fromDerivationPath:derivationPath], self.currentDashpayUsername); -#else - DSLog(@"Saving key at %@ for user %@", @"", @""); -#endif - } else { - OpaqueKey *privateKey = [self derivePrivateKeyAtIndexPath:path ofType:(int16_t) key->tag]; - NSAssert([DSKeyManager keysPublicKeyDataIsEqual:privateKey key2:key], @"The keys don't seem to match up"); - NSData *privateKeyData = [DSKeyManager privateKeyData:privateKey]; - NSAssert(privateKeyData, @"Private key data should exist"); - setKeychainData(privateKeyData, [self identifierForKeyAtPath:path fromDerivationPath:derivationPath], YES); -#if DEBUG - DSLogPrivate(@"Saving key after rederivation %@ for user %@", [self identifierForKeyAtPath:path fromDerivationPath:derivationPath], self.currentDashpayUsername ? self.currentDashpayUsername : self.uniqueIdString); -#else - DSLog(@"Saving key after rederivation %@ for user %@", @"", @""); -#endif - } - - blockchainIdentityKeyPathEntity.path = path; - blockchainIdentityKeyPathEntity.publicKeyData = [DSKeyManager publicKeyData:key]; - blockchainIdentityKeyPathEntity.keyID = (uint32_t)[path indexAtPosition:path.length - 1]; - [blockchainIdentityEntity addKeyPathsObject:blockchainIdentityKeyPathEntity]; - return YES; - } else { -#if DEBUG - DSLogPrivate(@"Already had saved this key %@", path); -#else - DSLog(@"Already had saved this key %@", @""); -#endif - return NO; //no need to save the context - } -} - -- (void)saveNewKey:(OpaqueKey *)key atPath:(NSIndexPath *)path withStatus:(DSBlockchainIdentityKeyStatus)status fromDerivationPath:(DSDerivationPath *)derivationPath inContext:(NSManagedObjectContext *)context { - NSAssert(self.isLocal, @"This should only be called on local blockchain identities"); - if (!self.isLocal) return; - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *blockchainIdentityEntity = [self blockchainIdentityEntityInContext:context]; - if ([self createNewKey:key forIdentityEntity:blockchainIdentityEntity atPath:path withStatus:status fromDerivationPath:derivationPath inContext:context]) { - [context ds_save]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSBlockchainIdentityKey: self, - DSBlockchainIdentityUpdateEvents: @[DSBlockchainIdentityUpdateEventKeyUpdate]}]; - }); - }]; -} - -- (void)saveNewRemoteIdentityKey:(OpaqueKey *)key forKeyWithIndexID:(uint32_t)keyID withStatus:(DSBlockchainIdentityKeyStatus)status inContext:(NSManagedObjectContext *)context { - NSAssert(self.isLocal == FALSE, @"This should only be called on non local blockchain identities"); - if (self.isLocal) return; - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *blockchainIdentityEntity = [self blockchainIdentityEntityInContext:context]; - NSUInteger count = [DSBlockchainIdentityKeyPathEntity countObjectsInContext:context matching:@"blockchainIdentity == %@ && keyID == %@", blockchainIdentityEntity, @(keyID)]; - if (!count) { - DSBlockchainIdentityKeyPathEntity *blockchainIdentityKeyPathEntity = [DSBlockchainIdentityKeyPathEntity managedObjectInBlockedContext:context]; - blockchainIdentityKeyPathEntity.keyType = key->tag; - blockchainIdentityKeyPathEntity.keyStatus = status; - blockchainIdentityKeyPathEntity.keyID = keyID; - blockchainIdentityKeyPathEntity.publicKeyData = [DSKeyManager publicKeyData:key]; - [blockchainIdentityEntity addKeyPathsObject:blockchainIdentityKeyPathEntity]; - [context ds_save]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSBlockchainIdentityKey: self, - DSBlockchainIdentityUpdateEvents: @[DSBlockchainIdentityUpdateEventKeyUpdate]}]; - }); - }]; -} - - -- (void)updateStatus:(DSBlockchainIdentityKeyStatus)status forKeyAtPath:(NSIndexPath *)path fromDerivationPath:(DSDerivationPath *)derivationPath inContext:(NSManagedObjectContext *)context { - NSAssert(self.isLocal, @"This should only be called on local blockchain identities"); - if (!self.isLocal) return; - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *entity = [self blockchainIdentityEntityInContext:context]; - DSDerivationPathEntity *derivationPathEntity = [derivationPath derivationPathEntityInContext:context]; - DSBlockchainIdentityKeyPathEntity *blockchainIdentityKeyPathEntity = [[DSBlockchainIdentityKeyPathEntity objectsInContext:context matching:@"blockchainIdentity == %@ && derivationPath == %@ && path == %@", entity, derivationPathEntity, path] firstObject]; - if (blockchainIdentityKeyPathEntity && (blockchainIdentityKeyPathEntity.keyStatus != status)) { - blockchainIdentityKeyPathEntity.keyStatus = status; - [context ds_save]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSBlockchainIdentityKey: self, - DSBlockchainIdentityUpdateEvents: @[DSBlockchainIdentityUpdateEventKeyUpdate]}]; - }); - }]; -} - -- (void)updateStatus:(DSBlockchainIdentityKeyStatus)status forKeyWithIndexID:(uint32_t)keyID inContext:(NSManagedObjectContext *)context { - NSAssert(self.isLocal == FALSE, @"This should only be called on non local blockchain identities"); - if (self.isLocal) return; - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *entity = [self blockchainIdentityEntityInContext:context]; - DSBlockchainIdentityKeyPathEntity *blockchainIdentityKeyPathEntity = [[DSBlockchainIdentityKeyPathEntity objectsInContext:context matching:@"blockchainIdentity == %@ && derivationPath == NULL && keyID == %@", entity, @(keyID)] firstObject]; - if (blockchainIdentityKeyPathEntity) { - DSBlockchainIdentityKeyPathEntity *blockchainIdentityKeyPathEntity = [DSBlockchainIdentityKeyPathEntity managedObjectInBlockedContext:context]; - blockchainIdentityKeyPathEntity.keyStatus = status; - [context ds_save]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification - object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSBlockchainIdentityKey: self, - DSBlockchainIdentityUpdateEvents: @[DSBlockchainIdentityUpdateEventKeyUpdate]}]; - }); - }]; -} - -- (void)saveNewUsername:(NSString *)username inDomain:(NSString *)domain status:(DSBlockchainIdentityUsernameStatus)status inContext:(NSManagedObjectContext *)context { - NSAssert([username containsString:@"."] == FALSE, @"This is most likely an error"); - NSAssert(domain, @"Domain must not be nil"); - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *entity = [self blockchainIdentityEntityInContext:context]; - DSBlockchainIdentityUsernameEntity *usernameEntity = [DSBlockchainIdentityUsernameEntity managedObjectInBlockedContext:context]; - usernameEntity.status = status; - usernameEntity.stringValue = username; - usernameEntity.salt = [self saltForUsernameFullPath:[self fullPathForUsername:username inDomain:domain] saveSalt:NO inContext:context]; - usernameEntity.domain = domain; - [entity addUsernamesObject:usernameEntity]; - [entity setDashpayUsername:usernameEntity]; - [context ds_save]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateUsernameStatusNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainIdentityKey: self}]; - }); - }]; -} - -- (void)setUsernameFullPaths:(NSArray *)usernameFullPaths toStatus:(DSBlockchainIdentityUsernameStatus)status { - for (NSString *string in usernameFullPaths) { - NSMutableDictionary *usernameStatusDictionary = [[self.usernameStatuses objectForKey:string] mutableCopy]; - if (!usernameStatusDictionary) { - usernameStatusDictionary = [NSMutableDictionary dictionary]; - } - usernameStatusDictionary[BLOCKCHAIN_USERNAME_STATUS] = @(status); - [self.usernameStatuses setObject:[usernameStatusDictionary copy] forKey:string]; - } -} - -- (void)setAndSaveUsernameFullPaths:(NSArray *)usernameFullPaths toStatus:(DSBlockchainIdentityUsernameStatus)status inContext:(NSManagedObjectContext *)context { - [self setUsernameFullPaths:usernameFullPaths toStatus:status]; - [self saveUsernamesInDictionary:[self.usernameStatuses dictionaryWithValuesForKeys:usernameFullPaths] toStatus:status inContext:context]; -} - -- (void)saveUsernameFullPaths:(NSArray *)usernameFullPaths toStatus:(DSBlockchainIdentityUsernameStatus)status inContext:(NSManagedObjectContext *)context { - [self saveUsernamesInDictionary:[self.usernameStatuses dictionaryWithValuesForKeys:usernameFullPaths] toStatus:status inContext:context]; -} - -- (void)saveUsernamesInDictionary:(NSDictionary *)fullPathUsernamesDictionary toStatus:(DSBlockchainIdentityUsernameStatus)status inContext:(NSManagedObjectContext *)context { - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - for (NSString *fullPathUsername in fullPathUsernamesDictionary) { - NSString *username = [fullPathUsernamesDictionary[fullPathUsername] objectForKey:BLOCKCHAIN_USERNAME_PROPER]; - NSString *domain = [fullPathUsernamesDictionary[fullPathUsername] objectForKey:BLOCKCHAIN_USERNAME_DOMAIN]; - [self saveUsername:username inDomain:domain status:status salt:nil commitSave:NO inContext:context]; - } - [context ds_save]; - }]; -} - -//-(void)saveUsernamesToStatuses:(NSDictionary*)dictionary { -// if (self.isTransient) return; -// [self.managedObjectContext performBlockAndWait:^{ -// for (NSString * username in statusDictionary) { -// DSBlockchainIdentityUsernameStatus status = [statusDictionary[username] intValue]; -// NSString * domain = domainDictionary[username]; -// [self saveUsername:username inDomain:domain status:status salt:nil commitSave:NO]; -// } -// [self.managedObjectContext ds_save]; -// }]; -//} - -- (void)saveUsernameFullPath:(NSString *)usernameFullPath status:(DSBlockchainIdentityUsernameStatus)status salt:(NSData *)salt commitSave:(BOOL)commitSave inContext:(NSManagedObjectContext *)context { - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *entity = [self blockchainIdentityEntityInContext:context]; - NSSet *usernamesPassingTest = [entity.usernames objectsPassingTest:^BOOL(DSBlockchainIdentityUsernameEntity *_Nonnull obj, BOOL *_Nonnull stop) { - if ([[self fullPathForUsername:obj.stringValue inDomain:obj.domain] isEqualToString:usernameFullPath]) { - *stop = TRUE; - return TRUE; - - } else { - return FALSE; - } - }]; - if ([usernamesPassingTest count]) { - NSAssert([usernamesPassingTest count] == 1, @"There should never be more than 1"); - DSBlockchainIdentityUsernameEntity *usernameEntity = [usernamesPassingTest anyObject]; - usernameEntity.status = status; - if (salt) { - usernameEntity.salt = salt; - } - if (commitSave) { - [context ds_save]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateUsernameStatusNotification object:nil userInfo:@{ - DSChainManagerNotificationChainKey: self.chain, - DSBlockchainIdentityKey: self, - DSBlockchainIdentityUsernameKey: usernameEntity.stringValue, - DSBlockchainIdentityUsernameDomainKey: usernameEntity.stringValue}]; - }); - } - }]; -} - -- (void)saveUsername:(NSString *)username inDomain:(NSString *)domain status:(DSBlockchainIdentityUsernameStatus)status salt:(NSData *)salt commitSave:(BOOL)commitSave inContext:(NSManagedObjectContext *)context { - if (self.isTransient) return; - if (!self.isActive) return; - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *entity = [self blockchainIdentityEntityInContext:context]; - NSSet *usernamesPassingTest = [entity.usernames objectsPassingTest:^BOOL(DSBlockchainIdentityUsernameEntity *_Nonnull obj, BOOL *_Nonnull stop) { - if ([obj.stringValue isEqualToString:username]) { - *stop = TRUE; - return TRUE; - - } else { - return FALSE; - } - }]; - if ([usernamesPassingTest count]) { - NSAssert([usernamesPassingTest count] == 1, @"There should never be more than 1"); - DSBlockchainIdentityUsernameEntity *usernameEntity = [usernamesPassingTest anyObject]; - usernameEntity.status = status; - if (salt) { - usernameEntity.salt = salt; - } - if (commitSave) { - [context ds_save]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateUsernameStatusNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainIdentityKey: self, DSBlockchainIdentityUsernameKey: username, DSBlockchainIdentityUsernameDomainKey: domain}]; - }); - } - }]; -} - -// MARK: Deletion - -- (void)deletePersistentObjectAndSave:(BOOL)save inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSBlockchainIdentityEntity *blockchainIdentityEntity = [self blockchainIdentityEntityInContext:context]; - if (blockchainIdentityEntity) { - NSSet *friendRequests = [blockchainIdentityEntity.matchingDashpayUser outgoingRequests]; - for (DSFriendRequestEntity *friendRequest in friendRequests) { - uint32_t accountNumber = friendRequest.account.index; - DSAccount *account = [self.wallet accountWithNumber:accountNumber]; - [account removeIncomingDerivationPathForFriendshipWithIdentifier:friendRequest.friendshipIdentifier]; - } - [blockchainIdentityEntity deleteObjectAndWait]; - if (save) { - [context ds_save]; - } - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainIdentityDidUpdateNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainIdentityKey: self}]; - }); - }]; -} - -// MARK: Entity - -- (DSBlockchainIdentityEntity *)blockchainIdentityEntity { - return [self blockchainIdentityEntityInContext:[NSManagedObjectContext viewContext]]; -} - -- (DSBlockchainIdentityEntity *)blockchainIdentityEntityInContext:(NSManagedObjectContext *)context { - __block DSBlockchainIdentityEntity *entity = nil; - [context performBlockAndWait:^{ - entity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", self.uniqueIDData]; - }]; - NSAssert(entity, @"An entity should always be found"); - return entity; -} - - -//-(DSBlockchainIdentityRegistrationTransition*)blockchainIdentityRegistrationTransition { -// if (!_blockchainIdentityRegistrationTransition) { -// _blockchainIdentityRegistrationTransition = (DSBlockchainIdentityRegistrationTransition*)[self.wallet.specialTransactionsHolder transactionForHash:self.registrationTransitionHash]; -// } -// return _blockchainIdentityRegistrationTransition; -//} - -//-(UInt256)lastTransitionHash { -// //this is not effective, do this locally in the future -// return [[self allTransitions] lastObject].transitionHash; -//} - - -- (NSString *)debugDescription { - return [[super debugDescription] stringByAppendingString:[NSString stringWithFormat:@" {%@-%@}", self.currentDashpayUsername, self.uniqueIdString]]; -} - -@end diff --git a/DashSync/shared/Models/Identity/DSBlockchainInvitation+Protected.h b/DashSync/shared/Models/Identity/DSBlockchainInvitation+Protected.h deleted file mode 100644 index 8e9265edb..000000000 --- a/DashSync/shared/Models/Identity/DSBlockchainInvitation+Protected.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Created by Samuel Westrich -// Copyright © 2564 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 "BigIntTypes.h" -#import "DSBlockchainInvitation.h" -#import "DSBlockchainInvitationEntity+CoreDataClass.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSChain; - -@interface DSBlockchainInvitation (Protected) - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet withBlockchainInvitationEntity:(DSBlockchainInvitationEntity *)blockchainInvitationEntity; - -- (instancetype)initWithUniqueId:(UInt256)uniqueId isTransient:(BOOL)isTransient onChain:(DSChain *)chain; - -- (instancetype)initAtIndex:(uint32_t)index inWallet:(DSWallet *)wallet; - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction inWallet:(DSWallet *)wallet; - -- (void)registerInWalletForBlockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId; -- (void)registerInWalletForRegistrationFundingTransaction:(DSCreditFundingTransaction *)fundingTransaction; - -- (void)deletePersistentObjectAndSave:(BOOL)save inContext:(NSManagedObjectContext *)context; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSBlockchainInvitation.m b/DashSync/shared/Models/Identity/DSBlockchainInvitation.m deleted file mode 100644 index 0a19369b1..000000000 --- a/DashSync/shared/Models/Identity/DSBlockchainInvitation.m +++ /dev/null @@ -1,457 +0,0 @@ -// -// Created by Samuel Westrich -// Copyright © 2564 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 "DSBlockchainInvitation.h" -#import "DSAuthenticationManager.h" -#import "DSBlockchainIdentity+Protected.h" -#import "DSBlockchainInvitationEntity+CoreDataClass.h" -#import "DSChainManager.h" -#import "DSCreditFundingDerivationPath.h" -#import "DSCreditFundingTransaction.h" -#import "DSDAPICoreNetworkService.h" -#import "DSDerivationPathFactory.h" -#import "DSIdentitiesManager+Protected.h" -#import "DSInstantSendTransactionLock.h" -#import "DSWallet.h" -#import "NSData+DSHash.h" -#import "NSError+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSManagedObjectContext+DSSugar.h" -#import "NSString+Dash.h" - -@interface DSBlockchainInvitation () - -@property (nonatomic, weak) DSWallet *wallet; -@property (nonatomic, strong) DSChain *chain; -@property (nonatomic, copy) NSString *link; -@property (nonatomic, strong) DSBlockchainIdentity *identity; -@property (nonatomic, assign) BOOL isTransient; -@property (nonatomic, assign) BOOL needsIdentityRetrieval; -@property (nonatomic, assign) BOOL createdLocally; - -@end - -@implementation DSBlockchainInvitation - -- (instancetype)initAtIndex:(uint32_t)index inWallet:(DSWallet *)wallet { - //this is the creation of a new blockchain identity - NSParameterAssert(wallet); - - if (!(self = [super init])) return nil; - self.wallet = wallet; - self.isTransient = FALSE; - self.createdLocally = YES; - self.identity = [[DSBlockchainIdentity alloc] initAtIndex:index inWallet:wallet]; - [self.identity setAssociatedInvitation:self]; - self.chain = wallet.chain; - self.needsIdentityRetrieval = NO; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withFundingTransaction:(DSCreditFundingTransaction *)transaction inWallet:(DSWallet *)wallet { - NSParameterAssert(wallet); - if (![transaction isCreditFundingTransaction]) return nil; - NSAssert(index != UINT32_MAX, @"index must be found"); - if (!(self = [super init])) return nil; - self.wallet = wallet; - self.isTransient = FALSE; - self.createdLocally = YES; - self.identity = [[DSBlockchainIdentity alloc] initAtIndex:index withFundingTransaction:transaction withUsernameDictionary:nil inWallet:wallet]; - [self.identity setAssociatedInvitation:self]; - self.chain = wallet.chain; - self.needsIdentityRetrieval = NO; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet { - NSParameterAssert(wallet); - NSAssert(index != UINT32_MAX, @"index must be found"); - if (!(self = [super init])) return nil; - self.wallet = wallet; - self.isTransient = FALSE; - self.createdLocally = YES; - self.identity = [[DSBlockchainIdentity alloc] initAtIndex:index withLockedOutpoint:lockedOutpoint inWallet:wallet]; - [self.identity setAssociatedInvitation:self]; - self.chain = wallet.chain; - self.needsIdentityRetrieval = NO; - return self; -} - -- (instancetype)initAtIndex:(uint32_t)index withLockedOutpoint:(DSUTXO)lockedOutpoint inWallet:(DSWallet *)wallet withBlockchainInvitationEntity:(DSBlockchainInvitationEntity *)blockchainInvitationEntity { - if (!(self = [super init])) return nil; - self.wallet = wallet; - self.isTransient = FALSE; - self.createdLocally = YES; - self.identity = [[DSBlockchainIdentity alloc] initAtIndex:index withLockedOutpoint:lockedOutpoint inWallet:wallet withBlockchainIdentityEntity:blockchainInvitationEntity.blockchainIdentity associatedToInvitation:self]; - self.link = blockchainInvitationEntity.link; - self.name = blockchainInvitationEntity.name; - self.tag = blockchainInvitationEntity.tag; - self.chain = wallet.chain; - self.needsIdentityRetrieval = NO; - return self; -} - -- (instancetype)initWithInvitationLink:(NSString *)invitationLink inWallet:(DSWallet *)wallet { - if (!(self = [super init])) return nil; - self.link = invitationLink; - self.wallet = wallet; - self.chain = wallet.chain; - self.needsIdentityRetrieval = YES; - self.createdLocally = NO; - return self; -} - -- (void)generateBlockchainInvitationsExtendedPublicKeysWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL registered))completion { - __block DSCreditFundingDerivationPath *derivationPathInvitationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:self.wallet]; - if ([derivationPathInvitationFunding hasExtendedPublicKey]) { - completion(YES); - return; - } - [[DSAuthenticationManager sharedInstance] seedWithPrompt:prompt - forWallet:self.wallet - forAmount:0 - forceAuthentication:NO - completion:^(NSData *_Nullable seed, BOOL cancelled) { - if (!seed) { - completion(NO); - return; - } - [derivationPathInvitationFunding generateExtendedPublicKeyFromSeed:seed - storeUnderWalletUniqueId:self.wallet.uniqueIDString]; - completion(YES); - }]; -} - - -- (void)registerInWalletForRegistrationFundingTransaction:(DSCreditFundingTransaction *)fundingTransaction { - NSAssert(self.identity != nil, @"The identity must already exist"); - [self.identity setInvitationRegistrationCreditFundingTransaction:fundingTransaction]; - [self registerInWalletForBlockchainIdentityUniqueId:fundingTransaction.creditBurnIdentityIdentifier]; - - //we need to also set the address of the funding transaction to being used so future identities past the initial gap limit are found - [fundingTransaction markInvitationAddressAsUsedInWallet:self.wallet]; -} - -- (void)registerInWalletForBlockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId { - [self.identity setInvitationUniqueId:blockchainIdentityUniqueId]; - [self registerInWallet]; -} - -- (BOOL)isRegisteredInWallet { - if (!self.wallet) return FALSE; - return [self.wallet containsBlockchainInvitation:self]; -} - -- (void)registerInWallet { - NSAssert(self.identity.isOutgoingInvitation, @"The underlying identity is not from an invitation"); - if (!self.identity.isOutgoingInvitation) return; - [self.wallet registerBlockchainInvitation:self]; - [self.identity saveInitial]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainInvitationDidUpdateNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainInvitationKey: self}]; - }); -} - -- (void)updateInWallet { - [self saveInContext:[NSManagedObjectContext platformContext]]; -} - -- (BOOL)unregisterLocally { - NSAssert(self.identity.isOutgoingInvitation, @"The underlying identity is not from an invitation"); - if (!self.identity.isOutgoingInvitation) return FALSE; - if (self.identity.isRegistered) return FALSE; //if the invitation has already been used we can not unregister it - [self.wallet unregisterBlockchainInvitation:self]; - [self deletePersistentObjectAndSave:YES inContext:[NSManagedObjectContext platformContext]]; - return TRUE; -} - -- (void)verifyInvitationLinkWithCompletion:(void (^_Nullable)(DSTransaction *transaction, bool spent, NSError *error))completion completionQueue:(dispatch_queue_t)completionQueue { - [DSBlockchainInvitation verifyInvitationLink:self.link onChain:self.wallet.chain completion:completion completionQueue:completionQueue]; -} - -+ (void)verifyInvitationLink:(NSString *)invitationLink onChain:(DSChain *)chain completion:(void (^_Nullable)(DSTransaction *transaction, bool spent, NSError *error))completion completionQueue:(dispatch_queue_t)completionQueue { - DSDAPICoreNetworkService *coreNetworkService = chain.chainManager.DAPIClient.DAPICoreNetworkService; - NSURLComponents *components = [NSURLComponents componentsWithString:invitationLink]; - NSArray *queryItems = components.queryItems; - UInt256 assetLockTransactionHash = UINT256_ZERO; - BOOL isEmptyFundingPrivateKey = true; - for (NSURLQueryItem *queryItem in queryItems) { - if ([queryItem.name isEqualToString:@"assetlocktx"]) { - assetLockTransactionHash = queryItem.value.hexToData.UInt256; - } else if ([queryItem.name isEqualToString:@"pk"]) { - isEmptyFundingPrivateKey = key_ecdsa_secret_key_is_empty([queryItem.value UTF8String], chain.chainType); - } - } - if (uint256_is_zero(assetLockTransactionHash)) { - if (completion) { - completion(nil, NO, [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation format is not valid"]); - } - return; - } - - if (isEmptyFundingPrivateKey) { - if (completion) { - completion(nil, NO, [NSError errorWithCode:400 localizedDescriptionKey:@"Funding private key is not valid"]); - } - return; - } - - [coreNetworkService getTransactionWithHash:assetLockTransactionHash - completionQueue:completionQueue - success:^(DSTransaction *_Nonnull transaction) { - NSAssert(transaction, @"transaction must not be null"); - if (!transaction || ![transaction isKindOfClass:[DSCreditFundingTransaction class]]) { - if (completion) { - completion(nil, NO, [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation transaction is not valid"]); - } - return; - } - if (completion) { - completion(transaction, NO, nil); - } - } - failure:^(NSError *_Nonnull error) { - if (completion) { - completion(nil, NO, [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation format is not valid"]); - } - }]; -} - -- (void)acceptInvitationUsingWalletIndex:(uint32_t)index setDashpayUsername:(NSString *)dashpayUsername authenticationPrompt:(NSString *)authenticationMessage identityRegistrationSteps:(DSBlockchainIdentityRegistrationStep)identityRegistrationSteps stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion completionQueue:(dispatch_queue_t)completionQueue { - DSDAPICoreNetworkService *coreNetworkService = self.chain.chainManager.DAPIClient.DAPICoreNetworkService; - NSURLComponents *components = [NSURLComponents componentsWithString:self.link]; - NSArray *queryItems = components.queryItems; - UInt256 assetLockTransactionHash = UINT256_ZERO; - OpaqueKey *fundingPrivateKey = nil; - for (NSURLQueryItem *queryItem in queryItems) { - if ([queryItem.name isEqualToString:@"assetlocktx"]) { - assetLockTransactionHash = queryItem.value.hexToData.UInt256; - } else if ([queryItem.name isEqualToString:@"pk"]) { - fundingPrivateKey = [DSKeyManager keyWithPrivateKeyString:queryItem.value ofKeyType:KeyKind_ECDSA forChainType:self.chain.chainType]; - } - } - if (uint256_is_zero(assetLockTransactionHash)) { - if (completion) { - completion(DSBlockchainIdentityRegistrationStep_None, [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation format is not valid"]); - } - return; - } - if (!fundingPrivateKey || !key_has_private_key(fundingPrivateKey)) { - if (completion) { - completion(DSBlockchainIdentityRegistrationStep_None, [NSError errorWithCode:400 localizedDescriptionKey:@"Funding private key is not valid"]); - } - return; - } - - [coreNetworkService getTransactionWithHash:assetLockTransactionHash - completionQueue:self.chain.chainManager.identitiesManager.identityQueue - success:^(DSTransaction *_Nonnull transaction) { - NSAssert(transaction, @"transaction must not be null"); - if (!transaction || ![transaction isKindOfClass:[DSCreditFundingTransaction class]]) { - if (completion) { - completion(DSBlockchainIdentityRegistrationStep_None, [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation transaction is not valid"]); - } - return; - } - self.identity = [[DSBlockchainIdentity alloc] initAtIndex:index withFundingTransaction:(DSCreditFundingTransaction *)transaction withUsernameDictionary:nil inWallet:self.wallet]; - [self.identity setAssociatedInvitation:self]; - [self.identity addDashpayUsername:dashpayUsername save:NO]; - [self.identity registerInWalletForRegistrationFundingTransaction: (DSCreditFundingTransaction *)transaction]; - BOOL success = [self.identity setExternalFundingPrivateKey:fundingPrivateKey]; - if (!success && fundingPrivateKey != NULL) - processor_destroy_opaque_key(fundingPrivateKey); - NSAssert(success, @"We must be able to set the external funding private key"); - if (success) { - [self.identity generateBlockchainIdentityExtendedPublicKeysWithPrompt:authenticationMessage - completion:^(BOOL registered) { - if (registered) { - [self.identity continueRegisteringIdentityOnNetwork:identityRegistrationSteps stepsCompleted:DSBlockchainIdentityRegistrationStep_L1Steps stepCompletion:stepCompletion completion:completion]; - } else if (completion) { - completion(DSBlockchainIdentityRegistrationStep_None, [NSError errorWithCode:500 localizedDescriptionKey:@"Error generating Identity keys"]); - } - }]; - } else { - if (completion) { - completion(DSBlockchainIdentityRegistrationStep_None, [NSError errorWithCode:500 localizedDescriptionKey:@"Error setting the external funding private key"]); - } - } - } - failure:^(NSError *_Nonnull error) { - if (completion) { - completion(DSBlockchainIdentityRegistrationStep_None, [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation format is not valid"]); - } - }]; -} - -- (void)createInvitationFullLinkFromIdentity:(DSBlockchainIdentity *)identity completion:(void (^_Nullable)(BOOL cancelled, NSString *invitationFullLink))completion { - if (!self.identity.registrationCreditFundingTransaction.instantSendLockAwaitingProcessing) { - if (completion) { - completion(NO, nil); - } - return; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSString *senderUsername = identity.currentDashpayUsername; - NSString *senderDisplayName = identity.displayName; - NSString *senderAvatarPath = identity.avatarPath; - NSString *fundingTransactionHexString = uint256_reverse_hex(self.identity.registrationCreditFundingTransaction.txHash); - __block OpaqueKey *registrationFundingPrivateKey = self.identity.registrationFundingPrivateKey; - __block BOOL rCancelled = FALSE; - - if (!registrationFundingPrivateKey) { - dispatch_semaphore_t sem = dispatch_semaphore_create(0); - dispatch_async(dispatch_get_main_queue(), ^{ - [[DSAuthenticationManager sharedInstance] seedWithPrompt:DSLocalizedString(@"Would you like to share this invitation?", nil) - forWallet:self.wallet - forAmount:0 - forceAuthentication:NO - completion:^(NSData *_Nullable seed, BOOL cancelled) { - rCancelled = cancelled; - if (seed) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - DSCreditFundingDerivationPath *derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:self.wallet]; - // TODO: cleanup? - registrationFundingPrivateKey = [derivationPathRegistrationFunding privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.identity.index] fromSeed:seed]; - dispatch_semaphore_signal(sem); - }); - } else { - dispatch_semaphore_signal(sem); - } - }]; - }); - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - } - if (!registrationFundingPrivateKey) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (completion) { - completion(rCancelled, nil); - } - }); - return; - } - //in WIF format - NSString *registrationFundingPrivateKeyString = [DSKeyManager serializedPrivateKey:registrationFundingPrivateKey chainType:self.chain.chainType]; - - NSString *serializedISLock = [self.identity.registrationCreditFundingTransaction.instantSendLockAwaitingProcessing.toData hexString]; - - NSURLComponents *components = [NSURLComponents componentsWithString:@"https://invitations.dashpay.io/applink"]; - NSMutableArray *queryItems = [NSMutableArray array]; - if (senderUsername) { - NSURLQueryItem *senderUsernameQueryItem = [NSURLQueryItem queryItemWithName:@"du" value:senderUsername]; - [queryItems addObject:senderUsernameQueryItem]; - } - if (senderDisplayName) { - NSURLQueryItem *senderDisplayNameQueryItem = [NSURLQueryItem queryItemWithName:@"display-name" value:senderDisplayName]; - [queryItems addObject:senderDisplayNameQueryItem]; - } - if (senderAvatarPath) { - NSURLQueryItem *senderAvatarPathQueryItem = [NSURLQueryItem queryItemWithName:@"avatar-url" value:senderAvatarPath]; - [queryItems addObject:senderAvatarPathQueryItem]; - } - - NSURLQueryItem *fundingTransactionQueryItem = [NSURLQueryItem queryItemWithName:@"assetlocktx" value:fundingTransactionHexString.lowercaseString]; - [queryItems addObject:fundingTransactionQueryItem]; - - NSURLQueryItem *registrationFundingPrivateKeyQueryItem = [NSURLQueryItem queryItemWithName:@"pk" value:registrationFundingPrivateKeyString]; - [queryItems addObject:registrationFundingPrivateKeyQueryItem]; - - NSURLQueryItem *serializedISLockQueryItem = [NSURLQueryItem queryItemWithName:@"islock" value:serializedISLock.lowercaseString]; - [queryItems addObject:serializedISLockQueryItem]; - - components.queryItems = queryItems; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (completion) { - completion(NO, components.URL.absoluteString); - } - }); - }); -} - -// MARK: Saving - -- (void)saveInContext:(NSManagedObjectContext *)context { - if (self.isTransient) return; - [context performBlockAndWait:^{ - BOOL changeOccured = NO; - NSMutableArray *updateEvents = [NSMutableArray array]; - DSBlockchainInvitationEntity *entity = [self blockchainInvitationEntityInContext:context]; - if (entity.tag != self.tag) { - entity.tag = self.tag; - changeOccured = YES; - [updateEvents addObject:DSBlockchainInvitationUpdateEvents]; - } - if (entity.name != self.name) { - entity.name = self.name; - changeOccured = YES; - [updateEvents addObject:DSBlockchainInvitationUpdateEvents]; - } - if (entity.link != self.link) { - entity.link = self.link; - changeOccured = YES; - [updateEvents addObject:DSBlockchainInvitationUpdateEventLink]; - } - if (changeOccured) { - [context ds_save]; - if (updateEvents.count) { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainInvitationDidUpdateNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainInvitationKey: self, DSBlockchainInvitationUpdateEvents: updateEvents}]; - }); - } - } - }]; -} - -// MARK: Deletion - -- (void)deletePersistentObjectAndSave:(BOOL)save inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - DSBlockchainInvitationEntity *blockchainInvitationEntity = [self blockchainInvitationEntityInContext:context]; - if (blockchainInvitationEntity) { - [blockchainInvitationEntity deleteObjectAndWait]; - if (save) { - [context ds_save]; - } - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSBlockchainInvitationDidUpdateNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSBlockchainInvitationKey: self}]; - }); - }]; -} - -// MARK: Entity - -- (DSBlockchainInvitationEntity *)blockchainInvitationEntity { - return [self blockchainInvitationEntityInContext:[NSManagedObjectContext viewContext]]; -} - -- (DSBlockchainInvitationEntity *)blockchainInvitationEntityInContext:(NSManagedObjectContext *)context { - __block DSBlockchainInvitationEntity *entity = nil; - [context performBlockAndWait:^{ - entity = [DSBlockchainInvitationEntity anyObjectInContext:context matching:@"blockchainIdentity.uniqueID == %@", self.identity.uniqueIDData]; - }]; - NSAssert(entity, @"An entity should always be found"); - return entity; -} - -- (NSString *)debugDescription { - return [[super debugDescription] stringByAppendingString:[NSString stringWithFormat:@" {%d-%@-%@}", self.identity.index, self.identity.currentDashpayUsername, self.identity.uniqueIdString]]; -} - - -@end diff --git a/DashSync/shared/Models/Identity/DSIdentity+ContactRequest.h b/DashSync/shared/Models/Identity/DSIdentity+ContactRequest.h new file mode 100644 index 000000000..c61c27074 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+ContactRequest.h @@ -0,0 +1,39 @@ +// +// 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 "DSIdentity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSIdentity (ContactRequest) + +- (void)fetchContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion; +- (void)fetchOutgoingContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion; +- (void)fetchOutgoingContactRequestsInContext:(NSManagedObjectContext *)context + startAfter:(NSData*_Nullable)startAfter + withCompletion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; +- (void)fetchIncomingContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion; +- (void)fetchIncomingContactRequestsInContext:(NSManagedObjectContext *)context + startAfter:(NSData*_Nullable)startAfter + withCompletion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSIdentity+ContactRequest.m b/DashSync/shared/Models/Identity/DSIdentity+ContactRequest.m new file mode 100644 index 000000000..6934e219e --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+ContactRequest.m @@ -0,0 +1,583 @@ +// +// 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 "DPContract.h" +#import "DSAccount.h" +#import "DSAccountEntity+CoreDataClass.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSChain+Identity.h" +#import "DSChainManager.h" +#import "DSDashpayUserEntity+CoreDataClass.h" +#import "DSDashPlatform.h" +#import "DSDerivationPathEntity+CoreDataClass.h" +#import "DSFriendRequestEntity+CoreDataClass.h" +#import "DSIdentitiesManager+CoreData.h" +#import "DSIdentity+ContactRequest.h" +#import "DSIdentity+Friendship.h" +#import "DSIdentity+Protected.h" +#import "DSPotentialOneWayFriendship.h" +#import "DSTransactionManager+Protected.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSManagedObject+Sugar.h" + +#define DAPI_DOCUMENT_RESPONSE_COUNT_LIMIT 100 +#define DEFAULT_CONTACT_REQUEST_FETCH_RETRIES 5 + +#define ERROR_DASHPAY_CONTRACT_IMPROPER_SETUP [NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"] +#define ERROR_IDENTITY_NOT_ACTIVATED [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity hasn't yet been locally activated"] +#define ERROR_IDENTITY_NO_LONGER_ACTIVE [NSError errorWithCode:410 localizedDescriptionKey:@"Identity no longer active in wallet"] +#define ERROR_KEY_FORMAT_DECRYPTION [NSError errorWithCode:500 localizedDescriptionKey:@"Incorrect key format after contact request decryption"] +#define ERROR_DERIVATION_FRIENDSHIP [NSError errorWithCode:500 localizedDescriptionKey:@"Could not create friendship derivation path"] +#define ERROR_CONTACT_REQUEST_KEY_ENCRYPTION [NSError errorWithCode:500 localizedDescriptionKey:@"Contact request extended public key is incorrectly encrypted."] + +@implementation DSIdentity (ContactRequest) + +- (void)fetchContactRequests:(void (^)(BOOL success, NSArray *errors))completion { + dispatch_async(self.identityQueue, ^{ + [self fetchContactRequestsInContext:self.platformContext + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; + }); +} + + +- (void)fetchContactRequestsInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + __weak typeof(self) weakSelf = self; + [self fetchIncomingContactRequestsInContext:context + startAfter:nil + withCompletion:^(BOOL success, NSArray *errors) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + if (completion) completion(NO, @[ERROR_MEM_ALLOC]); + return; + } + if (!success) { + if (completion) dispatch_async(completionQueue, ^{ completion(success, errors); }); + return; + } + [strongSelf fetchOutgoingContactRequestsInContext:context + startAfter:nil + withCompletion:completion + onCompletionQueue:completionQueue]; + } + onCompletionQueue:self.identityQueue]; +} + +- (void)fetchIncomingContactRequests:(void (^_Nullable)(BOOL success, NSArray *errors))completion { + [self fetchIncomingContactRequestsInContext:self.platformContext + startAfter:nil + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)fetchIncomingContactRequestsInContext:(NSManagedObjectContext *)context + startAfter:(NSData*_Nullable)startAfter + withCompletion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: fetch incoming contact requests: (startAfter: %@)", self.logPrefix, startAfter ? startAfter.hexString : @"NULL"]; + DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; + if (dashpayContract.contractState != DPContractState_Registered) { + [debugInfo appendFormat:@" : ERROR: DashPay Contract State: %lu", dashpayContract.contractState]; + DSLog(@"%@", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_DASHPAY_CONTRACT_IMPROPER_SETUP]); }); + return; + } + NSError *error = nil; + if (![self activePrivateKeysAreLoadedWithFetchingError:&error]) { + [debugInfo appendFormat:@" : ERROR: Active private keys are loaded with error: %@", error]; + DSLog(@"%@", debugInfo); + // The blockchain identity hasn't been intialized on the device, ask the user to activate the blockchain user, this action allows private keys to be cached on the blockchain identity level + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[error ? error : ERROR_IDENTITY_NOT_ACTIVATED]); }); + return; + } + u256 *user_id = u256_ctor_u(self.uniqueID); + uint64_t since = self.lastCheckedIncomingContactsTimestamp ? (self.lastCheckedIncomingContactsTimestamp - HOUR_TIME_INTERVAL) : 0; + Vec_u8 *start_after = startAfter ? bytes_ctor(startAfter) : nil; + __weak typeof(self) weakSelf = self; + DMaybeContactRequests *result = dash_spv_platform_document_contact_request_ContactRequestManager_stream_incoming_contact_requests_with_contract(self.chain.sharedRuntime, self.chain.sharedContactsObj, user_id, since, start_after, dashpayContract.raw_contract, DRetryLinear(5), dash_spv_platform_document_contact_request_ContactRequestValidator_None_ctor(), 1000); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeContactRequestsDtor(result); + [debugInfo appendFormat:@" : ERROR: %@", error]; + DSLog(@"%@", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[error]); }); + return; + } + + dispatch_async(self.identityQueue, ^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + [debugInfo appendFormat:@" : ERROR: Lost self context"]; + DSLog(@"%@", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_MEM_ALLOC]); }); + return; + } + DContactRequests *documents = result->ok; + NSAssert(completionQueue == self.identityQueue, @"we should be on identity queue"); + __block NSMutableArray *incomingNewRequests = [NSMutableArray array]; + __block NSMutableArray *rErrors = [NSMutableArray array]; + [context performBlockAndWait:^{ + for (int i = 0; i < documents->count; i++) { + DContactRequestKind *kind = documents->values[i]; + switch (kind->tag) { + case dash_spv_platform_models_contact_request_ContactRequestKind_Incoming: { + NSData *identifier = NSDataFromPtr(kind->incoming->owner_id); + //we are the recipient, this is an incoming request + DSFriendRequestEntity *exist = [DSFriendRequestEntity anyObjectInContext:context matching:@"destinationContact == %@ && sourceContact.associatedBlockchainIdentity.uniqueID == %@", [self matchingDashpayUserInContext:context], identifier]; + + // TODO: memory + if (!exist) + [incomingNewRequests addObject:[NSValue valueWithPointer:dash_spv_platform_document_contact_request_as_incoming_request(kind)]]; + break; + } + default: { + //we should not have received this + NSAssert(FALSE, @"the contact request needs to be either outgoing or incoming"); + break; + } + } + } + }]; + __block BOOL succeeded = YES; + dispatch_group_t dispatchGroup = dispatch_group_create(); + if ([incomingNewRequests count]) { + dispatch_group_enter(dispatchGroup); + [self handleIncomingRequests:incomingNewRequests + context:context + completion:^(BOOL success, NSArray *errors) { + if (!success) { + succeeded = NO; + [rErrors addObjectsFromArray:errors]; + } + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:completionQueue]; + } + dispatch_group_notify(dispatchGroup, completionQueue, ^{ + BOOL hasMore = documents->count == DAPI_DOCUMENT_RESPONSE_COUNT_LIMIT; + if (!hasMore) + [self.platformContext performBlockAndWait:^{ + self.lastCheckedIncomingContactsTimestamp = [[NSDate date] timeIntervalSince1970]; + }]; + [debugInfo appendFormat:@" : OK: %u: %@", succeeded, rErrors]; + DSLog(@"%@", debugInfo); + __block NSData * hasMoreStartAfter = nil; + if (documents->count > 0) { + DContactRequestKind *last = documents->values[documents->count-1]; + if (last->incoming) + hasMoreStartAfter = NSDataFromPtr(last->incoming->id); + } + if (succeeded && hasMoreStartAfter) + [self fetchIncomingContactRequestsInContext:context + startAfter:hasMoreStartAfter + withCompletion:completion + onCompletionQueue:completionQueue]; + else if (completion) + completion(succeeded, [rErrors copy]); + }); + }); +} + +- (void)fetchOutgoingContactRequests:(void (^)(BOOL success, NSArray *errors))completion { + [self fetchOutgoingContactRequestsInContext:self.platformContext + startAfter:nil + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)fetchOutgoingContactRequestsInContext:(NSManagedObjectContext *)context + startAfter:(NSData*_Nullable)startAfter + withCompletion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: fetch outgoing contact requests: (startAfter: %@)", self.logPrefix, startAfter ? startAfter.hexString : @"NULL"]; + DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; + if (dashpayContract.contractState != DPContractState_Registered) { + [debugInfo appendFormat:@" : ERROR: DashPay Contract State: %lu", dashpayContract.contractState]; + DSLog(@"%@", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_DASHPAY_CONTRACT_IMPROPER_SETUP]); }); + return; + } + NSError *error = nil; + if (![self activePrivateKeysAreLoadedWithFetchingError:&error]) { + [debugInfo appendFormat:@" : ERROR: Active private keys are loaded with error: %@", error]; + DSLog(@"%@", debugInfo); + //The blockchain identity hasn't been intialized on the device, ask the user to activate the blockchain user, this action allows private keys to be cached on the blockchain identity level + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[error ? error : ERROR_IDENTITY_NOT_ACTIVATED]); }); + return; + } + __weak typeof(self) weakSelf = self; + + Vec_u8 *start_after = startAfter ? bytes_ctor(startAfter) : NULL; + uint64_t since = self.lastCheckedOutgoingContactsTimestamp ? (self.lastCheckedOutgoingContactsTimestamp - HOUR_TIME_INTERVAL) : 0; + u256 *user_id = u256_ctor_u(self.uniqueID); + DMaybeContactRequests *result = dash_spv_platform_document_contact_request_ContactRequestManager_stream_outgoing_contact_requests_with_contract(self.chain.sharedRuntime, self.chain.sharedContactsObj, user_id, since, start_after, dashpayContract.raw_contract, DRetryLinear(5), dash_spv_platform_document_contact_request_ContactRequestValidator_None_ctor(), 1000); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeContactRequestsDtor(result); + [debugInfo appendFormat:@" : ERROR: %@", error]; + DSLog(@"%@", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[error]); }); + return; + } + + dispatch_async(self.identityQueue, ^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + [debugInfo appendFormat:@" : ERROR: Lost self context"]; + DSLog(@"%@", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_MEM_ALLOC]); }); + return; + } + DContactRequests *documents = result->ok; + NSAssert(completionQueue == self.identityQueue, @"we should be on identity queue"); + __block NSMutableArray *outgoingNewRequests = [NSMutableArray array]; + __block NSMutableArray *rErrors = [NSMutableArray array]; + [context performBlockAndWait:^{ + for (int i = 0; i < documents->count; i++) { + DContactRequestKind *kind = documents->values[i]; + switch (kind->tag) { + case dash_spv_platform_models_contact_request_ContactRequestKind_Outgoing: { + NSData *identifier = NSDataFromPtr(kind->outgoing->recipient); + + BOOL exist = [DSFriendRequestEntity countObjectsInContext:context matching:@"sourceContact == %@ && destinationContact.associatedBlockchainIdentity.uniqueID == %@", [self matchingDashpayUserInContext:context], identifier]; + // TODO: memory + if (!exist) [outgoingNewRequests addObject:[NSValue valueWithPointer:dash_spv_platform_document_contact_request_as_outgoing_request(kind)]]; + break; + } + default: { + //we should not have received this + NSAssert(FALSE, @"the contact request needs to be either outgoing or incoming"); + break; + } + } + } + }]; + __block BOOL succeeded = YES; + dispatch_group_t dispatchGroup = dispatch_group_create(); + if ([outgoingNewRequests count]) { + dispatch_group_enter(dispatchGroup); + [self handleOutgoingRequests:outgoingNewRequests + context:context + completion:^(BOOL success, NSArray *errors) { + if (!success) { + succeeded = NO; + [rErrors addObjectsFromArray:errors]; + } + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:completionQueue]; + } + dispatch_group_notify(dispatchGroup, completionQueue, ^{ + BOOL hasMore = documents->count == DAPI_DOCUMENT_RESPONSE_COUNT_LIMIT; + if (!hasMore) + [self.platformContext performBlockAndWait:^{ + self.lastCheckedOutgoingContactsTimestamp = [[NSDate date] timeIntervalSince1970]; + }]; + [debugInfo appendFormat:@" : OK: %u: %@", succeeded, rErrors]; + DSLog(@"%@", debugInfo); + __block NSData * hasMoreStartAfter = nil; + if (documents->count > 0) { + DContactRequestKind *last = documents->values[documents->count-1]; + if (last->outgoing) + hasMoreStartAfter = NSDataFromPtr(last->outgoing->id); + } + if (succeeded && hasMoreStartAfter) + [self fetchOutgoingContactRequestsInContext:context + startAfter:hasMoreStartAfter + withCompletion:completion + onCompletionQueue:completionQueue]; + else if (completion) + completion(succeeded, [rErrors copy]); + }); + }); +} + + +- (NSData *)decryptedPublicKeyDataWithKey:(DOpaqueKey *)key + request:(dash_spv_platform_models_contact_request_ContactRequest *)request { + NSParameterAssert(key); + DKeyKind *kind = DOpaqueKeyKind(key); + uint32_t index = uint256_eq(self.uniqueID, u256_cast(request->recipient)) ? request->sender_key_index : request->recipient_key_index; + DMaybeOpaqueKey *maybe_key = [self privateKeyAtIndex:index ofType:kind]; + NSAssert(maybe_key->ok, @"Key should exist"); + DMaybeKeyData *key_data = DOpaqueKeyDecrypt(maybe_key->ok, key, request->encrypted_public_key); + NSData *data = key_data->ok ? NSDataFromPtr(key_data->ok) : nil; + DMaybeKeyDataDtor(key_data); + DMaybeOpaqueKeyDtor(maybe_key); + return data; +} + +- (void)handleIncomingRequests:(NSArray *)incomingRequests + context:(NSManagedObjectContext *)context + completion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + if (!self.isActive) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_IDENTITY_NO_LONGER_ACTIVE]); }); + return; + } + [context performBlockAndWait:^{ + __block BOOL succeeded = YES; + __block NSMutableArray *errors = [NSMutableArray array]; + dispatch_group_t dispatchGroup = dispatch_group_create(); + + for (NSValue *contactRequest in incomingRequests) { + DContactRequest *request = contactRequest.pointerValue; + NSData *senderIdentityUniqueId = NSDataFromPtr(request->owner_id); + DSBlockchainIdentityEntity *externalIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", senderIdentityUniqueId]; + if (!externalIdentityEntity) { + //no externalBlockchainIdentity exists yet, which means no dashpay user + dispatch_group_enter(dispatchGroup); + DSIdentity *senderIdentity = [self.identitiesManager foreignIdentityWithUniqueId:senderIdentityUniqueId.UInt256 createIfMissing:YES inContext:context]; + [senderIdentity fetchNeededNetworkStateInformationInContext:self.platformContext + withCompletion:^(DSIdentityQueryStep failureStep, NSArray *_Nullable networkErrors) { + if (!failureStep) { + DOpaqueKey *senderPublicKey = [senderIdentity keyAtIndex:request->sender_key_index]; + NSData *extendedPublicKeyData = [self decryptedPublicKeyDataWithKey:senderPublicKey request:request]; + DMaybeOpaqueKey *extendedPublicKey = [DSKeyManager keyWithExtendedPublicKeyData:extendedPublicKeyData ofType:DKeyKindECDSA()]; + if (!extendedPublicKey) { + succeeded = FALSE; + [errors addObject:ERROR_KEY_FORMAT_DECRYPTION]; + } else { + DSDashpayUserEntity *senderDashpayUserEntity = [senderIdentity identityEntityInContext:context].matchingDashpayUser; + NSAssert(senderDashpayUserEntity, @"The sender should exist"); + [self addIncomingRequestFromContact:senderDashpayUserEntity + forExtendedPublicKey:extendedPublicKey + atTimestamp:request->created_at]; + } + } else { + [errors addObjectsFromArray:networkErrors]; + } + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + + } else { + if ([self.chain identityForUniqueId:externalIdentityEntity.uniqueID.UInt256]) { + //it's also local (aka both contacts are local to this device), we should store the extended public key for the destination + DSIdentity *sourceIdentity = [self.chain identityForUniqueId:externalIdentityEntity.uniqueID.UInt256]; + DSAccount *account = [sourceIdentity.wallet accountWithNumber:0]; + DSPotentialOneWayFriendship *potentialFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationIdentity:self destinationKeyIndex:request->recipient_key_index sourceIdentity:sourceIdentity sourceKeyIndex:request->sender_key_index account:account]; + if (![DSFriendRequestEntity existingFriendRequestEntityWithSourceIdentifier:sourceIdentity.uniqueID destinationIdentifier:self.uniqueID onAccountIndex:account.accountNumber inContext:context]) { + dispatch_group_enter(dispatchGroup); + [potentialFriendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { + if (success) { + DSDashpayUserEntity *matchingDashpayUserInContext = [self matchingDashpayUserInContext:context]; + DSFriendRequestEntity *friendRequest = [potentialFriendship outgoingFriendRequestForDashpayUserEntity:matchingDashpayUserInContext atTimestamp:request->created_at]; + [potentialFriendship storeExtendedPublicKeyAssociatedWithFriendRequest:friendRequest]; + [matchingDashpayUserInContext addIncomingRequestsObject:friendRequest]; + if ([[friendRequest.sourceContact.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUserInContext]] count]) + [matchingDashpayUserInContext addFriendsObject:friendRequest.sourceContact]; + [account addIncomingDerivationPath:incomingFundsDerivationPath + forFriendshipIdentifier:friendRequest.friendshipIdentifier + inContext:context]; + [context ds_save]; + [self.chain.chainManager.transactionManager updateTransactionsBloomFilter]; + } else { + succeeded = FALSE; + [errors addObject:ERROR_DERIVATION_FRIENDSHIP]; + } + dispatch_group_leave(dispatchGroup); + }]; + } + + } else { + DSIdentity *sourceIdentity = [[DSIdentity alloc] initWithIdentityEntity:externalIdentityEntity]; + NSAssert(sourceIdentity, @"This should not be null"); + if ([sourceIdentity activeKeyCount] > 0 && [sourceIdentity keyAtIndex:request->sender_key_index]) { + //the contact already existed, and has an encryption public key set, create the incoming friend request, add a friendship if an outgoing friend request also exists + DOpaqueKey *key = [sourceIdentity keyAtIndex:request->sender_key_index]; + NSData *decryptedExtendedPublicKeyData = [self decryptedPublicKeyDataWithKey:key request:request]; + NSAssert(decryptedExtendedPublicKeyData, @"Data should be decrypted"); + DMaybeOpaqueKey *extendedPublicKey = [DSKeyManager keyWithExtendedPublicKeyData:decryptedExtendedPublicKeyData ofType:DKeyKindECDSA()]; + if (!extendedPublicKey) { + succeeded = FALSE; + [errors addObject:ERROR_CONTACT_REQUEST_KEY_ENCRYPTION]; + return; + } + [self addIncomingRequestFromContact:externalIdentityEntity.matchingDashpayUser + forExtendedPublicKey:extendedPublicKey + atTimestamp:request->created_at]; + + DSDashpayUserEntity *matchingDashpayUserInContext = [self matchingDashpayUserInContext:context]; + if ([[externalIdentityEntity.matchingDashpayUser.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUserInContext]] count]) { + [matchingDashpayUserInContext addFriendsObject:[externalIdentityEntity matchingDashpayUser]]; + [context ds_save]; + } + + } else { + //the blockchain identity is already known, but needs to updated to get the right key, create the incoming friend request, add a friendship if an outgoing friend request also exists + dispatch_group_enter(dispatchGroup); + [sourceIdentity fetchNeededNetworkStateInformationInContext:context + withCompletion:^(DSIdentityQueryStep failureStep, NSArray *networkStateInformationErrors) { + if (!failureStep) { + [context performBlockAndWait:^{ + DOpaqueKey *key = [sourceIdentity keyAtIndex:request->sender_key_index]; + NSAssert(key, @"key should be known"); + NSData *decryptedExtendedPublicKeyData = [self decryptedPublicKeyDataWithKey:key request:request]; + NSAssert(decryptedExtendedPublicKeyData, @"Data should be decrypted"); + DMaybeOpaqueKey *extendedPublicKey = [DSKeyManager keyWithExtendedPublicKeyData:decryptedExtendedPublicKeyData ofType:DKeyKindECDSA()]; + NSAssert(extendedPublicKey, @"A key should be recovered"); + [self addIncomingRequestFromContact:externalIdentityEntity.matchingDashpayUser + forExtendedPublicKey:extendedPublicKey + atTimestamp:request->created_at]; + DSDashpayUserEntity *matchingDashpayUserInContext = [self matchingDashpayUserInContext:context]; + if ([[externalIdentityEntity.matchingDashpayUser.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUserInContext]] count]) { + [matchingDashpayUserInContext addFriendsObject:externalIdentityEntity.matchingDashpayUser]; + [context ds_save]; + } + }]; + } else { + succeeded = FALSE; + [errors addObjectsFromArray:networkStateInformationErrors]; + } + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } + } + } + } + dispatch_group_notify(dispatchGroup, completionQueue, ^{ + for (NSValue *request in incomingRequests) { + DContactRequestDtor(request.pointerValue); + } + + if (completion) completion(succeeded, [errors copy]); + }); + }]; +} + +- (void)handleOutgoingRequests:(NSArray *)outgoingRequests + context:(NSManagedObjectContext *)context + completion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + if (!self.isActive) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_IDENTITY_NO_LONGER_ACTIVE]); }); + return; + } + [context performBlockAndWait:^{ + __block NSMutableArray *errors = [NSMutableArray array]; + __block BOOL succeeded = YES; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (NSValue *contactRequest in outgoingRequests) { + DContactRequest *request = contactRequest.pointerValue; + + DSBlockchainIdentityEntity *recipientIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", NSDataFromPtr(request->recipient)]; + if (!recipientIdentityEntity) { + //no contact exists yet + dispatch_group_enter(dispatchGroup); + DSIdentity *recipientIdentity = [self.identitiesManager foreignIdentityWithUniqueId:u256_cast(request->recipient) + createIfMissing:YES + inContext:context]; + NSAssert([recipientIdentity identityEntityInContext:context], @"Entity should now exist"); + [recipientIdentity fetchNeededNetworkStateInformationInContext:context + withCompletion:^(DSIdentityQueryStep failureStep, NSArray *_Nullable networkErrors) { + if (!failureStep) { + [self addFriendshipFromSourceIdentity:self + sourceKeyIndex:request->sender_key_index + toRecipientIdentity:recipientIdentity + recipientKeyIndex:request->recipient_key_index + atTimestamp:request->created_at + inContext:context]; + } else { + succeeded = FALSE; + [errors addObjectsFromArray:networkErrors]; + } + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } else { + //the recipient blockchain identity is already known, meaning they had made a friend request to us before, and on another device we had accepted + //or the recipient blockchain identity is also local to the device + + DSWallet *recipientWallet = nil; + DSIdentity *recipientIdentity = [self.chain identityForUniqueId:recipientIdentityEntity.uniqueID.UInt256 + foundInWallet:&recipientWallet]; + BOOL isLocal = TRUE; + if (!recipientIdentity) { + //this is not local + recipientIdentity = [[DSIdentity alloc] initWithIdentityEntity:recipientIdentityEntity]; + isLocal = FALSE; + } + + dispatch_group_enter(dispatchGroup); + [recipientIdentity fetchIfNeededNetworkStateInformation:DSIdentityQueryStep_Profile & DSIdentityQueryStep_Username & DSIdentityQueryStep_Identity + inContext:context + withCompletion:^(DSIdentityQueryStep failureStep, NSArray *_Nullable networkErrors) { + if (!failureStep) { + [self addFriendshipFromSourceIdentity:self + sourceKeyIndex:request->sender_key_index + toRecipientIdentity:recipientIdentity + recipientKeyIndex:request->recipient_key_index + atTimestamp:request->created_at + inContext:context]; + } else { + succeeded = FALSE; + [errors addObjectsFromArray:networkErrors]; + } + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } + } + dispatch_group_notify(dispatchGroup, completionQueue, ^{ + for (NSValue *request in outgoingRequests) { + DContactRequestDtor(request.pointerValue); + } + if (completion) completion(succeeded, [errors copy]); + + }); + }]; +} + +- (void)addIncomingRequestFromContact:(DSDashpayUserEntity *)dashpayUserEntity + forExtendedPublicKey:(DMaybeOpaqueKey *)extendedPublicKey + atTimestamp:(NSTimeInterval)timestamp { + NSManagedObjectContext *context = dashpayUserEntity.managedObjectContext; + DSFriendRequestEntity *friendRequestEntity = [DSFriendRequestEntity managedObjectInBlockedContext:context]; + friendRequestEntity.sourceContact = dashpayUserEntity; + friendRequestEntity.destinationContact = [self matchingDashpayUserInContext:dashpayUserEntity.managedObjectContext]; + friendRequestEntity.timestamp = timestamp; + NSAssert(friendRequestEntity.sourceContact != friendRequestEntity.destinationContact, @"This must be different contacts"); + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity managedObjectInBlockedContext:context]; + derivationPathEntity.chain = [self.chain chainEntityInContext:context]; + friendRequestEntity.derivationPath = derivationPathEntity; + NSAssert(friendRequestEntity.derivationPath, @"There must be a derivation path"); + DSAccount *account = [self.wallet accountWithNumber:0]; + DSAccountEntity *accountEntity = [DSAccountEntity accountEntityForWalletUniqueID:self.wallet.uniqueIDString index:account.accountNumber onChain:self.chain inContext:dashpayUserEntity.managedObjectContext]; + derivationPathEntity.account = accountEntity; + friendRequestEntity.account = accountEntity; + [friendRequestEntity finalizeWithFriendshipIdentifier]; + //NSLog(@"->created derivation path entity %@ %@", friendRequestEntity.friendshipIdentifier.hexString, [NSThread callStackSymbols]); + DSIncomingFundsDerivationPath *derivationPath = [DSIncomingFundsDerivationPath externalDerivationPathWithExtendedPublicKey:extendedPublicKey withDestinationIdentityUniqueId:[self matchingDashpayUserInContext:dashpayUserEntity.managedObjectContext].associatedBlockchainIdentity.uniqueID.UInt256 sourceIdentityUniqueId:dashpayUserEntity.associatedBlockchainIdentity.uniqueID.UInt256 onChain:self.chain]; + derivationPathEntity.publicKeyIdentifier = derivationPath.standaloneExtendedPublicKeyUniqueID; + [derivationPath storeExternalDerivationPathExtendedPublicKeyToKeyChain]; + //incoming request uses an outgoing derivation path + [account addOutgoingDerivationPath:derivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:dashpayUserEntity.managedObjectContext]; + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:dashpayUserEntity.managedObjectContext]; + [matchingDashpayUser addIncomingRequestsObject:friendRequestEntity]; + if ([[friendRequestEntity.sourceContact.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact == %@", matchingDashpayUser]] count]) + [matchingDashpayUser addFriendsObject:friendRequestEntity.sourceContact]; + [context ds_save]; + [self.chain.chainManager.transactionManager updateTransactionsBloomFilter]; +} + +@end diff --git a/DashSync/shared/Models/Identity/DSIdentity+Friendship.h b/DashSync/shared/Models/Identity/DSIdentity+Friendship.h new file mode 100644 index 000000000..0f5bb17de --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Friendship.h @@ -0,0 +1,46 @@ +// +// 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 "DSIdentity.h" +#import "DSFriendRequestEntity+CoreDataClass.h" +#import "DSPotentialContact.h" +#import "DSPotentialOneWayFriendship.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSIdentity (Friendship) + +- (void)sendNewFriendRequestToIdentity:(DSIdentity *)identity + completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion; +- (void)sendNewFriendRequestToPotentialContact:(DSPotentialContact *)potentialContact + completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; +- (void)acceptFriendRequestFromIdentity:(DSIdentity *)otherIdentity + completion:(void (^)(BOOL success, NSArray *errors))completion; +- (void)acceptFriendRequest:(DSFriendRequestEntity *)friendRequest + completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; +- (void)addFriendshipFromSourceIdentity:(DSIdentity *)sourceIdentity + sourceKeyIndex:(uint32_t)sourceKeyIndex + toRecipientIdentity:(DSIdentity *)recipientIdentity + recipientKeyIndex:(uint32_t)recipientKeyIndex + atTimestamp:(NSTimeInterval)timestamp + inContext:(NSManagedObjectContext *)context; +- (DSIdentityFriendshipStatus)friendshipStatusForRelationshipWithIdentity:(DSIdentity *)otherIdentity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSIdentity+Friendship.m b/DashSync/shared/Models/Identity/DSIdentity+Friendship.m new file mode 100644 index 000000000..ea4f4c98f --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Friendship.m @@ -0,0 +1,381 @@ +// +// 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 "DPContract.h" +#import "DSAccount.h" +#import "DSAccountEntity+CoreDataClass.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSChain+Identity.h" +#import "DSChainManager.h" +#import "DSDashpayUserEntity+CoreDataClass.h" +#import "DSIdentity+ContactRequest.h" +#import "DSIdentity+Friendship.h" +#import "DSIdentity+Profile.h" +#import "DSIdentity+Protected.h" +#import "DSIdentitiesManager+CoreData.h" + +#import "DSTransactionManager+Protected.h" +#import "DSTransientDashpayUser.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSManagedObject+Sugar.h" + +#define ERROR_KEY_HANDLING [NSError errorWithCode:501 localizedDescriptionKey:@"Internal key handling error"] +#define ERROR_INCOMPLETE_ACTIONS [NSError errorWithCode:501 localizedDescriptionKey:@"User has actions to complete before being able to use Dashpay"] +#define ERROR_DERIVATION_FRIENDSHIP [NSError errorWithCode:500 localizedDescriptionKey:@"Could not create friendship derivation path"] +#define ERROR_FRIEND_REQUEST_NONE_FOUND [NSError errorWithCode:501 localizedDescriptionKey:@"You can only accept a friend request from identity who has sent you one, and none were found"] +#define ERROR_FRIEND_REQUEST_ACCEPT_FROM_NON_LOCAL_IDENTITY [NSError errorWithCode:501 localizedDescriptionKey:@"Accepting a friend request should only happen from a local identity"] + + +@implementation DSIdentity (Friendship) + +// MARK: Sending a Friend Request + +- (void)sendNewFriendRequestToIdentity:(DSIdentity *)identity + completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion { + [self sendNewFriendRequestToIdentity:identity + inContext:self.platformContext + completion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)sendNewFriendRequestToIdentity:(DSIdentity *)identity + inContext:(NSManagedObjectContext *)context + completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + if (identity.isTransient) { + identity.isTransient = FALSE; + [self.identitiesManager registerForeignIdentity:identity]; + if (identity.transientDashpayUser) { + [identity applyProfileChanges:identity.transientDashpayUser + inContext:context + saveContext:YES + completion:^(BOOL success, NSError *_Nullable error) { + if (success && !error) { + DSDashpayUserEntity *dashpayUser = [identity matchingDashpayUserInContext:context]; + if (identity.transientDashpayUser.revision == dashpayUser.remoteProfileDocumentRevision) + identity.transientDashpayUser = nil; + } + } + onCompletionQueue:self.identityQueue]; + } + } + [identity fetchNeededNetworkStateInformationInContext:context + withCompletion:^(DSIdentityQueryStep failureStep, NSArray *_Nullable errors) { + if (failureStep && failureStep != DSIdentityQueryStep_Profile) { //if profile fails we can still continue on + completion(NO, errors); + return; + } + if (![identity isDashpayReady]) { + dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_INCOMPLETE_ACTIONS]); }); + return; + } + uint32_t destinationKeyIndex = [identity firstIndexOfKeyOfType:self.currentMainKeyType createIfNotPresent:NO saveKey:NO]; + uint32_t sourceKeyIndex = [self firstIndexOfKeyOfType:self.currentMainKeyType createIfNotPresent:NO saveKey:NO]; + + + if (sourceKeyIndex == UINT32_MAX) { //not found + //to do register a new key + NSAssert(FALSE, @"we shouldn't be getting here"); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_KEY_HANDLING]); }); + return; + } + DSPotentialOneWayFriendship *potentialFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationIdentity:identity + destinationKeyIndex:destinationKeyIndex + sourceIdentity:self + sourceKeyIndex:sourceKeyIndex + account:[self.wallet accountWithNumber:0]]; + [potentialFriendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { + if (!success) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_KEY_HANDLING]); }); + return; + } + BOOL encrypted = [potentialFriendship encryptExtendedPublicKey]; + if (encrypted) { + [self sendNewFriendRequestMatchingPotentialFriendship:potentialFriendship + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } else if (completion) { + dispatch_async(completionQueue, ^{ completion(NO, @[ERROR_KEY_HANDLING]); }); + } + }]; + } + onCompletionQueue:self.identityQueue]; +} + +- (void)sendNewFriendRequestToPotentialContact:(DSPotentialContact *)potentialContact + completion:(void (^)(BOOL success, NSArray *_Nullable errors))completion { + NSAssert(self.isLocal, @"This should not be performed on a non local blockchain identity"); + if (!self.isLocal) return; + DMaybeDocumentsMap *result = dash_spv_platform_document_manager_DocumentsManager_dpns_documents_for_username(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, DChar(potentialContact.username)); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, @[error]); }); + DMaybeDocumentsMapDtor(result); + return; + } + DDocument *document = result->ok->values[0]; + DSLog(@"documents for PotentialContact: "); + dash_spv_platform_document_print_document(document); + + switch (document->tag) { + case dpp_document_Document_V0: { + DMaybeIdentity *identity_result = dash_spv_platform_identity_manager_IdentitiesManager_fetch_by_id(self.chain.sharedRuntime, self.chain.sharedIdentitiesObj, document->v0->owner_id); + if (identity_result->error) { + NSError *error = [NSError ffi_from_platform_error:identity_result->error]; + DMaybeIdentityDtor(identity_result); + DMaybeDocumentsMapDtor(result); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, @[error]); }); + return; + } + DIdentity *identity = identity_result->ok; + if (!identity) { + DMaybeIdentityDtor(identity_result); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, @[ERROR_MALFORMED_RESPONSE]); }); + return; + } + switch (identity->tag) { + case dpp_identity_identity_Identity_V0: { + u256 *identity_contact_unique_id = identity->v0->id->_0->_0; + UInt256 identityContactUniqueId = u256_cast(identity_contact_unique_id); + DSBlockchainIdentityEntity *potentialContactIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:self.platformContext matching:@"uniqueID == %@", NSDataFromPtr(identity_contact_unique_id)]; + DSIdentity *potentialContactIdentity = nil; + if (potentialContactIdentityEntity) { + potentialContactIdentity = [self.chain identityForUniqueId:identityContactUniqueId]; + if (!potentialContactIdentity) + potentialContactIdentity = [[DSIdentity alloc] initWithIdentityEntity:potentialContactIdentityEntity]; + } else { + potentialContactIdentity = [self.identitiesManager foreignIdentityWithUniqueId:identityContactUniqueId + createIfMissing:YES + inContext:self.platformContext]; + } + [potentialContactIdentity applyIdentity:identity save:YES inContext:self.platformContext]; + [self sendNewFriendRequestToIdentity:potentialContactIdentity + inContext:self.platformContext + completion:completion + onCompletionQueue:dispatch_get_main_queue()]; + + break; + } + + default: + break; + } + break; + } + default: + break; + } +} + +- (void)sendNewFriendRequestMatchingPotentialFriendship:(DSPotentialOneWayFriendship *)potentialFriendship + inContext:(NSManagedObjectContext *)context + completion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSAssert(self.isLocal, @"This should not be performed on a non local blockchain identity"); + if (!self.isLocal) return; + DSDashpayUserEntity *destinationDashpayUser = [potentialFriendship.destinationIdentity matchingDashpayUserInContext:context]; + if (!destinationDashpayUser) { + NSAssert([potentialFriendship.destinationIdentity matchingDashpayUserInContext:context], @"There must be a destination contact if the destination identity is not known"); + return; + } + DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; + NSData *entropyData = uint256_random_data; + u256 *identity_id = u256_ctor_u(potentialFriendship.sourceIdentity.uniqueID); + u256 *entropy = u256_ctor(entropyData); + DValue *value = [potentialFriendship toValue]; + uint32_t index = 0; + if (!self.keysCreated) { + [self createNewKeyOfType:DKeyKindECDSA() + securityLevel:DSecurityLevelMaster() + purpose:DPurposeAuth() + saveKey:!self.wallet.isTransient + returnIndex:&index]; + } + DMaybeOpaqueKey *private_key = [self privateKeyAtIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType]; + DMaybeStateTransitionProofResult *result = dash_spv_platform_PlatformSDK_send_friend_request_with_value(self.chain.sharedRuntime, self.chain.sharedPlatformObj, contract.raw_contract, identity_id, value, entropy, private_key->ok); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeStateTransitionProofResultDtor(result); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, @[error]); }); + return; + } + [context performBlockAndWait:^{ + [self addFriendship:potentialFriendship + inContext:context + completion:^(BOOL success, NSError *error) {}]; + }]; + [self fetchOutgoingContactRequestsInContext:context + startAfter:nil + withCompletion:^(BOOL success, NSArray *_Nonnull errors) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(success, errors); }); + } + onCompletionQueue:completionQueue]; +} + +- (void)acceptFriendRequestFromIdentity:(DSIdentity *)otherIdentity + completion:(void (^)(BOOL success, NSArray *errors))completion { + [self acceptFriendRequestFromIdentity:otherIdentity + inContext:self.platformContext + completion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)acceptFriendRequestFromIdentity:(DSIdentity *)otherIdentity + inContext:(NSManagedObjectContext *)context + completion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSAssert(self.isLocal, @"This should not be performed on a non local blockchain identity"); + if (!self.isLocal) { + if (completion) completion(NO, @[ERROR_FRIEND_REQUEST_ACCEPT_FROM_NON_LOCAL_IDENTITY]); + return; + } + + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + DSFriendRequestEntity *friendRequest = [[matchingDashpayUser.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(otherIdentity.uniqueID)]] anyObject]; + if (friendRequest) { + [self acceptFriendRequest:friendRequest + completion:completion + onCompletionQueue:completionQueue]; + } else if (completion) { + completion(NO, @[ERROR_FRIEND_REQUEST_NONE_FOUND]); + } + }]; +} + +- (void)acceptFriendRequest:(DSFriendRequestEntity *)friendRequest + completion:(void (^)(BOOL success, NSArray *errors))completion { + [self acceptFriendRequest:friendRequest + completion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)acceptFriendRequest:(DSFriendRequestEntity *)friendRequest + completion:(void (^)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSAssert(self.isLocal, @"This should not be performed on a non local blockchain identity"); + if (!self.isLocal) { + if (completion) completion(NO, @[ERROR_FRIEND_REQUEST_ACCEPT_FROM_NON_LOCAL_IDENTITY]); + return; + } + DSAccount *account = [self.wallet accountWithNumber:0]; + DSDashpayUserEntity *otherDashpayUser = friendRequest.sourceContact; + DSIdentity *otherIdentity = [self.chain identityForUniqueId:otherDashpayUser.associatedBlockchainIdentity.uniqueID.UInt256]; + if (!otherIdentity) + otherIdentity = [[DSIdentity alloc] initWithIdentityEntity:otherDashpayUser.associatedBlockchainIdentity]; + // DSPotentialContact *contact = [[DSPotentialContact alloc] initWithUsername:friendRequest.sourceContact.username avatarPath:friendRequest.sourceContact.avatarPath + // publicMessage:friendRequest.sourceContact.publicMessage]; + // [contact setAssociatedIdentityUniqueId:friendRequest.sourceContact.associatedBlockchainIdentity.uniqueID.UInt256]; + // DSKey * friendsEncyptionKey = [otherIdentity keyOfType:friendRequest.sourceEncryptionPublicKeyIndex atIndex:friendRequest.sourceEncryptionPublicKeyIndex]; + //[DSKey keyWithPublicKeyData:friendRequest.sourceContact.encryptionPublicKey forKeyType:friendRequest.sourceContact.encryptionPublicKeyType onChain:self.chain]; + // [contact addPublicKey:friendsEncyptionKey atIndex:friendRequest.sourceContact.encryptionPublicKeyIndex]; + // uint32_t sourceKeyIndex = [self firstIndexOfKeyOfType:friendRequest.sourceContact.encryptionPublicKeyType createIfNotPresent:NO]; + // if (sourceKeyIndex == UINT32_MAX) { //not found + // //to do register a new key + // NSAssert(FALSE, @"we shouldn't be getting here"); + // return; + // } + DSPotentialOneWayFriendship *potentialFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationIdentity:otherIdentity destinationKeyIndex:friendRequest.sourceKeyIndex sourceIdentity:self sourceKeyIndex:friendRequest.destinationKeyIndex account:account]; + [potentialFriendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { + if (success) { + BOOL encrypted = [potentialFriendship encryptExtendedPublicKey]; + if (!encrypted) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, @[ERROR_KEY_HANDLING]); }); + return; + } + [self sendNewFriendRequestMatchingPotentialFriendship:potentialFriendship + inContext:friendRequest.managedObjectContext + completion:completion + onCompletionQueue:completionQueue]; + } else if (completion) { + completion(NO, @[ERROR_DERIVATION_FRIENDSHIP]); + } + }]; +} + +- (void)addFriendship:(DSPotentialOneWayFriendship *)friendship + inContext:(NSManagedObjectContext *)context + completion:(void (^)(BOOL success, NSError *error))completion { + //DSFriendRequestEntity * friendRequestEntity = [friendship outgoingFriendRequestForDashpayUserEntity:friendship.destinationIdentity.matchingDashpayUser]; + DSFriendRequestEntity *friendRequestEntity = [DSFriendRequestEntity managedObjectInBlockedContext:context]; + friendRequestEntity.sourceContact = [friendship.sourceIdentity matchingDashpayUserInContext:context]; + friendRequestEntity.destinationContact = [friendship.destinationIdentity matchingDashpayUserInContext:context]; + friendRequestEntity.timestamp = friendship.createdAt; + NSAssert(friendRequestEntity.sourceContact != friendRequestEntity.destinationContact, @"This must be different contacts"); + DSAccountEntity *accountEntity = [DSAccountEntity accountEntityForWalletUniqueID:self.wallet.uniqueIDString index:0 onChain:self.chain inContext:context]; + friendRequestEntity.account = accountEntity; + [friendRequestEntity finalizeWithFriendshipIdentifier]; + [friendship createDerivationPathAndSaveExtendedPublicKeyWithCompletion:^(BOOL success, DSIncomingFundsDerivationPath *_Nonnull incomingFundsDerivationPath) { + if (!success) return; + friendRequestEntity.derivationPath = [friendship storeExtendedPublicKeyAssociatedWithFriendRequest:friendRequestEntity inContext:context]; + DSAccount *account = [self.wallet accountWithNumber:0]; + if (friendship.destinationIdentity.isLocal) { //the destination is also local + NSAssert(friendship.destinationIdentity.wallet, @"Wallet should be known"); + DSAccount *recipientAccount = [friendship.destinationIdentity.wallet accountWithNumber:0]; + NSAssert(recipientAccount, @"Recipient Wallet should exist"); + [recipientAccount addIncomingDerivationPath:incomingFundsDerivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:context]; + if (recipientAccount != account) + [account addOutgoingDerivationPath:incomingFundsDerivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:context]; + } else { + //todo update outgoing derivation paths to incoming derivation paths as blockchain users come in + [account addIncomingDerivationPath:incomingFundsDerivationPath forFriendshipIdentifier:friendRequestEntity.friendshipIdentifier inContext:context]; + } + NSAssert(friendRequestEntity.derivationPath, @"derivation path must be present"); + DSDashpayUserEntity *dashpayUserInChildContext = [self matchingDashpayUserInContext:context]; + [dashpayUserInChildContext addOutgoingRequestsObject:friendRequestEntity]; + if ([[[friendship.destinationIdentity matchingDashpayUserInContext:context].outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact == %@", dashpayUserInChildContext]] count]) + [dashpayUserInChildContext addFriendsObject:[friendship.destinationIdentity matchingDashpayUserInContext:context]]; + NSError *savingError = [context ds_save]; + [self.chain.chainManager.transactionManager updateTransactionsBloomFilter]; + if (completion) completion(savingError ? NO : YES, savingError); + }]; +} + +- (void)addFriendshipFromSourceIdentity:(DSIdentity *)sourceIdentity + sourceKeyIndex:(uint32_t)sourceKeyIndex + toRecipientIdentity:(DSIdentity *)recipientIdentity + recipientKeyIndex:(uint32_t)recipientKeyIndex + atTimestamp:(NSTimeInterval)timestamp + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSAccount *account = [self.wallet accountWithNumber:0]; + DSPotentialOneWayFriendship *realFriendship = [[DSPotentialOneWayFriendship alloc] initWithDestinationIdentity:recipientIdentity destinationKeyIndex:recipientKeyIndex sourceIdentity:self sourceKeyIndex:sourceKeyIndex account:account createdAt:timestamp]; + if (![DSFriendRequestEntity existingFriendRequestEntityWithSourceIdentifier:self.uniqueID destinationIdentifier:recipientIdentity.uniqueID onAccountIndex:account.accountNumber inContext:context]) { + //it was probably added already + //this could happen when have 2 blockchain identities in same wallet + //Identity A gets outgoing contacts + //Which are the same as Identity B incoming contacts, no need to add the friendships twice + [self addFriendship:realFriendship inContext:context completion:nil]; + } + }]; +} + +- (DSIdentityFriendshipStatus)friendshipStatusForRelationshipWithIdentity:(DSIdentity *)otherIdentity { + if (!self.matchingDashpayUserInViewContext) return DSIdentityFriendshipStatus_Unknown; + __block BOOL isIncoming; + __block BOOL isOutgoing; + [self.matchingDashpayUserInViewContext.managedObjectContext performBlockAndWait:^{ + isIncoming = ([self.matchingDashpayUserInViewContext.incomingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"sourceContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(otherIdentity.uniqueID)]].count > 0); + isOutgoing = ([self.matchingDashpayUserInViewContext.outgoingRequests filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"destinationContact.associatedBlockchainIdentity.uniqueID == %@", uint256_data(otherIdentity.uniqueID)]].count > 0); + }]; + return ((isIncoming << 1) | isOutgoing); +} + + +@end diff --git a/DashSync/shared/Models/Identity/DSIdentity+Profile.h b/DashSync/shared/Models/Identity/DSIdentity+Profile.h new file mode 100644 index 000000000..7a487f437 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Profile.h @@ -0,0 +1,100 @@ +// +// 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 "DSIdentity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSIdentity (Profile) + +// MARK: - Dashpay + +/*! @brief This is a helper to easily get the avatar path of the matching dashpay user. */ +@property (nonatomic, readonly, nullable) NSString *avatarPath; + +/*! @brief This is a helper to easily get the avatar fingerprint of the matching dashpay user. */ +@property (nonatomic, readonly) NSData *avatarFingerprint; + +/*! @brief This is a helper to easily get the avatar hash of the matching dashpay user. */ +@property (nonatomic, readonly, nullable) NSData *avatarHash; + +/*! @brief This is a helper to easily get the display name of the matching dashpay user. */ +@property (nonatomic, readonly, nullable) NSString *displayName; + +/*! @brief This is a helper to easily get the public message of the matching dashpay user. */ +@property (nonatomic, readonly, nullable) NSString *publicMessage; + +/*! @brief This is a helper to easily get the last time the profile was updated of the matching dashpay user. */ +@property (nonatomic, readonly) uint64_t dashpayProfileUpdatedAt; + +/*! @brief This is a helper to easily get the creation time of the profile of the matching dashpay user. */ +@property (nonatomic, readonly) uint64_t dashpayProfileCreatedAt; + +- (void)fetchProfileWithCompletion:(void (^_Nullable)(BOOL success, NSError *error))completion; +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName; +- (void)updateDashpayProfileWithPublicMessage:(NSString *)publicMessage; + +- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString; +- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString + avatarHash:(NSData *)avatarHash + avatarFingerprint:(NSData *)avatarFingerprint; +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage; +#if TARGET_OS_IOS +- (void)updateDashpayProfileWithAvatarImage:(UIImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString; +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarImage:(UIImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString; +#else +- (void)updateDashpayProfileWithAvatarImage:(NSImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString; +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarImage:(NSImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString; +#endif +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarURLString:(NSString *)avatarURLString; +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarURLString:(NSString *)avatarURLString + avatarHash:(NSData *)avatarHash + avatarFingerprint:(NSData *)avatarFingerprint; +- (void)signAndPublishProfileWithCompletion:(void (^)(BOOL success, BOOL cancelled, NSError *error))completion; + + +- (void)fetchProfileInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(BOOL success, NSError *error))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; + +- (void)applyProfileChanges:(DSTransientDashpayUser *)transientDashpayUser + inContext:(NSManagedObjectContext *)context + saveContext:(BOOL)saveContext + completion:(void (^_Nullable)(BOOL success, NSError *_Nullable error))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSIdentity+Profile.m b/DashSync/shared/Models/Identity/DSIdentity+Profile.m new file mode 100644 index 000000000..a5a18cef1 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Profile.m @@ -0,0 +1,545 @@ +// +// 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 "DSDashpayUserEntity+CoreDataClass.h" +#import "DPContract.h" +#import "DSIdentity+Profile.h" +#import "DSIdentity+Protected.h" +#import "DSIdentity+Username.h" +#import "DSTransientDashpayUser.h" +#import "DSWallet.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSManagedObject+Sugar.h" +#import + +#define ERROR_TRANSITION_NO_UPDATE [NSError errorWithCode:500 localizedDescriptionKey:@"Transition had nothing to update"] +#define ERROR_DASHPAY_CONTRACT_NOT_REGISTERED [NSError errorWithCode:500 localizedDescriptionKey:@"Dashpay Contract is not yet registered on network"] +#define ERROR_IDENTITY_NO_LONGER_ACTIVE [NSError errorWithCode:410 localizedDescriptionKey:@"Identity no longer active in wallet"] +#define ERROR_CONTRACT_SETUP [NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"] + +@implementation DSIdentity (Profile) + +- (NSString *)avatarPath { + if (self.transientDashpayUser) { + return self.transientDashpayUser.avatarPath; + } else { + return self.matchingDashpayUserInViewContext.avatarPath; + } +} + +- (NSData *)avatarFingerprint { + if (self.transientDashpayUser) { + return self.transientDashpayUser.avatarFingerprint; + } else { + return self.matchingDashpayUserInViewContext.avatarFingerprint; + } +} + +- (NSData *)avatarHash { + if (self.transientDashpayUser) { + return self.transientDashpayUser.avatarHash; + } else { + return self.matchingDashpayUserInViewContext.avatarHash; + } +} + +- (NSString *)displayName { + if (self.transientDashpayUser) { + return self.transientDashpayUser.displayName; + } else { + return self.matchingDashpayUserInViewContext.displayName; + } +} + +- (NSString *)publicMessage { + if (self.transientDashpayUser) { + return self.transientDashpayUser.publicMessage; + } else { + return self.matchingDashpayUserInViewContext.publicMessage; + } +} + +- (uint64_t)dashpayProfileUpdatedAt { + if (self.transientDashpayUser) { + return self.transientDashpayUser.updatedAt; + } else { + return self.matchingDashpayUserInViewContext.updatedAt; + } +} + +- (uint64_t)dashpayProfileCreatedAt { + if (self.transientDashpayUser) { + return self.transientDashpayUser.createdAt; + } else { + return self.matchingDashpayUserInViewContext.createdAt; + } +} + +// MARK: Profile + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName { + [self updateDashpayProfileWithDisplayName:displayName + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.displayName = displayName; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + +- (void)updateDashpayProfileWithPublicMessage:(NSString *)publicMessage { + [self updateDashpayProfileWithPublicMessage:publicMessage + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithPublicMessage:(NSString *)publicMessage + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.publicMessage = publicMessage; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + +- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString { + [self updateDashpayProfileWithAvatarURLString:avatarURLString + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.avatarPath = avatarURLString; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage { + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.displayName = displayName; + matchingDashpayUser.publicMessage = publicMessage; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarURLString:(NSString *)avatarURLString { + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + avatarURLString:avatarURLString + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarURLString:(NSString *)avatarURLString + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.displayName = displayName; + matchingDashpayUser.publicMessage = publicMessage; + matchingDashpayUser.avatarPath = avatarURLString; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + +#if TARGET_OS_IOS + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarImage:(UIImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString { + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + avatarImage:avatarImage + avatarData:data + avatarURLString:avatarURLString + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarImage:(UIImage *)avatarImage + avatarData:(NSData *)avatarData + avatarURLString:(NSString *)avatarURLString + inContext:(NSManagedObjectContext *)context { + NSData *avatarHash = uint256_data(avatarData.SHA256); + uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + avatarURLString:avatarURLString + avatarHash:avatarHash + avatarFingerprint:[NSData dataWithUInt64:fingerprint] + inContext:context]; +} + +- (void)updateDashpayProfileWithAvatarImage:(UIImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString { + [self updateDashpayProfileWithAvatarImage:avatarImage + avatarData:data + avatarURLString:avatarURLString + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithAvatarImage:(UIImage *)avatarImage + avatarData:(NSData *)avatarData + avatarURLString:(NSString *)avatarURLString + inContext:(NSManagedObjectContext *)context { + NSData *avatarHash = uint256_data(avatarData.SHA256); + uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; + [self updateDashpayProfileWithAvatarURLString:avatarURLString + avatarHash:avatarHash + avatarFingerprint:[NSData dataWithUInt64:fingerprint] + inContext:context]; +} + +#else + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarImage:(NSImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString { + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + avatarImage:avatarImage + avatarData:data + avatarURLString:avatarURLString + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarImage:(NSImage *)avatarImage + avatarData:(NSData *)avatarData + avatarURLString:(NSString *)avatarURLString + inContext:(NSManagedObjectContext *)context { + NSData *avatarHash = uint256_data(avatarData.SHA256); + uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + avatarURLString:avatarURLString + avatarHash:avatarHash + avatarFingerprint:[NSData dataWithUInt64:fingerprint] + inContext:context]; +} + +- (void)updateDashpayProfileWithAvatarImage:(NSImage *)avatarImage + avatarData:(NSData *)data + avatarURLString:(NSString *)avatarURLString { + [self updateDashpayProfileWithAvatarImage:avatarImage + avatarData:data + avatarURLString:avatarURLString + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithAvatarImage:(NSImage *)avatarImage + avatarData:(NSData *)avatarData + avatarURLString:(NSString *)avatarURLString + inContext:(NSManagedObjectContext *)context { + NSData *avatarHash = uint256_data(avatarData.SHA256); + uint64_t fingerprint = [[OSImageHashing sharedInstance] hashImage:avatarImage withProviderId:OSImageHashingProviderDHash]; + [self updateDashpayProfileWithAvatarURLString:avatarURLString + avatarHash:avatarHash + avatarFingerprint:[NSData dataWithUInt64:fingerprint] + inContext:context]; +} + +#endif + + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarURLString:(NSString *)avatarURLString + avatarHash:(NSData *)avatarHash + avatarFingerprint:(NSData *)avatarFingerprint { + [self updateDashpayProfileWithDisplayName:displayName + publicMessage:publicMessage + avatarURLString:avatarURLString + avatarHash:avatarHash + avatarFingerprint:avatarFingerprint + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithDisplayName:(NSString *)displayName + publicMessage:(NSString *)publicMessage + avatarURLString:(NSString *)avatarURLString + avatarHash:(NSData *)avatarHash + avatarFingerprint:(NSData *)avatarFingerprint + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.displayName = displayName; + matchingDashpayUser.publicMessage = publicMessage; + matchingDashpayUser.avatarPath = avatarURLString; + matchingDashpayUser.avatarFingerprint = avatarFingerprint; + matchingDashpayUser.avatarHash = avatarHash; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + +- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString + avatarHash:(NSData *)avatarHash + avatarFingerprint:(NSData *)avatarFingerprint { + [self updateDashpayProfileWithAvatarURLString:avatarURLString + avatarHash:avatarHash + avatarFingerprint:avatarFingerprint + inContext:self.platformContext]; +} + +- (void)updateDashpayProfileWithAvatarURLString:(NSString *)avatarURLString + avatarHash:(NSData *)avatarHash + avatarFingerprint:(NSData *)avatarFingerprint + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + matchingDashpayUser.avatarPath = avatarURLString; + matchingDashpayUser.avatarFingerprint = avatarFingerprint; + matchingDashpayUser.avatarHash = avatarHash; + if (!matchingDashpayUser.remoteProfileDocumentRevision) { + matchingDashpayUser.createdAt = [[NSDate date] timeIntervalSince1970] * 1000; + if (!matchingDashpayUser.originalEntropyData) + matchingDashpayUser.originalEntropyData = uint256_random_data; + } + matchingDashpayUser.updatedAt = [[NSDate date] timeIntervalSince1970] * 1000; + matchingDashpayUser.localProfileDocumentRevision++; + [context ds_save]; + }]; +} + +- (void)signAndPublishProfileWithCompletion:(void (^)(BOOL success, BOOL cancelled, NSError *error))completion { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: Sign and publish profile", self.logPrefix]; + DSLog(@"%@", debugInfo); + NSManagedObjectContext *context = self.platformContext; + __block uint32_t profileDocumentRevision; + [context performBlockAndWait:^{ + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + if (matchingDashpayUser.localProfileDocumentRevision > matchingDashpayUser.remoteProfileDocumentRevision) + matchingDashpayUser.localProfileDocumentRevision = matchingDashpayUser.remoteProfileDocumentRevision + 1; + profileDocumentRevision = matchingDashpayUser.localProfileDocumentRevision; + [context ds_save]; + }]; + DSDashpayUserEntity *matchingDashpayUser = [self matchingDashpayUserInContext:context]; + __block dash_spv_platform_models_profile_Profile *profile = nil; + __block NSData *entropyData = nil, *documentIdentifier = nil; + if (matchingDashpayUser && matchingDashpayUser.localProfileDocumentRevision) { + __block Vec_u8 *avatarFingerprint = nil, *avatarHash = nil; + __block uint64_t updatedAt, createdAt, revision; + __block char *publicMessage = nil, *avatarUrl = nil, *displayName = nil; + [context performBlockAndWait:^{ + updatedAt = matchingDashpayUser.updatedAt; + createdAt = matchingDashpayUser.createdAt; + revision = matchingDashpayUser.localProfileDocumentRevision; + if (matchingDashpayUser.publicMessage) + publicMessage = DChar(matchingDashpayUser.publicMessage); + if (matchingDashpayUser.avatarPath) + avatarUrl = DChar(matchingDashpayUser.avatarPath); + if (matchingDashpayUser.avatarFingerprint) + avatarFingerprint = bytes_ctor(matchingDashpayUser.avatarFingerprint); + if (matchingDashpayUser.avatarHash) + avatarHash = bytes_ctor(matchingDashpayUser.avatarHash); + if (matchingDashpayUser.displayName) + publicMessage = DChar(matchingDashpayUser.displayName); + entropyData = matchingDashpayUser.originalEntropyData; + documentIdentifier = matchingDashpayUser.documentIdentifier; + }]; + profile = dash_spv_platform_models_profile_Profile_ctor(updatedAt, createdAt, revision, publicMessage, avatarUrl, avatarFingerprint, avatarHash, displayName); + } else { + DSLog(@"%@: ERROR: No user or revision %@", debugInfo, matchingDashpayUser); + if (completion) completion(nil, NO, ERROR_TRANSITION_NO_UPDATE); + return; + } + + if (!self.keysCreated) { + uint32_t index; + [self createNewKeyOfType:DKeyKindECDSA() + securityLevel:DSecurityLevelMaster() + purpose:DPurposeAuth() + saveKey:!self.wallet.isTransient + returnIndex:&index]; + } + DMaybeOpaqueKey *private_key = [self privateKeyAtIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType]; + DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; + DMaybeStateTransitionProofResult *result = dash_spv_platform_PlatformSDK_sign_and_publish_profile(self.chain.sharedRuntime, self.chain.sharedPlatformObj, contract.raw_contract, u256_ctor_u(self.uniqueID), profile, u256_ctor(entropyData), u256_ctor(documentIdentifier), private_key->ok); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: ERROR: %@", debugInfo, error); + DMaybeStateTransitionProofResultDtor(result); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, NO, error); }); + return; + } + DMaybeStateTransitionProofResultDtor(result); + + [context performBlockAndWait:^{ + [self matchingDashpayUserInContext:context].remoteProfileDocumentRevision = profileDocumentRevision; + [context ds_save]; + }]; + DSLog(@"%@: OK", debugInfo); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(YES, NO, nil); }); + +} + +// MARK: Fetching + +- (void)fetchProfileWithCompletion:(void (^)(BOOL success, NSError *error))completion { + dispatch_async(self.identityQueue, ^{ + [self fetchProfileInContext:self.platformContext + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; + }); +} + +- (void)fetchProfileInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(BOOL success, NSError *error))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; + if ([dashpayContract contractState] != DPContractState_Registered) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, ERROR_DASHPAY_CONTRACT_NOT_REGISTERED); }); + return; + } + [self.identitiesManager fetchProfileForIdentity:self + withCompletion:^(BOOL success, DSTransientDashpayUser *_Nullable dashpayUserInfo, NSError *_Nullable error) { + if (!success || error || dashpayUserInfo == nil) { + if (completion) dispatch_async(completionQueue, ^{ completion(success, error); }); + return; + } + [self applyProfileChanges:dashpayUserInfo + inContext:context + saveContext:YES + completion:^(BOOL success, NSError *_Nullable error) { + if (!success) { + [self fetchUsernamesInContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + + } else if (completion) { + dispatch_async(completionQueue, ^{ completion(success, error); }); + } + + + } + onCompletionQueue:self.identityQueue]; + } + onCompletionQueue:self.identityQueue]; +} + + +- (void)applyProfileChanges:(DSTransientDashpayUser *)transientDashpayUser + inContext:(NSManagedObjectContext *)context + saveContext:(BOOL)saveContext + completion:(void (^)(BOOL success, NSError *error))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + if (![self isActive]) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, ERROR_IDENTITY_NO_LONGER_ACTIVE); }); + return; + } + __weak typeof(self) weakSelf = self; + dispatch_async(self.identityQueue, ^{ + [context performBlockAndWait:^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + if (completion) completion(NO, ERROR_MEM_ALLOC); + return; + } + if (![self isActive]) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, ERROR_IDENTITY_NO_LONGER_ACTIVE); }); + return; + } + DSDashpayUserEntity *contact = [[self identityEntityInContext:context] matchingDashpayUser]; + NSAssert(contact, @"It is weird to get here"); + if (!contact) + contact = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentity.uniqueID == %@", self.uniqueIDData]; + if (!contact || transientDashpayUser.updatedAt > contact.updatedAt) { + if (!contact) { + contact = [DSDashpayUserEntity managedObjectInBlockedContext:context]; + contact.chain = [strongSelf.wallet.chain chainEntityInContext:context]; + contact.associatedBlockchainIdentity = [strongSelf identityEntityInContext:context]; + } + NSError *error = [contact applyTransientDashpayUser:transientDashpayUser save:saveContext]; + if (error) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, error); }); + return; + } + } + [self saveProfileTimestamp]; + if (completion) dispatch_async(completionQueue, ^{ completion(YES, nil); }); + }]; + }); +} +@end diff --git a/DashSync/shared/Models/Identity/DSIdentity+Protected.h b/DashSync/shared/Models/Identity/DSIdentity+Protected.h new file mode 100644 index 000000000..97d190bbe --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Protected.h @@ -0,0 +1,144 @@ +// +// Created by Sam Westrich +// Copyright © 2020 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 "DSIdentity.h" +#import "DSIdentitiesManager.h" +#import "DSTransientDashpayUser.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSBlockchainIdentityEntity; + +@interface DSIdentity () +@property (nonatomic, weak) DSWallet *wallet; + +@property (nonatomic, readonly) DSBlockchainIdentityEntity *identityEntity; +@property (nullable, nonatomic, strong) DSTransientDashpayUser *transientDashpayUser; +@property (nonatomic, weak) DSInvitation *associatedInvitation; +@property (nonatomic, assign) DMaybeOpaqueKey *registrationFundingPrivateKey; +@property (nonatomic, assign) BOOL isLocal; +@property (nonatomic, assign) UInt256 registrationAssetLockTransactionHash; + +@property (nonatomic, readonly) NSManagedObjectContext *platformContext; +@property (nonatomic, strong) dispatch_queue_t identityQueue; +@property (nonatomic, strong) DSChain *chain; +@property (nonatomic, readonly) DSIdentitiesManager *identitiesManager; +@property (nonatomic, assign) DKeyKind *currentMainKeyType; +@property (nonatomic, assign) uint32_t currentMainKeyIndex; +@property (nonatomic, readonly) uint32_t keysCreated; + +@property (nonatomic, assign) IdentityModel *identity_model; + +@property (nonatomic, assign) BOOL isTransient; + +@property (nonatomic, assign) uint64_t lastCheckedIncomingContactsTimestamp; +@property (nonatomic, assign) uint64_t lastCheckedOutgoingContactsTimestamp; + +- (BOOL)isDashpayReady; +- (void)saveProfileTimestamp; + +- (DSBlockchainIdentityEntity *)identityEntityInContext:(NSManagedObjectContext *)context; + +- (instancetype)initWithIdentityEntity:(DSBlockchainIdentityEntity *)entity; + +//This one is called for a local identity that is being recreated from the network +- (instancetype)initAtIndex:(uint32_t)index + withUniqueId:(UInt256)uniqueId + inWallet:(DSWallet *)wallet + withIdentityEntity:(DSBlockchainIdentityEntity *)entity; + +//This one is called from an identity that was created locally by creating a credit funding transaction +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet + withIdentityEntity:(DSBlockchainIdentityEntity *)entity; + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet + withIdentityEntity:(DSBlockchainIdentityEntity *)entity + associatedToInvitation:(DSInvitation *)invitation; + +- (instancetype)initWithUniqueId:(UInt256)uniqueId + isTransient:(BOOL)isTransient + onChain:(DSChain *)chain; + +- (instancetype)initAtIndex:(uint32_t)index + inWallet:(DSWallet *)wallet; + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet; + +- (instancetype)initAtIndex:(uint32_t)index + withUniqueId:(UInt256)uniqueId + inWallet:(DSWallet *)wallet; + +- (instancetype)initAtIndex:(uint32_t)index + uniqueId:(UInt256)uniqueId + inWallet:(DSWallet *)wallet; +- (instancetype)initAtIndex:(uint32_t)index + withAssetLockTransaction:(DSAssetLockTransaction *)transaction + inWallet:(DSWallet *)wallet; + +- (DIdentityPublicKey *_Nullable)firstIdentityPublicKeyOfSecurityLevel:(DSecurityLevel *)security_level + andPurpose:(DPurpose *)purpose; +- (DMaybeOpaqueKey *_Nullable)publicKeyAtIndex:(uint32_t)index + ofType:(DKeyKind *)type; + +- (DMaybeOpaqueKey *_Nullable)privateKeyAtIndex:(uint32_t)index + ofType:(DKeyKind *)type; +- (void)deletePersistentObjectAndSave:(BOOL)save + inContext:(NSManagedObjectContext *)context; + +- (void)saveInitial; + +- (void)saveInitialInContext:(NSManagedObjectContext *)context; + +- (void)registerInWalletForIdentityUniqueId:(UInt256)identityUniqueId; + +- (BOOL)createFundingPrivateKeyWithSeed:(NSData *)seed + isForInvitation:(BOOL)isForInvitation; + + +- (void)setInvitationUniqueId:(UInt256)uniqueId; + +- (void)setInvitationAssetLockTransaction:(DSAssetLockTransaction *)transaction; + +- (void)fetchIfNeededNetworkStateInformation:(DSIdentityQueryStep)querySteps + inContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; + +- (void)fetchNeededNetworkStateInformationInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; +- (void)saveInContext:(NSManagedObjectContext *)context; +- (void)applyIdentity:(DIdentity *)identity + save:(BOOL)save + inContext:(NSManagedObjectContext *_Nullable)context; +- (uint32_t)firstIndexOfKeyOfType:(DKeyKind *)type + createIfNotPresent:(BOOL)createIfNotPresent + saveKey:(BOOL)saveKey; + +- (DAssetLockProof *)createProof:(DSInstantSendTransactionLock *_Nullable)isLock; + +- (UInt256)contractIdIfRegistered:(DDataContract *)contract; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSIdentity+Username.h b/DashSync/shared/Models/Identity/DSIdentity+Username.h new file mode 100644 index 000000000..b295efe49 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Username.h @@ -0,0 +1,63 @@ +// +// 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 "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" +#import "DSIdentity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSIdentity (Username) + +/*! @brief Related to DPNS. This is the list of usernames with their .dash domain that are associated to the identity in the domain "dash". These usernames however might not yet be registered or might be invalid. This can be used in tandem with the statusOfUsername: method */ +@property (nonatomic, readonly) NSArray *dashpayUsernameFullPaths; +@property (nonatomic, readonly) NSUInteger dashpayUsernameCount; +/*! @brief Related to DPNS. This is the list of usernames that are associated to the identity in the domain "dash". These usernames however might not yet be registered or might be invalid. This can be used in tandem with the statusOfUsername: method */ +@property (nonatomic, readonly) NSArray *dashpayUsernames; + +- (void)applyUsernameEntitiesFromIdentityEntity:(DSBlockchainIdentityEntity *)identityEntity; +- (void)collectUsernameEntitiesIntoIdentityEntityInContext:(DSBlockchainIdentityEntity *)identityEntity + context:(NSManagedObjectContext *)context; + +- (void)addDashpayUsername:(NSString *)username save:(BOOL)save; +- (void)addUsername:(NSString *)username + inDomain:(NSString *)domain + save:(BOOL)save; +- (void)addUsername:(NSString *)username + inDomain:(NSString *)domain + status:(DUsernameStatus *)status + save:(BOOL)save + registerOnNetwork:(BOOL)registerOnNetwork; +- (DUsernameStatus *)statusOfUsername:(NSString *)username + inDomain:(NSString *)domain; +- (DUsernameStatus *)statusOfDashpayUsername:(NSString *)username; +- (void)registerUsernamesWithCompletion:(void (^_Nullable)(BOOL success, NSArray *errors))completion; + +- (void)fetchUsernamesInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(BOOL success, NSError *error))completion + onCompletionQueue:(dispatch_queue_t)completionQueue; + +- (void)setAndSaveUsernameFullPaths:(NSArray *)usernameFullPaths + toStatus:(DUsernameStatus *)status + inContext:(NSManagedObjectContext *)context; + +- (BOOL)hasDashpayUsername:(NSString *)username; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSIdentity+Username.m b/DashSync/shared/Models/Identity/DSIdentity+Username.m new file mode 100644 index 000000000..58849b0cd --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity+Username.m @@ -0,0 +1,815 @@ +// +// 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 "DPContract.h" +#import "DSIdentity+Protected.h" +#import "DSIdentity+Username.h" +#import "DSChainManager.h" +#import "DSDashPlatform.h" +#import "DSUsernameFullPathSaveContext.h" +#import "DSWallet.h" +#import "NSArray+Dash.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSManagedObject+Sugar.h" +#import + +#define DEFAULT_FETCH_USERNAMES_RETRY_COUNT 5 + +#define ERROR_DPNS_CONTRACT_NOT_REGISTERED [NSError errorWithCode:500 localizedDescriptionKey:@"DPNS Contract is not yet registered on network"] +#define ERROR_TRANSITION_SIGNING [NSError errorWithCode:501 localizedDescriptionKey:@"Unable to sign transition"] +#define ERROR_UNSUPPORTED_DOCUMENT_VERSION(version) [NSError errorWithCode:500 descriptionKey:DSLocalizedFormat(@"Unsupported document version: %u", nil, version)] + +void usernames_save_context_caller(const void *context, DUsernameStatus *status) { + DSUsernameFullPathSaveContext *saveContext = ((__bridge DSUsernameFullPathSaveContext *)(context)); + switch (*status) { + case dash_spv_platform_document_usernames_UsernameStatus_PreorderRegistrationPending: { + [saveContext setAndSaveUsernameFullPaths:dash_spv_platform_document_usernames_UsernameStatus_PreorderRegistrationPending_ctor()]; + break; + } + case dash_spv_platform_document_usernames_UsernameStatus_Preordered: + [saveContext setAndSaveUsernameFullPaths:dash_spv_platform_document_usernames_UsernameStatus_Preordered_ctor()]; + break; + case dash_spv_platform_document_usernames_UsernameStatus_RegistrationPending: + [saveContext setAndSaveUsernameFullPaths:dash_spv_platform_document_usernames_UsernameStatus_RegistrationPending_ctor()]; + break; + case dash_spv_platform_document_usernames_UsernameStatus_Confirmed: + [saveContext setAndSaveUsernameFullPaths:dash_spv_platform_document_usernames_UsernameStatus_Confirmed_ctor()]; + break; + default: + break; + } + DUsernameStatusDtor(status); +} + +@implementation DSIdentity (Username) + +- (void)applyUsernameEntitiesFromIdentityEntity:(DSBlockchainIdentityEntity *)identityEntity { + for (DSBlockchainIdentityUsernameEntity *usernameEntity in identityEntity.usernames) { + NSData *salt = usernameEntity.salt; + NSString *domain = usernameEntity.domain; + NSString *username = usernameEntity.stringValue; + DUsernameStatus *status = DUsernameStatusFromIndex(usernameEntity.status); + if (salt) { + Vec_u8 *salt_bytes = bytes_ctor(salt); + dash_spv_platform_identity_model_IdentityModel_add_username_with_salt(self.identity_model, DChar(username), DChar(domain), status, salt_bytes); + DAddSaltForUsername(self.identity_model, username, salt_bytes); + } else { + DUsernameAdd(self.identity_model, username, domain ? domain : @"", status); + } + } +} + +- (void)collectUsernameEntitiesIntoIdentityEntityInContext:(DSBlockchainIdentityEntity *)identityEntity + context:(NSManagedObjectContext *)context { + DUsernameStatuses *username_statuses = dash_spv_platform_identity_model_IdentityModel_username_statuses(self.identity_model); + for (int i = 0; i < username_statuses->count; i++) { + char *username_full_path = username_statuses->keys[i]; + dash_spv_platform_identity_model_UsernameStatusInfo *info = username_statuses->values[i]; + DSBlockchainIdentityUsernameEntity *usernameEntity = [DSBlockchainIdentityUsernameEntity managedObjectInBlockedContext:context]; + NSString *usernameFullPath = NSStringFromPtr(username_full_path); + usernameEntity.status = DUsernameStatusIndex(info->status); + usernameEntity.stringValue = [self usernameOfUsernameFullPath:usernameFullPath]; + usernameEntity.domain = [self domainOfUsernameFullPath:usernameFullPath]; + usernameEntity.blockchainIdentity = identityEntity; + [identityEntity addUsernamesObject:usernameEntity]; + [identityEntity setDashpayUsername:usernameEntity]; + } + DUsernameStatusesDtor(username_statuses); +} + +// MARK: Usernames + +- (void)addDashpayUsername:(NSString *)username + save:(BOOL)save { + [self addUsername:username + inDomain:@"dash" + status:dash_spv_platform_document_usernames_UsernameStatus_Initial_ctor() + save:save + registerOnNetwork:YES]; +} + +- (void)addUsername:(NSString *)username + inDomain:(NSString *)domain + save:(BOOL)save { + [self addUsername:username + inDomain:domain + status:dash_spv_platform_document_usernames_UsernameStatus_Initial_ctor() + save:save + registerOnNetwork:YES]; +} + +- (void)addUsername:(NSString *)username + inDomain:(NSString *)domain + status:(DUsernameStatus *)status + save:(BOOL)save + registerOnNetwork:(BOOL)registerOnNetwork { + DUsernameAdd(self.identity_model, username, domain, status); + if (save) + dispatch_async(self.identityQueue, ^{ + [self saveNewUsername:username + inDomain:domain + status:dash_spv_platform_document_usernames_UsernameStatus_Initial + inContext:self.platformContext]; + if (registerOnNetwork && self.registered && !dash_spv_platform_document_usernames_UsernameStatus_is_confirmed(status)) + [self registerUsernamesWithCompletion:^(BOOL success, NSArray *errors) {}]; + }); +} + +- (DUsernameStatus *)statusOfUsername:(NSString *)username + inDomain:(NSString *)domain { + return dash_spv_platform_identity_model_IdentityModel_status_of_username(self.identity_model, DChar(username), DChar(domain)); +} + +- (DUsernameStatus *)statusOfDashpayUsername:(NSString *)username { + return dash_spv_platform_identity_model_IdentityModel_status_of_dashpay_username(self.identity_model, DChar(username)); +} + +- (DUsernameStatus *)statusOfUsernameFullPath:(NSString *)usernameFullPath { + return dash_spv_platform_identity_model_IdentityModel_status_of_username_full_path(self.identity_model, DChar(usernameFullPath)); +} + +- (NSString *)usernameOfUsernameFullPath:(NSString *)usernameFullPath { + char *result = dash_spv_platform_identity_model_IdentityModel_username_of_username_full_path(self.identity_model, DChar(usernameFullPath)); + NSString *res = NSStringFromPtr(result); + DCharDtor(result); + return res; +} + +- (NSString *)domainOfUsernameFullPath:(NSString *)usernameFullPath { + char *result = dash_spv_platform_identity_model_IdentityModel_domain_of_username_full_path(self.identity_model, DChar(usernameFullPath)); + NSString *res = NSStringFromPtr(result); + DCharDtor(result); + return res; +} + +- (NSString *)fullPathForUsername:(NSString *)username + inDomain:(NSString *)domain { + return [[username lowercaseString] stringByAppendingFormat:@".%@", [domain lowercaseString]]; +} +- (NSUInteger)dashpayUsernameCount { + return dash_spv_platform_identity_model_IdentityModel_dashpay_username_count(self.identity_model); +} +- (NSArray *)dashpayUsernameFullPaths { + Vec_String *result = dash_spv_platform_identity_model_IdentityModel_dashpay_username_full_paths(self.identity_model); + NSArray*arr = [NSArray ffi_from_vec_of_string:result]; + Vec_String_destroy(result); + return arr; +} +- (BOOL)hasDashpayUsername:(NSString *)username { + return dash_spv_platform_identity_model_IdentityModel_has_dashpay_username(self.identity_model, DChar(username)); +} +- (NSArray *)dashpayUsernames { + Vec_String *result = dash_spv_platform_identity_model_IdentityModel_dashpay_usernames(self.identity_model); + NSArray*arr = [NSArray ffi_from_vec_of_string:result]; + Vec_String_destroy(result); + return arr; +} + +// MARK: Username Helpers + + +- (NSMutableDictionary *)saltedDomainHashesForUsernameFullPaths:(NSArray *)usernameFullPaths + inContext:(NSManagedObjectContext *)context { + NSMutableDictionary *mSaltedDomainHashes = [NSMutableDictionary dictionary]; + for (NSString *unregisteredUsernameFullPath in usernameFullPaths) { + NSMutableData *saltedDomain = [NSMutableData data]; + NSData *salt; + BOOL is_initial = dash_spv_platform_identity_model_IdentityModel_status_of_username_full_path_is_initial(self.identity_model, DChar(unregisteredUsernameFullPath)); + Vec_u8 *maybe_salt = DSaltForUsername(self.identity_model, unregisteredUsernameFullPath); + if (is_initial || !maybe_salt) { + salt = uint256_data(uint256_random); + DAddSaltForUsername(self.identity_model, unregisteredUsernameFullPath, bytes_ctor(salt)); + [self saveUsername:[self usernameOfUsernameFullPath:unregisteredUsernameFullPath] + inDomain:[self domainOfUsernameFullPath:unregisteredUsernameFullPath] + status:DUsernameStatusIndex([self statusOfUsernameFullPath:unregisteredUsernameFullPath]) + salt:salt + commitSave:YES + inContext:context]; + } else { + salt = NSDataFromPtr(maybe_salt); + Vec_u8_destroy(maybe_salt); + } + + [saltedDomain appendData:salt]; + [saltedDomain appendData:[unregisteredUsernameFullPath dataUsingEncoding:NSUTF8StringEncoding]]; + mSaltedDomainHashes[unregisteredUsernameFullPath] = uint256_data([saltedDomain SHA256_2]); + DAddSaltForUsername(self.identity_model, unregisteredUsernameFullPath, bytes_ctor(salt)); + } + return [mSaltedDomainHashes copy]; +} + +- (void)saveNewUsername:(NSString *)username + inDomain:(NSString *)domain + status:(uint16_t)status + inContext:(NSManagedObjectContext *)context { + NSAssert([username containsString:@"."] == FALSE, @"This is most likely an error"); + NSAssert(domain, @"Domain must not be nil"); + if (self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *entity = [self identityEntityInContext:context]; + DSBlockchainIdentityUsernameEntity *usernameEntity = [DSBlockchainIdentityUsernameEntity managedObjectInBlockedContext:context]; + usernameEntity.status = status; + usernameEntity.stringValue = username; + NSString *usernameFullPath = [self fullPathForUsername:username inDomain:domain]; + BOOL is_initial = dash_spv_platform_identity_model_IdentityModel_status_of_username_full_path_is_initial(self.identity_model, DChar(usernameFullPath)); + Vec_u8 *maybe_salt = DSaltForUsername(self.identity_model, usernameFullPath); + if (is_initial || !maybe_salt) { + NSData *salt = uint256_data(uint256_random); + usernameEntity.salt = salt; + DAddSaltForUsername(self.identity_model, usernameFullPath, bytes_ctor(salt)); + } else { + usernameEntity.salt = NSDataFromPtr(maybe_salt); + Vec_u8_destroy(maybe_salt); + } + usernameEntity.domain = domain; + [entity addUsernamesObject:usernameEntity]; + [entity setDashpayUsername:usernameEntity]; + [context ds_save]; + [self notifyUsernameUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self + }]; + }]; +} + +- (void)setAndSaveUsernameFullPaths:(NSArray *)usernameFullPaths + toStatus:(DUsernameStatus *)status + inContext:(NSManagedObjectContext *)context { + Vec_String *username_full_paths = [NSArray ffi_to_vec_of_string:usernameFullPaths]; + dash_spv_platform_identity_model_IdentityModel_set_username_full_paths(self.identity_model, username_full_paths, status); + if (self.isTransient || !self.isActive) return; + Vec_Tuple_String_String *result = dash_spv_platform_identity_model_IdentityModel_usernames_and_domains(self.identity_model, username_full_paths); + [context performBlockAndWait:^{ + for (int i = 0; i < result->count; i++) { + Tuple_String_String *pair = result->values[i]; + NSString *username = NSStringFromPtr(pair->o_0); + NSString *domain = NSStringFromPtr(pair->o_1); + [self saveUsername:username + inDomain:domain + status:DUsernameStatusIndex(status) + salt:nil + commitSave:NO + inContext:context]; + } + [context ds_save]; + }]; + Vec_Tuple_String_String_destroy(result); +} + +- (void)saveUsernameFullPath:(NSString *)usernameFullPath + status:(DUsernameStatus *)status + salt:(NSData *)salt + commitSave:(BOOL)commitSave + inContext:(NSManagedObjectContext *)context { + if (self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *entity = [self identityEntityInContext:context]; + NSSet *usernamesPassingTest = [entity.usernames objectsPassingTest:^BOOL(DSBlockchainIdentityUsernameEntity *_Nonnull obj, BOOL *_Nonnull stop) { + BOOL isEqual = [[self fullPathForUsername:obj.stringValue inDomain:obj.domain] isEqualToString:usernameFullPath]; + if (isEqual) *stop = YES; + return isEqual; + }]; + if ([usernamesPassingTest count]) { + NSAssert([usernamesPassingTest count] == 1, @"There should never be more than 1"); + DSBlockchainIdentityUsernameEntity *usernameEntity = [usernamesPassingTest anyObject]; + usernameEntity.status = DUsernameStatusIndex(status); + if (salt) + usernameEntity.salt = salt; + if (commitSave) + [context ds_save]; + [self notifyUsernameUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUsernameKey: usernameEntity.stringValue, + DSIdentityUsernameDomainKey: usernameEntity.domain + }]; + } + }]; +} + +- (void)saveUsername:(NSString *)username + inDomain:(NSString *)domain + status:(uint16_t)status + salt:(NSData *)salt + commitSave:(BOOL)commitSave + inContext:(NSManagedObjectContext *)context { + if (self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *entity = [self identityEntityInContext:context]; + NSSet *usernamesPassingTest = [entity.usernames objectsPassingTest:^BOOL(DSBlockchainIdentityUsernameEntity *_Nonnull obj, BOOL *_Nonnull stop) { + BOOL isEqual = [obj.stringValue isEqualToString:username]; + if (isEqual) *stop = YES; + return isEqual; + }]; + if ([usernamesPassingTest count]) { + NSAssert([usernamesPassingTest count] == 1, @"There should never be more than 1"); + DSBlockchainIdentityUsernameEntity *usernameEntity = [usernamesPassingTest anyObject]; + usernameEntity.status = status; + if (salt) + usernameEntity.salt = salt; + if (commitSave) + [context ds_save]; + [self notifyUsernameUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUsernameKey: username, + DSIdentityUsernameDomainKey: domain + }]; + } + }]; +} + + + +- (void)fetchUsernamesInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(BOOL success, NSError *error))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: fetchUsernamesInContext", self.logPrefix]; + DSLog(@"%@", debugInfo); + DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; + if (contract.contractState != DPContractState_Registered) { + DSLog(@"%@: ERROR: %@", debugInfo, ERROR_DPNS_CONTRACT_NOT_REGISTERED); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, ERROR_DPNS_CONTRACT_NOT_REGISTERED); }); + return; + } + + DMaybeDocumentsMap *result = dash_spv_platform_document_manager_DocumentsManager_stream_dpns_documents_for_identity_with_user_id_using_contract(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, u256_ctor_u(self.uniqueID), contract.raw_contract, DRetryLinear(DEFAULT_FETCH_USERNAMES_RETRY_COUNT), DNotFoundAsAnError(), 1000); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: ERROR: %@", debugInfo, error); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, error); }); + return; + } + DDocumentsMap *documents = result->ok; + if (documents->count == 0) { + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: ERROR: No documents", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(YES, nil); }); + return; + } + [debugInfo appendFormat:@"docs: %lu", documents->count]; + for (int i = 0; i < documents->count; i++) { + DDocument *document = documents->values[i]; + switch (document->tag) { + case dpp_document_Document_V0: { + NSString *username = DGetTextDocProperty(document, @"label"); + NSString *lowercaseUsername = DGetTextDocProperty(document, @"normalizedLabel"); + NSString *domain = DGetTextDocProperty(document, @"normalizedParentDomainName"); + [debugInfo appendFormat:@"\t%i: %@ -- %@ -- %@", i, username, lowercaseUsername, domain]; + + if (username && lowercaseUsername && domain) { + BOOL isNew = dash_spv_platform_identity_model_IdentityModel_set_username_status_confirmed2(self.identity_model, DChar(username), DChar(domain), DChar(lowercaseUsername)); + if (isNew) { + [self saveNewUsername:username + inDomain:domain + status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed + inContext:context]; + } else { + [self saveUsername:username + inDomain:domain + status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed + salt:nil + commitSave:YES + inContext:context]; + } + } + + break; + } + default: + break; + } + } + DSLog(@"%@: OK", debugInfo); + DMaybeDocumentsMapDtor(result); + if (completion) dispatch_async(completionQueue, ^{ completion(YES, nil); }); +} + +- (void)registerUsernamesWithCompletion:(void (^_Nullable)(BOOL success, NSArray *errors))completion { + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_Initial_ctor() + inContext:self.platformContext completion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (NSError *_Nullable)registerUsernameWithSaltedDomainHash:(NSData *)saltedDomainHashData + usingContract:(DDataContract *)contract + andEntropyData:(NSData *)entropyData + withIdentityPublicKey:(DIdentityPublicKey *)identity_public_key + withPrivateKey:(DMaybeOpaqueKey *)maybe_private_key { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"[%@]: registerUsernameWithSaltedDomainHash [%@]", self.logPrefix, saltedDomainHashData.hexString]; + DDocumentResult *result = dash_spv_platform_PlatformSDK_register_preordered_salted_domain_hash_for_username_full_path(self.chain.sharedRuntime, self.chain.sharedPlatformObj, contract, u256_ctor_u(self.uniqueID), identity_public_key, bytes_ctor(saltedDomainHashData), u256_ctor(entropyData)); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DDocumentResultDtor(result); + DSLog(@"%@: ERROR: (%@)", debugInfo, error); + return error; + } + DDocument *document = result->ok; + switch (document->tag) { + case dpp_document_Document_V0: { + DSLog(@"%@: OK: (%@)", debugInfo, u256_hex(document->v0->id->_0->_0)); + DDocumentResultDtor(result); + return nil; + } + default: { + NSError *error = ERROR_UNSUPPORTED_DOCUMENT_VERSION(document->tag); + DSLog(@"%@: ERROR: (%@)", debugInfo, error); + DDocumentResultDtor(result); + return error; + } + } +} + +- (void)registerUsernamesAtStage:(DUsernameStatus *)status + inContext:(NSManagedObjectContext *)context + completion:(void (^_Nullable)(BOOL success, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"[%@]: registerUsernamesAtStage [%lu]", self.logPrefix, (unsigned long) status]; + DSLog(@"%@", debugInfo); + Vec_String *result = dash_spv_platform_identity_model_IdentityModel_username_full_paths_with_status(self.identity_model, status); + NSArray *usernameFullPaths = [NSArray ffi_from_vec_of_string:result]; + Vec_String_destroy(result); + + uint8_t status_index = DUsernameStatusIndex(status); + switch (status_index) { + case dash_spv_platform_document_usernames_UsernameStatus_Initial: { + [debugInfo appendFormat:@" (Initial) usernameFullPaths: %@\n", usernameFullPaths]; + if (usernameFullPaths.count) { + NSData *entropyData = uint256_random_data; + NSDictionary *saltedDomainHashesForUsernameFullPaths = [self saltedDomainHashesForUsernameFullPaths:usernameFullPaths inContext:context]; + [debugInfo appendFormat:@" saltedDomainHashesForUsernameFullPaths: %@\n", saltedDomainHashesForUsernameFullPaths]; + if (![saltedDomainHashesForUsernameFullPaths count]) { + DSLog(@"%@ ERROR: No usernamePreorderDocuments", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, nil); }); + return; + } + DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; + DMaybeOpaqueKey *private_key = [self privateKeyAtIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType]; + + if (!self.keysCreated) { + uint32_t index; + [self createNewKeyOfType:DKeyKindECDSA() + securityLevel:DSecurityLevelHigh() + purpose:DPurposeAuth() + saveKey:!self.wallet.isTransient + returnIndex:&index]; + } + + DSecurityLevel *level = DSecurityLevelHigh(); + DPurpose *purpose = DPurposeAuth(); + DIdentityPublicKey *maybe_identity_public_key = [self firstIdentityPublicKeyOfSecurityLevel:level andPurpose:purpose]; + if (!maybe_identity_public_key) { + DSecurityLevelDtor(level); + DPurposeDtor(purpose); + NSAssert(NULL, @"Key with security_level: HIGH and purpose: AUTHENTICATION should exist"); + } + + uintptr_t i = 0; + NSMutableArray *errors = [NSMutableArray array]; + for (NSData *saltedDomainHashData in [saltedDomainHashesForUsernameFullPaths allValues]) { + + NSError *error = [self registerUsernameWithSaltedDomainHash:saltedDomainHashData + usingContract:contract.raw_contract + andEntropyData:entropyData + withIdentityPublicKey:maybe_identity_public_key + withPrivateKey:private_key]; + if (error) [errors addObject:error]; + i++; + } + if ([errors count]) { + if (completion) dispatch_async(completionQueue, ^{ completion(NO, [errors copy]); }); + return; + } + DSLog(@"%@: OK", debugInfo); + if (completion) + dispatch_async(self.identityQueue, ^{ + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_PreorderRegistrationPending_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + }); + } else { + DSLog(@"%@: Ok (No usernameFullPaths)", debugInfo); + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_PreorderRegistrationPending_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } + break; + } +// case DSIdentityUsernameStatus_Initial: { +// [debugInfo appendFormat:@" (Initial) usernameFullPaths: %@\n", usernameFullPaths]; +// if (usernameFullPaths.count) { +// NSData *entropyData = uint256_random_data; +// NSDictionary *saltedDomainHashesForUsernameFullPaths = [self saltedDomainHashesForUsernameFullPaths:usernameFullPaths inContext:context]; +// [debugInfo appendFormat:@" saltedDomainHashesForUsernameFullPaths: %@\n", saltedDomainHashesForUsernameFullPaths]; +// if (![saltedDomainHashesForUsernameFullPaths count]) { +// DSLog(@"%@ ERROR: No usernamePreorderDocuments", debugInfo); +// if (completion) dispatch_async(completionQueue, ^{ completion(NO, nil); }); +// return; +// } +// uintptr_t i = 0; +// Vec_u8 **salted_domain_hashes_values = malloc(sizeof(Vec_u8 *) * saltedDomainHashesForUsernameFullPaths.count); +// for (NSData *saltedDomainHashData in [saltedDomainHashesForUsernameFullPaths allValues]) { +// salted_domain_hashes_values[i] = bytes_ctor(saltedDomainHashData); +// i++; +// } +// if (!self.keysCreated) { +// uint32_t index; +// [self createNewKeyOfType:DKeyKindECDSA() saveKey:!self.wallet.isTransient returnIndex:&index]; +// } +// DMaybeOpaqueKey *private_key = [self privateKeyAtIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType]; +// DSUsernameFullPathSaveContext *saveContext = [DSUsernameFullPathSaveContext contextWithUsernames:usernameFullPaths forIdentity:self inContext:context]; +// DUsernameStatusCallback save_callback = { .caller = &usernames_save_context_caller }; +// DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; +// DMaybeStateTransitionProofResult *result = dash_spv_platform_PlatformSDK_register_preordered_salted_domain_hashes_for_username_full_paths(self.chain.sharedRuntime, self.chain.sharedPlatformObj, contract.raw_contract, u256_ctor_u(self.uniqueID), Vec_Vec_u8_ctor(i, salted_domain_hashes_values), u256_ctor(entropyData), private_key->ok, ((__bridge void *)(saveContext)), save_callback); +// if (result->error) { +// NSError *error = [NSError ffi_from_platform_error:result->error]; +// DMaybeStateTransitionProofResultDtor(result); +// DSLog(@"%@: ERROR: %@", debugInfo, error); +// if (completion) dispatch_async(completionQueue, ^{ completion(NO, error); }); +// return; +// } +// DMaybeStateTransitionProofResultDtor(result); +// DSLog(@"%@: OK", debugInfo); +// if (completion) +// dispatch_async(self.identityQueue, ^{ +// [self registerUsernamesAtStage:DSIdentityUsernameStatus_PreorderRegistrationPending +// inContext:context +// completion:completion +// onCompletionQueue:completionQueue]; +// }); +// } else { +// DSLog(@"%@: Ok (No usernameFullPaths)", debugInfo); +// [self registerUsernamesAtStage:DSIdentityUsernameStatus_PreorderRegistrationPending +// inContext:context +// completion:completion +// onCompletionQueue:completionQueue]; +// } +// break; +// } + case dash_spv_platform_document_usernames_UsernameStatus_PreorderRegistrationPending: { + [debugInfo appendFormat:@" (PreorderRegistrationPending) usernameFullPaths: %@", usernameFullPaths]; + NSDictionary *saltedDomainHashes = [self saltedDomainHashesForUsernameFullPaths:usernameFullPaths inContext:context]; + [debugInfo appendFormat:@", saltedDomainHashes: %@", saltedDomainHashes]; + if (saltedDomainHashes.count) { + Vec_Vec_u8 *vec_hashes = [NSArray ffi_to_vec_vec_u8:[saltedDomainHashes allValues]]; + DMaybeDocumentsMap *result = dash_spv_platform_document_salted_domain_hashes_SaltedDomainHashesManager_preorder_salted_domain_hashes_stream(self.chain.sharedRuntime, self.chain.sharedSaltedDomainHashesObj, vec_hashes, DRetryLinear(4), dash_spv_platform_document_salted_domain_hashes_SaltedDomainHashValidator_None_ctor(), 100); + BOOL allFound = NO; + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: ERROR: %@", debugInfo, error); + if (completion) dispatch_async(self.identityQueue, ^{ completion(allFound, @[error]); }); + return; + } + for (NSString *usernameFullPath in saltedDomainHashes) { + NSData *saltedDomainHashData = saltedDomainHashes[usernameFullPath]; + DDocumentsMap *documents = result->ok; + for (int i = 0; i < documents->count; i++) { + allFound &= [self processSaltedDomainHashDocument:usernameFullPath + hash:saltedDomainHashData + document:documents->values[i] + inContext:context]; + } + } + DSLog(@"%@: OK: allFound: %u", debugInfo, allFound); + DMaybeDocumentsMapDtor(result); + if (completion) + dispatch_async(self.identityQueue, ^{ + if (allFound) { + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_Preordered_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } else { + //todo: This needs to be done per username and not for all usernames + [self setAndSaveUsernameFullPaths:usernameFullPaths + toStatus:dash_spv_platform_document_usernames_UsernameStatus_Initial_ctor() + inContext:context]; + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_Initial_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } + }); + } else { + DSLog(@"%@: OK (No saltedDomainHashes)", debugInfo); + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_Preordered_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } + break; + } + case dash_spv_platform_document_usernames_UsernameStatus_Preordered: { + [debugInfo appendFormat:@" (Preordered) usernameFullPaths: %@: ", usernameFullPaths]; + if (usernameFullPaths.count) { + NSError *error = nil; + NSData *entropyData = uint256_random_data; + NSDictionary *saltedDomainHashesForUsernameFullPaths = [self saltedDomainHashesForUsernameFullPaths:usernameFullPaths inContext:context]; + if (![saltedDomainHashesForUsernameFullPaths count]) { + DSLog(@"%@ ERROR: No username preorder documents", debugInfo); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[error]); }); + return; + } + + uintptr_t i = 0; + DValue **values_values = malloc(sizeof(DValue *) * saltedDomainHashesForUsernameFullPaths.count); + for (NSString *usernameFullPath in saltedDomainHashesForUsernameFullPaths) { + NSString *username = [self usernameOfUsernameFullPath:usernameFullPath]; + NSString *domain = [self domainOfUsernameFullPath:usernameFullPath]; + DValuePair **values = malloc(sizeof(DValuePair *) * 6); + DValuePair **records = malloc(sizeof(DValuePair *) * 1); + DValuePair **subdomain_rules = malloc(sizeof(DValuePair *) * 1); + records[0] = DValueTextBytesPairCtor(@"identity", uint256_data(self.uniqueID)); + subdomain_rules[0] = DValueTextBoolPairCtor(@"allowSubdomains", false); + values[0] = DValueTextTextPairCtor(@"label", username); + values[1] = DValueTextTextPairCtor(@"normalizedLabel", [username lowercaseString]); + values[2] = DValueTextTextPairCtor(@"normalizedParentDomainName", domain); + values[3] = DValueTextPairCtor(@"preorderSalt", platform_value_Value_Bytes_ctor(DSaltForUsername(self.identity_model, usernameFullPath))); + values[4] = DValueTextMapPairCtor(@"records", DValueMapCtor(DValuePairVecCtor(1, records))); + values[5] = DValueTextMapPairCtor(@"subdomainRules", DValueMapCtor(DValuePairVecCtor(1, subdomain_rules))); + values_values[i] = platform_value_Value_Map_ctor(DValueMapCtor(DValuePairVecCtor(6, values))); + i++; + } + if (!self.keysCreated) { + uint32_t index; + [self createNewKeyOfType:DKeyKindECDSA() + securityLevel:DSecurityLevelMaster() + purpose:DPurposeAuth() + saveKey:!self.wallet.isTransient + returnIndex:&index]; + } + DMaybeOpaqueKey *private_key = [self privateKeyAtIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType]; + DSUsernameFullPathSaveContext *saveContext = [DSUsernameFullPathSaveContext contextWithUsernames:usernameFullPaths forIdentity:self inContext:context]; + DUsernameStatusCallback save_callback = { .caller = &usernames_save_context_caller }; + DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; + DMaybeStateTransitionProofResult *result = dash_spv_platform_PlatformSDK_register_username_domains_for_username_full_paths(self.chain.sharedRuntime, self.chain.sharedPlatformObj, contract.raw_contract, u256_ctor_u(self.uniqueID), DValueVecCtor(i, values_values), u256_ctor(entropyData), private_key->ok, ((__bridge void *)(saveContext)), save_callback); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeStateTransitionProofResultDtor(result); + DSLog(@"%@: ERROR: %@", debugInfo, error); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, @[error]); }); + return; + } + DMaybeStateTransitionProofResultDtor(result); + DSLog(@"%@: OK", debugInfo); + if (completion) + dispatch_async(completionQueue, ^{ + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_RegistrationPending_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + }); + } else { + DSLog(@"%@: OK (No usernameFullPaths)", debugInfo); + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_RegistrationPending_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } + break; + } + case dash_spv_platform_document_usernames_UsernameStatus_RegistrationPending: { + [debugInfo appendFormat:@" (RegistrationPending) usernameFullPaths: %@", usernameFullPaths]; + if (usernameFullPaths.count) { + NSMutableDictionary *domains = [NSMutableDictionary dictionary]; + for (NSString *usernameFullPath in usernameFullPaths) { + NSArray *components = [usernameFullPath componentsSeparatedByString:@"."]; + NSString *domain = @""; + NSString *name = components[0]; + if (components.count > 1) + domain = [[components subarrayWithRange:NSMakeRange(1, components.count - 1)] componentsJoinedByString:@"."]; + if (!domains[domain]) + domains[domain] = [NSMutableArray array]; + [domains[domain] addObject:name]; + } + __block BOOL finished = FALSE; + __block NSUInteger countAllFound = 0; + __block NSUInteger countReturned = 0; + for (NSString *domain in domains) { + NSArray *usernames = domains[domain]; + DPContract *contract = [DSDashPlatform sharedInstanceForChain:self.chain].dpnsContract; + Vec_String *usernames_vec = [NSArray ffi_to_vec_of_string:usernames]; + DMaybeDocumentsMap *result = dash_spv_platform_document_usernames_UsernamesManager_stream_usernames_with_contract(self.chain.sharedRuntime, self.chain.shareCore.usernames->obj, DChar(domain), usernames_vec, contract.raw_contract, DRetryLinear(5), dash_spv_platform_document_usernames_UsernameValidator_None_ctor(), 5 * NSEC_PER_SEC); + + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: ERROR: %@", debugInfo, error); + dispatch_async(completionQueue, ^{ completion(FALSE, @[error]); }); + return; + } + DDocumentsMap *documents = result->ok; + BOOL allDomainFound = NO; + + for (NSString *username in usernames) { + for (int i = 0; i < documents->count; i++) { + DDocument *document = documents->values[i]; + NSString *normalizedLabel = DGetTextDocProperty(document, @"normalizedLabel"); + NSString *label = DGetTextDocProperty(document, @"label"); + NSString *normalizedParentDomainName = DGetTextDocProperty(document, @"normalizedParentDomainName"); + BOOL equal = [normalizedLabel isEqualToString:[username lowercaseString]]; + DSLog(@"%@: %u: %@ == %@", debugInfo, equal, normalizedLabel, [username lowercaseString]); + allDomainFound &= equal; + + if (equal) { + dash_spv_platform_identity_model_IdentityModel_set_username_status_confirmed(self.identity_model, DChar(username), DChar(normalizedParentDomainName), DChar(label)); + [self saveUsername:username + inDomain:normalizedParentDomainName + status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed + salt:nil + commitSave:YES + inContext:context]; + } + } + } + DMaybeDocumentsMapDtor(result); + if (allDomainFound) + countAllFound++; + countReturned++; + if (countReturned == domains.count) { + finished = TRUE; + if (countAllFound == domains.count) { + dispatch_async(completionQueue, ^{ completion(YES, nil); }); + } else { + //todo: This needs to be done per username and not for all usernames + [self setAndSaveUsernameFullPaths:usernameFullPaths + toStatus:dash_spv_platform_document_usernames_UsernameStatus_Preordered_ctor() + inContext:context]; + [self registerUsernamesAtStage:dash_spv_platform_document_usernames_UsernameStatus_Preordered_ctor() + inContext:context + completion:completion + onCompletionQueue:completionQueue]; + } + if (completion) + completion(countAllFound == domains.count, nil); + } + } + DSLog(@"%@: OK: all found (%lu) == domains (%lu)", debugInfo, countAllFound, domains.count); + if (completion) + completion(countAllFound == domains.count, nil); + } else if (completion) { + DSLog(@"%@: OK (No usernameFullPaths)", debugInfo); + dispatch_async(completionQueue, ^{ completion(YES, nil); }); + } + break; + } + default: + if (completion) dispatch_async(completionQueue, ^{ completion(NO, nil); }); + break; + } +} + + +- (BOOL)processSaltedDomainHashDocument:(NSString *)usernameFullPath + hash:(NSData *)hash + document:(DDocument *)document + inContext:(NSManagedObjectContext *)context { + switch (document->tag) { + case dpp_document_Document_V0: { + NSData *saltedDomainHash = DGetBytesDocProperty(document, @"saltedDomainHash"); + if ([saltedDomainHash isEqualToData:hash]) { + DUsernameStatus *status = dash_spv_platform_document_usernames_UsernameStatus_Preordered_ctor(); + dash_spv_platform_identity_model_IdentityModel_set_username_status(self.identity_model, DChar(usernameFullPath), status); + [self saveUsernameFullPath:usernameFullPath + status:status + salt:nil + commitSave:YES + inContext:context]; + return YES; + } + break; + } + default: + break; + } + return NO; +} + +- (void)notifyUsernameUpdate:(nullable NSDictionary *)userInfo { + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSIdentityDidUpdateUsernameStatusNotification + object:nil + userInfo:userInfo]; + }); + +} +@end diff --git a/DashSync/shared/Models/Identity/DSIdentity.h b/DashSync/shared/Models/Identity/DSIdentity.h new file mode 100644 index 000000000..7139dafb9 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity.h @@ -0,0 +1,241 @@ +// +// DSIdentity.h +// DashSync +// +// Created by Sam Westrich on 7/26/18. +// + +#import "BigIntTypes.h" +#import "DSDerivationPath.h" +#import "DSKeyManager.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +#define FLAG_IS_SET(value, flag) ((value & flag) == flag) + + +@class DSWallet, DSAccount, DSChain, DSDashpayUserEntity, DSPotentialOneWayFriendship, DSTransaction, DSFriendRequestEntity, DSPotentialContact, DSAssetLockTransaction, DSTransientDashpayUser, DSInvitation, DSAuthenticationKeysDerivationPath, UIImage; + +typedef NS_ENUM(NSUInteger, DSIdentityRegistrationStep) +{ + DSIdentityRegistrationStep_None = 0, + DSIdentityRegistrationStep_FundingTransactionCreation = 1, + DSIdentityRegistrationStep_FundingTransactionAccepted = 2, + DSIdentityRegistrationStep_LocalInWalletPersistence = 4, + DSIdentityRegistrationStep_ProofAvailable = 8, + DSIdentityRegistrationStep_L1Steps = DSIdentityRegistrationStep_FundingTransactionCreation | DSIdentityRegistrationStep_FundingTransactionAccepted | DSIdentityRegistrationStep_LocalInWalletPersistence | DSIdentityRegistrationStep_ProofAvailable, + DSIdentityRegistrationStep_Identity = 16, + DSIdentityRegistrationStep_RegistrationSteps = DSIdentityRegistrationStep_L1Steps | DSIdentityRegistrationStep_Identity, + DSIdentityRegistrationStep_Username = 32, + DSIdentityRegistrationStep_RegistrationStepsWithUsername = DSIdentityRegistrationStep_RegistrationSteps | DSIdentityRegistrationStep_Username, + DSIdentityRegistrationStep_Profile = 64, + DSIdentityRegistrationStep_RegistrationStepsWithUsernameAndDashpayProfile = DSIdentityRegistrationStep_RegistrationStepsWithUsername | DSIdentityRegistrationStep_Profile, + DSIdentityRegistrationStep_All = DSIdentityRegistrationStep_RegistrationStepsWithUsernameAndDashpayProfile, + DSIdentityRegistrationStep_Cancelled = 1 << 30 +}; + +typedef NS_ENUM(NSUInteger, DSIdentityMonitorOptions) +{ + DSIdentityMonitorOptions_None = 0, + DSIdentityMonitorOptions_AcceptNotFoundAsNotAnError = 1, +}; + +typedef NS_ENUM(NSUInteger, DSIdentityQueryStep) +{ + DSIdentityQueryStep_None = DSIdentityRegistrationStep_None, //0 + DSIdentityQueryStep_Identity = DSIdentityRegistrationStep_Identity, //16 + DSIdentityQueryStep_Username = DSIdentityRegistrationStep_Username, //32 + DSIdentityQueryStep_Profile = DSIdentityRegistrationStep_Profile, //64 + DSIdentityQueryStep_IncomingContactRequests = 128, + DSIdentityQueryStep_OutgoingContactRequests = 256, + DSIdentityQueryStep_ContactRequests = DSIdentityQueryStep_IncomingContactRequests | DSIdentityQueryStep_OutgoingContactRequests, + DSIdentityQueryStep_AllForForeignIdentity = DSIdentityQueryStep_Identity | DSIdentityQueryStep_Username | DSIdentityQueryStep_Profile, + DSIdentityQueryStep_AllForLocalIdentity = DSIdentityQueryStep_Identity | DSIdentityQueryStep_Username | DSIdentityQueryStep_Profile | DSIdentityQueryStep_ContactRequests, + DSIdentityQueryStep_NoIdentity = 1 << 28, + DSIdentityQueryStep_BadQuery = 1 << 29, + DSIdentityQueryStep_Cancelled = 1 << 30 +}; + +typedef NS_ENUM(NSUInteger, DSIdentityFriendshipStatus) +{ + DSIdentityFriendshipStatus_Unknown = NSUIntegerMax, + DSIdentityFriendshipStatus_None = 0, + DSIdentityFriendshipStatus_Outgoing = 1, + DSIdentityFriendshipStatus_Incoming = 2, + DSIdentityFriendshipStatus_Friends = DSIdentityFriendshipStatus_Outgoing | DSIdentityFriendshipStatus_Incoming, +}; + +#define ERROR_MEM_ALLOC [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"] +#define ERROR_MALFORMED_RESPONSE [NSError errorWithCode:501 localizedDescriptionKey:@"Malformed platform response"] + +FOUNDATION_EXPORT NSString *const DSIdentityDidUpdateNotification; +FOUNDATION_EXPORT NSString *const DSIdentityDidUpdateUsernameStatusNotification; +FOUNDATION_EXPORT NSString *const DSIdentityKey; +FOUNDATION_EXPORT NSString *const DSIdentityUsernameKey; +FOUNDATION_EXPORT NSString *const DSIdentityUsernameDomainKey; + +FOUNDATION_EXPORT NSString *const DSIdentityUpdateEvents; +FOUNDATION_EXPORT NSString *const DSIdentityUpdateEventKeyUpdate; +FOUNDATION_EXPORT NSString *const DSIdentityUpdateEventRegistration; +FOUNDATION_EXPORT NSString *const DSIdentityUpdateEventCreditBalance; +FOUNDATION_EXPORT NSString *const DSIdentityUpdateEventType; +FOUNDATION_EXPORT NSString *const DSIdentityUpdateEventDashpaySyncronizationBlockHash; + +NSString * DSRegistrationStepsDescription(DSIdentityRegistrationStep step); +NSString * DSIdentityQueryStepsDescription(DSIdentityQueryStep step); + +@interface DSIdentity : NSObject + +/*! @brief This is the unique identifier representing the blockchain identity. It is derived from the asset lock transaction credit burn UTXO (as of dpp v10). Returned as a 256 bit number */ +@property (nonatomic, readonly) UInt256 uniqueID; +/*! @brief This is the unique identifier representing the blockchain identity. It is derived from the asset lock transaction credit burn UTXO (as of dpp v10). Returned as a base 58 string of a 256 bit number */ +@property (nonatomic, readonly) NSString *uniqueIdString; +/*! @brief This is the unique identifier representing the blockchain identity. It is derived from the asset lock transaction credit burn UTXO (as of dpp v10). Returned as a NSData of a 256 bit number */ +@property (nonatomic, readonly) NSData *uniqueIDData; +/*! @brief This is the outpoint of the registration credit funding transaction. It is used to determine the unique ID by double SHA256 its value. Returned as a UTXO { .hash , .n } */ +@property (nonatomic, readonly) DSUTXO lockedOutpoint; +/*! @brief This is the outpoint of the registration credit funding transaction. It is used to determine the unique ID by double SHA256 its value. Returned as an NSData of a UTXO { .hash , .n } */ +@property (nonatomic, readonly) NSData *lockedOutpointData; +/*! @brief This is if the blockchain identity is present in wallets or not. If this is false then the blockchain identity is known for example from being a dashpay friend. */ +@property (nonatomic, readonly) BOOL isLocal; +/*! @brief This is if the blockchain identity is made for being an invitation. All invitations should be marked as non local as well. */ +@property (nonatomic, readonly) BOOL isOutgoingInvitation; +/*! @brief This is if the blockchain identity is made from an invitation we received. */ +@property (nonatomic, readonly) BOOL isFromIncomingInvitation; +/*! @brief This is TRUE if the blockchain identity is an effemeral identity returned when searching. */ +@property (nonatomic, readonly) BOOL isTransient; +/*! @brief This is TRUE only if the blockchain identity is contained within a wallet. It could be in a cleanup phase where it was removed from the wallet but still being help in memory by callbacks. */ +@property (nonatomic, readonly) BOOL isActive; +/*! @brief This references transient Dashpay user info if on a transient blockchain identity. */ +@property (nonatomic, readonly) DSTransientDashpayUser *transientDashpayUser; +/*! @brief This is the bitwise steps that the identity has already performed in registration. */ +@property (nonatomic, readonly) DSIdentityRegistrationStep stepsCompleted; +/*! @brief This is the wallet holding the blockchain identity. There should always be a wallet associated to a blockchain identity if the blockchain identity is local, but never if it is not. */ +@property (nonatomic, weak, readonly) DSWallet *wallet; +/*! @brief This is invitation that is identity originated from. */ +@property (nonatomic, weak, readonly) DSInvitation *associatedInvitation; +/*! @brief This is the index of the blockchain identity in the wallet. The index is the top derivation used to derive an extended set of keys for the identity. No two local blockchain identities should be allowed to have the same index in a wallet. For example m/.../.../.../index/key */ +@property (nonatomic, readonly) uint32_t index; +/*! @brief Related to DPNS. This is current and most likely username associated to the identity. It is not necessarily registered yet on L2 however so its state should be determined with the statusOfUsername: method + @discussion There are situations where this is nil as it is not yet known or if no username has yet been set. */ +@property (nullable, nonatomic, readonly) NSString *currentDashpayUsername; +/*! @brief Related to registering the identity. This is the address used to fund the registration of the identity. Dash sent to this address in the special credit funding transaction will be converted to L2 credits */ +@property (nonatomic, readonly) NSString *registrationFundingAddress; +/*! @brief The known balance in credits of the identity */ +@property (nonatomic, readonly) uint64_t creditBalance; +/*! @brief The number of registered active keys that the blockchain identity has */ +@property (nonatomic, readonly) uintptr_t activeKeyCount; +/*! @brief The number of all keys that the blockchain identity has, registered, in registration, or inactive */ +@property (nonatomic, readonly) uintptr_t totalKeyCount; +/*! @brief This is the transaction on L1 that has an output that is used to fund the creation of this blockchain identity. + @discussion There are situations where this is nil as it is not yet known ; if the blockchain identity is being retrieved from L2 or if we are resyncing the chain. */ +@property (nullable, nonatomic, readonly) DSAssetLockTransaction *registrationAssetLockTransaction; +@property (nullable, nonatomic, readonly) NSArray *topUpAssetLockTransactions; +/*! @brief This is the hash of the transaction on L1 that has an output that is used to fund the creation of this blockchain identity. + @discussion There are situations where this is nil as it is not yet known ; if the blockchain identity is being retrieved from L2 or if we are resyncing the chain. */ +@property (nonatomic, readonly) UInt256 registrationAssetLockTransactionHash; +@property (nonatomic, readonly) NSMutableArray *topupAssetLockTransactionHashes; +/*! @brief In our system a contact is a vue on a blockchain identity for Dashpay. A blockchain identity is therefore represented by a contact that will have relationships in the system. This is in the default backgroundContext. */ +@property (nonatomic, readonly) DSDashpayUserEntity *matchingDashpayUserInViewContext; +/*! @brief This is the status of the registration of the identity. It starts off in an initial status, and ends in a confirmed status */ +@property (nonatomic, readonly) DIdentityRegistrationStatus *registrationStatus; +/*! @brief This is the localized status of the registration of the identity returned as a string. It starts off in an initial status, and ends in a confirmed status */ +@property (nonatomic, readonly) NSString *localizedRegistrationStatusString; +/*! @brief This is a convenience method that checks to see if registrationStatus is confirmed */ +@property (nonatomic, readonly, getter=isRegistered) BOOL registered; +/*! @brief DashpaySyncronizationBlock represents the last L1 block height for which Dashpay would be synchronized, if this isn't at the end of the chain then we need to query L2 to make sure we don't need to update our bloom filter */ +@property (nonatomic, readonly) uint32_t dashpaySyncronizationBlockHeight; +/*! @brief DashpaySyncronizationBlock represents the last L1 block hash for which Dashpay would be synchronized */ +@property (nonatomic, readonly) UInt256 dashpaySyncronizationBlockHash; + + +- (BOOL)isDefault; +// MARK: - Contracts + +- (void)fetchAndUpdateContract:(DPContract *)contract; + +// MARK: - Helpers + +- (DSDashpayUserEntity *)matchingDashpayUserInContext:(NSManagedObjectContext *)context; + +// MARK: - Identity + +- (void)registerOnNetwork:(DSIdentityRegistrationStep)steps + withFundingAccount:(DSAccount *)account + forTopupAmount:(uint64_t)topupDuffAmount + pinPrompt:(NSString *)prompt + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion; + +- (void)continueRegisteringOnNetwork:(DSIdentityRegistrationStep)steps + withFundingAccount:(DSAccount *)fundingAccount + forTopupAmount:(uint64_t)topupDuffAmount + pinPrompt:(NSString *)prompt + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion; + +- (void)continueRegisteringIdentityOnNetwork:(DSIdentityRegistrationStep)steps + stepsCompleted:(DSIdentityRegistrationStep)stepsAlreadyCompleted + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion; + +- (void)fetchIdentityNetworkStateInformationWithCompletion:(void (^)(BOOL success, BOOL found, NSError *error))completion; +- (void)fetchAllNetworkStateInformationWithCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion; + +- (void)createFundingPrivateKeyWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion; +- (void)createFundingPrivateKeyForInvitationWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion; +- (void)createAndPublishRegistrationTransitionWithCompletion:(void (^_Nullable)(BOOL success, NSError *_Nullable error))completion; + +- (void)createAndPublishTopUpTransitionForAmount:(uint64_t)amount + fundedByAccount:(DSAccount *)fundingAccount + pinPrompt:(NSString *)prompt + withCompletion:(void (^)(BOOL, NSError *_Nullable))completion; + +/*! @brief Register the blockchain identity to its wallet. This should only be done once on the creation of the blockchain identity. +*/ +- (void)registerInWallet; + +/*! @brief Unregister the blockchain identity from the wallet. This should only be used if the blockchain identity is not yet registered or if a progressive wallet wipe is happening. + @discussion When a blockchain identity is registered on the network it is automatically retrieved from the L1 chain on resync. If a client wallet wishes to change their default blockchain identity in a wallet it should be done by marking the default blockchain identity index in the wallet. Clients should not try to delete a registered blockchain identity from a wallet. + */ +- (BOOL)unregisterLocally; + +/*! @brief Register the blockchain identity to its wallet from a credit funding registration transaction. This should only be done once on the creation of the blockchain identity. + @param transaction The asset lock transaction used to initially fund the blockchain identity. +*/ +- (void)registerInWalletForAssetLockTransaction:(DSAssetLockTransaction *)transaction; + +// MARK: - Keys + +/*! @brief Register the blockchain identity to its wallet from a credit funding registration transaction. This should only be done once on the creation of the blockchain identity. +*/ + +- (void)generateIdentityExtendedPublicKeysWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL registered))completion; +- (BOOL)setExternalFundingPrivateKey:(DMaybeOpaqueKey *)privateKey; +- (BOOL)hasIdentityExtendedPublicKeys; +- (DIdentityKeyStatus *)statusOfKeyAtIndex:(NSUInteger)index; +- (DOpaqueKey *_Nullable)keyAtIndex:(NSUInteger)index; ++ (NSString *)localizedStatusOfKeyForIdentityKeyStatus:(DIdentityKeyStatus *)status; +- (NSString *)localizedStatusOfKeyAtIndex:(NSUInteger)index; +- (DMaybeOpaqueKey *_Nullable)createNewKeyOfType:(DKeyKind *)type + securityLevel:(DSecurityLevel *)security_level + purpose:(DPurpose *)purpose + saveKey:(BOOL)saveKey + returnIndex:(uint32_t *)rIndex; +- (BOOL)containsPublicKey:(DIdentityPublicKey *)identity_public_key; + +- (BOOL)activePrivateKeysAreLoadedWithFetchingError:(NSError **)error; +- (BOOL)verifyKeysForWallet:(DSWallet *)wallet; + +- (BOOL)containsTopupTransaction:(DSAssetLockTransaction *)transaction; + +- (NSString *)logPrefix; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSIdentity.m b/DashSync/shared/Models/Identity/DSIdentity.m new file mode 100644 index 000000000..e691b0c34 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSIdentity.m @@ -0,0 +1,2451 @@ +// +// DSIdentity.m +// DashSync +// +// Created by Sam Westrich on 7/26/18. +// +#import "DSIdentity.h" +#import "DPContract+Protected.h" +#import "DSAccount.h" +#import "DSAccountEntity+CoreDataClass.h" +#import "DSAssetLockDerivationPath.h" +#import "DSAssetLockTransaction.h" +#import "DSAuthenticationKeysDerivationPath.h" +#import "DSAuthenticationManager.h" +#import "DSBlockchainIdentityKeyPathEntity+CoreDataClass.h" +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChain+Transaction.h" +#import "DSChain+Wallet.h" +#import "DSChainEntity+CoreDataClass.h" +#import "DSChainManager.h" +#import "DSDashpayUserEntity+CoreDataClass.h" +#import "DSDerivationPathFactory.h" +#import "DSIdentitiesManager+Protected.h" +#import "DSIdentitiesManager+CoreData.h" +#import "DSIdentity+ContactRequest.h" +#import "DSIdentity+Profile.h" +#import "DSIdentity+Protected.h" +#import "DSIdentity+Username.h" +#import "DSInstantSendTransactionLock.h" +#import "DSInvitation+Protected.h" +#import "DSFriendRequestEntity+CoreDataClass.h" +#import "DSMerkleBlock.h" +#import "DSOptionsManager.h" +#import "DSTransactionHashEntity+CoreDataClass.h" +#import "DSWallet+Identity.h" +#import "NSData+Encryption.h" +#import "NSDate+Utils.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSIndexPath+Dash.h" +#import "NSManagedObject+Sugar.h" +#import "NSMutableData+Dash.h" +#import "NSObject+Notification.h" + +#define BLOCKCHAIN_USER_UNIQUE_IDENTIFIER_KEY @"BLOCKCHAIN_USER_UNIQUE_IDENTIFIER_KEY" +#define DEFAULT_FETCH_IDENTITY_RETRY_COUNT 5 + +#define ERROR_REGISTER_KEYS_BEFORE_IDENTITY [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity extended public keys need to be registered before you can register a identity."] +#define ERROR_FUNDING_TX_CREATION [NSError errorWithCode:500 localizedDescriptionKey:@"Funding transaction could not be created"] +#define ERROR_FUNDING_TX_SIGNING [NSError errorWithCode:500 localizedDescriptionKey:@"Transaction could not be signed"] +#define ERROR_FUNDING_TX_TIMEOUT [NSError errorWithCode:500 localizedDescriptionKey:@"Timeout while waiting for funding transaction to be accepted by network"] +#define ERROR_FUNDING_TX_ISD_TIMEOUT [NSError errorWithCode:500 localizedDescriptionKey:@"Timeout while waiting for funding transaction to acquire an instant send lock"] +#define ERROR_REG_TRANSITION [NSError errorWithCode:501 localizedDescriptionKey:@"Unable to register registration transition"] +#define ERROR_REG_TRANSITION_CREATION [NSError errorWithCode:501 localizedDescriptionKey:@"Unable to create registration transition"] +#define ERROR_ATTEMPT_QUERY_WITHOUT_KEYS [NSError errorWithCode:501 localizedDescriptionKey:@"Attempt to query DAPs for identity with no active keys"] +#define ERROR_NO_FUNDING_PRV_KEY [NSError errorWithCode:500 localizedDescriptionKey:@"The blockchain identity funding private key should be first created with createFundingPrivateKeyWithCompletion"] +#define ERROR_FUNDING_TX_NOT_MINED [NSError errorWithCode:500 localizedDescriptionKey:@"The registration credit funding transaction has not been mined yet and has no instant send lock"] +#define ERROR_NO_IDENTITY [NSError errorWithCode:500 localizedDescriptionKey:@"Platform returned no identity when one was expected"] + +typedef NS_ENUM(NSUInteger, DSIdentityKeyDictionary) { + DSIdentityKeyDictionary_Key = 0, + DSIdentityKeyDictionary_KeyType = 1, + DSIdentityKeyDictionary_KeyStatus = 2, + DSIdentityKeyDictionary_KeyLevel = 3, + DSIdentityKeyDictionary_KeyPurpose = 4, +}; + +NSString * DSRegistrationStepsDescription(DSIdentityRegistrationStep step) { + NSMutableArray *components = [NSMutableArray array]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_None)) + [components addObject:@"None"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_FundingTransactionCreation)) + [components addObject:@"FundingTransactionCreation"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_FundingTransactionAccepted)) + [components addObject:@"FundingTransactionAccepted"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_LocalInWalletPersistence)) + [components addObject:@"LocalInWalletPersistence"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_ProofAvailable)) + [components addObject:@"ProofAvailable"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_L1Steps)) + [components addObject:@"L1Steps"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_Identity)) + [components addObject:@"Identity"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_RegistrationSteps)) + [components addObject:@"RegistrationSteps"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_Username)) + [components addObject:@"Username"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_RegistrationStepsWithUsername)) + [components addObject:@"RegistrationStepsWithUsername"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_Profile)) + [components addObject:@"Profile"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_RegistrationStepsWithUsernameAndDashpayProfile)) + [components addObject:@"RegistrationStepsWithUsernameAndDashpayProfile"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_All)) + [components addObject:@"All"]; + if (FLAG_IS_SET(step, DSIdentityRegistrationStep_Cancelled)) + [components addObject:@"Cancelled"]; + return [components componentsJoinedByString:@" | "]; +} + +NSString * DSIdentityQueryStepsDescription(DSIdentityQueryStep step) { + NSMutableArray *components = [NSMutableArray array]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_None)) + [components addObject:@"None"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_Identity)) + [components addObject:@"Identity"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_Username)) + [components addObject:@"Username"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_Profile)) + [components addObject:@"Profile"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_IncomingContactRequests)) + [components addObject:@"IncomingContactRequests"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_OutgoingContactRequests)) + [components addObject:@"OutgoingContactRequests"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_ContactRequests)) + [components addObject:@"ContactRequests"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_AllForForeignIdentity)) + [components addObject:@"AllForForeignIdentity"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_AllForLocalIdentity)) + [components addObject:@"AllForLocalIdentity"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_NoIdentity)) + [components addObject:@"NoIdentity"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_BadQuery)) + [components addObject:@"BadQuery"]; + if (FLAG_IS_SET(step, DSIdentityQueryStep_Cancelled)) + [components addObject:@"Cancelled"]; + return [components componentsJoinedByString:@" | "]; +} + +@interface DSIdentity () + +@property (nonatomic, assign) UInt256 uniqueID; +@property (nonatomic, assign) BOOL isOutgoingInvitation; +@property (nonatomic, assign) BOOL isFromIncomingInvitation; +@property (nonatomic, assign) DSUTXO lockedOutpoint; +@property (nonatomic, assign) uint32_t index; + +@property (nonatomic, assign) uint64_t creditBalance; +@property (nonatomic, strong) DSDashpayUserEntity *matchingDashpayUserInViewContext; +@property (nonatomic, strong) DSDashpayUserEntity *matchingDashpayUserInPlatformContext; +@property (nonatomic, assign) DMaybeOpaqueKey *internalRegistrationFundingPrivateKey; +@property (nonatomic, assign) DMaybeOpaqueKey *internalTopupFundingPrivateKey; +@property (nonatomic, assign) UInt256 dashpaySyncronizationBlockHash; +@property (nonatomic, strong) DSAssetLockTransaction *registrationAssetLockTransaction; +@property (nonatomic, assign) uint64_t lastCheckedUsernamesTimestamp; +@property (nonatomic, assign) uint64_t lastCheckedProfileTimestamp; + +@end + +@implementation DSIdentity + +- (void)dealloc { + if (_internalRegistrationFundingPrivateKey != NULL) + DMaybeOpaqueKeyDtor(_internalRegistrationFundingPrivateKey); + if (_internalTopupFundingPrivateKey != NULL) + DMaybeOpaqueKeyDtor(_internalTopupFundingPrivateKey); + // TODO: identity_model dtor +// if [(_identity_model != NULL) +// TODO::// NO +} +// MARK: - Initialization + +- (instancetype)initWithUniqueId:(UInt256)uniqueId + isTransient:(BOOL)isTransient + onChain:(DSChain *)chain { + //this is the initialization of a non local identity + if (!(self = [super init])) return nil; + NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); + _uniqueID = uniqueId; + _isLocal = FALSE; + _isTransient = isTransient; + _keysCreated = 0; + _currentMainKeyIndex = 0; + _currentMainKeyType = DKeyKindECDSA(); + self.identity_model = dash_spv_platform_identity_model_IdentityModel_new(DIdentityRegistrationStatusRegistered()); + self.chain = chain; + return self; +} + +- (instancetype)initWithIdentityEntity:(DSBlockchainIdentityEntity *)entity { + if (!(self = [self initWithUniqueId:entity.uniqueID.UInt256 + isTransient:FALSE + onChain:entity.chain.chain])) return nil; + [self applyIdentityEntity:entity]; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet + withIdentityEntity:(DSBlockchainIdentityEntity *)entity { + if (!(self = [self initAtIndex:index + withLockedOutpoint:lockedOutpoint + inWallet:wallet])) return nil; + [self applyIdentityEntity:entity]; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withUniqueId:(UInt256)uniqueId + inWallet:(DSWallet *)wallet + withIdentityEntity:(DSBlockchainIdentityEntity *)entity { + if (!(self = [self initAtIndex:index + withUniqueId:uniqueId + inWallet:wallet])) return nil; + [self applyIdentityEntity:entity]; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet + withIdentityEntity:(DSBlockchainIdentityEntity *)entity + associatedToInvitation:(DSInvitation *)invitation { + if (!(self = [self initAtIndex:index + withLockedOutpoint:lockedOutpoint + inWallet:wallet])) return nil; + [self setAssociatedInvitation:invitation]; + [self applyIdentityEntity:entity]; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + inWallet:(DSWallet *)wallet { + //this is the creation of a new blockchain identity + NSParameterAssert(wallet); + if (!(self = [super init])) return nil; + self.wallet = wallet; + self.isLocal = YES; + self.isOutgoingInvitation = NO; + self.isTransient = FALSE; + _keysCreated = 0; + self.currentMainKeyIndex = 0; + self.currentMainKeyType = DKeyKindECDSA(); + self.index = index; + self.identity_model = dash_spv_platform_identity_model_IdentityModel_new(dash_spv_platform_identity_model_IdentityRegistrationStatus_Unknown_ctor()); + self.chain = wallet.chain; + return self; +} + +- (void)saveProfileTimestamp { + [self.platformContext performBlockAndWait:^{ + self.lastCheckedProfileTimestamp = [NSDate timeIntervalSince1970]; + //[self saveInContext:self.platformContext]; + }]; +} + +- (void)registerKeyFromKeyPathEntity:(DSBlockchainIdentityKeyPathEntity *)entity { + DKeyKind *keyType = DKeyKindFromIndex(entity.keyType); + DMaybeOpaqueKey *key = DMaybeOpaqueKeyWithPublicKeyData(keyType, slice_ctor(entity.publicKeyData)); + DSecurityLevel *level = DSecurityLevelFromIndex(entity.securityLevel); + DPurpose *purpose = DPurposeFromIndex(entity.purpose); + _keysCreated = MAX(self.keysCreated, entity.keyID + 1); + [self addKeyInfo:key->ok + type:keyType + securityLevel:level + purpose:purpose + status:DIdentityKeyStatusFromIndex(entity.keyStatus) + index:entity.keyID]; +} +- (void)applyIdentityEntity:(DSBlockchainIdentityEntity *)identityEntity { + [self applyUsernameEntitiesFromIdentityEntity:identityEntity]; + _creditBalance = identityEntity.creditBalance; + DIdentityModelSetStatus(self.identity_model, DIdentityRegistrationStatusFromIndex(identityEntity.registrationStatus)); + _lastCheckedProfileTimestamp = identityEntity.lastCheckedProfileTimestamp; + _lastCheckedUsernamesTimestamp = identityEntity.lastCheckedUsernamesTimestamp; + _lastCheckedIncomingContactsTimestamp = identityEntity.lastCheckedIncomingContactsTimestamp; + _lastCheckedOutgoingContactsTimestamp = identityEntity.lastCheckedOutgoingContactsTimestamp; + + self.dashpaySyncronizationBlockHash = identityEntity.dashpaySyncronizationBlockHash.UInt256; + for (DSBlockchainIdentityKeyPathEntity *keyPathEntity in identityEntity.keyPaths) { + NSIndexPath *keyIndexPath = (NSIndexPath *)[keyPathEntity path]; + DKeyKind *keyType = DKeyKindFromIndex(keyPathEntity.keyType); + BOOL added = NO; + if (keyIndexPath) { + DSecurityLevel *level = DSecurityLevelFromIndex(keyPathEntity.securityLevel); + DPurpose *purpose = DPurposeFromIndex(keyPathEntity.purpose); + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:keyType]; + NSIndexPath *nonhardenedPath = [keyIndexPath softenAllItems]; + NSIndexPath *hardenedPath = [nonhardenedPath hardenAllItems]; + DMaybeOpaqueKey *key = [derivationPath publicKeyAtIndexPath:hardenedPath]; + if (key->ok) { + uint32_t index = (uint32_t)[nonhardenedPath indexAtPosition:[nonhardenedPath length] - 1]; + _keysCreated = MAX(self.keysCreated, index + 1); + DKeyInfo *key_info = dash_spv_platform_identity_model_KeyInfo_ctor(key->ok, keyType, DIdentityKeyStatusFromIndex(keyPathEntity.keyStatus), level, purpose); + dash_spv_platform_identity_model_IdentityModel_add_key_info(self.identity_model, index, key_info); + added = YES; + } + } + if (!added) + [self registerKeyFromKeyPathEntity:keyPathEntity]; + } + if (self.isLocal || self.isOutgoingInvitation) { + if (identityEntity.registrationFundingTransaction) { + self.registrationAssetLockTransactionHash = identityEntity.registrationFundingTransaction.transactionHash.txHash.UInt256; + DSLog(@"%@: AssetLockTX: Entity Attached: txHash: %@: entity: %@", self.logPrefix, uint256_hex(self.registrationAssetLockTransactionHash), identityEntity.registrationFundingTransaction); + } else { + NSData *transactionHashData = uint256_data(uint256_reverse(self.lockedOutpoint.hash)); + DSLog(@"%@: AssetLockTX: Load: lockedOutpoint: %@: %lu %@", self.logPrefix, uint256_hex(self.lockedOutpoint.hash), self.lockedOutpoint.n, transactionHashData.hexString); + DSAssetLockTransactionEntity *assetLockEntity = [DSAssetLockTransactionEntity anyObjectInContext:identityEntity.managedObjectContext matching:@"transactionHash.txHash == %@", transactionHashData]; + if (assetLockEntity) { + self.registrationAssetLockTransactionHash = assetLockEntity.transactionHash.txHash.UInt256; + DSLog(@"%@: AssetLockTX: Found: txHash: %@: entity: %@", self.logPrefix, uint256_hex(self.registrationAssetLockTransactionHash), assetLockEntity); + DSAssetLockTransaction *registrationAssetLockTransaction = (DSAssetLockTransaction *)[assetLockEntity transactionForChain:self.chain]; + BOOL correctIndex = self.isOutgoingInvitation ? + [registrationAssetLockTransaction checkInvitationDerivationPathIndexForWallet:self.wallet isIndex:self.index] : + [registrationAssetLockTransaction checkDerivationPathIndexForWallet:self.wallet isIndex:self.index]; + if (!correctIndex) { + DSLog(@"%@: AssetLockTX: IncorrectIndex %u (%@)", self.logPrefix, self.index, registrationAssetLockTransaction.toData.hexString); + //NSAssert(FALSE, @"We should implement this"); + } + } + } + } +} + + +- (void)setAssociatedInvitation:(DSInvitation *)associatedInvitation { + _associatedInvitation = associatedInvitation; + // It was created locally, we are sending the invite + if (associatedInvitation.createdLocally) { + self.isOutgoingInvitation = TRUE; + self.isFromIncomingInvitation = FALSE; + self.isLocal = FALSE; + } else { + // It was created on another device, we are receiving the invite + self.isOutgoingInvitation = FALSE; + self.isFromIncomingInvitation = TRUE; + self.isLocal = TRUE; + } +} + +- (instancetype)initAtIndex:(uint32_t)index + withUniqueId:(UInt256)uniqueId + inWallet:(DSWallet *)wallet { + if (!(self = [self initAtIndex:index inWallet:wallet])) return nil; + self.uniqueID = uniqueId; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet { + if (!(self = [self initAtIndex:index inWallet:wallet])) return nil; + NSAssert(dsutxo_hash_is_not_zero(lockedOutpoint), @"utxo must not be nil"); + + self.lockedOutpoint = lockedOutpoint; + self.uniqueID = [dsutxo_data(lockedOutpoint) SHA256_2]; + DSLog(@"%@: initAtIndex: %u lockedOutpoint: %@: %lu", self.logPrefix, index, uint256_hex(lockedOutpoint.hash), lockedOutpoint.n); + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withAssetLockTransaction:(DSAssetLockTransaction *)transaction + inWallet:(DSWallet *)wallet { + NSParameterAssert(wallet); + NSAssert(index != UINT32_MAX, @"index must be found"); + if (!(self = [self initAtIndex:index withLockedOutpoint:transaction.lockedOutpoint inWallet:wallet])) return nil; + self.registrationAssetLockTransaction = transaction; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + uniqueId:(UInt256)uniqueId + inWallet:(DSWallet *)wallet { + NSParameterAssert(wallet); + if (!(self = [super init])) return nil; + self.wallet = wallet; + self.isLocal = YES; + self.isOutgoingInvitation = NO; + self.isTransient = FALSE; + _keysCreated = 0; + self.currentMainKeyIndex = 0; + self.currentMainKeyType = DKeyKindECDSA(); + self.uniqueID = uniqueId; + self.identity_model = dash_spv_platform_identity_model_IdentityModel_new(DIdentityRegistrationStatusRegistered()); + self.chain = wallet.chain; + self.index = index; + return self; +} + +- (dispatch_queue_t)identityQueue { + if (_identityQueue) return _identityQueue; + _identityQueue = self.chain.chainManager.identitiesManager.identityQueue; + return _identityQueue; +} + +// MARK: - Full Registration agglomerate + +- (DSIdentityRegistrationStep)stepsCompleted { + DSIdentityRegistrationStep stepsCompleted = DSIdentityRegistrationStep_None; + if (self.isRegistered) { + stepsCompleted = DSIdentityRegistrationStep_RegistrationSteps; + if (dash_spv_platform_identity_model_IdentityModel_confirmed_username_full_paths_count(self.identity_model)) + stepsCompleted |= DSIdentityRegistrationStep_Username; + } else if (self.registrationAssetLockTransaction) { + stepsCompleted |= DSIdentityRegistrationStep_FundingTransactionCreation; + DSAccount *account = [self.chain firstAccountThatCanContainTransaction:self.registrationAssetLockTransaction]; + if (self.registrationAssetLockTransaction.blockHeight != TX_UNCONFIRMED || [account transactionIsVerified:self.registrationAssetLockTransaction]) + stepsCompleted |= DSIdentityRegistrationStep_FundingTransactionAccepted; + if ([self isRegisteredInWallet]) + stepsCompleted |= DSIdentityRegistrationStep_LocalInWalletPersistence; + if (self.registrationAssetLockTransaction.instantSendLockAwaitingProcessing) + stepsCompleted |= DSIdentityRegistrationStep_ProofAvailable; + } + return stepsCompleted; +} + +- (void)continueRegisteringProfileOnNetwork:(DSIdentityRegistrationStep)steps + stepsCompleted:(DSIdentityRegistrationStep)stepsAlreadyCompleted + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *error))completion { + __block DSIdentityRegistrationStep stepsCompleted = stepsAlreadyCompleted; + if (!(steps & DSIdentityRegistrationStep_Profile)) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); + return; + } + //todo:we need to still do profile + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); +} + +- (void)continueRegisteringUsernamesOnNetwork:(DSIdentityRegistrationStep)steps + stepsCompleted:(DSIdentityRegistrationStep)stepsAlreadyCompleted + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion { + __block DSIdentityRegistrationStep stepsCompleted = stepsAlreadyCompleted; + if (!(steps & DSIdentityRegistrationStep_Username)) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); + return; + } + [self registerUsernamesWithCompletion:^(BOOL success, NSArray *errors) { + if (!success) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, errors); }); + return; + } + if (stepCompletion) dispatch_async(dispatch_get_main_queue(), ^{ stepCompletion(DSIdentityRegistrationStep_Username); }); + stepsCompleted |= DSIdentityRegistrationStep_Username; + [self continueRegisteringProfileOnNetwork:steps + stepsCompleted:stepsCompleted + stepCompletion:stepCompletion + completion:completion]; + }]; +} + +- (void)continueRegisteringIdentityOnNetwork:(DSIdentityRegistrationStep)steps + stepsCompleted:(DSIdentityRegistrationStep)stepsAlreadyCompleted + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion { + __block DSIdentityRegistrationStep stepsCompleted = stepsAlreadyCompleted; + if (!(steps & DSIdentityRegistrationStep_Identity)) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); + return; + } + [self createAndPublishRegistrationTransitionWithCompletion:^(BOOL success, NSError *_Nullable error) { + if (error) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, @[error]); }); + return; + } + if (stepCompletion) dispatch_async(dispatch_get_main_queue(), ^{ stepCompletion(DSIdentityRegistrationStep_Identity); }); + stepsCompleted |= DSIdentityRegistrationStep_Identity; + [self continueRegisteringUsernamesOnNetwork:steps + stepsCompleted:stepsCompleted + stepCompletion:stepCompletion + completion:completion]; + }]; +} + +- (void)continueRegisteringOnNetwork:(DSIdentityRegistrationStep)steps + withFundingAccount:(DSAccount *)fundingAccount + forTopupAmount:(uint64_t)topupDuffAmount + pinPrompt:(NSString *)prompt + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion { + [self continueRegisteringOnNetwork:steps + withFundingAccount:fundingAccount + forTopupAmount:topupDuffAmount + pinPrompt:prompt + inContext:self.platformContext + stepCompletion:stepCompletion + completion:completion]; +} + +- (void)continueRegisteringOnNetwork:(DSIdentityRegistrationStep)steps + withFundingAccount:(DSAccount *)fundingAccount + forTopupAmount:(uint64_t)topupDuffAmount + pinPrompt:(NSString *)prompt + inContext:(NSManagedObjectContext *)context + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion { + if (!self.registrationAssetLockTransaction) { + [self registerOnNetwork:steps + withFundingAccount:fundingAccount + forTopupAmount:topupDuffAmount + pinPrompt:prompt + stepCompletion:stepCompletion + completion:completion]; + } else if (dash_spv_platform_identity_model_IdentityModel_is_registered(self.identity_model)) { + [self continueRegisteringIdentityOnNetwork:steps + stepsCompleted:DSIdentityRegistrationStep_L1Steps + stepCompletion:stepCompletion + completion:completion]; + } else if (dash_spv_platform_identity_model_IdentityModel_unregistered_username_full_paths_count(self.identity_model)) { + [self continueRegisteringUsernamesOnNetwork:steps + stepsCompleted:DSIdentityRegistrationStep_L1Steps | DSIdentityRegistrationStep_Identity + stepCompletion:stepCompletion + completion:completion]; + } else if ([self matchingDashpayUserInContext:context].remoteProfileDocumentRevision < 1) { + [self continueRegisteringProfileOnNetwork:steps + stepsCompleted:DSIdentityRegistrationStep_L1Steps | DSIdentityRegistrationStep_Identity + stepCompletion:stepCompletion + completion:completion]; + } +} + +- (void)submitAssetLockTransactionAndWaitForInstantSendLock:(DSAssetLockTransaction *)assetLockTransaction + withFundingAccount:(DSAccount *)fundingAccount + registrator:(BOOL (^_Nullable)(DSAssetLockTransaction *assetLockTransaction))registrator + pinPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL success, BOOL cancelled, NSError *error))completion { + [fundingAccount signTransaction:assetLockTransaction + withPrompt:prompt + completion:^(BOOL signedTransaction, BOOL cancelled) { + if (!signedTransaction) { + if (completion) + dispatch_async(dispatch_get_main_queue(), ^{ + completion(NO, cancelled, cancelled ? nil : ERROR_FUNDING_TX_SIGNING); + }); + return; + } + BOOL canContinue = registrator(assetLockTransaction); + if (!canContinue) + return; + + }]; +} + +- (void)publishTransactionAndWait:(DSAssetLockTransaction *)transaction + completion:(void (^_Nullable)(BOOL published, DSInstantSendTransactionLock *_Nullable instantSendLock, NSError *_Nullable error))completion { + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block BOOL transactionSuccessfullyPublished = FALSE; + __block DSInstantSendTransactionLock *instantSendLock = nil; + __block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil + queue:nil + usingBlock:^(NSNotification *note) { + DSTransaction *tx = [note.userInfo objectForKey:DSTransactionManagerNotificationTransactionKey]; + if ([tx isEqual:transaction]) { + NSDictionary *changes = [note.userInfo objectForKey:DSTransactionManagerNotificationTransactionChangesKey]; + if (changes) { + NSNumber *accepted = changes[DSTransactionManagerNotificationTransactionAcceptedStatusKey]; + NSNumber *lockVerified = changes[DSTransactionManagerNotificationInstantSendTransactionLockVerifiedKey]; + DSInstantSendTransactionLock *lock = changes[DSTransactionManagerNotificationInstantSendTransactionLockKey]; + if ([lockVerified boolValue] && lock != nil) { + instantSendLock = lock; + transactionSuccessfullyPublished = TRUE; + dispatch_semaphore_signal(sem); + } else if ([accepted boolValue]) { + transactionSuccessfullyPublished = TRUE; + } + } + } + }]; + [self.chain.chainManager.transactionManager publishTransaction:transaction completion:^(NSError *_Nullable error) { + if (error) { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + completion(NO, nil, error); + return; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_SEC)); + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + completion(transactionSuccessfullyPublished, instantSendLock, nil); + }); + }]; + +} + +- (void)registerOnNetwork:(DSIdentityRegistrationStep)steps + withFundingAccount:(DSAccount *)fundingAccount + forTopupAmount:(uint64_t)topupDuffAmount + pinPrompt:(NSString *)prompt + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion { + DSLog(@"%@: registerOnNetwork: %@", self.logPrefix, DSRegistrationStepsDescription(steps)); + __block DSIdentityRegistrationStep stepsCompleted = DSIdentityRegistrationStep_None; + if (![self hasIdentityExtendedPublicKeys]) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, @[ERROR_REGISTER_KEYS_BEFORE_IDENTITY]); }); + return; + } + if (!(steps & DSIdentityRegistrationStep_FundingTransactionCreation)) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); + return; + } + NSString *assetLockRegistrationAddress = [self registrationFundingAddress]; + + + + DSAssetLockTransaction *assetLockTransaction = [fundingAccount assetLockTransactionFor:topupDuffAmount + to:assetLockRegistrationAddress + withFee:YES]; + if (!assetLockTransaction) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, @[ERROR_FUNDING_TX_CREATION]); }); + return; + } + [fundingAccount signTransaction:assetLockTransaction + withPrompt:prompt + completion:^(BOOL signedTransaction, BOOL cancelled) { + if (!signedTransaction) { + if (completion) + dispatch_async(dispatch_get_main_queue(), ^{ + if (cancelled) stepsCompleted |= DSIdentityRegistrationStep_Cancelled; + completion(stepsCompleted, cancelled ? nil : @[ERROR_FUNDING_TX_SIGNING]); + }); + return; + } + if (stepCompletion) dispatch_async(dispatch_get_main_queue(), ^{ stepCompletion(DSIdentityRegistrationStep_FundingTransactionCreation); }); + stepsCompleted |= DSIdentityRegistrationStep_FundingTransactionCreation; + + //In wallet registration occurs now + + if (!(steps & DSIdentityRegistrationStep_LocalInWalletPersistence)) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); + return; + } + if (self.isOutgoingInvitation) { + [self.associatedInvitation registerInWalletForAssetLockTransaction:assetLockTransaction]; + } else { + [self registerInWalletForAssetLockTransaction:assetLockTransaction]; + } + if (stepCompletion) dispatch_async(dispatch_get_main_queue(), ^{ stepCompletion(DSIdentityRegistrationStep_LocalInWalletPersistence); }); + stepsCompleted |= DSIdentityRegistrationStep_LocalInWalletPersistence; + if (!(steps & DSIdentityRegistrationStep_FundingTransactionAccepted)) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, nil); }); + return; + } + + [self publishTransactionAndWait:assetLockTransaction completion:^(BOOL published, DSInstantSendTransactionLock *_Nullable instantSendLock, NSError *_Nullable error) { + if (!published) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, @[ERROR_FUNDING_TX_TIMEOUT]); }); + return; + } + if (stepCompletion) dispatch_async(dispatch_get_main_queue(), ^{ stepCompletion(DSIdentityRegistrationStep_FundingTransactionAccepted); }); + stepsCompleted |= DSIdentityRegistrationStep_FundingTransactionAccepted; + if (!instantSendLock) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(stepsCompleted, @[ERROR_FUNDING_TX_ISD_TIMEOUT]); }); + return; + } + if (stepCompletion) dispatch_async(dispatch_get_main_queue(), ^{ stepCompletion(DSIdentityRegistrationStep_ProofAvailable); }); + stepsCompleted |= DSIdentityRegistrationStep_ProofAvailable; + [self continueRegisteringIdentityOnNetwork:steps + stepsCompleted:stepsCompleted + stepCompletion:stepCompletion + completion:completion]; + + }]; + }]; +} + +// MARK: - Local Registration and Generation + +- (BOOL)hasIdentityExtendedPublicKeys { + NSAssert(_isLocal || _isOutgoingInvitation, @"This should not be performed on a non local identity (but can be done for an invitation)"); + if (!_isLocal && !_isOutgoingInvitation) return FALSE; + if (_isLocal) { + DSAuthenticationKeysDerivationPath *derivationPathBLS = [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:self.wallet]; + DSAuthenticationKeysDerivationPath *derivationPathECDSA = [[DSDerivationPathFactory sharedInstance] identityECDSAKeysDerivationPathForWallet:self.wallet]; + DSAssetLockDerivationPath *derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:self.wallet]; + DSAssetLockDerivationPath *derivationPathTopupFunding = [[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:self.wallet]; + return [derivationPathBLS hasExtendedPublicKey] + && [derivationPathECDSA hasExtendedPublicKey] + && [derivationPathRegistrationFunding hasExtendedPublicKey] + && [derivationPathTopupFunding hasExtendedPublicKey]; + } + if (_isOutgoingInvitation) { + DSAssetLockDerivationPath *derivationPathInvitationFunding = [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:self.wallet]; + return [derivationPathInvitationFunding hasExtendedPublicKey]; + } + return NO; +} + +- (void)generateIdentityExtendedPublicKeysWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL registered))completion { + NSAssert(_isLocal || _isOutgoingInvitation, @"This should not be performed on a non local identity (but can be done for an invitation)"); + if (!_isLocal && !_isOutgoingInvitation) return; + if ([self hasIdentityExtendedPublicKeys]) { + if (completion) completion(YES); + return; + } + [[DSAuthenticationManager sharedInstance] seedWithPrompt:prompt + forWallet:self.wallet + forAmount:0 + forceAuthentication:NO + completion:^(NSData *_Nullable seed, BOOL cancelled) { + if (!seed) { + if (completion) completion(NO); + return; + } + if (self->_isLocal) { + DSAuthenticationKeysDerivationPath *derivationPathBLS = [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:self.wallet]; + DSAuthenticationKeysDerivationPath *derivationPathECDSA = [[DSDerivationPathFactory sharedInstance] identityECDSAKeysDerivationPathForWallet:self.wallet]; + + [derivationPathBLS generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; + [derivationPathECDSA generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; + if (!self->_isFromIncomingInvitation) { + DSAssetLockDerivationPath *derivationPathRegistrationFunding = [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:self.wallet]; + DSAssetLockDerivationPath *derivationPathTopupFunding = [[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:self.wallet]; + [derivationPathRegistrationFunding generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; + [derivationPathTopupFunding generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; + } + } + if (self->_isOutgoingInvitation) { + DSAssetLockDerivationPath *derivationPathInvitationFunding = [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:self.wallet]; + [derivationPathInvitationFunding generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.wallet.uniqueIDString]; + } + if (completion) completion(YES); + }]; +} + +- (void)registerInWalletForAssetLockTransaction:(DSAssetLockTransaction *)transaction { + NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); + if (!_isLocal) return; + self.registrationAssetLockTransactionHash = transaction.txHash; + DSUTXO lockedOutpoint = transaction.lockedOutpoint; + UInt256 creditBurnIdentityIdentifier = transaction.creditBurnIdentityIdentifier; + DSLog(@"%@: registerInWalletForAssetLockTransaction: txHash: %@: creditBurnIdentityID: %@, creditBurnPublicKeyHash: %@, lockedOutpoint: %@: %lu", self.logPrefix, uint256_hex(transaction.txHash), uint256_hex(creditBurnIdentityIdentifier), uint160_hex(transaction.creditBurnPublicKeyHash), uint256_hex(lockedOutpoint.hash), lockedOutpoint.n); + self.lockedOutpoint = lockedOutpoint; + [self registerInWalletForIdentityUniqueId:creditBurnIdentityIdentifier]; + //we need to also set the address of the funding transaction to being used so future identities past the initial gap limit are found + [transaction markAddressAsUsedInWallet:self.wallet]; +} + +- (void)registerInWalletForAssetLockTopupTransaction:(DSAssetLockTransaction *)transaction { + NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); + if (!_isLocal) return; + [self.topupAssetLockTransactionHashes addObject:uint256_data(transaction.txHash)]; + + DSUTXO lockedOutpoint = transaction.lockedOutpoint; + UInt256 creditBurnIdentityIdentifier = transaction.creditBurnIdentityIdentifier; + DSLog(@"%@: registerInWalletForAssetLockTopupTransaction: txHash: %@: creditBurnIdentityID: %@, creditBurnPublicKeyHash: %@, lockedOutpoint: %@: %lu", self.logPrefix, uint256_hex(transaction.txHash), uint256_hex(creditBurnIdentityIdentifier), uint160_hex(transaction.creditBurnPublicKeyHash), uint256_hex(lockedOutpoint.hash), lockedOutpoint.n); +// self.lockedOutpoint = lockedOutpoint; + [self registerInWalletForIdentityUniqueId:creditBurnIdentityIdentifier]; + //we need to also set the address of the funding transaction to being used so future identities past the initial gap limit are found + [transaction markAddressAsUsedInWallet:self.wallet]; +} + +- (void)registerInWalletForIdentityUniqueId:(UInt256)identityUniqueId { + NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); + if (!_isLocal) return; + self.uniqueID = identityUniqueId; + [self registerInWallet]; +} + +- (BOOL)isRegisteredInWallet { + NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); + if (!_isLocal) return FALSE; + if (!self.wallet) return FALSE; + return [self.wallet containsIdentity:self]; +} + +- (void)registerInWallet { + NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); + if (!_isLocal) return; + [self.wallet registerIdentity:self]; + [self saveInitial]; +} + +- (BOOL)unregisterLocally { + NSAssert(_isLocal, @"This should not be performed on a non local blockchain identity"); + if (!_isLocal) return FALSE; + if (self.isRegistered) return FALSE; //if it is already registered we can not unregister it from the wallet + [self.wallet unregisterIdentity:self]; + [self deletePersistentObjectAndSave:YES inContext:self.platformContext]; + return TRUE; +} + +- (void)setInvitationUniqueId:(UInt256)uniqueId { + NSAssert(_isOutgoingInvitation, @"This can only be done on an invitation"); + if (!_isOutgoingInvitation) return; + self.uniqueID = uniqueId; +} + +- (void)setInvitationAssetLockTransaction:(DSAssetLockTransaction *)transaction { + NSParameterAssert(transaction); + NSAssert(_isOutgoingInvitation, @"This can only be done on an invitation"); + if (!_isOutgoingInvitation) return; + self.registrationAssetLockTransaction = transaction; + self.lockedOutpoint = transaction.lockedOutpoint; + +} + +// MARK: - Read Only Property Helpers + +- (BOOL)isActive { + if (self.isLocal) { + if (!self.wallet) return NO; + return self.wallet.identities[self.uniqueIDData] != nil; + } else { + return [self.chain.chainManager.identitiesManager foreignIdentityWithUniqueId:self.uniqueID] != nil; + } +} + +- (DSAssetLockTransaction *)registrationAssetLockTransaction { + if (!_registrationAssetLockTransaction) { + _registrationAssetLockTransaction = (DSAssetLockTransaction *)[self.chain transactionForHash:self.registrationAssetLockTransactionHash]; + } + return _registrationAssetLockTransaction; +} + +- (NSData *)uniqueIDData { + return uint256_data(self.uniqueID); +} + +- (NSData *)lockedOutpointData { + return dsutxo_data(self.lockedOutpoint); +} + +- (NSString *)currentDashpayUsername { + return [self.dashpayUsernames firstObject]; +} + +- (NSArray *)derivationPaths { + if (!_isLocal) return nil; + return [[DSDerivationPathFactory sharedInstance] unloadedSpecializedDerivationPathsForWallet:self.wallet]; +} + +- (NSString *)uniqueIdString { + return [uint256_data(self.uniqueID) base58String]; +} + +- (dispatch_queue_t)networkingQueue { + return self.chain.networkingQueue; +} + +- (NSManagedObjectContext *)platformContext { + // NSAssert(![NSThread isMainThread], @"We should not be on main thread"); + return [NSManagedObjectContext platformContext]; +} + +- (DSIdentitiesManager *)identitiesManager { + return self.chain.chainManager.identitiesManager; +} + +// ECDSA +- (DMaybeOpaqueKey *)registrationFundingPrivateKey { + return self.internalRegistrationFundingPrivateKey; +} +- (DMaybeOpaqueKey *)topupFundingPrivateKey { + return self.internalTopupFundingPrivateKey; +} + +- (void)setDashpaySyncronizationBlockHash:(UInt256)dashpaySyncronizationBlockHash { + _dashpaySyncronizationBlockHash = dashpaySyncronizationBlockHash; + if (uint256_is_zero(_dashpaySyncronizationBlockHash)) { + _dashpaySyncronizationBlockHeight = 0; + } else { + _dashpaySyncronizationBlockHeight = [self.chain heightForBlockHash:_dashpaySyncronizationBlockHash]; + if (_dashpaySyncronizationBlockHeight == UINT32_MAX) { + _dashpaySyncronizationBlockHeight = 0; + } + } +} + + +// MARK: - Keys + +- (BOOL)createFundingPrivateKeyWithSeed:(NSData *)seed + isForInvitation:(BOOL)isForInvitation { + DSAssetLockDerivationPath *path = isForInvitation ? + [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:self.wallet] : + [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:self.wallet]; + self.internalRegistrationFundingPrivateKey = [path privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.index] fromSeed:seed]; + BOOL ok = self.internalRegistrationFundingPrivateKey; + return ok; +} +- (BOOL)createTopupFundingPrivateKeyWithSeed:(NSData *)seed { + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:self.wallet]; + self.internalTopupFundingPrivateKey = [path privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.index] fromSeed:seed]; + BOOL ok = self.internalTopupFundingPrivateKey; + return ok; +} + +- (BOOL)setExternalFundingPrivateKey:(DMaybeOpaqueKey *)privateKey { + if (!self.isFromIncomingInvitation) return FALSE; + self.internalRegistrationFundingPrivateKey = privateKey; + return self.internalRegistrationFundingPrivateKey; +} + +- (void)createFundingPrivateKeyForInvitationWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion { + [self createFundingPrivateKeyWithPrompt:prompt + isForInvitation:YES + completion:completion]; +} + +- (void)createFundingPrivateKeyWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion { + [self createFundingPrivateKeyWithPrompt:prompt + isForInvitation:NO + completion:completion]; +} + +- (void)createFundingPrivateKeyWithPrompt:(NSString *)prompt + isForInvitation:(BOOL)isForInvitation + completion:(void (^_Nullable)(BOOL success, BOOL cancelled))completion { + dispatch_async(dispatch_get_main_queue(), ^{ + [[DSAuthenticationManager sharedInstance] seedWithPrompt:prompt + forWallet:self.wallet + forAmount:0 + forceAuthentication:NO + completion:^(NSData *_Nullable seed, BOOL cancelled) { + if (!seed) { + if (completion) completion(NO, cancelled); + return; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + BOOL success = [self createFundingPrivateKeyWithSeed:seed isForInvitation:isForInvitation]; + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(success, NO); }); + }); + }]; + }); +} + +- (BOOL)activePrivateKeysAreLoadedWithFetchingError:(NSError **)error { + BOOL loaded = YES; + DKeyInfoDictionaries *key_infos = DGetRegisteredKeyInfoDictionaries(self.identity_model); + for (uint32_t index = 0; index < key_infos->count; index++) { + DKeyInfo *key_info = key_infos->values[index]; + loaded &= !_isLocal ? NO : hasKeychainData([self identifierForKeyAtPath:[self indexPathForIndex:key_infos->keys[index]] fromDerivationPath:[self derivationPathForType:key_info->key_type]], error); + if (*error) { + DKeyInfoDictionariesDtor(key_infos); + return NO; + } + } + DKeyInfoDictionariesDtor(key_infos); + return loaded; +} + +- (uintptr_t)activeKeyCount { + return dash_spv_platform_identity_model_IdentityModel_active_key_count(self.identity_model); +} + +- (uintptr_t)totalKeyCount { + return dash_spv_platform_identity_model_IdentityModel_total_key_count(self.identity_model); +} + +- (BOOL)verifyKeysForWallet:(DSWallet *)wallet { + DSWallet *originalWallet = self.wallet; + self.wallet = wallet; + DKeyInfoDictionaries *key_info_dictionaries = DGetKeyInfoDictionaries(self.identity_model); + for (uint32_t index = 0; index < key_info_dictionaries->count; index++) { + DKeyInfo *key_info = key_info_dictionaries->values[index]; + if (!key_info->key) { + self.wallet = originalWallet; + DKeyInfoDictionariesDtor(key_info_dictionaries); + return NO; + } + DOpaqueKey *key = [self keyAtIndex:index]; + DKeyKind *key_kind = key_info->key_type; + BOOL hasSameKind = DOpaqueKeyHasKind(key, key_kind); + if (!hasSameKind) { + self.wallet = originalWallet; + DKeyInfoDictionariesDtor(key_info_dictionaries); + return NO; + } + DMaybeOpaqueKey *derivedKey = [self publicKeyAtIndex:index ofType:key_kind]; + if (!derivedKey || !derivedKey->ok) { + DKeyInfoDictionariesDtor(key_info_dictionaries); + return NO; + } + BOOL isEqual = [DSKeyManager keysPublicKeyDataIsEqual:derivedKey->ok key2:key]; + DMaybeOpaqueKeyDtor(derivedKey); + if (!isEqual) { + self.wallet = originalWallet; + DKeyInfoDictionariesDtor(key_info_dictionaries); + return NO; + } + } + DKeyInfoDictionariesDtor(key_info_dictionaries); + return TRUE; +} + +- (DIdentityKeyStatus *)statusOfKeyAtIndex:(NSUInteger)index { + return dash_spv_platform_identity_model_IdentityModel_status_of_key_at_index(self.identity_model, (uint32_t) index); +} + +- (DOpaqueKey *_Nullable)keyAtIndex:(NSUInteger)index { + return dash_spv_platform_identity_model_IdentityModel_key_at_index(self.identity_model, (uint32_t) index); +} + +- (NSString *)localizedStatusOfKeyAtIndex:(NSUInteger)index { + return [[self class] localizedStatusOfKeyForIdentityKeyStatus:[self statusOfKeyAtIndex:index]]; +} + ++ (NSString *)localizedStatusOfKeyForIdentityKeyStatus:(DIdentityKeyStatus *)status { + char *str = dash_spv_platform_identity_model_IdentityKeyStatus_string(status); + char *desc = dash_spv_platform_identity_model_IdentityKeyStatus_string_description(status); + NSString *localizedStatus = DSLocalizedString(NSStringFromPtr(str), NSStringFromPtr(desc)); + DCharDtor(str); + DCharDtor(desc); + return localizedStatus; +} + +- (DSAuthenticationKeysDerivationPath *)derivationPathForType:(DKeyKind *)type { + if (!_isLocal) return nil; + // TODO: ed25519 + bls basic + int16_t index = DKeyKindIndex(type); + switch (index) { + case dash_spv_crypto_keys_key_KeyKind_ECDSA: + return [[DSDerivationPathFactory sharedInstance] identityECDSAKeysDerivationPathForWallet:self.wallet]; + case dash_spv_crypto_keys_key_KeyKind_BLS: + case dash_spv_crypto_keys_key_KeyKind_BLSBasic: + return [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:self.wallet]; + default: + return nil; + } +} + +- (NSIndexPath *)indexPathForIndex:(uint32_t)index { + const NSUInteger indexes[] = {_index | BIP32_HARD, index | BIP32_HARD}; + NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; + return indexPath; +} + +- (DMaybeOpaqueKey *)privateKeyAtIndex:(uint32_t)index ofType:(DKeyKind *)type { + if (!_isLocal) return nil; + NSIndexPath *indexPath = [self indexPathForIndex:index]; + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; + NSError *error = nil; + NSData *keySecret = getKeychainData([self identifierForKeyAtPath:indexPath fromDerivationPath:derivationPath], &error); + NSAssert(keySecret, @"This should be present"); + if (!keySecret || error) return nil; + return [DSKeyManager keyWithPrivateKeyData:keySecret ofType:type]; +} + +- (DMaybeOpaqueKey *)derivePrivateKeyAtIndexPath:(NSIndexPath *)indexPath ofType:(DKeyKind *)type { + if (!_isLocal) return nil; + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; + return [derivationPath privateKeyAtIndexPath:[indexPath hardenAllItems]]; +} + +- (DMaybeOpaqueKey *_Nullable)publicKeyAtIndex:(uint32_t)index ofType:(DKeyKind *)type { + if (!_isLocal) return nil; + NSIndexPath *hardenedIndexPath = [self indexPathForIndex:index]; + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; + return [derivationPath publicKeyAtIndexPath:hardenedIndexPath]; +} + +- (DMaybeOpaqueKey *)createNewKeyOfType:(DKeyKind *)type + securityLevel:(DSecurityLevel *)security_level + purpose:(DPurpose *)purpose + saveKey:(BOOL)saveKey + returnIndex:(uint32_t *)rIndex { + if (!_isLocal) return nil; + uint32_t keyIndex = self.keysCreated; + NSIndexPath *hardenedIndexPath = [self indexPathForIndex:keyIndex]; + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; + DMaybeOpaqueKey *publicKey = [derivationPath publicKeyAtIndexPath:hardenedIndexPath]; + NSAssert([derivationPath hasExtendedPrivateKey], @"The derivation path should have an extended private key"); + DMaybeOpaqueKey *privateKey = [derivationPath privateKeyAtIndexPath:hardenedIndexPath]; + NSAssert(privateKey && privateKey->ok, @"The private key should have been derived"); + NSAssert([DSKeyManager keysPublicKeyDataIsEqual:publicKey->ok key2:privateKey->ok], @"These should be equal"); + _keysCreated++; + if (rIndex) + *rIndex = keyIndex; + [self addKeyInfo:publicKey->ok + type:type + securityLevel:security_level + purpose:purpose + status:dash_spv_platform_identity_model_IdentityKeyStatus_Registering_ctor() + index:keyIndex]; + if (saveKey) + [self saveNewKey:publicKey->ok + atPath:hardenedIndexPath + withStatus:dash_spv_platform_identity_model_IdentityKeyStatus_Registering_ctor() + withSecurityLevel:security_level + withPurpose:purpose + fromDerivationPath:derivationPath + inContext:[NSManagedObjectContext viewContext]]; + return publicKey; +} + + +- (uint32_t)firstIndexOfKeyOfType:(DKeyKind *)type + createIfNotPresent:(BOOL)createIfNotPresent + saveKey:(BOOL)saveKey { + DKeyInfoDictionaries *key_info_dictionaries = DGetKeyInfoDictionaries(self.identity_model); + for (uint32_t index = 0; index < key_info_dictionaries->count; index++) { + uint32_t key_info_index = key_info_dictionaries->keys[index]; + DKeyInfo *key_info = key_info_dictionaries->values[index]; + DKeyKind *key_type = key_info->key_type; + if (DKeyKindIndex(key_type) == DKeyKindIndex(type)) { + DKeyInfoDictionariesDtor(key_info_dictionaries); + return key_info_index; + } + } + DKeyInfoDictionariesDtor(key_info_dictionaries); + if (_isLocal && createIfNotPresent) { + uint32_t rIndex; + [self createNewKeyOfType:type + securityLevel:DSecurityLevelMaster() + purpose:DPurposeAuth() + saveKey:saveKey + returnIndex:&rIndex]; + return rIndex; + } else { + return UINT32_MAX; + } +} + +- (DIdentityPublicKey *_Nullable)firstIdentityPublicKeyOfSecurityLevel:(DSecurityLevel *)security_level + andPurpose:(DPurpose *)purpose { + return dash_spv_platform_identity_model_IdentityModel_first_identity_public_key(self.identity_model, security_level, purpose); +} + +- (void)addKey:(DOpaqueKey *)key + securityLevel:(DSecurityLevel *)security_level + purpose:(DPurpose *)purpose + atIndex:(uint32_t)index + withStatus:(DIdentityKeyStatus *)status + save:(BOOL)save + inContext:(NSManagedObjectContext *)context { + DKeyKind *type = DOpaqueKeyKind(key); + DSLogPrivate(@"Identity (local: %u) add key: %p at %u of %u with %lu", self.isLocal, key, index, DKeyKindIndex(type), (unsigned long)status); + if (self.isLocal) { + [self addKey:key + securityLevel:security_level + purpose:purpose + atIndexPath:[NSIndexPath indexPathWithIndexes:(const NSUInteger[]){_index, index} length:2] + ofType:type + withStatus:status + save:save + inContext:context]; + } else { + DKeyInfo *key_info = DKeyInfoAtIndex(self.identity_model, index); + if (key_info) { + DOpaqueKey *maybe_opaque_key = key_info->key; + DIdentityKeyStatus *keyToCheckInDictionaryStatus = key_info->key_status; + if (maybe_opaque_key && [DSKeyManager keysPublicKeyDataIsEqual:maybe_opaque_key key2:key]) { + if (save && status != keyToCheckInDictionaryStatus) + [self updateStatus:status forKeyWithIndexID:index inContext:context]; + } else { + NSAssert(FALSE, @"these should really match up"); + DSLog(@"these should really match up"); + DKeyInfoDtor(key_info); + return; + } + } else { + _keysCreated = MAX(self.keysCreated, index + 1); + if (save) + [self saveNewRemoteIdentityKey:key + forKeyWithIndexID:index + withStatus:status + withSecurityLevel:security_level + withPurpose:purpose + inContext:context]; + } + [self addKeyInfo:key type:type securityLevel:security_level purpose:purpose status:status index:index]; + if (key_info) + DKeyInfoDtor(key_info); + } +} + +- (void)addKey:(DOpaqueKey *)key + securityLevel:(DSecurityLevel *)security_level + purpose:(DPurpose *)purpose + atIndexPath:(NSIndexPath *)indexPath + ofType:(DKeyKind *)type + withStatus:(DIdentityKeyStatus *)status + save:(BOOL)save + inContext:(NSManagedObjectContext *_Nullable)context { + NSAssert(self.isLocal, @"This should only be called on local blockchain identities"); + if (!self.isLocal) return; + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:type]; + //derivationPath will be nil if not local + + DMaybeOpaqueKey *keyToCheck = [derivationPath publicKeyAtIndexPath:[indexPath hardenAllItems]]; + NSAssert(keyToCheck != nil && keyToCheck->ok, @"This key should be found"); + if ([DSKeyManager keysPublicKeyDataIsEqual:keyToCheck->ok key2:key]) { //if it isn't local we shouldn't verify + uint32_t index = (uint32_t)[indexPath indexAtPosition:[indexPath length] - 1]; + DKeyInfo *key_info = DKeyInfoAtIndex(self.identity_model, index); + + if (key_info) { + if (key_info->key && [DSKeyManager keysPublicKeyDataIsEqual:key_info->key key2:key]) { + if (save) + [self updateStatus:status + forKeyAtPath:indexPath + fromDerivationPath:derivationPath + inContext:context]; + } else { + NSAssert(FALSE, @"these should really match up"); + DSLog(@"these should really match up"); + DKeyInfoDtor(key_info); + return; + } + } else { + _keysCreated = MAX(self.keysCreated, index + 1); + if (save) + [self saveNewKey:key + atPath:indexPath + withStatus:status + withSecurityLevel:security_level + withPurpose:purpose + fromDerivationPath:derivationPath + inContext:context]; + } + [self addKeyInfo:key + type:type + securityLevel:security_level + purpose:purpose + status:status + index:index]; + if (key_info) + DKeyInfoDtor(key_info); + } else { + DSLog(@"these should really match up"); + } +} + +- (void)addKeyInfo:(DOpaqueKey *)key + type:(DKeyKind *)type + securityLevel:(DSecurityLevel *)security_level + purpose:(DPurpose *)purpose + status:(DIdentityKeyStatus *)status + index:(uint32_t)index { +// DSLogPrivate(@"%@: addKeyInfo: %p %u %hhu %u", self.logPrefix, key, DKeyKindIndex(type), DSecurityLevelIndex(security_level), index); + DKeyInfo *key_info = dash_spv_platform_identity_model_KeyInfo_ctor(key, type, status, security_level, purpose); + dash_spv_platform_identity_model_IdentityModel_add_key_info(self.identity_model, index, key_info); +} + + +// MARK: - Funding + +- (NSString *)registrationFundingAddress { + if (self.registrationAssetLockTransaction) { + return [DSKeyManager addressFromHash160:self.registrationAssetLockTransaction.creditBurnPublicKeyHash forChain:self.chain]; + } else { + DSAssetLockDerivationPath *path = self.isOutgoingInvitation + ? [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:self.wallet] + : [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:self.wallet]; + return [path addressAtIndexPath:[NSIndexPath indexPathWithIndex:self.index]]; + } +} + + +// MARK: Helpers + +- (BOOL)isRegistered { + return dash_spv_platform_identity_model_IdentityModel_is_registered(self.identity_model); +} + +- (NSString *)localizedRegistrationStatusString { + char *status_string = dash_spv_platform_identity_model_IdentityRegistrationStatus_string(self.registrationStatus); + NSString *status = NSStringFromPtr(status_string); + DCharDtor(status_string); + return status; +} + +- (void)applyIdentity:(DIdentity *)identity + save:(BOOL)save + inContext:(NSManagedObjectContext *_Nullable)context { + switch (identity->tag) { + case dpp_identity_identity_Identity_V0: { + dpp_identity_v0_IdentityV0 *versioned = identity->v0; + _creditBalance = versioned->balance; + DIdentityPublicKeysMap *public_keys = versioned->public_keys; + for (int k = 0; k < public_keys->count; k++) { + DIdentityPublicKey *public_key = public_keys->values[k]; + switch (public_key->tag) { + case dpp_identity_identity_public_key_IdentityPublicKey_V0: { + DKeyID *key_id = public_keys->keys[k]; + DMaybeOpaqueKey *opaque = DOpaqueKeyFromIdentityPubKey(public_key); + [self addKey:opaque->ok + securityLevel:public_key->v0->security_level + purpose:public_key->v0->purpose + atIndex:key_id->_0 + withStatus:DIdentityKeyStatusRegistered() + save:save + inContext:context]; + break; + } + default: + break; + } + } + break; + } + + default: + break; + } +} + +// MARK: Registering + +/// instant lock verification will work for recently signed instant locks. +/// we expect clients to use ChainAssetLockProof. +- (DAssetLockProof *)createProof:(DSInstantSendTransactionLock *_Nullable)isLock { + return isLock ? [self createInstantProof:isLock.lock] : [self createChainProof]; +} + +- (DAssetLockProof *)createInstantProof:(DInstantLock *)isLock { + uint16_t tx_version = self.registrationAssetLockTransaction.version; + uint32_t lock_time = self.registrationAssetLockTransaction.lockTime; + NSArray *inputs = self.registrationAssetLockTransaction.inputs; + NSUInteger inputsCount = inputs.count; + DTxIn **tx_inputs = malloc(sizeof(DTxIn *) * inputsCount); + for (int i = 0; i < inputs.count; i++) { + DSTransactionInput *o = inputs[i]; + DScriptBuf *script = o.signature ? DScriptBufCtor(bytes_ctor(o.signature)) : o.inScript ? DScriptBufCtor(bytes_ctor(o.inScript)) : NULL; + DOutPoint *prev_output = DOutPointCtorU(o.inputHash, o.index); + tx_inputs[i] = DTxInCtor(prev_output, script, o.sequence); + } + + NSArray *outputs = self.registrationAssetLockTransaction.outputs; + NSUInteger outputsCount = outputs.count; + DTxOut **tx_outputs = malloc(sizeof(DTxOut *) * outputsCount); + for (int i = 0; i < outputs.count; i++) { + DSTransactionOutput *o = outputs[i]; + tx_outputs[i] = DTxOutCtor(o.amount, DScriptBufCtor(o.outScript ? bytes_ctor(o.outScript) : bytes_ctor([NSData data]))); + } + uint8_t asset_lock_payload_version = self.registrationAssetLockTransaction.specialTransactionVersion; + + NSArray *creditOutputs = self.registrationAssetLockTransaction.creditOutputs; + NSUInteger creditOutputsCount = creditOutputs.count; + DTxOut **credit_outputs = malloc(sizeof(DTxOut *) * creditOutputsCount); + for (int i = 0; i < creditOutputsCount; i++) { + DSTransactionOutput *o = creditOutputs[i]; + credit_outputs[i] = DTxOutCtor(o.amount, DScriptBufCtor(o.outScript ? bytes_ctor(o.outScript) : bytes_ctor([NSData data]))); + } + + DTxInputs *input_vec = DTxInputsCtor(inputsCount, tx_inputs); + DTxOutputs *output_vec = DTxOutputsCtor(outputsCount, tx_outputs); + DTxOutputs *credit_output_vec = DTxOutputsCtor(creditOutputsCount, credit_outputs); + uint32_t output_index = (uint32_t ) self.registrationAssetLockTransaction.lockedOutpoint.n; + + return dash_spv_platform_transition_instant_proof(output_index, isLock, tx_version, lock_time, input_vec, output_vec, asset_lock_payload_version, credit_output_vec); + +} + +- (DAssetLockProof *)createChainProof { + DSUTXO lockedOutpoint = self.registrationAssetLockTransaction.lockedOutpoint; + u256 *txid = u256_ctor_u(uint256_reverse(lockedOutpoint.hash)); + uint32_t vout = (uint32_t) lockedOutpoint.n; + return dash_spv_platform_transition_chain_proof(self.registrationAssetLockTransaction.blockHeight, txid, vout); +} + +- (void)registerIdentityWithProof:(DAssetLockProof *)proof + public_key:(DIdentityPublicKey *)public_key + atIndex:(uint32_t)index + completion:(void (^)(BOOL, NSError *))completion { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: Register Identity using public key (%u: %p) at %u with private key: %p", self.logPrefix, public_key->tag, public_key, index, self.internalRegistrationFundingPrivateKey->ok]; + DSLog(@"%@", debugInfo); + DMaybeStateTransitionProofResult *state_transition_result = dash_spv_platform_PlatformSDK_identity_register_using_public_key_at_index(self.chain.sharedRuntime, self.chain.sharedPlatformObj, public_key, index, proof, self.internalRegistrationFundingPrivateKey->ok); + if (state_transition_result->error) { + NSError *error = [NSError ffi_from_platform_error:state_transition_result->error]; + DSLog(@"%@: ERROR: %@", debugInfo, error); + switch (state_transition_result->error->tag) { + case dash_spv_platform_error_Error_InstantSendSignatureVerificationError: + DSLog(@"%@: Probably isd lock is outdated... try with chain lock proof", debugInfo); + DMaybeStateTransitionProofResultDtor(state_transition_result); + DAssetLockProof *proof = [self createChainProof]; + [self registerIdentityWithProof:proof public_key:public_key atIndex:index completion:completion]; + break; + + default: { + DMaybeStateTransitionProofResultDtor(state_transition_result); + if (completion) completion(nil, ERROR_REG_TRANSITION_CREATION); + break; + } + } + return; + } + [self processStateTransitionResult:state_transition_result]; + + DSLog(@"%@: OK %p -> monitor_with_delay", debugInfo, state_transition_result->ok); + DMaybeIdentity *result = dash_spv_platform_identity_manager_IdentitiesManager_monitor_with_delay(self.chain.sharedRuntime, self.chain.sharedIdentitiesObj, u256_ctor(self.uniqueIDData), DRetryLinear(5), DRaiseIdentityNotFound(), 4); + + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: monitor_with_delay: ERROR: %@", debugInfo, error); + DMaybeIdentityDtor(result); + completion(NO, error); + } else if (result->ok) { + DSLog(@"%@: monitor_with_delay: OK: (%p)", debugInfo, result->ok); + DMaybeIdentityDtor(result); + completion(YES, NULL); + } else { + DMaybeIdentityDtor(result); + completion(NO, ERROR_REG_TRANSITION); + } + +} + +- (BOOL)containsPublicKey:(DIdentityPublicKey *)identity_public_key { + return dash_spv_platform_identity_model_IdentityModel_has_identity_public_key(self.identity_model, identity_public_key); +} + +- (BOOL)containsTopupTransaction:(DSAssetLockTransaction *)transaction { + return [self.topupAssetLockTransactionHashes containsObject:uint256_data(transaction.txHash)]; +} + +- (void)registerIdentityWithProof2:(DAssetLockProof *)proof + public_key:(DIdentityPublicKey *)public_key + atIndex:(uint32_t)index + completion:(void (^)(BOOL, NSError *_Nullable))completion { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: Register Identity using public key (%u: %p) at %u with private key: %p", self.logPrefix, public_key->tag, public_key, index, self.internalRegistrationFundingPrivateKey->ok]; + DSLog(@"%@", debugInfo); + Result_ok_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error *state_transition_result = dash_spv_platform_PlatformSDK_identity_register_using_public_key_at_index2(self.chain.sharedRuntime, self.chain.sharedPlatformObj, public_key, index, proof, self.internalRegistrationFundingPrivateKey->ok); + if (state_transition_result->error) { + NSError *error = [NSError ffi_from_platform_error:state_transition_result->error]; + DSLog(@"%@: ERROR: %@", debugInfo, error); + switch (state_transition_result->error->tag) { + case dash_spv_platform_error_Error_InstantSendSignatureVerificationError: + DSLog(@"%@: Probably isd lock is outdated... try with chain lock proof", debugInfo); + Result_ok_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error_destroy(state_transition_result); + DAssetLockProof *proof = [self createChainProof]; + [self registerIdentityWithProof2:proof public_key:public_key atIndex:index completion:completion]; + break; + + default: { + Result_ok_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error_destroy(state_transition_result); + if (completion) completion(nil, ERROR_REG_TRANSITION_CREATION); + break; + } + } + return; + } + DIdentity *identity = state_transition_result->ok; + [self applyIdentity:identity save:YES inContext:self.platformContext]; + Result_ok_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error_destroy(state_transition_result); + DSLog(@"%@: OK ", debugInfo); + completion(YES, NULL); +} + +- (void)topupIdentityWithProof:(DAssetLockProof *)proof + public_key:(DIdentityPublicKey *)public_key + atIndex:(uint32_t)index + completion:(void (^)(BOOL, NSError *_Nullable))completion { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: TopUp Identity using public key (%u: %p) at %u with private key: %p", self.logPrefix, public_key->tag, public_key, index, self.internalTopupFundingPrivateKey->ok]; + DSLog(@"%@", debugInfo); + u256 *identity_id = u256_ctor_u(self.uniqueID); + DMaybeStateTransitionProofResult *state_transition_result = dash_spv_platform_PlatformSDK_identity_topup(self.chain.sharedRuntime, self.chain.sharedPlatformObj, identity_id, proof, self.internalTopupFundingPrivateKey->ok); + if (state_transition_result->error) { + NSError *error = [NSError ffi_from_platform_error:state_transition_result->error]; + DSLog(@"%@: ERROR: %@", debugInfo, error); + switch (state_transition_result->error->tag) { + case dash_spv_platform_error_Error_InstantSendSignatureVerificationError: + DSLog(@"%@: Probably isd lock is outdated... try with chain lock proof", debugInfo); + DMaybeStateTransitionProofResultDtor(state_transition_result); + DAssetLockProof *proof = [self createChainProof]; + [self topupIdentityWithProof:proof public_key:public_key atIndex:index completion:completion]; + break; + + default: { + DMaybeStateTransitionProofResultDtor(state_transition_result); + if (completion) completion(nil, ERROR_REG_TRANSITION_CREATION); + break; + } + } + return; + } + DSLog(@"%@: OK", debugInfo); + [self processStateTransitionResult:state_transition_result]; + DMaybeStateTransitionProofResultDtor(state_transition_result); + completion(YES, NULL); +} + +// [self.identity topupTransactionForTopupAmount:topupAmount fundedByAccount:self.fundingAccount completion:^(DSIdentityTopupTransition *identityTopupTransaction) { +// if (identityTopupTransaction) { +// [self.fundingAccount signTransaction:identityTopupTransaction withPrompt:@"Fund Transaction" completion:^(BOOL signedTransaction, BOOL cancelled) { +// if (signedTransaction) { +// [self.chainManager.transactionManager publishTransaction:identityTopupTransaction completion:^(NSError * _Nullable error) { +// if (error) { +// [self raiseIssue:@"Error" message:error.localizedDescription]; +// +// } else { +// [self.navigationController popViewControllerAnimated:TRUE]; +// } +// }]; +// } else { +// [self raiseIssue:@"Error" message:@"Transaction was not signed."]; +// +// } +// }]; +// } else { +// [self raiseIssue:@"Error" message:@"Unable to create BlockchainIdentityTopupTransaction."]; +// +// } +// }]; + +- (void)createAndPublishTopUpTransitionForAmount:(uint64_t)amount + fundedByAccount:(DSAccount *)fundingAccount + pinPrompt:(NSString *)prompt + withCompletion:(void (^)(BOOL, NSError *_Nullable))completion { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: CREATE AND PUBLISH IDENTITY TOPUP TRANSITION", self.logPrefix]; + DSLog(@"%@", debugInfo); + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:self.wallet]; + NSString *topupAddress = [path addressAtIndexPath:[NSIndexPath indexPathWithIndex:self.index]]; + DSAssetLockTransaction *assetLockTransaction = [fundingAccount assetLockTransactionFor:amount to:topupAddress withFee:YES]; + if (!assetLockTransaction) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, ERROR_FUNDING_TX_CREATION); }); + return; + } + + [fundingAccount signTransaction:assetLockTransaction + withPrompt:prompt + completion:^(BOOL signedTransaction, BOOL cancelled) { + if (!signedTransaction) { + if (completion) + dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, cancelled ? nil : ERROR_FUNDING_TX_SIGNING); }); + return; + } + [self publishTransactionAndWait:assetLockTransaction completion:^(BOOL published, DSInstantSendTransactionLock *_Nullable instantSendLock, NSError *_Nullable error) { + if (!published) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, ERROR_FUNDING_TX_TIMEOUT); }); + return; + } + if (!instantSendLock) { + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, ERROR_FUNDING_TX_ISD_TIMEOUT); }); + return; + } + if (!self.internalTopupFundingPrivateKey) { + DSLog(@"%@: ERROR: No TopUp Funding Private Key", debugInfo); + if (completion) completion(nil, ERROR_NO_FUNDING_PRV_KEY); + return; + } + uint32_t index = [self firstIndexOfKeyOfType:DKeyKindECDSA() createIfNotPresent:YES saveKey:!self.wallet.isTransient]; + [debugInfo appendFormat:@", index: %u", index]; + DOpaqueKey *publicKey = [self keyAtIndex:index]; + [debugInfo appendFormat:@", public_key: %p", publicKey]; + DSInstantSendTransactionLock *isLock = assetLockTransaction.instantSendLockAwaitingProcessing; + [debugInfo appendFormat:@", is_lock: %p", isLock]; + if (!isLock && assetLockTransaction.blockHeight == BLOCK_UNKNOWN_HEIGHT) { + DSLog(@"%@: ERROR: Funding Tx Not Mined", debugInfo); + if (completion) completion(nil, ERROR_FUNDING_TX_NOT_MINED); + return; + } + DIdentityPublicKey *public_key = DIdentityRegistrationPublicKey(index, publicKey); + DAssetLockProof *proof = [self createProof:isLock]; + DSLog(@"%@ Proof: %u: %p", debugInfo, proof->tag, proof); + [self topupIdentityWithProof:proof public_key:public_key atIndex:index completion:completion]; + }]; + + }]; +} + +- (void)createAndPublishRegistrationTransitionWithCompletion:(void (^)(BOOL, NSError *))completion { + NSMutableString *debugInfo = [NSMutableString stringWithFormat:@"%@: CREATE AND PUBLISH IDENTITY REGISTRATION TRANSITION", self.logPrefix]; + DSLog(@"%@", debugInfo); + if (!self.internalRegistrationFundingPrivateKey) { + DSLog(@"%@: ERROR: No Funding Private Key", debugInfo); + if (completion) completion(nil, ERROR_NO_FUNDING_PRV_KEY); + return; + } + uint32_t index = [self firstIndexOfKeyOfType:DKeyKindECDSA() createIfNotPresent:YES saveKey:!self.wallet.isTransient]; + [debugInfo appendFormat:@", index: %u", index]; + DOpaqueKey *publicKey = [self keyAtIndex:index]; + [debugInfo appendFormat:@", public_key: %p", publicKey]; + NSAssert((index & ~(BIP32_HARD)) == 0, @"The index should be 0 here"); + NSAssert(self.registrationAssetLockTransaction, @"The registration credit funding transaction must be known %@", uint256_hex(self.registrationAssetLockTransactionHash)); + DSInstantSendTransactionLock *isLock = self.registrationAssetLockTransaction.instantSendLockAwaitingProcessing; + [debugInfo appendFormat:@", is_lock: %p", isLock]; + if (!isLock && self.registrationAssetLockTransaction.blockHeight == BLOCK_UNKNOWN_HEIGHT) { + DSLog(@"%@: ERROR: Funding Tx Not Mined", debugInfo); + if (completion) completion(nil, ERROR_FUNDING_TX_NOT_MINED); + return; + } + DIdentityPublicKey *public_key = DIdentityRegistrationPublicKey(index, publicKey); + DAssetLockProof *proof = [self createProof:isLock]; + DSLog(@"%@ Proof: %u: %p", debugInfo, proof->tag, proof); + [self registerIdentityWithProof2:proof public_key:public_key atIndex:index completion:completion]; +} + +// MARK: Retrieval + +- (void)fetchIdentityNetworkStateInformationWithCompletion:(void (^)(BOOL success, BOOL found, NSError *error))completion { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@: Fetch Identity State", self.logPrefix]; + DSLog(@"%@", debugString); + dispatch_async(self.identityQueue, ^{ + DMaybeIdentity *result = dash_spv_platform_identity_manager_IdentitiesManager_monitor_for_id_bytes(self.chain.sharedRuntime, self.chain.sharedIdentitiesObj, u256_ctor(self.uniqueIDData), DRetryDown50(DEFAULT_FETCH_IDENTITY_RETRY_COUNT), self.isLocal ? DAcceptIdentityNotFound() : DRaiseIdentityNotFound()); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: ERROR: %@", debugString, error); + DMaybeIdentityDtor(result); + completion(NO, NO, error); + return; + } + DIdentity *identity = result->ok; + if (!identity) { + DSLog(@"%@ ERROR: None", debugString); + DMaybeIdentityDtor(result); + completion(YES, NO, nil); + return; + } + switch (identity->tag) { + case dpp_identity_identity_Identity_V0: { + dpp_identity_v0_IdentityV0 *identity_v0 = identity->v0; + self->_creditBalance = identity_v0->balance; + DIdentityPublicKeysMap *public_keys = identity_v0->public_keys; + for (int i = 0; i < public_keys->count; i++) { + DIdentityPublicKey *key = public_keys->values[i]; + switch (key->tag) { + case dpp_identity_identity_public_key_IdentityPublicKey_V0: { + DMaybeOpaqueKey *maybe_key = DOpaqueKeyFromIdentityPubKey(key); + [self addKey:maybe_key->ok + securityLevel:key->v0->security_level + purpose:key->v0->purpose + atIndex:i + withStatus:DIdentityKeyStatusRegistered() + save:!self.isTransient + inContext:self.platformContext]; + break; + } + default: + DSLog(@"%@ WARN: Unsupported Identity Public Key Version %u", debugString, key->tag); + break; + } + } + DIdentityModelSetStatus(self.identity_model, DIdentityRegistrationStatusRegistered()); + break; + } + default: + break; + } + DMaybeIdentityDtor(result); + DSLog(@"%@: OK", debugString); + completion(YES, YES, nil); + }); +} + +- (void)fetchAllNetworkStateInformationWithCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion { + dispatch_async(self.identityQueue, ^{ + [self fetchAllNetworkStateInformationInContext:self.platformContext + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; + }); +} + +- (void)fetchAllNetworkStateInformationInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + dispatch_async(self.identityQueue, ^{ + DSIdentityQueryStep query = DSIdentityQueryStep_None; + if ([DSOptionsManager sharedInstance].syncType & DSSyncType_Identities) + query |= DSIdentityQueryStep_Identity; + if ([DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) + query |= DSIdentityQueryStep_Username; + if ([DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) { + query |= DSIdentityQueryStep_Profile; + if (self.isLocal) + query |= DSIdentityQueryStep_ContactRequests; + } + [self fetchNetworkStateInformation:query + inContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + }); +} + +- (void)fetchL3NetworkStateInformation:(DSIdentityQueryStep)queryStep + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion { + [self fetchL3NetworkStateInformation:queryStep + inContext:self.platformContext + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)fetchL3NetworkStateInformation:(DSIdentityQueryStep)queryStep + inContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + DSLog(@"%@: Fetch L3 State (%@)", self.logPrefix, DSIdentityQueryStepsDescription(queryStep)); + if (!(queryStep & DSIdentityQueryStep_Identity) && (!self.activeKeyCount)) { + // We need to fetch keys if we want to query other information + if (completion) completion(DSIdentityQueryStep_BadQuery, @[ERROR_ATTEMPT_QUERY_WITHOUT_KEYS]); + return; + } + __block DSIdentityQueryStep failureStep = DSIdentityQueryStep_None; + __block NSMutableArray *groupedErrors = [NSMutableArray array]; + dispatch_group_t dispatchGroup = dispatch_group_create(); + if (queryStep & DSIdentityQueryStep_Username) { + dispatch_group_enter(dispatchGroup); + [self fetchUsernamesInContext:context + withCompletion:^(BOOL success, NSError *error) { + failureStep |= success & DSIdentityQueryStep_Username; + if (error) [groupedErrors addObject:error]; + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } + + if (queryStep & DSIdentityQueryStep_Profile) { + dispatch_group_enter(dispatchGroup); + [self fetchProfileInContext:context withCompletion:^(BOOL success, NSError *error) { + failureStep |= success & DSIdentityQueryStep_Profile; + if (error) [groupedErrors addObject:error]; + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } + + if (queryStep & DSIdentityQueryStep_OutgoingContactRequests) { + dispatch_group_enter(dispatchGroup); + [self fetchOutgoingContactRequestsInContext:context + startAfter:nil + withCompletion:^(BOOL success, NSArray *errors) { + failureStep |= success & DSIdentityQueryStep_OutgoingContactRequests; + if ([errors count]) { + [groupedErrors addObjectsFromArray:errors]; + dispatch_group_leave(dispatchGroup); + } else { + if (queryStep & DSIdentityQueryStep_IncomingContactRequests) { + [self fetchIncomingContactRequestsInContext:context + startAfter:nil + withCompletion:^(BOOL success, NSArray *errors) { + failureStep |= success & DSIdentityQueryStep_IncomingContactRequests; + if ([errors count]) [groupedErrors addObjectsFromArray:errors]; + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } else { + dispatch_group_leave(dispatchGroup); + } + } + } + onCompletionQueue:self.identityQueue]; + } else if (queryStep & DSIdentityQueryStep_IncomingContactRequests) { + dispatch_group_enter(dispatchGroup); + [self fetchIncomingContactRequestsInContext:context + startAfter:nil + withCompletion:^(BOOL success, NSArray *errors) { + failureStep |= success & DSIdentityQueryStep_IncomingContactRequests; + if ([errors count]) [groupedErrors addObjectsFromArray:errors]; + dispatch_group_leave(dispatchGroup); + } + onCompletionQueue:self.identityQueue]; + } + + __weak typeof(self) weakSelf = self; + if (completion) { + dispatch_group_notify(dispatchGroup, self.identityQueue, ^{ +#if DEBUG + DSLogPrivate(@"%@: Completed fetching of identity information for user %@ (query %@ - failures %@)", self.logPrefix, + self.currentDashpayUsername ? self.currentDashpayUsername : self.uniqueIdString, DSIdentityQueryStepsDescription(queryStep), DSIdentityQueryStepsDescription(failureStep)); +#else + DSLog(@"%@: Completed fetching of identity information for user %@ (query %@ - failures %@)", + @"", self.logPrefix, DSIdentityQueryStepsDescription(queryStep), DSIdentityQueryStepsDescription(failureStep)); +#endif /* DEBUG */ + if (!(failureStep & DSIdentityQueryStep_ContactRequests)) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) return; + //todo This needs to be eventually set with the blockchain returned by platform. + strongSelf.dashpaySyncronizationBlockHash = strongSelf.chain.lastTerminalBlock.blockHash; + } + dispatch_async(completionQueue, ^{ completion(failureStep, [groupedErrors copy]); }); + }); + } +} + +- (void)fetchNetworkStateInformation:(DSIdentityQueryStep)querySteps + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion { + [self fetchNetworkStateInformation:querySteps + inContext:self.platformContext + withCompletion:completion + onCompletionQueue:dispatch_get_main_queue()]; +} + +- (void)fetchNetworkStateInformation:(DSIdentityQueryStep)querySteps + inContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@: fetchNetworkStateInformation (%@)", self.logPrefix, DSIdentityQueryStepsDescription(querySteps)]; + DSLog(@"%@", debugString); + if (querySteps & DSIdentityQueryStep_Identity) { + [self fetchIdentityNetworkStateInformationWithCompletion:^(BOOL success, BOOL found, NSError *error) { + if (!success) { + DSLog(@"%@: ERROR: %@", debugString, error); + if (completion) dispatch_async(completionQueue, ^{ completion(DSIdentityQueryStep_Identity, error ? @[error] : @[]); }); + return; + } + if (!found) { + DSLog(@"%@: ERROR: NoIdentity", debugString); + if (completion) dispatch_async(completionQueue, ^{ completion(DSIdentityQueryStep_NoIdentity, @[]); }); + return; + } + [self fetchL3NetworkStateInformation:querySteps + inContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + }]; + } else { + NSAssert([self identityEntityInContext:context], @"Blockchain identity entity should be known"); + [self fetchL3NetworkStateInformation:querySteps + inContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + } +} + +- (void)fetchIfNeededNetworkStateInformation:(DSIdentityQueryStep)querySteps + inContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + dispatch_async(self.identityQueue, ^{ + if (!self.activeKeyCount) { + if (self.isLocal) { + [self fetchNetworkStateInformation:querySteps + inContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + } else { + DSIdentityQueryStep stepsNeeded = DSIdentityQueryStep_None; + if ([DSOptionsManager sharedInstance].syncType & DSSyncType_Identities) + stepsNeeded |= DSIdentityQueryStep_Identity; + if (!self.dashpayUsernameCount && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) + stepsNeeded |= DSIdentityQueryStep_Username; + if ((self.lastCheckedProfileTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_Profile; + if (stepsNeeded != DSIdentityQueryStep_None) { + [self fetchNetworkStateInformation:stepsNeeded & querySteps inContext:context withCompletion:completion onCompletionQueue:completionQueue]; + } else if (completion) { + completion(DSIdentityQueryStep_None, @[]); + } + } + } else { + DSIdentityQueryStep stepsNeeded = DSIdentityQueryStep_None; + if (!self.dashpayUsernameCount && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) { + stepsNeeded |= DSIdentityQueryStep_Username; + } + __block uint64_t createdAt; + [context performBlockAndWait:^{ + createdAt = [[self matchingDashpayUserInContext:context] createdAt]; + }]; + if (!createdAt && (self.lastCheckedProfileTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_Profile; + if (self.isLocal && (self.lastCheckedIncomingContactsTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_IncomingContactRequests; + if (self.isLocal && (self.lastCheckedOutgoingContactsTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_OutgoingContactRequests; + if (stepsNeeded != DSIdentityQueryStep_None) { + [self fetchNetworkStateInformation:stepsNeeded & querySteps inContext:context withCompletion:completion onCompletionQueue:completionQueue]; + } else if (completion) { + completion(DSIdentityQueryStep_None, @[]); + } + } + }); +} + +- (void)fetchNeededNetworkStateInformationInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(DSIdentityQueryStep failureStep, NSArray *errors))completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@: fetchNeededNetworkStateInformationInContext (local: %u, active keys: %lu) ", self.logPrefix, self.isLocal, self.activeKeyCount]; + DSLog(@"%@", debugString); + dispatch_async(self.identityQueue, ^{ + if (!self.activeKeyCount) { + if (self.isLocal) { + [self fetchAllNetworkStateInformationWithCompletion:completion]; + } else { + DSIdentityQueryStep stepsNeeded = DSIdentityQueryStep_None; + if ([DSOptionsManager sharedInstance].syncType & DSSyncType_Identities) + stepsNeeded |= DSIdentityQueryStep_Identity; + if (!self.dashpayUsernameCount && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) + stepsNeeded |= DSIdentityQueryStep_Username; + if ((self.lastCheckedProfileTimestamp < [NSDate timeIntervalSince1970Minus:HOUR_TIME_INTERVAL]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_Profile; + if (stepsNeeded != DSIdentityQueryStep_None) { + [self fetchNetworkStateInformation:stepsNeeded + inContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + } else if (completion) { + completion(DSIdentityQueryStep_None, @[]); + } + } + } else { + DSIdentityQueryStep stepsNeeded = DSIdentityQueryStep_None; + if (!self.dashpayUsernameCount && self.lastCheckedUsernamesTimestamp == 0 && [DSOptionsManager sharedInstance].syncType & DSSyncType_DPNS) + stepsNeeded |= DSIdentityQueryStep_Username; + if (![[self matchingDashpayUserInContext:context] createdAt] && (self.lastCheckedProfileTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_Profile; + if (self.isLocal && (self.lastCheckedIncomingContactsTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_IncomingContactRequests; + if (self.isLocal && (self.lastCheckedOutgoingContactsTimestamp < [NSDate timeIntervalSince1970MinusHour]) && [DSOptionsManager sharedInstance].syncType & DSSyncType_Dashpay) + stepsNeeded |= DSIdentityQueryStep_OutgoingContactRequests; + if (stepsNeeded != DSIdentityQueryStep_None) { + [self fetchNetworkStateInformation:stepsNeeded + inContext:context + withCompletion:completion + onCompletionQueue:completionQueue]; + } else if (completion) { + completion(DSIdentityQueryStep_None, @[]); + } + } + }); +} + +- (BOOL)processStateTransitionResult:(DMaybeStateTransitionProofResult *)result { +#if (defined(DPP_STATE_TRANSITIONS)) + dpp_state_transition_proof_result_StateTransitionProofResult *proof_result = result->ok; + switch (proof_result->tag) { + case dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedDataContract: { + NSData *identifier = NSDataFromPtr(proof_result->verified_data_contract->v0->id->_0->_0); + DSLog(@"%@: VerifiedDataContract: %@", self.logPrefix, identifier.hexString); + break; + } + case dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedIdentity: { + NSData *identifier = NSDataFromPtr(proof_result->verified_identity->v0->id->_0->_0); + DSLog(@"%@: VerifiedIdentity: %@", self.logPrefix, identifier.hexString); + break; + } + case dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedPartialIdentity: { + NSData *identifier = NSDataFromPtr(proof_result->verified_partial_identity>id->_0->_0); + DSLog(@"%@: VerifiedPartialIdentity: %@", self.logPrefix, identifier.hexString); + break; + } + case dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedBalanceTransfer: { + dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedBalanceTransfer_Body *transfer = proof_result->verified_balance_transfer; + NSData *from_identifier = NSDataFromPtr(transfer->_0->id->_0->_0); + NSData *to_identifier = NSDataFromPtr(transfer->_1->id->_0->_0); + DSLog(@"%@: VerifiedBalanceTransfer: %@ --> %@", self.logPrefix, from_identifier.hexString, to_identifier.hexString); + break; + } + case dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedDocuments: { + std_collections_Map_keys_platform_value_types_identifier_Identifier_values_Option_dpp_document_Document *verified_documents = proof_result->verified_documents; + DSLog(@"%@: VerifiedDocuments: %u", self.logPrefix, verified_documents->count); + break; + } + case dpp_state_transition_proof_result_StateTransitionProofResult_VerifiedMasternodeVote: { + dpp_voting_votes_Vote *verified_masternode_vote = proof_result->verified_masternode_vote; + DSLog(@"%@: VerifiedMasternodeVote: %u", self.logPrefix, verified_masternode_vote->tag); + break; + } + default: + break; + } +#endif + return YES; +} + +// MARK: - Contracts + +- (void)fetchAndUpdateContract:(DPContract *)contract { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@: fetchAndUpdateContract (%lu) ", self.logPrefix, (unsigned long) contract.contractState]; + DSLog(@"%@", debugString); + NSManagedObjectContext *context = [NSManagedObjectContext platformContext]; + __weak typeof(contract) weakContract = contract; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //this is so we don't get service immediately + BOOL isDPNSEmpty = [contract.name isEqual:@"DPNS"] && uint256_is_zero(self.chain.dpnsContractID); + BOOL isDashpayEmpty = [contract.name isEqual:@"DashPay"] && uint256_is_zero(self.chain.dashpayContractID); + BOOL isOtherContract = !([contract.name isEqual:@"DashPay"] || [contract.name isEqual:@"DPNS"]); + if (((isDPNSEmpty || isDashpayEmpty || isOtherContract) && uint256_is_zero(contract.registeredIdentityUniqueID)) || contract.contractState == DPContractState_NotRegistered) { + [contract registerCreator:self]; + [contract saveAndWaitInContext:context]; + + if (!self.keysCreated) { + uint32_t index; + [self createNewKeyOfType:DKeyKindECDSA() + securityLevel:DSecurityLevelMaster() + purpose:DPurposeAuth() + saveKey:!self.wallet.isTransient + returnIndex:&index]; + } + DMaybeOpaqueKey *privateKey = [self privateKeyAtIndex:self.currentMainKeyIndex ofType:self.currentMainKeyType]; + + DMaybeStateTransitionProofResult *state_transition_result = dash_spv_platform_PlatformSDK_data_contract_create2(self.chain.sharedRuntime, self.chain.sharedPlatformObj, data_contracts_SystemDataContract_DPNS_ctor(), u256_ctor_u(self.uniqueID), 0, privateKey->ok); + + if (state_transition_result->error) { + DSLog(@"%@: ERROR: %@", debugString, [NSError ffi_from_platform_error:state_transition_result->error]); + DMaybeStateTransitionProofResultDtor(state_transition_result); + return; + } + DSLog(@"%@: OK", debugString); + if ([self processStateTransitionResult:state_transition_result]) { + contract.contractState = DPContractState_Registering; + } else { + contract.contractState = DPContractState_Unknown; + } + [contract saveAndWaitInContext:context]; + + DMaybeContract *monitor_result = dash_spv_platform_contract_manager_ContractsManager_monitor_for_id_bytes(self.chain.sharedRuntime, self.chain.sharedContractsObj, u256_ctor_u(contract.contractId), DRetryLinear(2), dash_spv_platform_contract_manager_ContractValidator_None_ctor()); + + if (monitor_result->error) { + DMaybeContractDtor(monitor_result); + DSLog(@"%@: Contract Monitoring Error: %@", self.logPrefix, [NSError ffi_from_platform_error:monitor_result->error]); + return; + } + if (monitor_result->ok) { + NSData *identifier = NSDataFromPtr(monitor_result->ok->v0->id->_0->_0); + if ([identifier isEqualToData:uint256_data(contract.contractId)]) { + DSLog(@"%@: Contract Monitoring OK", self.logPrefix); + contract.contractState = DPContractState_Registered; + [contract saveAndWaitInContext:context]; + } else { + DSLog(@"%@: Contract Monitoring Error: Ids dont match", self.logPrefix); + } + } + DSLog(@"%@: Contract Monitoring Error", self.logPrefix); + + } else if (contract.contractState == DPContractState_Registered || contract.contractState == DPContractState_Registering) { + DSLog(@"%@: Fetching contract for verification %@", self.logPrefix, contract.base58ContractId); + DMaybeContract *contract_result = dash_spv_platform_contract_manager_ContractsManager_fetch_contract_by_id_bytes(self.chain.sharedRuntime, self.chain.sharedContractsObj, u256_ctor_u(contract.contractId)); + + dispatch_async(self.identityQueue, ^{ + __strong typeof(weakContract) strongContract = weakContract; + if (!weakContract || !contract_result) return; + if (!contract_result->ok) { + DSLog(@"%@: Contract Monitoring ERROR: NotRegistered ", self.logPrefix); + strongContract.contractState = DPContractState_NotRegistered; + [strongContract saveAndWaitInContext:context]; + DMaybeContractDtor(contract_result); + return; + } + DSLog(@"%@: Contract Monitoring OK: %@ ", self.logPrefix, strongContract); + if (strongContract.contractState == DPContractState_Registered && !dash_spv_platform_contract_manager_has_equal_document_type_keys(contract_result->ok, strongContract.raw_contract)) { + strongContract.contractState = DPContractState_NotRegistered; + [strongContract saveAndWaitInContext:context]; + //DSLog(@"Contract dictionary is %@", contractDictionary); + } + DMaybeContractDtor(contract_result); + + }); + } + }); +} + +// MARK: - Monitoring + +- (void)updateCreditBalance { + __weak typeof(self) weakSelf = self; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //this is so we don't get DAPINetworkService immediately + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) return; + DMaybeIdentityBalance *result = dash_spv_platform_identity_manager_IdentitiesManager_fetch_balance_by_id_bytes(strongSelf.chain.sharedRuntime, strongSelf.chain.sharedIdentitiesObj, u256_ctor(self.uniqueIDData)); + if (!result->ok) { + DSLog(@"%@: updateCreditBalance: ERROR RESULT: %u", self.logPrefix, result->error->tag); + DMaybeIdentityBalanceDtor(result); + return; + } + uint64_t balance = result->ok[0]; + DMaybeIdentityBalanceDtor(result); + DSLog(@"%@: updateCreditBalance: OK: %llu", self.logPrefix, balance); + dispatch_async(self.identityQueue, ^{ + strongSelf.creditBalance = balance; + }); + }); +} + + +// MARK: Helpers + +- (BOOL)isDashpayReady { + return self.activeKeyCount > 0 && self.isRegistered; +} + +- (UInt256)contractIdIfRegistered:(DDataContract *)contract { + NSMutableData *mData = [NSMutableData data]; + [mData appendUInt256:self.uniqueID]; + DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath identitiesECDSAKeysDerivationPathForWallet:self.wallet]; + Result_ok_Vec_u8_err_dash_spv_platform_error_Error *result = dash_spv_platform_contract_manager_ContractsManager_contract_serialized_hash(self.chain.sharedContractsObj, contract); + NSData *serializedHash = NSDataFromPtr(result->ok); + Result_ok_Vec_u8_err_dash_spv_platform_error_Error_destroy(result); + NSMutableData *entropyData = [serializedHash mutableCopy]; + [entropyData appendUInt256:self.uniqueID]; + [entropyData appendData:[derivationPath publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:UINT32_MAX - 1]]]; //use the last key in 32 bit space (it won't probably ever be used anyways) + [mData appendData:uint256_data([entropyData SHA256])]; + return [mData SHA256_2]; //this is the contract ID +} + +- (DIdentityRegistrationStatus *)registrationStatus { + return dash_spv_platform_identity_model_IdentityModel_registration_status(self.identity_model); +} + +// MARK: - Persistence + +// MARK: Saving + +- (void)saveInitial { + [self saveInitialInContext:self.platformContext]; +} + +- (DSBlockchainIdentityEntity *)initialEntityInContext:(NSManagedObjectContext *)context { + DSChainEntity *chainEntity = [self.chain chainEntityInContext:context]; + DSBlockchainIdentityEntity *entity = [DSBlockchainIdentityEntity managedObjectInBlockedContext:context]; + entity.uniqueID = uint256_data(self.uniqueID); + entity.isLocal = self.isLocal; + entity.registrationStatus = DIdentityRegistrationStatusIndex(self.identity_model); + if (self.isLocal) + entity.registrationFundingTransaction = [DSAssetLockTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", uint256_data(self.registrationAssetLockTransaction.txHash)]; + entity.chain = chainEntity; + [self collectUsernameEntitiesIntoIdentityEntityInContext:entity context:context]; + DKeyInfoDictionaries *key_info_dictionaries = DGetKeyInfoDictionaries(self.identity_model); + + for (uint32_t index = 0; index < key_info_dictionaries->count; index++) { + uint32_t key_info_index = key_info_dictionaries->keys[index]; + DKeyInfo *key_info = key_info_dictionaries->values[index]; + DIdentityKeyStatus *status = key_info->key_status; + DKeyKind *key_type = key_info->key_type; + DOpaqueKey *key = key_info->key; + DSecurityLevel *level = key_info->security_level; + DPurpose *purpose = key_info->purpose; + DSAuthenticationKeysDerivationPath *derivationPath = [self derivationPathForType:key_type]; + const NSUInteger indexes[] = {_index, key_info_index}; + [self createNewKey:key + forIdentityEntity:entity + atPath:[NSIndexPath indexPathWithIndexes:indexes length:2] + withStatus:status + withSecurityLevel:level + withPurpose:purpose + fromDerivationPath:derivationPath + inContext:context]; + } + DKeyInfoDictionariesDtor(key_info_dictionaries); + DSDashpayUserEntity *dashpayUserEntity = [DSDashpayUserEntity managedObjectInBlockedContext:context]; + dashpayUserEntity.chain = chainEntity; + entity.matchingDashpayUser = dashpayUserEntity; + if (self.isOutgoingInvitation) { + DSBlockchainInvitationEntity *invitationEntity = [DSBlockchainInvitationEntity managedObjectInBlockedContext:context]; + invitationEntity.chain = chainEntity; + entity.associatedInvitation = invitationEntity; + } + return entity; +} + +- (void)saveInitialInContext:(NSManagedObjectContext *)context { + if (self.isTransient) return; + //no need for active check, in fact it will cause an infinite loop + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *entity = [self initialEntityInContext:context]; + DSDashpayUserEntity *dashpayUserEntity = entity.matchingDashpayUser; + [context ds_saveInBlockAndWait]; + [[NSManagedObjectContext viewContext] performBlockAndWait:^{ + self.matchingDashpayUserInViewContext = [[NSManagedObjectContext viewContext] objectWithID:dashpayUserEntity.objectID]; + }]; + [[NSManagedObjectContext platformContext] performBlockAndWait:^{ + self.matchingDashpayUserInPlatformContext = [[NSManagedObjectContext platformContext] objectWithID:dashpayUserEntity.objectID]; + }]; + if ([self isLocal]) + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self + }]; + }]; +} + +- (void)saveInContext:(NSManagedObjectContext *)context { + if (self.isTransient) return; + if (!self.isActive) return; + [context performBlockAndWait:^{ + BOOL changeOccured = NO; + NSMutableArray *updateEvents = [NSMutableArray array]; + DSBlockchainIdentityEntity *entity = [self identityEntityInContext:context]; + if (entity.creditBalance != self.creditBalance) { + entity.creditBalance = self.creditBalance; + changeOccured = YES; + [updateEvents addObject:DSIdentityUpdateEventCreditBalance]; + } + + uint16_t registrationStatus = DIdentityRegistrationStatusIndex(self.identity_model); + if (entity.registrationStatus != registrationStatus) { + entity.registrationStatus = registrationStatus; + changeOccured = YES; + [updateEvents addObject:DSIdentityUpdateEventRegistration]; + } + + if (!uint256_eq(entity.dashpaySyncronizationBlockHash.UInt256, self.dashpaySyncronizationBlockHash)) { + entity.dashpaySyncronizationBlockHash = uint256_data(self.dashpaySyncronizationBlockHash); + changeOccured = YES; + [updateEvents addObject:DSIdentityUpdateEventDashpaySyncronizationBlockHash]; + } + + if (entity.lastCheckedUsernamesTimestamp != self.lastCheckedUsernamesTimestamp) { + entity.lastCheckedUsernamesTimestamp = self.lastCheckedUsernamesTimestamp; + changeOccured = YES; + } + + if (entity.lastCheckedProfileTimestamp != self.lastCheckedProfileTimestamp) { + entity.lastCheckedProfileTimestamp = self.lastCheckedProfileTimestamp; + changeOccured = YES; + } + + if (entity.lastCheckedIncomingContactsTimestamp != self.lastCheckedIncomingContactsTimestamp) { + entity.lastCheckedIncomingContactsTimestamp = self.lastCheckedIncomingContactsTimestamp; + changeOccured = YES; + } + + if (entity.lastCheckedOutgoingContactsTimestamp != self.lastCheckedOutgoingContactsTimestamp) { + entity.lastCheckedOutgoingContactsTimestamp = self.lastCheckedOutgoingContactsTimestamp; + changeOccured = YES; + } + + if (changeOccured) { + [context ds_save]; + if (updateEvents.count) + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUpdateEvents: updateEvents + }]; + } + }]; +} + +- (NSString *)identifierForKeyAtPath:(NSIndexPath *)path + fromDerivationPath:(DSDerivationPath *)derivationPath { + return [NSString stringWithFormat:@"%@-%@-%@", self.uniqueIdString, derivationPath.standaloneExtendedPublicKeyUniqueID, [[path softenAllItems] indexPathString]]; +} + +- (BOOL)createNewKey:(DOpaqueKey *)key + forIdentityEntity:(DSBlockchainIdentityEntity *)identityEntity + atPath:(NSIndexPath *)path + withStatus:(DIdentityKeyStatus *)status + withSecurityLevel:(DSecurityLevel *)security_level + withPurpose:(DPurpose *)purpose + fromDerivationPath:(DSDerivationPath *)derivationPath + inContext:(NSManagedObjectContext *)context { + NSAssert(identityEntity, @"Entity should be present"); + + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:derivationPath inContext:context]; + NSUInteger count = [DSBlockchainIdentityKeyPathEntity countObjectsInContext:context matching:@"blockchainIdentity == %@ && derivationPath == %@ && path == %@", identityEntity, derivationPathEntity, path]; + if (!count) { + DSBlockchainIdentityKeyPathEntity *keyPathEntity = [DSBlockchainIdentityKeyPathEntity managedObjectInBlockedContext:context]; + keyPathEntity.derivationPath = derivationPathEntity; + // TODO: that's wrong should convert KeyType <-> KeyKind + keyPathEntity.keyType = DOpaqueKeyToKeyTypeIndex(key); + keyPathEntity.keyStatus = DIdentityKeyStatusToIndex(status); + NSData *privateKeyData = [DSKeyManager privateKeyData:key]; + if (!privateKeyData) { + DKeyKind *kind = DOpaqueKeyKind(key); + DMaybeOpaqueKey *privateKey = [self derivePrivateKeyAtIndexPath:path ofType:kind]; + NSAssert([DSKeyManager keysPublicKeyDataIsEqual:privateKey->ok key2:key], @"The keys don't seem to match up"); + privateKeyData = [DSKeyManager privateKeyData:privateKey->ok]; + NSAssert(privateKeyData, @"Private key data should exist"); + } + NSString *identifier = [self identifierForKeyAtPath:path fromDerivationPath:derivationPath]; + setKeychainData(privateKeyData, identifier, YES); + + keyPathEntity.path = path; + keyPathEntity.publicKeyData = [DSKeyManager publicKeyData:key]; + keyPathEntity.keyID = (uint32_t)[path indexAtPosition:path.length - 1]; + keyPathEntity.securityLevel = DSecurityLevelIndex(security_level); + keyPathEntity.purpose = DPurposeIndex(purpose); + [identityEntity addKeyPathsObject:keyPathEntity]; + return YES; + } else { + return NO; //no need to save the context + } +} + +- (void)saveNewKey:(DOpaqueKey *)key + atPath:(NSIndexPath *)path + withStatus:(DIdentityKeyStatus *)status + withSecurityLevel:(DSecurityLevel *)security_level + withPurpose:(DPurpose *)purpose +fromDerivationPath:(DSDerivationPath *)derivationPath + inContext:(NSManagedObjectContext *)context { + NSAssert(self.isLocal, @"This should only be called on local blockchain identities"); + if (!self.isLocal || self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *identityEntity = [self identityEntityInContext:context]; + if ([self createNewKey:key + forIdentityEntity:identityEntity + atPath:path + withStatus:status + withSecurityLevel:security_level + withPurpose:purpose + fromDerivationPath:derivationPath + inContext:context]) + [context ds_save]; + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUpdateEvents: @[DSIdentityUpdateEventKeyUpdate] + }]; + }]; +} + +- (void)saveNewRemoteIdentityKey:(DOpaqueKey *)key + forKeyWithIndexID:(uint32_t)keyID + withStatus:(DIdentityKeyStatus *)status + withSecurityLevel:(DSecurityLevel *)security_level + withPurpose:(DPurpose *)purpose + inContext:(NSManagedObjectContext *)context { + NSAssert(self.isLocal == FALSE, @"This should only be called on non local identities"); + if (self.isLocal || self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *identityEntity = [self identityEntityInContext:context]; + NSUInteger count = [DSBlockchainIdentityKeyPathEntity countObjectsInContext:context matching:@"blockchainIdentity == %@ && keyID == %@", identityEntity, @(keyID)]; + if (!count) { + DSBlockchainIdentityKeyPathEntity *keyPathEntity = [DSBlockchainIdentityKeyPathEntity managedObjectInBlockedContext:context]; + // TODO: migrate OpaqueKey/KeyKind to KeyType + keyPathEntity.keyType = DOpaqueKeyToKeyTypeIndex(key); + keyPathEntity.keyStatus = DIdentityKeyStatusToIndex(status); + keyPathEntity.keyID = keyID; + keyPathEntity.publicKeyData = [DSKeyManager publicKeyData:key]; + keyPathEntity.securityLevel = DSecurityLevelIndex(security_level); + keyPathEntity.purpose = DPurposeIndex(purpose); + [identityEntity addKeyPathsObject:keyPathEntity]; + [context ds_save]; + } + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUpdateEvents: @[DSIdentityUpdateEventKeyUpdate] + }]; + }]; +} + + +- (void)updateStatus:(DIdentityKeyStatus *)status + forKeyAtPath:(NSIndexPath *)path + fromDerivationPath:(DSDerivationPath *)derivationPath + inContext:(NSManagedObjectContext *)context { + NSAssert(self.isLocal, @"This should only be called on local identities"); + if (!self.isLocal || self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *identityEntity = [self identityEntityInContext:context]; + DSDerivationPathEntity *derivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:derivationPath inContext:context]; + DSBlockchainIdentityKeyPathEntity *keyPathEntity = [[DSBlockchainIdentityKeyPathEntity objectsInContext:context matching:@"blockchainIdentity == %@ && derivationPath == %@ && path == %@", identityEntity, derivationPathEntity, path] firstObject]; + uint16_t keyStatus = DIdentityKeyStatusToIndex(status); + if (keyPathEntity && (keyPathEntity.keyStatus != keyStatus)) { + keyPathEntity.keyStatus = keyStatus; + [context ds_save]; + } + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUpdateEvents: @[DSIdentityUpdateEventKeyUpdate] + }]; + }]; +} + +- (void)updateStatus:(DIdentityKeyStatus *)status + forKeyWithIndexID:(uint32_t)keyID + inContext:(NSManagedObjectContext *)context { + NSAssert(self.isLocal == FALSE, @"This should only be called on non local identities"); + if (self.isLocal || self.isTransient || !self.isActive) return; + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *identityEntity = [self identityEntityInContext:context]; + DSBlockchainIdentityKeyPathEntity *keyPathEntity = [[DSBlockchainIdentityKeyPathEntity objectsInContext:context matching:@"blockchainIdentity == %@ && derivationPath == NULL && keyID == %@", identityEntity, @(keyID)] firstObject]; + if (keyPathEntity) { + DSBlockchainIdentityKeyPathEntity *keyPathEntity = [DSBlockchainIdentityKeyPathEntity managedObjectInBlockedContext:context]; + keyPathEntity.keyStatus = DIdentityKeyStatusToIndex(status); + [context ds_save]; + } + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self, + DSIdentityUpdateEvents: @[DSIdentityUpdateEventKeyUpdate] + }]; + }]; +} + +// MARK: Deletion + +- (void)deletePersistentObjectAndSave:(BOOL)save + inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSBlockchainIdentityEntity *identityEntity = [self identityEntityInContext:context]; + if (identityEntity) { + NSSet *friendRequests = [identityEntity.matchingDashpayUser outgoingRequests]; + for (DSFriendRequestEntity *friendRequest in friendRequests) { + uint32_t accountNumber = friendRequest.account.index; + DSAccount *account = [self.wallet accountWithNumber:accountNumber]; + [account removeIncomingDerivationPathForFriendshipWithIdentifier:friendRequest.friendshipIdentifier]; + } + [identityEntity deleteObjectAndWait]; + if (save) + [context ds_save]; + } + [self notifyUpdate:@{ + DSChainManagerNotificationChainKey: self.chain, + DSIdentityKey: self + }]; + }]; +} + +// MARK: Entity + +- (DSBlockchainIdentityEntity *)identityEntity { + return [self identityEntityInContext:[NSManagedObjectContext viewContext]]; +} + +- (DSBlockchainIdentityEntity *)identityEntityInContext:(NSManagedObjectContext *)context { + __block DSBlockchainIdentityEntity *entity = nil; + [context performBlockAndWait:^{ + entity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", self.uniqueIDData]; + }]; + NSAssert(entity, @"An entity should always be found"); + return entity; +} + +- (DSDashpayUserEntity *)matchingDashpayUserInViewContext { + if (!_matchingDashpayUserInViewContext) { + _matchingDashpayUserInViewContext = [self matchingDashpayUserInContext:[NSManagedObjectContext viewContext]]; + } + return _matchingDashpayUserInViewContext; +} + +- (DSDashpayUserEntity *)matchingDashpayUserInPlatformContext { + if (!_matchingDashpayUserInPlatformContext) { + _matchingDashpayUserInPlatformContext = [self matchingDashpayUserInContext:[NSManagedObjectContext platformContext]]; + } + return _matchingDashpayUserInPlatformContext; +} + +- (DSDashpayUserEntity *)matchingDashpayUserInContext:(NSManagedObjectContext *)context { + if (_matchingDashpayUserInViewContext || _matchingDashpayUserInPlatformContext) { + if (context == [_matchingDashpayUserInPlatformContext managedObjectContext]) return _matchingDashpayUserInPlatformContext; + if (context == [_matchingDashpayUserInViewContext managedObjectContext]) return _matchingDashpayUserInViewContext; + if (_matchingDashpayUserInPlatformContext) { + __block NSManagedObjectID *managedId; + [[NSManagedObjectContext platformContext] performBlockAndWait:^{ + managedId = _matchingDashpayUserInPlatformContext.objectID; + }]; + return [context objectWithID:managedId]; + } else { + __block NSManagedObjectID *managedId; + [[NSManagedObjectContext viewContext] performBlockAndWait:^{ + managedId = _matchingDashpayUserInViewContext.objectID; + }]; + return [context objectWithID:managedId]; + } + } else { + __block DSDashpayUserEntity *dashpayUserEntity = nil; + [context performBlockAndWait:^{ + dashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentity.uniqueID == %@", uint256_data(self.uniqueID)]; + }]; + return dashpayUserEntity; + } +} + +- (void)notifyUpdate:(NSDictionary *_Nullable)userInfo { + [self notify:DSIdentityDidUpdateNotification userInfo:userInfo]; +} + +- (BOOL)isDefault { + return self.wallet.defaultIdentity == self; +} + + +- (NSString *)debugDescription { + return [[super debugDescription] stringByAppendingString:[NSString stringWithFormat:@" {%@-%@}", self.currentDashpayUsername, self.uniqueIdString]]; +} + +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [Identity: %@] ", self.chain.name, uint256_hex(self.uniqueID)]; +} + + +@end diff --git a/DashSync/shared/Models/Identity/DSInvitation+Protected.h b/DashSync/shared/Models/Identity/DSInvitation+Protected.h new file mode 100644 index 000000000..e45cd6ee4 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSInvitation+Protected.h @@ -0,0 +1,55 @@ +// +// Created by Samuel Westrich +// Copyright © 2564 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 "BigIntTypes.h" +#import "DSInvitation.h" +#import "DSBlockchainInvitationEntity+CoreDataClass.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DSChain; + +@interface DSInvitation (Protected) + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet; + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet + withInvitationEntity:(DSBlockchainInvitationEntity *)invitationEntity; + +- (instancetype)initWithUniqueId:(UInt256)uniqueId + isTransient:(BOOL)isTransient + onChain:(DSChain *)chain; + +- (instancetype)initAtIndex:(uint32_t)index + inWallet:(DSWallet *)wallet; +- (instancetype)initAtIndex:(uint32_t)index + withAssetLockTransaction:(DSAssetLockTransaction *)transaction + inWallet:(DSWallet *)wallet; + +- (void)registerInWalletForIdentityUniqueId:(UInt256)identityUniqueId; +- (void)registerInWalletForAssetLockTransaction:(DSAssetLockTransaction *)transaction; + +- (void)deletePersistentObjectAndSave:(BOOL)save + inContext:(NSManagedObjectContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSBlockchainInvitation.h b/DashSync/shared/Models/Identity/DSInvitation.h similarity index 66% rename from DashSync/shared/Models/Identity/DSBlockchainInvitation.h rename to DashSync/shared/Models/Identity/DSInvitation.h index 111592b25..428d8f21f 100644 --- a/DashSync/shared/Models/Identity/DSBlockchainInvitation.h +++ b/DashSync/shared/Models/Identity/DSInvitation.h @@ -15,26 +15,27 @@ // limitations under the License. // -#import "DSBlockchainIdentity.h" +#import "DSAssetLockTransaction.h" +#import "DSIdentity.h" #import -@class DSBlockchainIdentity, DSWallet, DSCreditFundingTransaction; +@class DSIdentity, DSWallet; NS_ASSUME_NONNULL_BEGIN -FOUNDATION_EXPORT NSString *const DSBlockchainInvitationDidUpdateNotification; -FOUNDATION_EXPORT NSString *const DSBlockchainInvitationKey; -FOUNDATION_EXPORT NSString *const DSBlockchainInvitationUpdateEvents; -FOUNDATION_EXPORT NSString *const DSBlockchainInvitationUpdateEventLink; +FOUNDATION_EXPORT NSString *const DSInvitationDidUpdateNotification; +FOUNDATION_EXPORT NSString *const DSInvitationKey; +FOUNDATION_EXPORT NSString *const DSInvitationUpdateEvents; +FOUNDATION_EXPORT NSString *const DSInvitationUpdateEventLink; -@interface DSBlockchainInvitation : NSObject +@interface DSInvitation : NSObject /*! @brief Initialized with an invitation link. The wallet must be on a chain that supports platform features. */ - (instancetype)initWithInvitationLink:(NSString *)invitationLink inWallet:(DSWallet *)wallet; /*! @brief This is the identity that was made from the invitation. There should always be an identity associated to a blockchain invitation. This identity might not yet be registered on Dash Platform. */ -@property (nonatomic, readonly) DSBlockchainIdentity *identity; +@property (nonatomic, readonly) DSIdentity *identity; /*! @brief This is an invitation that was created locally. */ @property (nonatomic, readonly) BOOL createdLocally; @@ -54,18 +55,22 @@ FOUNDATION_EXPORT NSString *const DSBlockchainInvitationUpdateEventLink; /*! @brief Verifies the current invitation link in the invitation was created with a link. If the invitation is valid a transaction will be returned, as well as if the transaction has already been spent. TODO:Spent currently does not work */ -- (void)verifyInvitationLinkWithCompletion:(void (^_Nullable)(DSTransaction *transaction, bool spent, NSError *error))completion completionQueue:(dispatch_queue_t)completionQueue; +- (void)verifyInvitationLinkWithCompletion:(void (^_Nullable)(Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *result))completion + completionQueue:(dispatch_queue_t)completionQueue; /*! @brief Verifies an invitation link. The chain must support platform features. If the invitation is valid a transaction will be returned, as well as if the transaction has already been spent. TODO:Spent currently does not work */ -+ (void)verifyInvitationLink:(NSString *)invitationLink onChain:(DSChain *)chain completion:(void (^_Nullable)(DSTransaction *transaction, bool spent, NSError *error))completion completionQueue:(dispatch_queue_t)completionQueue; ++ (void)verifyInvitationLink:(NSString *)invitationLink + onChain:(DSChain *)chain + completion:(void (^_Nullable)(Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *result))completion + completionQueue:(dispatch_queue_t)completionQueue; /*! @brief Registers the blockchain identity if the invitation was created with an invitation link. The blockchain identity is then associated with the invitation. */ -- (void)acceptInvitationUsingWalletIndex:(uint32_t)index setDashpayUsername:(NSString *)dashpayUsername authenticationPrompt:(NSString *)authenticationMessage identityRegistrationSteps:(DSBlockchainIdentityRegistrationStep)identityRegistrationSteps stepCompletion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSBlockchainIdentityRegistrationStep stepsCompleted, NSError *error))completion completionQueue:(dispatch_queue_t)completionQueue; +- (void)acceptInvitationUsingWalletIndex:(uint32_t)index setDashpayUsername:(NSString *)dashpayUsername authenticationPrompt:(NSString *)authenticationMessage identityRegistrationSteps:(DSIdentityRegistrationStep)identityRegistrationSteps stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion completionQueue:(dispatch_queue_t)completionQueue; /*! @brief Generates blockchain invitations' extended public keys by asking the user to authentication with the prompt. */ -- (void)generateBlockchainInvitationsExtendedPublicKeysWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL registered))completion; +- (void)generateInvitationsExtendedPublicKeysWithPrompt:(NSString *)prompt completion:(void (^_Nullable)(BOOL registered))completion; /*! @brief Register the blockchain identity to its wallet. This should only be done once on the creation of the blockchain identity. */ @@ -80,15 +85,15 @@ FOUNDATION_EXPORT NSString *const DSBlockchainInvitationUpdateEventLink; */ - (BOOL)unregisterLocally; -/*! @brief Register the blockchain invitation to its wallet from a credit funding registration transaction. This should only be done once on the creation of the blockchain invitation. - @param fundingTransaction The funding transaction used to initially fund the blockchain identity. +/*! @brief Register the blockchain invitation to its wallet from a asset lock registration transaction. This should only be done once on the creation of the blockchain invitation. + @param transaction The asset lock transaction used to initially fund the blockchain identity. */ -- (void)registerInWalletForRegistrationFundingTransaction:(DSCreditFundingTransaction *)fundingTransaction; +- (void)registerInWalletForAssetLockTransaction:(DSAssetLockTransaction *)transaction; /*! @brief Create the invitation full link and mark the "fromIdentity" as the source of the invitation. @param identity The source of the invitation. */ -- (void)createInvitationFullLinkFromIdentity:(DSBlockchainIdentity *)identity completion:(void (^_Nullable)(BOOL cancelled, NSString *invitationFullLink))completion; +- (void)createInvitationFullLinkFromIdentity:(DSIdentity *)identity completion:(void (^_Nullable)(BOOL cancelled, NSString *_Nullable invitationFullLink))completion; @end diff --git a/DashSync/shared/Models/Identity/DSInvitation.m b/DashSync/shared/Models/Identity/DSInvitation.m new file mode 100644 index 000000000..afc18d8ae --- /dev/null +++ b/DashSync/shared/Models/Identity/DSInvitation.m @@ -0,0 +1,526 @@ +// +// Created by Samuel Westrich +// Copyright © 2564 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 "DSInvitation.h" +#import "DSAuthenticationManager.h" +#import "DSIdentity+Profile.h" +#import "DSIdentity+Protected.h" +#import "DSIdentity+Username.h" +#import "DSBlockchainInvitationEntity+CoreDataClass.h" +#import "DSChain+Params.h" +#import "DSChainManager.h" +#import "DSAssetLockDerivationPath.h" +#import "DSDerivationPathFactory.h" +#import "DSIdentitiesManager+Protected.h" +#import "DSInstantSendTransactionLock.h" +#import "DSWallet.h" +#import "DSWallet+Identity.h" +#import "DSWallet+Invitation.h" +#import "NSData+DSHash.h" +#import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSManagedObject+Sugar.h" +#import "NSManagedObjectContext+DSSugar.h" +#import "NSString+Dash.h" + +#define ERROR_INVITATION_FORMAT [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation format is not valid"] +#define ERROR_SETTING_EXT_PRV_KEY [NSError errorWithCode:500 localizedDescriptionKey:@"Error setting the external funding private key"] +#define ERROR_GEN_IDENTITY_KEYS [NSError errorWithCode:500 localizedDescriptionKey:@"Error generating Identity keys"] +#define ERROR_INVALID_FUNDING_PRV_KEY [NSError errorWithCode:400 localizedDescriptionKey:@"Funding private key is not valid"] +#define ERROR_INVALID_INV_TX [NSError errorWithCode:400 localizedDescriptionKey:@"Invitation transaction is not valid"] + +@interface DSInvitation () + +@property (nonatomic, weak) DSWallet *wallet; +@property (nonatomic, strong) DSChain *chain; +@property (nonatomic, copy) NSString *link; +@property (nonatomic, strong) DSIdentity *identity; +@property (nonatomic, assign) BOOL isTransient; +@property (nonatomic, assign) BOOL needsIdentityRetrieval; +@property (nonatomic, assign) BOOL createdLocally; + +@end + +@implementation DSInvitation + +- (instancetype)initAtIndex:(uint32_t)index + inWallet:(DSWallet *)wallet { + //this is the creation of a new blockchain identity + NSParameterAssert(wallet); + + if (!(self = [super init])) return nil; + self.wallet = wallet; + self.isTransient = FALSE; + self.createdLocally = YES; + self.identity = [[DSIdentity alloc] initAtIndex:index inWallet:wallet]; + [self.identity setAssociatedInvitation:self]; + self.chain = wallet.chain; + self.needsIdentityRetrieval = NO; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withAssetLockTransaction:(DSAssetLockTransaction *)transaction + inWallet:(DSWallet *)wallet { + NSParameterAssert(wallet); + NSAssert(index != UINT32_MAX, @"index must be found"); + if (!(self = [super init])) return nil; + self.wallet = wallet; + self.isTransient = FALSE; + self.createdLocally = YES; + self.identity = [[DSIdentity alloc] initAtIndex:index withAssetLockTransaction:transaction inWallet:wallet]; + [self.identity setAssociatedInvitation:self]; + self.chain = wallet.chain; + self.needsIdentityRetrieval = NO; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet { + NSParameterAssert(wallet); + NSAssert(index != UINT32_MAX, @"index must be found"); + if (!(self = [super init])) return nil; + self.wallet = wallet; + self.isTransient = FALSE; + self.createdLocally = YES; + self.identity = [[DSIdentity alloc] initAtIndex:index withLockedOutpoint:lockedOutpoint inWallet:wallet]; + [self.identity setAssociatedInvitation:self]; + self.chain = wallet.chain; + self.needsIdentityRetrieval = NO; + return self; +} + +- (instancetype)initAtIndex:(uint32_t)index + withLockedOutpoint:(DSUTXO)lockedOutpoint + inWallet:(DSWallet *)wallet + withInvitationEntity:(DSBlockchainInvitationEntity *)invitationEntity { + if (!(self = [super init])) return nil; + self.wallet = wallet; + self.isTransient = FALSE; + self.createdLocally = YES; + self.identity = [[DSIdentity alloc] initAtIndex:index withLockedOutpoint:lockedOutpoint inWallet:wallet withIdentityEntity:invitationEntity.blockchainIdentity associatedToInvitation:self]; + self.link = invitationEntity.link; + self.name = invitationEntity.name; + self.tag = invitationEntity.tag; + self.chain = wallet.chain; + self.needsIdentityRetrieval = NO; + return self; +} + +- (instancetype)initWithInvitationLink:(NSString *)invitationLink + inWallet:(DSWallet *)wallet { + if (!(self = [super init])) return nil; + self.link = invitationLink; + self.wallet = wallet; + self.chain = wallet.chain; + self.needsIdentityRetrieval = YES; + self.createdLocally = NO; + return self; +} + +- (void)generateInvitationsExtendedPublicKeysWithPrompt:(NSString *)prompt + completion:(void (^_Nullable)(BOOL registered))completion { + __block DSAssetLockDerivationPath *derivationPathInvitationFunding = [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:self.wallet]; + if ([derivationPathInvitationFunding hasExtendedPublicKey]) { + completion(YES); + return; + } + [[DSAuthenticationManager sharedInstance] seedWithPrompt:prompt + forWallet:self.wallet + forAmount:0 + forceAuthentication:NO + completion:^(NSData *_Nullable seed, BOOL cancelled) { + if (!seed) { + completion(NO); + return; + } + [derivationPathInvitationFunding generateExtendedPublicKeyFromSeed:seed + storeUnderWalletUniqueId:self.wallet.uniqueIDString]; + completion(YES); + }]; +} + +- (void)registerInWalletForAssetLockTransaction:(DSAssetLockTransaction *)transaction { + NSAssert(self.identity != nil, @"The identity must already exist"); + [self.identity setInvitationAssetLockTransaction:transaction]; + [self registerInWalletForIdentityUniqueId:transaction.creditBurnIdentityIdentifier]; + //we need to also set the address of the funding transaction to being used so future identities past the initial gap limit are found + [transaction markInvitationAddressAsUsedInWallet:self.wallet]; +} + +- (void)registerInWalletForIdentityUniqueId:(UInt256)identityUniqueId { + [self.identity setInvitationUniqueId:identityUniqueId]; + [self registerInWallet]; +} + +- (BOOL)isRegisteredInWallet { + if (!self.wallet) return FALSE; + return [self.wallet containsInvitation:self]; +} + +- (void)registerInWallet { + NSAssert(self.identity.isOutgoingInvitation, @"The underlying identity is not from an invitation"); + if (!self.identity.isOutgoingInvitation) return; + [self.wallet registerInvitation:self]; + [self.identity saveInitial]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSInvitationDidUpdateNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSInvitationKey: self + }]; + }); +} + +- (void)updateInWallet { + [self saveInContext:[NSManagedObjectContext platformContext]]; +} + +- (BOOL)unregisterLocally { + NSAssert(self.identity.isOutgoingInvitation, @"The underlying identity is not from an invitation"); + if (!self.identity.isOutgoingInvitation) return FALSE; + if (self.identity.isRegistered) return FALSE; //if the invitation has already been used we can not unregister it + [self.wallet unregisterInvitation:self]; + [self deletePersistentObjectAndSave:YES inContext:[NSManagedObjectContext platformContext]]; + return TRUE; +} + +//- (void)verifyInvitationLinkWithCompletion:(void (^_Nullable)(DSTransaction *transaction, bool spent, NSError *error))completion +- (void)verifyInvitationLinkWithCompletion:(void (^_Nullable)(Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *result))completion + completionQueue:(dispatch_queue_t)completionQueue { + [DSInvitation verifyInvitationLink:self.link + onChain:self.wallet.chain + completion:completion + completionQueue:completionQueue]; +} + ++ (void)verifyInvitationLink:(NSString *)invitationLink + onChain:(DSChain *)chain + completion:(void (^_Nullable)(Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *result))completion +// completion:(void (^_Nullable)(DSTransaction *transaction, bool spent, NSError *error))completion + completionQueue:(dispatch_queue_t)completionQueue { +// DSDAPICoreNetworkService *coreNetworkService = chain.chainManager.DAPIClient.DAPICoreNetworkService; + NSURLComponents *components = [NSURLComponents componentsWithString:invitationLink]; + NSArray *queryItems = components.queryItems; + UInt256 assetLockTransactionHash = UINT256_ZERO; + BOOL isEmptyFundingPrivateKey = true; + for (NSURLQueryItem *queryItem in queryItems) { + if ([queryItem.name isEqualToString:@"assetlocktx"]) { + assetLockTransactionHash = queryItem.value.hexToData.UInt256; + } else if ([queryItem.name isEqualToString:@"pk"]) { +// isEmptyFundingPrivateKey = key_ecdsa_secret_key_is_empty(DChar(queryItem.value), chain.chainType); + isEmptyFundingPrivateKey = DECDSAKeyContainsSecretKey(DChar(queryItem.value), chain.chainType); + } + } + if (uint256_is_zero(assetLockTransactionHash)) { + if (completion) completion(nil); +// if (completion) completion(nil, NO, ERROR_INVITATION_FORMAT); + return; + } + if (isEmptyFundingPrivateKey) { + if (completion) completion(nil); + return; + } + + Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *result = dash_spv_platform_PlatformSDK_get_transaction_with_hash(chain.sharedRuntime, chain.sharedPlatformObj, u256_ctor_u(assetLockTransactionHash)); + + dispatch_async(completionQueue, ^{ + if (completion) completion(result); +// if (result->error) { +// Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error_destroy(result); +// if (completion) completion(nil, NO, ERROR_INVITATION_FORMAT); +// return; +// } +// if (!result->ok) { +// Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error_destroy(result); +// if (completion) completion(nil, NO, ERROR_INVALID_INV_TX); +// return; +// } +// [DSAssetLockTransaction ffi] +// if (completion) completion(tra) + }); + +// [coreNetworkService getTransactionWithHash:assetLockTransactionHash +// completionQueue:completionQueue +// success:^(DSTransaction *_Nonnull transaction) { +// NSAssert(transaction, @"transaction must not be null"); +// if (!transaction || ![transaction isKindOfClass:[DSAssetLockTransaction class]]) { +// if (completion) completion(nil, NO, ERROR_INVALID_INV_TX); +// return; +// } +// if (completion) completion(transaction, NO, nil); +// } +// failure:^(NSError *_Nonnull error) { +// if (completion) completion(nil, NO, ERROR_INVITATION_FORMAT); +// }]; +} + +- (void)acceptInvitationUsingWalletIndex:(uint32_t)index + setDashpayUsername:(NSString *)dashpayUsername + authenticationPrompt:(NSString *)authenticationMessage + identityRegistrationSteps:(DSIdentityRegistrationStep)identityRegistrationSteps + stepCompletion:(void (^_Nullable)(DSIdentityRegistrationStep stepCompleted))stepCompletion + completion:(void (^_Nullable)(DSIdentityRegistrationStep stepsCompleted, NSArray *errors))completion + completionQueue:(dispatch_queue_t)completionQueue { +// DSDAPICoreNetworkService *coreNetworkService = self.chain.chainManager.DAPIClient.DAPICoreNetworkService; + NSURLComponents *components = [NSURLComponents componentsWithString:self.link]; + NSArray *queryItems = components.queryItems; + UInt256 assetLockTransactionHash = UINT256_ZERO; + DMaybeOpaqueKey *fundingPrivateKey = nil; + for (NSURLQueryItem *queryItem in queryItems) { + if ([queryItem.name isEqualToString:@"assetlocktx"]) { + assetLockTransactionHash = queryItem.value.hexToData.UInt256; + } else if ([queryItem.name isEqualToString:@"pk"]) { + fundingPrivateKey = DMaybeOpaqueKeyWithPrivateKey(DKeyKindECDSA(), DChar(queryItem.value), self.chain.chainType); + } + } + if (uint256_is_zero(assetLockTransactionHash)) { + if (completion) completion(DSIdentityRegistrationStep_None, @[ERROR_INVITATION_FORMAT]); + return; + } + if (!fundingPrivateKey || !DOpaqueKeyHasPrivateKey(fundingPrivateKey->ok)) { + if (completion) completion(DSIdentityRegistrationStep_None, @[ERROR_INVALID_FUNDING_PRV_KEY]); + return; + } + + Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *result = dash_spv_platform_PlatformSDK_get_transaction_with_hash(self.chain.sharedRuntime, self.chain.sharedPlatformObj, u256_ctor_u(assetLockTransactionHash)); + dispatch_async(self.chain.chainManager.identitiesManager.identityQueue, ^{ + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error_destroy(result); + if (completion) completion(DSIdentityRegistrationStep_None, @[error]); + return; + } + DSAssetLockTransaction *tx = [DSAssetLockTransaction ffi_from:result->ok onChain:self.chain]; + Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error_destroy(result); + + self.identity = [[DSIdentity alloc] initAtIndex:index + withAssetLockTransaction:tx + inWallet:self.wallet]; + [self.identity setAssociatedInvitation:self]; + [self.identity addDashpayUsername:dashpayUsername save:NO]; + [self.identity registerInWalletForAssetLockTransaction:tx]; + BOOL success = [self.identity setExternalFundingPrivateKey:fundingPrivateKey]; + if (!success && fundingPrivateKey != NULL) + DMaybeOpaqueKeyDtor(fundingPrivateKey); + + NSAssert(success, @"We must be able to set the external funding private key"); + if (success) { + [self.identity generateIdentityExtendedPublicKeysWithPrompt:authenticationMessage + completion:^(BOOL registered) { + if (registered) { + [self.identity continueRegisteringIdentityOnNetwork:identityRegistrationSteps + stepsCompleted:DSIdentityRegistrationStep_L1Steps + stepCompletion:stepCompletion + completion:completion]; + } else if (completion) { + completion(DSIdentityRegistrationStep_None, @[ERROR_GEN_IDENTITY_KEYS]); + } + }]; + } else if (completion) { + completion(DSIdentityRegistrationStep_None, @[ERROR_SETTING_EXT_PRV_KEY]); + } + +// failure:^(NSError *_Nonnull error) { +// if (completion) completion(DSIdentityRegistrationStep_None, ERROR_INVITATION_FORMAT); + + }); +// [coreNetworkService getTransactionWithHash:assetLockTransactionHash +// completionQueue:self.chain.chainManager.identitiesManager.identityQueue +// success:^(DSTransaction *_Nonnull transaction) { +// NSAssert(transaction, @"transaction must not be null"); +// if (!transaction || ![transaction isKindOfClass:[DSAssetLockTransaction class]]) { +// if (completion) completion(DSIdentityRegistrationStep_None, ERROR_INVALID_INV_TX); +// return; +// } +// self.identity = [[DSIdentity alloc] initAtIndex:index +// withAssetLockTransaction:(DSAssetLockTransaction *)transaction +// withUsernameDictionary:nil +// inWallet:self.wallet]; +// [self.identity setAssociatedInvitation:self]; +// [self.identity addDashpayUsername:dashpayUsername save:NO]; +// [self.identity registerInWalletForAssetLockTransaction: (DSAssetLockTransaction *)transaction]; +// BOOL success = [self.identity setExternalFundingPrivateKey:fundingPrivateKey]; +// if (!success && fundingPrivateKey != NULL) +// DMaybeOpaqueKeyDtor(fundingPrivateKey); +// +// NSAssert(success, @"We must be able to set the external funding private key"); +// if (success) { +// [self.identity generateIdentityExtendedPublicKeysWithPrompt:authenticationMessage +// completion:^(BOOL registered) { +// if (registered) { +// [self.identity continueRegisteringIdentityOnNetwork:identityRegistrationSteps +// stepsCompleted:DSIdentityRegistrationStep_L1Steps +// stepCompletion:stepCompletion +// completion:completion]; +// } else if (completion) { +// completion(DSIdentityRegistrationStep_None, ERROR_GEN_IDENTITY_KEYS); +// } +// }]; +// } else if (completion) { +// completion(DSIdentityRegistrationStep_None, ERROR_SETTING_EXT_PRV_KEY); +// } +// } +// failure:^(NSError *_Nonnull error) { +// if (completion) completion(DSIdentityRegistrationStep_None, ERROR_INVITATION_FORMAT); +// }]; +} + +- (void)createInvitationFullLinkFromIdentity:(DSIdentity *)identity + completion:(void (^_Nullable)(BOOL cancelled, NSString *_Nullable invitationFullLink))completion { + if (!self.identity.registrationAssetLockTransaction.instantSendLockAwaitingProcessing) { + if (completion) completion(NO, nil); + return; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *senderUsername = identity.currentDashpayUsername; + NSString *senderDisplayName = identity.displayName; + NSString *senderAvatarPath = identity.avatarPath; + NSString *fundingTransactionHexString = uint256_reverse_hex(self.identity.registrationAssetLockTransaction.txHash); + __block DMaybeOpaqueKey *registrationFundingPrivateKey = self.identity.registrationFundingPrivateKey; + __block BOOL rCancelled = FALSE; + + if (!registrationFundingPrivateKey) { + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + dispatch_async(dispatch_get_main_queue(), ^{ + [[DSAuthenticationManager sharedInstance] seedWithPrompt:DSLocalizedString(@"Would you like to share this invitation?", nil) + forWallet:self.wallet + forAmount:0 + forceAuthentication:NO + completion:^(NSData *_Nullable seed, BOOL cancelled) { + rCancelled = cancelled; + if (seed) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:self.wallet]; + // TODO: cleanup? + registrationFundingPrivateKey = [path privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.identity.index] fromSeed:seed]; + dispatch_semaphore_signal(sem); + }); + } else { + dispatch_semaphore_signal(sem); + } + }]; + }); + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + } + if (!registrationFundingPrivateKey) { + dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(rCancelled, nil); }); + return; + } + //in WIF format + NSString *registrationFundingPrivateKeyString = [DSKeyManager serializedPrivateKey:registrationFundingPrivateKey->ok chainType:self.chain.chainType]; + NSString *serializedISLock = [self.identity.registrationAssetLockTransaction.instantSendLockAwaitingProcessing.toData hexString]; + NSURLComponents *components = [NSURLComponents componentsWithString:@"https://invitations.dashpay.io/applink"]; + NSMutableArray *queryItems = [NSMutableArray array]; + if (senderUsername) + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"du" value:senderUsername]]; + if (senderDisplayName) + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"display-name" value:senderDisplayName]]; + if (senderAvatarPath) + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"avatar-url" value:senderAvatarPath]]; + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"assetlocktx" value:fundingTransactionHexString.lowercaseString]]; + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"pk" value:registrationFundingPrivateKeyString]]; + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"islock" value:serializedISLock.lowercaseString]]; + components.queryItems = queryItems; + dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(NO, components.URL.absoluteString); }); + }); +} + +// MARK: Saving + +- (void)saveInContext:(NSManagedObjectContext *)context { + if (self.isTransient) return; + [context performBlockAndWait:^{ + BOOL changeOccured = NO; + NSMutableArray *updateEvents = [NSMutableArray array]; + DSBlockchainInvitationEntity *entity = [self invitationEntityInContext:context]; + if (entity.tag != self.tag) { + entity.tag = self.tag; + changeOccured = YES; + [updateEvents addObject:DSInvitationUpdateEvents]; + } + if (entity.name != self.name) { + entity.name = self.name; + changeOccured = YES; + [updateEvents addObject:DSInvitationUpdateEvents]; + } + if (entity.link != self.link) { + entity.link = self.link; + changeOccured = YES; + [updateEvents addObject:DSInvitationUpdateEventLink]; + } + if (changeOccured) { + [context ds_save]; + if (updateEvents.count) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSInvitationDidUpdateNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSInvitationKey: self, + DSInvitationUpdateEvents: updateEvents + }]; + }); + } + } + }]; +} + +// MARK: Deletion + +- (void)deletePersistentObjectAndSave:(BOOL)save inContext:(NSManagedObjectContext *)context { + [context performBlockAndWait:^{ + DSBlockchainInvitationEntity *invitationEntity = [self invitationEntityInContext:context]; + if (invitationEntity) { + [invitationEntity deleteObjectAndWait]; + if (save) [context ds_save]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSInvitationDidUpdateNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSInvitationKey: self + }]; + }); + }]; +} + +// MARK: Entity + +- (DSBlockchainInvitationEntity *)invitationEntity { + return [self invitationEntityInContext:[NSManagedObjectContext viewContext]]; +} + +- (DSBlockchainInvitationEntity *)invitationEntityInContext:(NSManagedObjectContext *)context { + __block DSBlockchainInvitationEntity *entity = nil; + [context performBlockAndWait:^{ + entity = [DSBlockchainInvitationEntity anyObjectInContext:context matching:@"blockchainIdentity.uniqueID == %@", self.identity.uniqueIDData]; + }]; + NSAssert(entity, @"An entity should always be found"); + return entity; +} + +- (NSString *)debugDescription { + return [[super debugDescription] stringByAppendingString:[NSString stringWithFormat:@" {%d-%@-%@}", self.identity.index, self.identity.currentDashpayUsername, self.identity.uniqueIdString]]; +} + + +@end diff --git a/DashSync/shared/Models/Identity/DSPotentialContact.h b/DashSync/shared/Models/Identity/DSPotentialContact.h index 39e618cb5..31607f34a 100644 --- a/DashSync/shared/Models/Identity/DSPotentialContact.h +++ b/DashSync/shared/Models/Identity/DSPotentialContact.h @@ -28,16 +28,14 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy) NSString *displayName; @property (nonatomic, copy) NSString *avatarPath; @property (nonatomic, copy) NSString *publicMessage; -@property (nonatomic, assign) UInt256 associatedBlockchainIdentityUniqueId; +@property (nonatomic, assign) UInt256 associatedIdentityUniqueId; - (instancetype)initWithUsername:(NSString *)username; - -- (instancetype)initWithUsername:(NSString *)username avatarPath:(NSString *)avatarPath publicMessage:(NSString *)publicMessage; - +- (instancetype)initWithUsername:(NSString *)username + avatarPath:(NSString *)avatarPath + publicMessage:(NSString *)publicMessage; - (instancetype)initWithDashpayUser:(DSDashpayUserEntity *)contactEntity; - - (void)addPublicKey:(NSValue *)key atIndex:(NSUInteger)index; - - (NSValue *)publicKeyAtIndex:(NSUInteger)index; @end diff --git a/DashSync/shared/Models/Identity/DSPotentialContact.m b/DashSync/shared/Models/Identity/DSPotentialContact.m index 0db48b80e..ffd1fd7bf 100644 --- a/DashSync/shared/Models/Identity/DSPotentialContact.m +++ b/DashSync/shared/Models/Identity/DSPotentialContact.m @@ -17,7 +17,7 @@ #import "DSPotentialContact.h" #import "BigIntTypes.h" -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" #import "DSDashpayUserEntity+CoreDataClass.h" @@ -35,13 +35,15 @@ - (instancetype)initWithUsername:(NSString *)username { self = [super init]; if (self) { _username = username; - _associatedBlockchainIdentityUniqueId = UINT256_ZERO; + _associatedIdentityUniqueId = UINT256_ZERO; self.keyDictionary = [NSMutableDictionary dictionary]; } return self; } -- (instancetype)initWithUsername:(NSString *)username avatarPath:(NSString *)avatarPath publicMessage:(NSString *)publicMessage { +- (instancetype)initWithUsername:(NSString *)username + avatarPath:(NSString *)avatarPath + publicMessage:(NSString *)publicMessage { self = [self initWithUsername:username]; if (self) { _avatarPath = avatarPath; @@ -52,15 +54,17 @@ - (instancetype)initWithUsername:(NSString *)username avatarPath:(NSString *)ava - (instancetype)initWithDashpayUser:(DSDashpayUserEntity *)dashpayUserEntity { DSBlockchainIdentityUsernameEntity *usernameEntity = [dashpayUserEntity.associatedBlockchainIdentity.usernames anyObject]; - self = [self initWithUsername:usernameEntity.stringValue avatarPath:dashpayUserEntity.avatarPath publicMessage:dashpayUserEntity.publicMessage]; + self = [self initWithUsername:usernameEntity.stringValue + avatarPath:dashpayUserEntity.avatarPath + publicMessage:dashpayUserEntity.publicMessage]; if (self) { - _associatedBlockchainIdentityUniqueId = dashpayUserEntity.associatedBlockchainIdentity.uniqueID.UInt256; + _associatedIdentityUniqueId = dashpayUserEntity.associatedBlockchainIdentity.uniqueID.UInt256; } return self; } - (NSString *)debugDescription { - return [NSString stringWithFormat:@"%@ - %@ - %@", [super debugDescription], self.username, uint256_hex(self.associatedBlockchainIdentityUniqueId)]; + return [NSString stringWithFormat:@"%@ - %@ - %@", [super debugDescription], self.username, uint256_hex(self.associatedIdentityUniqueId)]; } - (void)addPublicKey:(NSValue *)key atIndex:(NSUInteger)index { diff --git a/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.h b/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.h index be1dcc43e..9cddcf733 100644 --- a/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.h +++ b/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.h @@ -16,39 +16,53 @@ // #import "BigIntTypes.h" +#import "dash_spv_apple_bindings.h" +#import "DSKeyManager.h" #import "DSDashPlatform.h" #import NS_ASSUME_NONNULL_BEGIN -@class DSBlockchainIdentity, DSAccount, DSBlockchainIdentityRegistrationTransition, DSFriendRequestEntity, DSPotentialContact, DSDashpayUserEntity, DSIncomingFundsDerivationPath, DSDerivationPathEntity; +@class DSIdentity, DSAccount, DSFriendRequestEntity, DSPotentialContact, DSDashpayUserEntity, DSIncomingFundsDerivationPath, DSDerivationPathEntity; @interface DSPotentialOneWayFriendship : NSObject @property (nonatomic, readonly) DSAccount *account; -@property (nonatomic, readonly) DSBlockchainIdentity *destinationBlockchainIdentity; -@property (nonatomic, readonly) DSBlockchainIdentity *sourceBlockchainIdentity; //this is the holder of the contacts, not the destination +@property (nonatomic, readonly) DSIdentity *destinationIdentity; +@property (nonatomic, readonly) DSIdentity *sourceIdentity; //this is the holder of the contacts, not the destination @property (nonatomic, readonly) NSTimeInterval createdAt; @property (nonatomic, readonly) DSIncomingFundsDerivationPath *derivationPath; @property (nonatomic, readonly) uint32_t sourceKeyIndex; @property (nonatomic, readonly) uint32_t destinationKeyIndex; +@property (nonatomic, readonly) UInt256 destinationIdentityUniqueId; -- (instancetype)initWithDestinationBlockchainIdentity:(DSBlockchainIdentity *)destinationBlockchainIdentity destinationKeyIndex:(uint32_t)destinationKeyIndex sourceBlockchainIdentity:(DSBlockchainIdentity *)sourceBlockchainIdentity sourceKeyIndex:(uint32_t)sourceKeyIndex account:(DSAccount *)account; +- (instancetype)initWithDestinationIdentity:(DSIdentity *)destinationIdentity + destinationKeyIndex:(uint32_t)destinationKeyIndex + sourceIdentity:(DSIdentity *)sourceIdentity + sourceKeyIndex:(uint32_t)sourceKeyIndex + account:(DSAccount *)account; -- (instancetype)initWithDestinationBlockchainIdentity:(DSBlockchainIdentity *)destinationBlockchainIdentity destinationKeyIndex:(uint32_t)destinationKeyIndex sourceBlockchainIdentity:(DSBlockchainIdentity *)sourceBlockchainIdentity sourceKeyIndex:(uint32_t)sourceKeyIndex account:(DSAccount *)account createdAt:(NSTimeInterval)createdAt; +- (instancetype)initWithDestinationIdentity:(DSIdentity *)destinationIdentity + destinationKeyIndex:(uint32_t)destinationKeyIndex + sourceIdentity:(DSIdentity *)sourceIdentity + sourceKeyIndex:(uint32_t)sourceKeyIndex + account:(DSAccount *)account + createdAt:(NSTimeInterval)createdAt; //-(DSFriendRequestEntity*)outgoingFriendRequest; -- (DSFriendRequestEntity *)outgoingFriendRequestForDashpayUserEntity:(DSDashpayUserEntity *)dashpayUserEntity atTimestamp:(NSTimeInterval)timestamp; +- (DSFriendRequestEntity *)outgoingFriendRequestForDashpayUserEntity:(DSDashpayUserEntity *)dashpayUserEntity + atTimestamp:(NSTimeInterval)timestamp; -- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)friendRequestEntity inContext:(NSManagedObjectContext *)context; -- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)friendRequestEntity; +- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)entity + inContext:(NSManagedObjectContext *)context; +- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)entity; - (void)createDerivationPathAndSaveExtendedPublicKeyWithCompletion:(void (^)(BOOL success, DSIncomingFundsDerivationPath *incomingFundsDerivationPath))completion; -- (void)encryptExtendedPublicKeyWithCompletion:(void (^)(BOOL success))completion; +- (BOOL)encryptExtendedPublicKey; -- (DPDocument *)contactRequestDocumentWithEntropy:(NSData *)entropyData; +- (DValue *)toValue; @end diff --git a/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.m b/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.m index ecd9b15ec..379ec574b 100644 --- a/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.m +++ b/DashSync/shared/Models/Identity/DSPotentialOneWayFriendship.m @@ -15,13 +15,14 @@ // limitations under the License. // -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSPotentialOneWayFriendship.h" #import "DSAccount.h" -#import "DSBlockchainIdentity+Protected.h" +#import "DSIdentity+Protected.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSDashPlatform.h" #import "DSDashpayUserEntity+CoreDataClass.h" +#import "DSDerivationPath+Protected.h" #import "DSDerivationPathEntity+CoreDataClass.h" #import "DSDerivationPathFactory.h" #import "DSFriendRequestEntity+CoreDataClass.h" @@ -36,11 +37,11 @@ @interface DSPotentialOneWayFriendship () @property (nonatomic, strong) DSAccount *account; -@property (nonatomic, strong) DSBlockchainIdentity *sourceBlockchainIdentity; -@property (nonatomic, strong) DSBlockchainIdentity *destinationBlockchainIdentity; +@property (nonatomic, strong) DSIdentity *sourceIdentity; +@property (nonatomic, strong) DSIdentity *destinationIdentity; @property (nonatomic, strong) DSPotentialContact *destinationContact; @property (nonatomic, strong) DSIncomingFundsDerivationPath *fundsDerivationPathForContact; -@property (nonatomic, assign) OpaqueKey *extendedPublicKey; +@property (nonatomic, assign) DMaybeOpaqueKey *extendedPublicKey; @property (nonatomic, strong) NSData *encryptedExtendedPublicKeyData; @property (nonatomic, assign) uint32_t sourceKeyIndex; @property (nonatomic, assign) uint32_t destinationKeyIndex; @@ -50,15 +51,29 @@ @interface DSPotentialOneWayFriendship () @implementation DSPotentialOneWayFriendship -- (instancetype)initWithDestinationBlockchainIdentity:(DSBlockchainIdentity *)destinationBlockchainIdentity destinationKeyIndex:(uint32_t)destinationKeyIndex sourceBlockchainIdentity:(DSBlockchainIdentity *)sourceBlockchainIdentity sourceKeyIndex:(uint32_t)sourceKeyIndex account:(DSAccount *)account { - return [self initWithDestinationBlockchainIdentity:destinationBlockchainIdentity destinationKeyIndex:destinationKeyIndex sourceBlockchainIdentity:sourceBlockchainIdentity sourceKeyIndex:sourceKeyIndex account:account createdAt:[[NSDate date] timeIntervalSince1970]]; -} - -- (instancetype)initWithDestinationBlockchainIdentity:(DSBlockchainIdentity *)destinationBlockchainIdentity destinationKeyIndex:(uint32_t)destinationKeyIndex sourceBlockchainIdentity:(DSBlockchainIdentity *)sourceBlockchainIdentity sourceKeyIndex:(uint32_t)sourceKeyIndex account:(DSAccount *)account createdAt:(NSTimeInterval)createdAt { +- (instancetype)initWithDestinationIdentity:(DSIdentity *)destinationIdentity + destinationKeyIndex:(uint32_t)destinationKeyIndex + sourceIdentity:(DSIdentity *)sourceIdentity + sourceKeyIndex:(uint32_t)sourceKeyIndex + account:(DSAccount *)account { + return [self initWithDestinationIdentity:destinationIdentity + destinationKeyIndex:destinationKeyIndex + sourceIdentity:sourceIdentity + sourceKeyIndex:sourceKeyIndex + account:account + createdAt:[[NSDate date] timeIntervalSince1970]]; +} + +- (instancetype)initWithDestinationIdentity:(DSIdentity *)destinationIdentity + destinationKeyIndex:(uint32_t)destinationKeyIndex + sourceIdentity:(DSIdentity *)sourceIdentity + sourceKeyIndex:(uint32_t)sourceKeyIndex + account:(DSAccount *)account + createdAt:(NSTimeInterval)createdAt { if (!(self = [super init])) return nil; - self.destinationBlockchainIdentity = destinationBlockchainIdentity; + self.destinationIdentity = destinationIdentity; self.account = account; - self.sourceBlockchainIdentity = sourceBlockchainIdentity; + self.sourceIdentity = sourceIdentity; self.sourceKeyIndex = sourceKeyIndex; self.destinationKeyIndex = destinationKeyIndex; self.createdAt = createdAt; @@ -66,23 +81,23 @@ - (instancetype)initWithDestinationBlockchainIdentity:(DSBlockchainIdentity *)de return self; } -- (UInt256)destinationBlockchainIdentityUniqueId { - if (self.destinationBlockchainIdentity) { - return self.destinationBlockchainIdentity.uniqueID; +- (UInt256)destinationIdentityUniqueId { + if (self.destinationIdentity) { + return self.destinationIdentity.uniqueID; } else if (self.destinationContact) { - return self.destinationContact.associatedBlockchainIdentityUniqueId; + return self.destinationContact.associatedIdentityUniqueId; } return UINT256_ZERO; } -- (OpaqueKey *)sourceKeyAtIndex { - NSAssert(self.sourceBlockchainIdentity != nil, @"The source identity should be present"); - return [self.sourceBlockchainIdentity keyAtIndex:self.sourceKeyIndex]; +- (DOpaqueKey *)sourceKeyAtIndex { + NSAssert(self.sourceIdentity != nil, @"The source identity should be present"); + return [self.sourceIdentity keyAtIndex:self.sourceKeyIndex]; } -- (OpaqueKey *)destinationKeyAtIndex { - if (self.destinationBlockchainIdentity) { - return [self.destinationBlockchainIdentity keyAtIndex:self.destinationKeyIndex]; +- (DOpaqueKey *)destinationKeyAtIndex { + if (self.destinationIdentity) { + return [self.destinationIdentity keyAtIndex:self.destinationKeyIndex]; } else if (self.destinationContact) { return [self.destinationContact publicKeyAtIndex:self.destinationKeyIndex].pointerValue; } @@ -90,114 +105,100 @@ - (OpaqueKey *)destinationKeyAtIndex { } - (DSIncomingFundsDerivationPath *)derivationPath { - NSAssert(uint256_is_not_zero([self destinationBlockchainIdentityUniqueId]), @"destinationBlockchainIdentityUniqueId must not be null"); - DSIncomingFundsDerivationPath *fundsDerivationPathForContact = [DSIncomingFundsDerivationPath - contactBasedDerivationPathWithDestinationBlockchainIdentityUniqueId:[self destinationBlockchainIdentityUniqueId] - sourceBlockchainIdentityUniqueId:self.sourceBlockchainIdentity.uniqueID - forAccountNumber:self.account.accountNumber - onChain:self.sourceBlockchainIdentity.wallet.chain]; - fundsDerivationPathForContact.account = self.account; - return fundsDerivationPathForContact; + NSAssert(uint256_is_not_zero([self destinationIdentityUniqueId]), @"destinationBlockchainIdentityUniqueId must not be null"); + return [DSIncomingFundsDerivationPath contactBasedDerivationPathWithDestinationIdentityUniqueId:[self destinationIdentityUniqueId] + sourceIdentityUniqueId:self.sourceIdentity.uniqueID + forAccount:self.account + onChain:self.sourceIdentity.wallet.chain]; } - (void)createDerivationPathAndSaveExtendedPublicKeyWithCompletion:(void (^)(BOOL success, DSIncomingFundsDerivationPath *incomingFundsDerivationPath))completion { - NSAssert(uint256_is_not_zero([self destinationBlockchainIdentityUniqueId]), @"destinationBlockchainIdentityUniqueId must not be null"); + NSAssert(uint256_is_not_zero([self destinationIdentityUniqueId]), @"destinationBlockchainIdentityUniqueId must not be null"); self.fundsDerivationPathForContact = [self derivationPath]; DSDerivationPath *masterContactsDerivationPath = [self.account masterContactsDerivationPath]; - self.extendedPublicKey = [self.fundsDerivationPathForContact generateExtendedPublicKeyFromParentDerivationPath:masterContactsDerivationPath storeUnderWalletUniqueId:nil]; - if (completion) { - completion(YES, self.fundsDerivationPathForContact); - } -} - -- (void)encryptExtendedPublicKeyWithCompletion:(void (^)(BOOL success))completion { - NSAssert(self.extendedPublicKey, @"Problem creating extended public key for potential contact?"); - __weak typeof(self) weakSelf = self; - OpaqueKey *recipientKey = [self destinationKeyAtIndex]; - [self.sourceBlockchainIdentity encryptData:[DSKeyManager extendedPublicKeyData:self.extendedPublicKey] - withKeyAtIndex:self.sourceKeyIndex - forRecipientKey:recipientKey - completion:^(NSData *_Nonnull encryptedData) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - completion(NO); - } - return; - } - strongSelf.encryptedExtendedPublicKeyData = encryptedData; - if (completion) { - completion(YES); - } - }]; + if (completion) completion(YES, self.fundsDerivationPathForContact); +} + +- (BOOL)encryptExtendedPublicKey { + NSAssert(self.extendedPublicKey && self.extendedPublicKey->ok, @"Problem creating extended public key for potential contact?"); + DOpaqueKey *recipient_key = [self destinationKeyAtIndex]; + NSParameterAssert(recipient_key); + DMaybeKeyData *pub_key_data = DOpaqueKeyExtendedPublicKeyData(self.extendedPublicKey->ok); + NSParameterAssert(pub_key_data->ok); + NSData *data = NSDataFromPtr(pub_key_data->ok); + DMaybeKeyDataDtor(pub_key_data); + DKeyKind *kind = DOpaqueKeyKind(recipient_key); + DMaybeOpaqueKey *privateKey = [self.sourceIdentity privateKeyAtIndex:self.sourceKeyIndex ofType:kind]; + Slice_u8 *slice = slice_ctor(data); + DMaybeKeyData *result = DOpaqueKeyEncryptData(privateKey->ok, recipient_key, slice); + NSData *encrypted = result->ok ? NSDataFromPtr(result->ok) : nil; + DMaybeKeyDataDtor(result); + DMaybeOpaqueKeyDtor(privateKey); + self.encryptedExtendedPublicKeyData = encrypted; + return encrypted; } - (uint32_t)createAccountReference { // TODO: make test - OpaqueKey *key = [self sourceKeyAtIndex]; - return key_create_account_reference(key, self.extendedPublicKey, self.account.accountNumber); + return DOpaqueKeyCreateAccountRef([self sourceKeyAtIndex], self.extendedPublicKey->ok, self.account.accountNumber); } -- (DPDocument *)contactRequestDocumentWithEntropy:(NSData *)entropyData { - NSAssert(uint256_is_not_zero([self destinationBlockchainIdentityUniqueId]), @"the destination contact's associatedBlockchainIdentityUniqueId must be set before making a friend request"); - NSAssert([self.encryptedExtendedPublicKeyData length] > 0, @"The encrypted extended public key must exist"); - NSAssert(self.extendedPublicKey, @"Problem creating extended public key for potential contact?"); - NSError *error = nil; - - uint64_t createAtMs = (self.createdAt) * 1000; - DSStringValueDictionary *data = @{ - @"$createdAt": @(createAtMs), - @"toUserId": uint256_data([self destinationBlockchainIdentityUniqueId]), - @"encryptedPublicKey": self.encryptedExtendedPublicKeyData, - @"senderKeyIndex": @(self.sourceKeyIndex), - @"recipientKeyIndex": @(self.destinationKeyIndex), - @"accountReference": @([self createAccountReference]) - }; - - - DPDocument *contact = [self.sourceBlockchainIdentity.dashpayDocumentFactory documentOnTable:@"contactRequest" withDataDictionary:data usingEntropy:entropyData error:&error]; - NSAssert(error == nil, @"Failed to build a contact"); - return contact; +- (DValue *)toValue { + uintptr_t field_count = 6; + DValuePair **values = malloc(sizeof(DValuePair) * field_count); + values[0] = DValueTextU64PairCtor(@"$createdAt", self.createdAt * 1000); + values[1] = DValueTextIdentifierPairCtor(@"toUserId", platform_value_Hash256_ctor(u256_ctor_u([self destinationIdentityUniqueId]))); + values[2] = DValueTextBytesPairCtor(@"encryptedPublicKey", self.encryptedExtendedPublicKeyData); + values[3] = DValueTextU32PairCtor(@"senderKeyIndex", self.sourceKeyIndex); + values[4] = DValueTextU32PairCtor(@"recipientKeyIndex", self.destinationKeyIndex); + values[5] = DValueTextU32PairCtor(@"accountReference", [self createAccountReference]); + return platform_value_Value_Map_ctor(DValueMapCtor(DValuePairVecCtor(field_count, values))); } -- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)friendRequestEntity inContext:(NSManagedObjectContext *)context { - [self.fundsDerivationPathForContact storeExtendedPublicKeyUnderWalletUniqueId:self.account.wallet.uniqueIDString]; +- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)entity + inContext:(NSManagedObjectContext *)context { +// [self.fundsDerivationPathForContact storeExtendedPublicKeyUnderWalletUniqueId:self.account.wallet.uniqueIDString]; + + NSData *data = [DSKeyManager extendedPublicKeyData:self.fundsDerivationPathForContact.extendedPublicKey->ok]; + setKeychainData(data, [self.fundsDerivationPathForContact walletBasedExtendedPublicKeyLocationStringForWalletUniqueID:self.account.wallet.uniqueIDString], NO); __block DSDerivationPathEntity *fundsDerivationPathEntity = nil; [context performBlockAndWait:^{ - fundsDerivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self.fundsDerivationPathForContact associateWithFriendRequest:friendRequestEntity - inContext:context]; + fundsDerivationPathEntity = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self.fundsDerivationPathForContact + associateWithFriendRequest:entity + inContext:context]; }]; return fundsDerivationPathEntity; } -- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)friendRequestEntity { - return [self storeExtendedPublicKeyAssociatedWithFriendRequest:friendRequestEntity inContext:friendRequestEntity.managedObjectContext]; +- (DSDerivationPathEntity *)storeExtendedPublicKeyAssociatedWithFriendRequest:(DSFriendRequestEntity *)entity { + return [self storeExtendedPublicKeyAssociatedWithFriendRequest:entity + inContext:entity.managedObjectContext]; } -- (DSFriendRequestEntity *)outgoingFriendRequestForDashpayUserEntity:(DSDashpayUserEntity *)dashpayUserEntity atTimestamp:(NSTimeInterval)timestamp { +- (DSFriendRequestEntity *)outgoingFriendRequestForDashpayUserEntity:(DSDashpayUserEntity *)dashpayUserEntity + atTimestamp:(NSTimeInterval)timestamp { NSParameterAssert(dashpayUserEntity); - NSAssert(uint256_eq(dashpayUserEntity.associatedBlockchainIdentity.uniqueID.UInt256, [self destinationBlockchainIdentityUniqueId]), @"contact entity must match"); - NSAssert(self.sourceBlockchainIdentity.matchingDashpayUserInViewContext, @"The own contact of the source Identity must be set"); + NSAssert(uint256_eq(dashpayUserEntity.associatedBlockchainIdentity.uniqueID.UInt256, [self destinationIdentityUniqueId]), @"contact entity must match"); + NSAssert(self.sourceIdentity.matchingDashpayUserInViewContext, @"The own contact of the source Identity must be set"); DSFriendRequestEntity *friendRequestEntity = [DSFriendRequestEntity managedObjectInBlockedContext:dashpayUserEntity.managedObjectContext]; - friendRequestEntity.sourceContact = [self.sourceBlockchainIdentity matchingDashpayUserInContext:friendRequestEntity.managedObjectContext]; + friendRequestEntity.sourceContact = [self.sourceIdentity matchingDashpayUserInContext:friendRequestEntity.managedObjectContext]; friendRequestEntity.destinationContact = dashpayUserEntity; NSAssert(friendRequestEntity.sourceContact != friendRequestEntity.destinationContact, @"This must be different contacts"); friendRequestEntity.derivationPath = [DSDerivationPathEntity derivationPathEntityMatchingDerivationPath:self.fundsDerivationPathForContact inContext:dashpayUserEntity.managedObjectContext]; NSAssert(friendRequestEntity.derivationPath, @"There must be a derivation path"); friendRequestEntity.account = friendRequestEntity.derivationPath.account; friendRequestEntity.timestamp = timestamp; - [friendRequestEntity finalizeWithFriendshipIdentifier]; return friendRequestEntity; } //-(DSFriendRequestEntity*)outgoingFriendRequest { -// NSAssert(uint256_is_not_zero(self.destinationContact.associatedBlockchainIdentityUniqueId), @"destination contact must be known"); -// DSDashpayUserEntity * dashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentityUniqueId == %@",uint256_data(self.destinationContact.associatedBlockchainIdentityUniqueId)]; +// NSAssert(uint256_is_not_zero(self.destinationContact.associatedIdentityUniqueId), @"destination contact must be known"); +// DSDashpayUserEntity * dashpayUserEntity = [DSDashpayUserEntity anyObjectInContext:context matching:@"associatedBlockchainIdentityUniqueId == %@",uint256_data(self.destinationContact.associatedIdentityUniqueId)]; // if (!dashpayUserEntity) { // dashpayUserEntity = [DSDashpayUserEntity managedObject]; // dashpayUserEntity.avatarPath = self.destinationContact.avatarPath; @@ -210,28 +211,21 @@ - (DSFriendRequestEntity *)outgoingFriendRequestForDashpayUserEntity:(DSDashpayU //} - (BOOL)isEqual:(id)object { - if (self == object) { - return TRUE; - } - - if (![object isKindOfClass:[self class]]) { - return FALSE; - } - - if (uint256_eq(self.destinationBlockchainIdentity.uniqueID, ((DSPotentialOneWayFriendship *)object).destinationBlockchainIdentity.uniqueID) && uint256_eq(self.sourceBlockchainIdentity.uniqueID, ((DSPotentialOneWayFriendship *)object).sourceBlockchainIdentity.uniqueID) && + if (self == object) return TRUE; + if (![object isKindOfClass:[self class]]) return FALSE; + if (uint256_eq(self.destinationIdentity.uniqueID, ((DSPotentialOneWayFriendship *)object).destinationIdentity.uniqueID) && uint256_eq(self.sourceIdentity.uniqueID, ((DSPotentialOneWayFriendship *)object).sourceIdentity.uniqueID) && self.account.accountNumber == ((DSPotentialOneWayFriendship *)object).account.accountNumber) { return TRUE; } - return FALSE; } - (NSUInteger)hash { - return self.destinationBlockchainIdentity.hash ^ self.sourceBlockchainIdentity.hash ^ self.account.accountNumber; + return self.destinationIdentity.hash ^ self.sourceIdentity.hash ^ self.account.accountNumber; } - (NSString *)debugDescription { - return [NSString stringWithFormat:@"%@ - s:%@ d:%@", [super debugDescription], self.sourceBlockchainIdentity.currentDashpayUsername, self.destinationBlockchainIdentity.currentDashpayUsername]; + return [NSString stringWithFormat:@"%@ - s:%@ d:%@", [super debugDescription], self.sourceIdentity.currentDashpayUsername, self.destinationIdentity.currentDashpayUsername]; } @end diff --git a/DashSync/shared/Models/Identity/DSTransientDashpayUser.h b/DashSync/shared/Models/Identity/DSTransientDashpayUser.h index f292a884b..5c8eea19f 100644 --- a/DashSync/shared/Models/Identity/DSTransientDashpayUser.h +++ b/DashSync/shared/Models/Identity/DSTransientDashpayUser.h @@ -16,6 +16,7 @@ // #import +#import "dash_spv_apple_bindings.h" NS_ASSUME_NONNULL_BEGIN @@ -32,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSTimeInterval updatedAt; - (instancetype)initWithDashpayProfileDocument:(NSDictionary *)profileDocument; +- (instancetype)initWithDocument:(DDocument *)document; @end diff --git a/DashSync/shared/Models/Identity/DSTransientDashpayUser.m b/DashSync/shared/Models/Identity/DSTransientDashpayUser.m index d9654a4eb..97dc62649 100644 --- a/DashSync/shared/Models/Identity/DSTransientDashpayUser.m +++ b/DashSync/shared/Models/Identity/DSTransientDashpayUser.m @@ -15,6 +15,7 @@ // limitations under the License. // +#import "DSKeyManager.h" #import "DSTransientDashpayUser+Protected.h" @implementation DSTransientDashpayUser @@ -34,5 +35,36 @@ - (instancetype)initWithDashpayProfileDocument:(NSDictionary *)profileDocument { } return self; } +- (instancetype)initWithDocument:(DDocument *)document { + self = [super init]; + if (self) { + DSLog(@"DSTransientDashpayUser: "); + dash_spv_platform_document_print_document(document); + switch (document->tag) { + case dpp_document_Document_V0: { +// break; + dpp_document_v0_DocumentV0 *v0 = document->v0; + self.revision = v0->revision ? (uint32_t) v0->revision->_0 : 0; + self.createdAt = v0->created_at->_0; + self.updatedAt = v0->updated_at->_0; + self.documentIdentifier = NSDataFromPtr(v0->id->_0->_0); + self.avatarPath = DGetTextDocProperty(document, @"avatarUrl"); +// v0 : id:EH766JkXC948sHdCovfMUJd1cmogZNtX5RsdzLibVEb9 owner_id:7im3W3XdyhQQ7tgaL8gwXV2HRD9UtipVRUR4KPgyWRMR +// created_at:2024-10-21 18:38:20 +// updated_at:2025-01-28 06:53:28 +// avatarFingerprint:bytes 5580290000410106 +// avatarHash:bytes32 7bhfqnTVCmm7IVNhUpclZaBiGUgAdKakVgw+TigY+ac= avatarUrl:string https://i.imgur.com/[...(32)] displayName:string disp publicMessage:string ab + self.avatarFingerprint = DGetBytesDocProperty(document, @"avatarFingerprint"); + self.avatarHash = DGetBytes32DocProperty(document, @"avatarHash"); + self.publicMessage = DGetTextDocProperty(document, @"publicMessage"); + self.displayName = DGetTextDocProperty(document, @"displayName"); + break; + } + default: + break; + } + } + return self; +} @end diff --git a/DashSync/shared/Models/Identity/DSUsernameFullPathSaveContext.h b/DashSync/shared/Models/Identity/DSUsernameFullPathSaveContext.h new file mode 100644 index 000000000..2bd7f86d3 --- /dev/null +++ b/DashSync/shared/Models/Identity/DSUsernameFullPathSaveContext.h @@ -0,0 +1,32 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "DSIdentity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSUsernameFullPathSaveContext : NSObject +@property (nonatomic, assign) NSArray *usernames; +@property (nonatomic, assign) DSIdentity *identity; +@property (nonatomic, assign) NSManagedObjectContext *context; ++ (instancetype)contextWithUsernames:(NSArray *)usernames forIdentity:(DSIdentity *)identity inContext:(NSManagedObjectContext *)context; +- (void)setAndSaveUsernameFullPaths:(DUsernameStatus *)status; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Identity/DSUsernameFullPathSaveContext.m b/DashSync/shared/Models/Identity/DSUsernameFullPathSaveContext.m new file mode 100644 index 000000000..8068686ca --- /dev/null +++ b/DashSync/shared/Models/Identity/DSUsernameFullPathSaveContext.m @@ -0,0 +1,36 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "DSUsernameFullPathSaveContext.h" +#import "DSIdentity+Username.h" + +@implementation DSUsernameFullPathSaveContext + ++ (instancetype)contextWithUsernames:(NSArray *)usernames forIdentity:(DSIdentity *)identity inContext:(NSManagedObjectContext *)context { + DSUsernameFullPathSaveContext *ctx = [[DSUsernameFullPathSaveContext alloc] init]; + ctx.usernames = usernames; + ctx.identity = identity; + ctx.context = context; + return ctx; +} +- (void)setAndSaveUsernameFullPaths:(DUsernameStatus *)status { + [self.identity setAndSaveUsernameFullPaths:self.usernames + toStatus:status + inContext:self.context]; +} +@end + diff --git a/DashSync/shared/Models/Keys/NSData+Encryption.h b/DashSync/shared/Models/Keys/NSData+Encryption.h index 2a209a043..825cc241c 100644 --- a/DashSync/shared/Models/Keys/NSData+Encryption.h +++ b/DashSync/shared/Models/Keys/NSData+Encryption.h @@ -16,16 +16,16 @@ // #import -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" NS_ASSUME_NONNULL_BEGIN @interface NSData (Encryption) -- (nullable NSData *)encryptWithSecretKey:(OpaqueKey *)secretKey forPublicKey:(OpaqueKey *)peerPubKey; -- (nullable NSData *)encryptWithSecretKey:(OpaqueKey *)secretKey forPublicKey:(OpaqueKey *)peerPubKey usingInitializationVector:(NSData *)initializationVector; -- (nullable NSData *)decryptWithSecretKey:(OpaqueKey *)secretKey fromPublicKey:(OpaqueKey *)peerPubKey; -- (nullable NSData *)encryptWithDHKey:(OpaqueKey *)dhKey; +- (nullable NSData *)encryptWithSecretKey:(DOpaqueKey *)secretKey forPublicKey:(DOpaqueKey *)peerPubKey; +- (nullable NSData *)encryptWithSecretKey:(DOpaqueKey *)secretKey forPublicKey:(DOpaqueKey *)peerPubKey usingInitializationVector:(NSData *)initializationVector; +- (nullable NSData *)decryptWithSecretKey:(DOpaqueKey *)secretKey fromPublicKey:(DOpaqueKey *)peerPubKey; +- (nullable NSData *)encryptWithDHKey:(DOpaqueKey *)dhKey; - (nullable NSData *)encapsulatedDHEncryptionWithKeys:(NSArray *)keys; - (nullable NSData *)encapsulatedDHDecryptionWithKeys:(NSArray *)keys; - (nullable NSData *)encapsulatedDHEncryptionWithKeys:(NSArray *)keys usingInitializationVector:(NSData *)initializationVector; diff --git a/DashSync/shared/Models/Keys/NSData+Encryption.mm b/DashSync/shared/Models/Keys/NSData+Encryption.mm index 9bd36d597..3bb2a57d3 100644 --- a/DashSync/shared/Models/Keys/NSData+Encryption.mm +++ b/DashSync/shared/Models/Keys/NSData+Encryption.mm @@ -18,267 +18,43 @@ #import "DSKeyManager.h" #import "NSData+Encryption.h" -//#import - NS_ASSUME_NONNULL_BEGIN -//static NSData *_Nullable AES256EncryptDecrypt(CCOperation operation, -// NSData *data, -// const void *key, -// const void *iv) { -// size_t bufferSize = [data length] + kCCBlockSizeAES128; -// void *buffer = malloc(bufferSize); -// -// size_t encryptedSize = 0; -// CCCryptorStatus cryptStatus = CCCrypt(operation, -// kCCAlgorithmAES, -// kCCOptionPKCS7Padding, -// key, -// kCCKeySizeAES256, -// iv, -// data.bytes, -// data.length, -// buffer, -// bufferSize, -// &encryptedSize); -// -// if (cryptStatus == kCCSuccess) { -// NSData *result = [NSData dataWithBytes:buffer length:encryptedSize]; -// free(buffer); -// -// return result; -// } else { -// free(buffer); -// -// return nil; -// } -//} - @implementation NSData (Encryption) -//+ (NSData *)randomInitializationVectorOfSize:(NSUInteger)size { -// unsigned char iv[size]; //16 -// for (int i = 0; i < sizeof(iv); i++) { -// iv[i] = arc4random_uniform(UCHAR_MAX - 1); -// } -// return [NSData dataWithBytes:&iv length:size]; -//} -// -//- (nullable NSData *)encryptWithBLSSecretKey:(DSBLSKey *)secretKey forPublicKey:(DSBLSKey *)publicKey { -// NSData *ivData = [NSData randomInitializationVectorOfSize:kCCBlockSizeAES128]; -// -// return [self encryptWithBLSSecretKey:secretKey forPublicKey:publicKey usingInitializationVector:ivData]; -//} -// -//- (nullable NSData *)encryptWithBLSSecretKey:(DSBLSKey *)secretKey forPublicKey:(DSBLSKey *)peerPubKey usingInitializationVector:(NSData *)ivData { -// if (secretKey.useLegacy != peerPubKey.useLegacy) { -// NSLog(@"encryptWithBLSSecretKey: BLS keys are from different mode %u != %u", secretKey.useLegacy, peerPubKey.useLegacy); -// return NULL; -// } -// bls::G1Element pk = secretKey.blsPrivateKey * peerPubKey.blsPublicKey; -// -// std::vector symKey = pk.Serialize(secretKey.useLegacy); -// symKey.resize(32); -// -// NSData *resultData = AES256EncryptDecrypt(kCCEncrypt, self, (uint8_t *)symKey.data(), ivData.bytes); -// -// NSMutableData *finalData = [ivData mutableCopy]; -// [finalData appendData:resultData]; -// return finalData; -//} -// -//- (nullable NSData *)encryptWithDHBLSKey:(DSBLSKey *)dhKey { -// NSData *ivData = [NSData randomInitializationVectorOfSize:kCCBlockSizeAES128]; -// -// return [self encryptWithDHBLSKey:dhKey usingInitializationVector:ivData]; -//} -// -//- (nullable NSData *)encryptWithDHBLSKey:(DSBLSKey *)dhKey usingInitializationVector:(NSData *)initializationVector { -// unsigned char *iv = (unsigned char *)initializationVector.bytes; -// -// std::vector symKey = dhKey.blsPublicKey.Serialize(dhKey.useLegacy); -// symKey.resize(32); -// -// NSData *resultData = AES256EncryptDecrypt(kCCEncrypt, self, (uint8_t *)symKey.data(), initializationVector.length ? iv : 0); -// -// NSMutableData *finalData = [initializationVector mutableCopy]; -// [finalData appendData:resultData]; -// return finalData; -//} -// -//- (nullable NSData *)decryptWithBLSSecretKey:(DSBLSKey *)secretKey fromPublicKey:(DSBLSKey *)peerPubKey usingIVSize:(NSUInteger)ivSize { -// if (self.length < ivSize) { -// return nil; -// } -// -// bls::G1Element pk = secretKey.blsPrivateKey * peerPubKey.blsPublicKey; -// std::vector symKey = pk.Serialize(peerPubKey.useLegacy); -// symKey.resize(32); -// -// unsigned char iv[ivSize]; -// -// [self getBytes:iv length:ivSize]; -// -// NSData *encryptedData = [self subdataWithRange:NSMakeRange(ivSize, self.length - ivSize)]; -// -// NSData *resultData = AES256EncryptDecrypt(kCCDecrypt, encryptedData, (uint8_t *)symKey.data(), ivSize ? iv : 0); -// -// return resultData; -//} -// -//- (nullable NSData *)decryptWithDHBLSKey:(DSBLSKey *)key { -// return [self decryptWithDHBLSKey:key usingIVSize:kCCBlockSizeAES128]; -//} -// -//- (nullable NSData *)decryptWithDHBLSKey:(DSBLSKey *)key usingIVSize:(NSUInteger)ivSize { -// if (self.length < ivSize) { -// return nil; -// } -// -// bls::G1Element pk = key.blsPublicKey; -// std::vector symKey = pk.Serialize(key.useLegacy); -// symKey.resize(32); -// -// unsigned char iv[ivSize]; -// -// [self getBytes:iv length:ivSize]; -// -// NSData *encryptedData = [self subdataWithRange:NSMakeRange(ivSize, self.length - ivSize)]; -// -// NSData *resultData = AES256EncryptDecrypt(kCCDecrypt, encryptedData, (uint8_t *)symKey.data(), ivSize ? iv : 0); -// -// return resultData; -//} -// -//- (nullable NSData *)encryptWithECDSASecretKey:(DSECDSAKey *)secretKey forPublicKey:(DSECDSAKey *)peerPubKey { -// DSECDSAKey *key = [DSECDSAKey keyWithDHKeyExchangeWithPublicKey:peerPubKey forPrivateKey:secretKey]; -// -// return [self encryptWithECDSAKey:key]; -//} -// -//- (nullable NSData *)encryptWithECDSASecretKey:(DSECDSAKey *)secretKey forPublicKey:(DSECDSAKey *)peerPubKey useInitializationVectorForTesting:(NSData *)initializationVector { -// DSECDSAKey *key = [DSECDSAKey keyWithDHKeyExchangeWithPublicKey:peerPubKey forPrivateKey:secretKey]; -// -// return [self encryptWithDHECDSAKey:key usingInitializationVector:initializationVector]; -//} -// -//- (nullable NSData *)encryptWithECDSAKey:(DSECDSAKey *)dhKey { -// NSData *ivData = [NSData randomInitializationVectorOfSize:kCCBlockSizeAES128]; -// -// return [self encryptWithDHECDSAKey:dhKey usingInitializationVector:ivData]; -//} -// -//- (nullable NSData *)encryptWithDHECDSAKey:(DSECDSAKey *)dhKey usingInitializationVector:(NSData *)initializationVector { -// unsigned char *iv = (unsigned char *)initializationVector.bytes; -// -// NSData *resultData = AES256EncryptDecrypt(kCCEncrypt, self, (uint8_t *)dhKey.publicKeyData.bytes, initializationVector.length ? iv : 0); -// -// NSMutableData *finalData = [initializationVector mutableCopy]; -// [finalData appendData:resultData]; -// return finalData; -//} -// -//- (nullable NSData *)decryptWithECDSASecretKey:(DSECDSAKey *)secretKey fromPublicKey:(DSECDSAKey *)peerPubKey usingIVSize:(NSUInteger)ivSize { -// if (self.length < ivSize) { -// return nil; -// } -// -// DSECDSAKey *key = [DSECDSAKey keyWithDHKeyExchangeWithPublicKey:peerPubKey forPrivateKey:secretKey]; -// -// return [self decryptWithDHECDSAKey:key usingIVSize:ivSize]; -//} -// -//- (nullable NSData *)decryptWithDHECDSAKey:(DSECDSAKey *)key { -// return [self decryptWithDHECDSAKey:key usingIVSize:kCCBlockSizeAES128]; -//} -// -//- (nullable NSData *)decryptWithDHECDSAKey:(DSECDSAKey *)key usingIVSize:(NSUInteger)ivSize { -// if (self.length < ivSize) { -// return nil; -// } -// -// unsigned char iv[ivSize]; -// -// [self getBytes:iv length:ivSize]; -// -// NSData *encryptedData = [self subdataWithRange:NSMakeRange(ivSize, self.length - ivSize)]; -// -// NSData *resultData = AES256EncryptDecrypt(kCCDecrypt, encryptedData, (uint8_t *)key.publicKeyData.bytes, ivSize ? iv : 0); -// -// return resultData; -//} -// -- (nullable NSData *)encryptWithSecretKey:(OpaqueKey *)secretKey forPublicKey:(OpaqueKey *)peerPubKey { +- (nullable NSData *)encryptWithSecretKey:(DOpaqueKey *)secretKey forPublicKey:(DOpaqueKey *)peerPubKey { return [DSKeyManager encryptData:self secretKey:secretKey publicKey:peerPubKey]; -// if ([secretKey isMemberOfClass:[DSBLSKey class]] && [peerPubKey isMemberOfClass:[DSBLSKey class]]) { -// return [self encryptWithBLSSecretKey:(DSBLSKey *)secretKey forPublicKey:(DSBLSKey *)peerPubKey]; -// } else if ([secretKey isMemberOfClass:[DSECDSAKey class]] && [peerPubKey isMemberOfClass:[DSECDSAKey class]]) { -// return [self encryptWithECDSASecretKey:(DSECDSAKey *)secretKey forPublicKey:(DSECDSAKey *)peerPubKey]; -// } else { -// NSAssert(FALSE, @"Keys should be of same type"); -// } -// return nil; } -- (nullable NSData *)encryptWithSecretKey:(OpaqueKey *)secretKey forPublicKey:(OpaqueKey *)peerPubKey usingInitializationVector:(NSData *)initializationVector { +- (nullable NSData *)encryptWithSecretKey:(DOpaqueKey *)secretKey forPublicKey:(DOpaqueKey *)peerPubKey usingInitializationVector:(NSData *)initializationVector { return [DSKeyManager encryptData:self secretKey:secretKey publicKey:peerPubKey usingIV:initializationVector]; -// if ([secretKey isMemberOfClass:[DSBLSKey class]] && [peerPubKey isMemberOfClass:[DSBLSKey class]]) { -// return [self encryptWithBLSSecretKey:(DSBLSKey *)secretKey forPublicKey:(DSBLSKey *)peerPubKey usingInitializationVector:initializationVector]; -// } else if ([secretKey isMemberOfClass:[DSECDSAKey class]] && [peerPubKey isMemberOfClass:[DSECDSAKey class]]) { -// return [self encryptWithECDSASecretKey:(DSECDSAKey *)secretKey forPublicKey:(DSECDSAKey *)peerPubKey useInitializationVectorForTesting:initializationVector]; -// } else { -// NSAssert(FALSE, @"Keys should be of same type"); -// } -// return nil; } -- (nullable NSData *)decryptWithSecretKey:(OpaqueKey *)secretKey fromPublicKey:(OpaqueKey *)peerPubKey { +- (nullable NSData *)decryptWithSecretKey:(DOpaqueKey *)secretKey fromPublicKey:(DOpaqueKey *)peerPubKey { return [DSKeyManager decryptData:self secretKey:secretKey publicKey:peerPubKey]; } -- (nullable NSData *)decryptWithSecretKey:(OpaqueKey *)secretKey fromPublicKey:(OpaqueKey *)peerPubKey usingIVSize:(NSUInteger)ivSize { +- (nullable NSData *)decryptWithSecretKey:(DOpaqueKey *)secretKey fromPublicKey:(DOpaqueKey *)peerPubKey usingIVSize:(NSUInteger)ivSize { return [DSKeyManager decryptData:self secretKey:secretKey publicKey:peerPubKey usingIVSize:ivSize]; -// if ([secretKey isMemberOfClass:[DSBLSKey class]] && [peerPubKey isMemberOfClass:[DSBLSKey class]]) { -// return [self decryptWithBLSSecretKey:(DSBLSKey *)secretKey fromPublicKey:(DSBLSKey *)peerPubKey usingIVSize:ivSize]; -// } else if ([secretKey isMemberOfClass:[DSECDSAKey class]] && [peerPubKey isMemberOfClass:[DSECDSAKey class]]) { -// return [self decryptWithECDSASecretKey:(DSECDSAKey *)secretKey fromPublicKey:(DSECDSAKey *)peerPubKey usingIVSize:ivSize]; -// } else { -// NSAssert(FALSE, @"Keys should be of same type"); -// } -// return nil; } -- (nullable NSData *)encryptWithDHKey:(OpaqueKey *)dhKey { +- (nullable NSData *)encryptWithDHKey:(DOpaqueKey *)dhKey { return [DSKeyManager encryptData:self withDHKey:dhKey]; -// if ([dhKey isMemberOfClass:[DSBLSKey class]]) { -// return [self encryptWithDHBLSKey:(DSBLSKey *)dhKey]; -// } else if ([dhKey isMemberOfClass:[DSECDSAKey class]]) { -// return [self encryptWithECDSAKey:(DSECDSAKey *)dhKey]; -// } else { -// NSAssert(FALSE, @"Keys should be of a known type"); -// } -// return nil; } -- (nullable NSData *)decryptWithDHKey:(OpaqueKey *)dhKey { +- (nullable NSData *)decryptWithDHKey:(DOpaqueKey *)dhKey { return [DSKeyManager decryptData:self withDHKey:dhKey]; -// if ([dhKey isMemberOfClass:[DSBLSKey class]]) { -// return [self decryptWithDHBLSKey:(DSBLSKey *)dhKey]; -// } else if ([dhKey isMemberOfClass:[DSECDSAKey class]]) { -// return [self decryptWithDHECDSAKey:(DSECDSAKey *)dhKey]; -// } else { -// NSAssert(FALSE, @"Keys should be of a known type"); -// } -// return nil; } - (nullable NSData *)encapsulatedDHDecryptionWithKeys:(NSArray *)keys usingIVSize:(NSUInteger)ivSize { NSAssert(keys.count > 1, @"There should be at least two key (first pair)"); if ([keys count] < 2) return self; - OpaqueKey *firstKey = (OpaqueKey *)[keys firstObject].pointerValue; - OpaqueKey *secondKey = (OpaqueKey *)[keys objectAtIndex:1].pointerValue; - NSData *encryptedData = [self decryptWithSecretKey:secondKey fromPublicKey:firstKey usingIVSize:ivSize]; + DMaybeOpaqueKey *firstKey = (DMaybeOpaqueKey *)[keys firstObject].pointerValue; + DMaybeOpaqueKey *secondKey = (DMaybeOpaqueKey *)[keys objectAtIndex:1].pointerValue; + NSAssert(firstKey->ok, @"First key should be ok"); + NSAssert(secondKey->ok, @"Second key should be ok"); + NSData *encryptedData = [self decryptWithSecretKey:secondKey->ok fromPublicKey:firstKey->ok usingIVSize:ivSize]; if (keys.count == 2) { //not really necessary but easier to read return encryptedData; } else { @@ -289,8 +65,9 @@ - (nullable NSData *)encapsulatedDHDecryptionWithKeys:(NSArray *)keys - (nullable NSData *)encapsulatedDHDecryptionWithKeys:(NSArray *)keys { NSAssert(keys.count > 0, @"There should be at least one key"); if (![keys count]) return self; - OpaqueKey *firstKey = (OpaqueKey *) [keys firstObject].pointerValue; - NSData *encryptedData = [self decryptWithDHKey:firstKey]; + DMaybeOpaqueKey *firstKey = (DMaybeOpaqueKey *) [keys firstObject].pointerValue; + NSAssert(firstKey->ok, @"First key should be ok"); + NSData *encryptedData = [self decryptWithDHKey:firstKey->ok]; if (keys.count == 1) { //not really necessary but easier to read return encryptedData; } else { @@ -301,8 +78,9 @@ - (nullable NSData *)encapsulatedDHDecryptionWithKeys:(NSArray *)keys - (nullable NSData *)encapsulatedDHEncryptionWithKeys:(NSArray *)keys { NSAssert(keys.count > 0, @"There should be at least one key"); if (![keys count]) return self; - OpaqueKey *firstKey = (OpaqueKey *) [keys firstObject].pointerValue; - NSData *encryptedData = [self encryptWithDHKey:firstKey]; + DMaybeOpaqueKey *firstKey = (DMaybeOpaqueKey *) [keys firstObject].pointerValue; + NSAssert(firstKey->ok, @"First key should be ok"); + NSData *encryptedData = [self encryptWithDHKey:firstKey->ok]; if (keys.count == 1) { //not really necessary but easier to read return encryptedData; } else { @@ -314,10 +92,12 @@ - (nullable NSData *)encapsulatedDHEncryptionWithKeys:(NSArray *)keys NSAssert(keys.count > 1, @"There should be at least two key (first pair)"); if ([keys count] < 2) return self; - OpaqueKey *firstKey = (OpaqueKey *) [keys firstObject].pointerValue; - OpaqueKey *secondKey = (OpaqueKey *) [keys objectAtIndex:1].pointerValue; - - NSData *encryptedData = [self encryptWithSecretKey:firstKey forPublicKey:secondKey usingInitializationVector:initializationVector]; + DMaybeOpaqueKey *firstKey = (DMaybeOpaqueKey *) [keys firstObject].pointerValue; + DMaybeOpaqueKey *secondKey = (DMaybeOpaqueKey *) [keys objectAtIndex:1].pointerValue; + NSAssert(firstKey->ok, @"First key should be ok"); + NSAssert(secondKey->ok, @"Second key should be ok"); + + NSData *encryptedData = [self encryptWithSecretKey:firstKey->ok forPublicKey:secondKey->ok usingInitializationVector:initializationVector]; if (keys.count == 2) { //not really necessary but easier to read return encryptedData; } else { diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h index e24ddb3fc..3ae80d6a6 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Protected.h @@ -33,8 +33,8 @@ typedef NS_ENUM(uint16_t, DSChainNotificationType) { - (void)wipeMasternodeInfo; -- (void)notify:(NSNotificationName)name userInfo:(NSDictionary *_Nullable)userInfo; - (void)notifySyncStateChanged; +- (void)notifyMasternodeSyncStateChange:(uint32_t)lastBlockHeihgt storedCount:(uintptr_t)storedCount; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h index 7e17e178f..38c1c3f10 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.h @@ -22,6 +22,8 @@ NS_ASSUME_NONNULL_BEGIN @interface DSChainManager (Transactions) +@property (nonatomic, readonly) NSData *chainSynchronizationFingerprint; + - (BOOL)shouldRequestMerkleBlocksForZoneBetweenHeight:(uint32_t)blockHeight andEndHeight:(uint32_t)endBlockHeight; - (BOOL)shouldRequestMerkleBlocksForZoneAfterHeight:(uint32_t)blockHeight; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m index 0ef4efefd..fd5d59e37 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager+Transactions.m @@ -15,6 +15,7 @@ // limitations under the License. // +#import "DSChain+Params.h" #import "DSChainManager+Protected.h" #import "DSChainManager+Transactions.h" #import "DSWallet+Protected.h" @@ -33,10 +34,10 @@ @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; +@property (nonatomic, assign) uint32_t maxTransactionsInfoDataFirstHeight; +@property (nonatomic, assign) uint32_t maxTransactionsInfoDataLastHeight; @end @@ -56,10 +57,17 @@ - (RHIntervalTree *)heightTransactionZones { return objc_getAssociatedObject(self, &heightTransactionZonesKey); } +- (void)setChainSynchronizationFingerprint:(NSData *)chainSynchronizationFingerprint { + objc_setAssociatedObject(self, &chainSynchronizationFingerprintKey, chainSynchronizationFingerprint, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +- (NSData *)chainSynchronizationFingerprint { + return objc_getAssociatedObject(self, &chainSynchronizationFingerprintKey); +} - (void)setChainSynchronizationBlockZones:(NSOrderedSet *)chainSynchronizationBlockZones { objc_setAssociatedObject(self, &chainSynchronizationBlockZonesKey, chainSynchronizationBlockZones, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + - (NSOrderedSet *)chainSynchronizationBlockZones { NSOrderedSet *obj = objc_getAssociatedObject(self, &chainSynchronizationBlockZonesKey); if (!obj) { @@ -67,9 +75,27 @@ - (NSOrderedSet *)chainSynchronizationBlockZones { [self setChainSynchronizationBlockZones:obj]; } return obj; +} +- (void)setMaxTransactionsInfoDataFirstHeight:(uint32_t)maxTransactionsInfoDataFirstHeight { + objc_setAssociatedObject(self, &maxTransactionsInfoDataFirstHeightKey, @(maxTransactionsInfoDataFirstHeight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (uint32_t)maxTransactionsInfoDataFirstHeight { + NSNumber *value = objc_getAssociatedObject(self, &maxTransactionsInfoDataFirstHeightKey); + return value ? [value unsignedIntValue] : 0; +} + +- (void)setMaxTransactionsInfoDataLastHeight:(uint32_t)maxTransactionsInfoDataLastHeight { + objc_setAssociatedObject(self, &maxTransactionsInfoDataLastHeightKey, @(maxTransactionsInfoDataLastHeight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (uint32_t)maxTransactionsInfoDataLastHeight { + NSNumber *value = objc_getAssociatedObject(self, &maxTransactionsInfoDataLastHeightKey); + return value ? [value unsignedIntValue] : 0; +} + + - (void)loadHeightTransactionZones { NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; @@ -151,12 +177,10 @@ - (uint32_t)firstHeightOutOfAverageRangeWithStart500RangeHeight:(uint32_t)height } i++; } - if (rAverage) { + if (rAverage) *rAverage = currentAverage; - } - if (rAverages) { + if (rAverages) *rAverages = averagesAtHeights; - } return checkHeight; } diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h index 0d0ba4bbe..421212f78 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.h @@ -53,7 +53,7 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncFinishedNotificatio FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncFailedNotification; FOUNDATION_EXPORT NSString *_Nonnull const DSChainManagerSyncStateDidChangeNotification; -@class DSGovernanceSyncManager, DSMasternodeManager, DSSporkManager, DSPeerManager, DSGovernanceVote, DSDAPIClient, DSTransactionManager, DSIdentitiesManager, DSBackgroundManager, DSBloomFilter, DSBlock, DSFullBlock, DSKeyManager, DSSyncState; +@class DSGovernanceSyncManager, DSMasternodeManager, DSSporkManager, DSPeerManager, DSGovernanceVote, 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); @@ -64,13 +64,11 @@ typedef void (^MultipleBlockMiningCompletionBlock)(NSArray *block @property (nonatomic, readonly) DSSporkManager *sporkManager; @property (nonatomic, readonly) DSMasternodeManager *masternodeManager; @property (nonatomic, readonly) DSGovernanceSyncManager *governanceSyncManager; -@property (nonatomic, readonly) DSDAPIClient *DAPIClient; @property (nonatomic, readonly) DSIdentitiesManager *identitiesManager; @property (nonatomic, readonly) DSTransactionManager *transactionManager; @property (nonatomic, readonly) DSPeerManager *peerManager; @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; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m index 2672588d6..a95640b87 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainManager.m @@ -24,12 +24,13 @@ // THE SOFTWARE. #import "DSBloomFilter.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.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" #import "DSFullBlock.h" @@ -48,6 +49,7 @@ #import "NSDate+Utils.h" #import "NSError+Dash.h" #import "NSString+Bitcoin.h" +#import "NSObject+Notification.h" #import "RHIntervalTree.h" #define SYNC_STARTHEIGHT_KEY @"SYNC_STARTHEIGHT" @@ -62,7 +64,6 @@ @interface DSChainManager () @property (nonatomic, strong) DSKeyManager *keyManager; @property (nonatomic, strong) DSGovernanceSyncManager *governanceSyncManager; @property (nonatomic, strong) DSIdentitiesManager *identitiesManager; -@property (nonatomic, strong) DSDAPIClient *DAPIClient; @property (nonatomic, strong) DSTransactionManager *transactionManager; @property (nonatomic, strong) DSPeerManager *peerManager; @property (nonatomic, assign) uint64_t sessionConnectivityNonce; @@ -87,7 +88,6 @@ - (instancetype)initWithChain:(DSChain *)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 [self.masternodeManager setUp]; self.governanceSyncManager = [[DSGovernanceSyncManager alloc] initWithChain:chain]; self.transactionManager = [[DSTransactionManager alloc] initWithChain:chain]; @@ -105,10 +105,14 @@ - (instancetype)initWithChain:(DSChain *)chain { //[self loadHeightTransactionZones]; self.miningQueue = dispatch_queue_create([[NSString stringWithFormat:@"org.dashcore.dashsync.mining.%@", self.chain.uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL); - DSLog(@"[%@] DSChainManager.initWithChain %@", chain.name, chain); + DSLog(@"%@ initWithChain %@", self.logPrefix, chain); return self; } +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [Chain Manager] ", self.chain.name]; +} + - (BOOL)isSynced { return self.syncState.combinedSyncProgress == 1.0; } @@ -127,12 +131,12 @@ - (void)relayedNewItem { - (void)startSync { [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - DSLog(@"[%@] startSync -> peerManager::connect", self.chain.name); + DSLog(@"%@ start -> peerManager::connect", self.logPrefix); [self.peerManager connect]; } - (void)stopSync { - DSLog(@"[%@] stopSync (chain switch)", self.chain.name); + DSLog(@"%@ stop (chain switch)", self.logPrefix); [self.masternodeManager stopSync]; [self.peerManager disconnect:DSDisconnectReason_ChainSwitch]; self.syncState.syncPhase = DSChainSyncPhase_Offline; @@ -153,7 +157,7 @@ - (void)disconnectedMasternodeListAndBlocksRescan { [self removeNonMainnetTrustedPeer]; [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - DSLog(@"[%@] disconnectedMasternodeListAndBlocksRescan -> peerManager::connect", self.chain.name); + DSLog(@"[%@] Disconnected (MasternodeListAndBlocksRescan) -> peerManager::connect", self.logPrefix); [self.peerManager connect]; } @@ -163,7 +167,7 @@ - (void)disconnectedMasternodeListRescan { [self removeNonMainnetTrustedPeer]; [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - DSLog(@"[%@] disconnectedMasternodeListRescan -> peerManager::connect", self.chain.name); + DSLog(@"%@ Disconnected (MasternodeListRescan) -> peerManager::connect", self.logPrefix); [self.peerManager connect]; } @@ -173,7 +177,7 @@ - (void)disconnectedSyncBlocksRescan { [self removeNonMainnetTrustedPeer]; [self notify:DSChainManagerSyncWillStartNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - DSLog(@"[%@] disconnectedSyncBlocksRescan -> peerManager::connect", self.chain.name); + DSLog(@"%@ Disconnected (SyncBlocksRescan) -> peerManager::connect", self.logPrefix); [self.peerManager connect]; } @@ -219,7 +223,7 @@ - (void)chain:(DSChain *)chain didSetBlockHeight:(int32_t)height andTimestamp:(N [self.transactionManager chain:chain didSetBlockHeight:height andTimestamp:timestamp forTransactionHashes:txHashes updatedTransactions:updatedTransactions]; } -- (void)chain:(DSChain *)chain didFinishInChainSyncPhaseFetchingBlockchainIdentityDAPInformation:(DSBlockchainIdentity *)blockchainIdentity { +- (void)chain:(DSChain *)chain didFinishInChainSyncPhaseFetchingIdentityDAPInformation:(DSIdentity *)identity { dispatch_async(chain.networkingQueue, ^{ [self.peerManager resumeBlockchainSynchronizationOnPeers]; }); @@ -268,7 +272,7 @@ - (void)chainShouldStartSyncingBlockchain:(DSChain *)chain onPeer:(DSPeer *)peer - (void)chainFinishedSyncingInitialHeaders:(DSChain *)chain fromPeer:(DSPeer *)peer onMainChain:(BOOL)onMainChain { if (onMainChain && peer && (peer == self.peerManager.downloadPeer)) [self relayedNewItem]; - + DSLog(@"%@ Sync Status: initial headers: OK -> sync masternode lists & quorums", self.logPrefix); [self.peerManager chainSyncStopped]; if (([[DSOptionsManager sharedInstance] syncType] & DSSyncType_MasternodeList)) { // make sure we care about masternode lists @@ -278,7 +282,7 @@ - (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); + DSLog(@"%@ Sync Status: transactions and blocks: OK -> sync mempool, sporks & governance", self.logPrefix); self.syncState.chainSyncStartHeight = 0; self.syncState.syncPhase = DSChainSyncPhase_Synced; @@ -301,13 +305,13 @@ - (void)setSyncPhase:(DSChainSyncPhase)syncPhase { } - (void)syncBlockchain { - DSLog(@"[%@] syncBlockchain connected peers: %lu phase: %d", self.chain.name, self.peerManager.connectedPeerCount, self.syncPhase); + DSLog(@"%@ syncBlockchain connected peers: %lu phase: %d", self.logPrefix, self.peerManager.connectedPeerCount, self.syncPhase); if (self.peerManager.connectedPeerCount == 0) { if (self.syncPhase == DSChainSyncPhase_InitialTerminalBlocks) { self.syncState.syncPhase = DSChainSyncPhase_ChainSync; [self notifySyncStateChanged]; } - DSLog(@"[%@] syncBlockchain -> peerManager::connect", self.chain.name); + DSLog(@"%@ syncBlockchain -> peerManager::connect", self.logPrefix); [self.peerManager connect]; } else if (!self.peerManager.masternodeList && self.masternodeManager.currentMasternodeList) { [self.peerManager useMasternodeList:self.masternodeManager.currentMasternodeList withConnectivityNonce:self.sessionConnectivityNonce]; @@ -319,28 +323,35 @@ - (void)syncBlockchain { } - (void)chainFinishedSyncingMasternodeListsAndQuorums:(DSChain *)chain { - DSLog(@"[%@] finished syncing masternode list and quorums, it should start syncing chain", self.chain.name); if (chain.isEvolutionEnabled) { - [self.identitiesManager syncBlockchainIdentitiesWithCompletion:^(NSArray *_Nullable blockchainIdentities) { + DSLog(@"%@ Sync Status: masternode list and quorums: OK -> sync identities", self.logPrefix); + [self.identitiesManager syncIdentitiesWithCompletion:^(NSArray *_Nullable identities) { + DSLog(@"%@ Sync Status: identities: OK -> sync chain", self.logPrefix); [self syncBlockchain]; }]; } else { + DSLog(@"%@ Sync Status: masternode list and quorums: OK -> sync chain", self.logPrefix); [self syncBlockchain]; } } - (void)chain:(DSChain *)chain badBlockReceivedFromPeer:(DSPeer *)peer { - DSLog(@"[%@: %@:%d] peer is misbehaving", self.chain.name, peer.host, peer.port); + DSLog(@"%@ [%@:%d] peer is misbehaving", self.logPrefix, peer.host, peer.port); [self.peerManager peerMisbehaving:peer errorMessage:@"Bad block received from peer"]; } +- (void)chain:(DSChain *)chain badMasternodeListReceivedFromPeer:(DSPeer *)peer { + DSLog(@"%@ [%@:%d] peer is misbehaving", self.logPrefix, peer.host, peer.port); + [self.peerManager peerMisbehaving:peer errorMessage:@"Issue with Deterministic Masternode list"]; +} + - (void)chain:(DSChain *)chain receivedOrphanBlock:(DSBlock *)block fromPeer:(DSPeer *)peer { // ignore orphans older than one week ago if (block.timestamp < [NSDate timeIntervalSince1970] - WEEK_TIME_INTERVAL) return; // call getblocks, unless we already did with the previous block, or we're still downloading the chain if (self.chain.lastSyncBlockHeight >= peer.lastBlockHeight && !uint256_eq(self.chain.lastOrphan.blockHash, block.prevBlock)) { - DSLog(@"[%@: %@:%d] calling getblocks", self.chain.name, peer.host, peer.port); + DSLog(@"%@ [%@:%d] calling getblocks", self.logPrefix, peer.host, peer.port); [peer sendGetblocksMessageWithLocators:[self.chain chainSyncBlockLocatorArray] andHashStop:UINT256_ZERO]; } } @@ -359,7 +370,9 @@ - (void)resetSyncCountInfo:(DSSyncCountInfo)syncCountInfo inContext:(NSManagedOb [self setCount:0 forSyncCountInfo:syncCountInfo inContext:context]; } -- (void)setCount:(uint32_t)count forSyncCountInfo:(DSSyncCountInfo)syncCountInfo inContext:(NSManagedObjectContext *)context { +- (void)setCount:(uint32_t)count +forSyncCountInfo:(DSSyncCountInfo)syncCountInfo + inContext:(NSManagedObjectContext *)context { switch (syncCountInfo) { case DSSyncCountInfo_GovernanceObject: self.chain.totalGovernanceObjectsCount = count; @@ -391,7 +404,7 @@ - (void)peer:(DSPeer *)peer relayedSyncInfo:(DSSyncCountInfo)syncCountInfo count if (peer.governanceRequestState == DSGovernanceRequestState_GovernanceObjectVoteHashesReceived) { if (count == 0) { //there were no votes - DSLog(@"[%@: %@:%d] no votes on object, going to next object", self.chain.name, peer.host, peer.port); + DSLog(@"%@ [%@:%d] no votes on object, going to next object", self.logPrefix, peer.host, peer.port); peer.governanceRequestState = DSGovernanceRequestState_GovernanceObjectVotes; [self.governanceSyncManager finishedGovernanceVoteSyncWithPeer:peer]; } else { @@ -485,6 +498,14 @@ - (void)setupNotificationTimer:(void (^ __nullable)(void))completion { } } +- (void)notifyMasternodeSyncStateChange:(uint32_t)lastBlockHeihgt storedCount:(uintptr_t)storedCount { + @synchronized (self.syncState) { + self.syncState.masternodeListSyncInfo.lastBlockHeight = lastBlockHeihgt; + self.syncState.masternodeListSyncInfo.storedCount = storedCount; + [self notifySyncStateChanged]; + } +} + - (void)notifySyncStateChanged { [self setupNotificationTimer:^{ @synchronized (self) { @@ -499,9 +520,4 @@ - (void)notifySyncStateChanged { } -- (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/DSChainsManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.h index d3dadb65b..3132fb55d 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.h @@ -55,7 +55,7 @@ FOUNDATION_EXPORT NSString *const DSChainsDidChangeNotification; sporkAddress:(NSString *)sporkAddress sporkPrivateKey:(NSString *)sporkPrivateKey; -- (DSChain *_Nullable)registerDevnetChainWithIdentifier:(DevnetType)devnetType +- (DSChain *_Nullable)registerDevnetChainWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType forServiceLocations:(NSOrderedSet *)serviceLocations withMinimumDifficultyBlocks:(uint32_t)minimumDifficultyBlocks standardPort:(uint32_t)standardPort diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.m index 9dee6f6b6..bb6916284 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSChainsManager.m @@ -23,8 +23,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#import "DPContract+Protected.h" #import "DSChainsManager.h" +#import "DSChain+Checkpoint.h" +#import "DSChain+Identity.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainManager+Protected.h" #import "DSDashPlatform.h" @@ -188,24 +193,29 @@ - (void)updateDevnetChain:(DSChain *)chain chain.dpnsContractID = dpnsContractID; DPContract *contract = [DSDashPlatform sharedInstanceForChain:chain].dpnsContract; if (uint256_is_not_zero(dpnsContractID)) { - DSBlockchainIdentity *blockchainIdentity = [chain blockchainIdentityThatCreatedContract:[DPContract localDPNSContractForChain:chain] withContractId:dpnsContractID foundInWallet:nil]; - if (blockchainIdentity) { - [contract registerCreator:blockchainIdentity inContext:[NSManagedObjectContext platformContext]]; + DSIdentity *identity = [chain identityThatCreatedContract:[DPContract localDPNSContractForChain:chain].raw_contract withContractId:dpnsContractID foundInWallet:nil]; + if (identity) { + [contract registerCreator:identity]; + [contract saveAndWaitInContext:[NSManagedObjectContext platformContext]]; } } else { - [contract unregisterCreatorInContext:[NSManagedObjectContext platformContext]]; + [contract unregisterCreator]; + [contract saveAndWaitInContext:[NSManagedObjectContext platformContext]]; } } if (!uint256_eq(dashpayContractID, chain.dashpayContractID)) { chain.dashpayContractID = dashpayContractID; DPContract *contract = [DSDashPlatform sharedInstanceForChain:chain].dashPayContract; if (uint256_is_not_zero(dashpayContractID)) { - DSBlockchainIdentity *blockchainIdentity = [chain blockchainIdentityThatCreatedContract:[DPContract localDashpayContractForChain:chain] withContractId:dashpayContractID foundInWallet:nil]; - if (blockchainIdentity) { - [contract registerCreator:blockchainIdentity inContext:[NSManagedObjectContext platformContext]]; + DSIdentity *identity = [chain identityThatCreatedContract:[DPContract localDashpayContractForChain:chain].raw_contract withContractId:dashpayContractID foundInWallet:nil]; + if (identity) { + [contract registerCreator:identity]; + [contract saveAndWaitInContext:[NSManagedObjectContext platformContext]]; } } else { - [contract unregisterCreatorInContext:[NSManagedObjectContext platformContext]]; + [contract unregisterCreator]; + [contract saveAndWaitInContext:[NSManagedObjectContext platformContext]]; + } } for (NSString *serviceLocation in serviceLocations) { @@ -233,7 +243,7 @@ - (void)updateDevnetChain:(DSChain *)chain } } -- (DSChain *_Nullable)registerDevnetChainWithIdentifier:(DevnetType)devnetType +- (DSChain *_Nullable)registerDevnetChainWithIdentifier:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType forServiceLocations:(NSOrderedSet *)serviceLocations withMinimumDifficultyBlocks:(uint32_t)minimumDifficultyBlocks standardPort:(uint32_t)standardPort @@ -287,7 +297,8 @@ - (DSChain *_Nullable)registerDevnetChainWithIdentifier:(DevnetType)devnetType NSMutableDictionary *registeredDevnetsDictionary = [getKeychainDict(DEVNET_CHAINS_KEY, @[[NSString class], [NSArray class], [DSCheckpoint class]], &error) mutableCopy]; if (!registeredDevnetsDictionary) registeredDevnetsDictionary = [NSMutableDictionary dictionary]; - NSString *devnetIdentifier = [DSKeyManager NSStringFrom:chain_devnet_identifier(devnetType)]; + char *devnet_id = dash_spv_crypto_network_chain_type_ChainType_devnet_identifier(chain.chainType); + NSString *devnetIdentifier = [DSKeyManager NSStringFrom:devnet_id]; if (![[registeredDevnetsDictionary allKeys] containsObject:devnetIdentifier]) { [registeredDevnetsDictionary setObject:chain.checkpoints forKey:devnetIdentifier]; setKeychainDict(registeredDevnetsDictionary, DEVNET_CHAINS_KEY, NO); @@ -309,8 +320,6 @@ - (void)removeDevnetChain:(DSChain *)chain { NSError *error = nil; DSChainManager *chainManager = [self chainManagerForChain:chain]; DSPeerManager *peerManager = chainManager.peerManager; - DSMasternodeManager *masternodeManager = chainManager.masternodeManager; - [masternodeManager destroyProcessors]; [peerManager clearRegisteredPeers]; NSMutableDictionary *registeredDevnetsDictionary = [getKeychainDict(DEVNET_CHAINS_KEY, @[[NSString class], [NSArray class], [DSCheckpoint class]], &error) mutableCopy]; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSGovernanceSyncManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSGovernanceSyncManager.m index 6d004a78e..eec67cfa1 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSGovernanceSyncManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSGovernanceSyncManager.m @@ -25,6 +25,7 @@ #import "DSGovernanceSyncManager.h" #import "DSAccount.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataProperties.h" #import "DSChainsManager.h" @@ -40,7 +41,6 @@ #import "DSOptionsManager.h" #import "DSPeer.h" #import "DSPeerManager+Protected.h" -#import "DSSimplifiedMasternodeEntry.h" #import "NSData+DSHash.h" #import "NSManagedObject+Sugar.h" diff --git a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry+Mndiff.h b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+CoreData.h similarity index 55% rename from DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry+Mndiff.h rename to DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+CoreData.h index 31cc5be97..44c40e424 100644 --- a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry+Mndiff.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+CoreData.h @@ -1,6 +1,6 @@ -// +// // Created by Vladimir Pirogov -// Copyright © 2021 Dash Core Group. All rights reserved. +// 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. @@ -15,20 +15,20 @@ // limitations under the License. // -#import "DSChain.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "dash_shared_core.h" #import +#import "DSIdentity.h" +#import "DSIdentitiesManager.h" NS_ASSUME_NONNULL_BEGIN -@interface DSSimplifiedMasternodeEntry (Mndiff) - -+ (instancetype)simplifiedEntryWith:(MasternodeEntry *)entry onChain:(DSChain *)chain; -+ (NSDictionary *)simplifiedEntriesWith:(MasternodeEntry *_Nullable *_Nonnull)entries count:(uintptr_t)count onChain:(DSChain *)chain; - -- (MasternodeEntry *)ffi_malloc; -+ (void)ffi_free:(MasternodeEntry *)entry; +@interface DSIdentitiesManager (CoreData) +- (void)setup; +- (void)loadExternalIdentities; +- (void)registerForeignIdentity:(DSIdentity *)identity; +- (DSIdentity *)foreignIdentityWithUniqueId:(UInt256)uniqueId; +- (DSIdentity *)foreignIdentityWithUniqueId:(UInt256)uniqueId + createIfMissing:(BOOL)addIfMissing + inContext:(NSManagedObjectContext *_Nullable)context; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+CoreData.m b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+CoreData.m new file mode 100644 index 000000000..b11377347 --- /dev/null +++ b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+CoreData.m @@ -0,0 +1,87 @@ +// +// 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 "DSIdentitiesManager+CoreData.h" +#import "DSIdentity+Protected.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "NSManagedObject+Sugar.h" +#import "NSManagedObjectContext+DSSugar.h" + +@interface DSIdentitiesManager () +@property (nonatomic, strong) NSMutableDictionary *foreignIdentities; +@end + +@implementation DSIdentitiesManager (CoreData) + +- (void)clearExternalIdentities { + self.foreignIdentities = [NSMutableDictionary dictionary]; +} + +- (void)setup { + [self clearExternalIdentities]; + [self loadExternalIdentities]; +} + + +- (void)loadExternalIdentities { + NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; //shouldn't matter what context is used + + [context performBlockAndWait:^{ + NSArray *externalIdentityEntities = [DSBlockchainIdentityEntity objectsInContext:context matching:@"chain == %@ && isLocal == FALSE", [self.chain chainEntityInContext:context]]; + for (DSBlockchainIdentityEntity *entity in externalIdentityEntities) { + DSIdentity *identity = [[DSIdentity alloc] initWithIdentityEntity:entity]; + if (identity) { + self.foreignIdentities[uint256_data(identity.uniqueID)] = identity; + } + } + }]; +} + +- (void)registerForeignIdentity:(DSIdentity *)identity { + NSAssert(!identity.isTransient, @"Dash Identity should no longer be transient"); + @synchronized(self.foreignIdentities) { + if (!self.foreignIdentities[uint256_data(identity.uniqueID)]) { + [identity saveInitial]; + self.foreignIdentities[uint256_data(identity.uniqueID)] = identity; + } + } +} +- (DSIdentity *)foreignIdentityWithUniqueId:(UInt256)uniqueId { + return [self foreignIdentityWithUniqueId:uniqueId createIfMissing:NO inContext:nil]; +} + +- (DSIdentity *)foreignIdentityWithUniqueId:(UInt256)uniqueId + createIfMissing:(BOOL)addIfMissing + inContext:(NSManagedObjectContext *_Nullable)context { + //foreign blockchain identities are for local blockchain identies' contacts, not for search. + @synchronized(self.foreignIdentities) { + DSIdentity *foreignIdentity = self.foreignIdentities[uint256_data(uniqueId)]; + if (foreignIdentity) { + NSAssert(context ? [foreignIdentity identityEntityInContext:context] : foreignIdentity.identityEntity, @"Blockchain identity entity should exist"); + return foreignIdentity; + } else if (addIfMissing) { + foreignIdentity = [[DSIdentity alloc] initWithUniqueId:uniqueId isTransient:FALSE onChain:self.chain]; + [foreignIdentity saveInitialInContext:context]; + self.foreignIdentities[uint256_data(uniqueId)] = foreignIdentity; + return self.foreignIdentities[uint256_data(uniqueId)]; + } + return nil; + } +} + + +@end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+Protected.h b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+Protected.h index 3ae4db917..4db9f984e 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager+Protected.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN @interface DSIdentitiesManager (Protected) -- (void)clearExternalBlockchainIdentities; +- (void)clearExternalIdentities; @property (nonatomic, readonly) dispatch_queue_t identityQueue; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.h index 1d94284a9..137e61afd 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.h @@ -20,15 +20,15 @@ NS_ASSUME_NONNULL_BEGIN -@class DSChain, DSBlockchainIdentity, DSCreditFundingTransaction, DSTransientDashpayUser; +@class DSChain, DSIdentity, DSAssetLockTransaction, DSTransientDashpayUser; @protocol DSDAPINetworkServiceRequest; -typedef void (^IdentitiesSuccessCompletionBlock)(NSArray *_Nullable blockchainIdentities); -typedef void (^IdentitiesCompletionBlock)(BOOL success, NSArray *_Nullable blockchainIdentities, NSArray *errors); -typedef void (^IdentityCompletionBlock)(BOOL success, DSBlockchainIdentity *_Nullable blockchainIdentity, NSError *_Nullable error); +typedef void (^IdentitiesSuccessCompletionBlock)(NSArray *_Nullable identities); +typedef void (^IdentitiesCompletionBlock)(BOOL success, NSArray *_Nullable identities, NSArray *errors); +typedef void (^IdentityCompletionBlock)(BOOL success, DSIdentity *_Nullable identity, NSError *_Nullable error); +typedef void (^DashpayUserInfosCompletionBlock)(BOOL success, NSDictionary *_Nullable dashpayUserInfosByIdentityUniqueId, NSError *_Nullable error); typedef void (^DashpayUserInfoCompletionBlock)(BOOL success, DSTransientDashpayUser *_Nullable dashpayUserInfo, NSError *_Nullable error); -typedef void (^DashpayUserInfosCompletionBlock)(BOOL success, NSDictionary *_Nullable dashpayUserInfosByBlockchainIdentityUniqueId, NSError *_Nullable error); @interface DSIdentitiesManager : NSObject @@ -42,48 +42,52 @@ typedef void (^DashpayUserInfosCompletionBlock)(BOOL success, NSDictionary)searchIdentityByDashpayUsername:(NSString *)name withCompletion:(IdentityCompletionBlock)completion; +- (void)syncIdentitiesWithCompletion:(IdentitiesSuccessCompletionBlock)completion; -- (id)searchIdentityByName:(NSString *)namePrefix inDomain:(NSString *)domain withCompletion:(IdentityCompletionBlock)completion; +//- (void)retrieveAllIdentitiesChainStates:(IdentitiesSuccessCompletionBlock)completion; -- (id)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo withCompletion:(IdentitiesCompletionBlock)completion; +- (void)checkAssetLockTransactionForPossibleNewIdentity:(DSAssetLockTransaction *)transaction; -- (id)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix startAfter:(NSData* _Nullable)startAfter limit:(uint32_t)limit queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo withCompletion:(IdentitiesCompletionBlock)completion; +- (void)searchIdentityByDashpayUsername:(NSString *)name + withCompletion:(IdentityCompletionBlock)completion; -- (id)searchIdentitiesByNamePrefix:(NSString *)namePrefix inDomain:(NSString *)domain startAfter:(NSData* _Nullable)startAfter limit:(uint32_t)limit withCompletion:(IdentitiesCompletionBlock)completion; +- (void)searchIdentityByName:(NSString *)namePrefix + inDomain:(NSString *)domain + withCompletion:(IdentityCompletionBlock)completion; -- (id)fetchProfileForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity withCompletion:(DashpayUserInfoCompletionBlock)completion onCompletionQueue:(dispatch_queue_t)completionQueue; +- (void)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix + queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo + withCompletion:(IdentitiesCompletionBlock)completion; -- (id)fetchProfileForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - retryCount:(uint32_t)retryCount - delay:(uint32_t)delay - delayIncrease:(uint32_t)delayIncrease - withCompletion:(DashpayUserInfoCompletionBlock)completion - onCompletionQueue:(dispatch_queue_t)completionQueue; +- (void)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix + startAfter:(NSData* _Nullable)startAfter + limit:(uint32_t)limit + queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo + withCompletion:(IdentitiesCompletionBlock)completion; -- (id)fetchProfilesForBlockchainIdentities:(NSArray *)blockchainIdentities withCompletion:(DashpayUserInfosCompletionBlock)completion onCompletionQueue:(dispatch_queue_t)completionQueue; +- (void)searchIdentitiesByNamePrefix:(NSString *)namePrefix + startAfter:(NSData* _Nullable)startAfter + limit:(uint32_t)limit + withCompletion:(IdentitiesCompletionBlock)completion; -- (void)searchIdentitiesByDPNSRegisteredBlockchainIdentityUniqueID:(NSData *)userID withCompletion:(IdentitiesCompletionBlock)completion; +- (void)fetchProfileForIdentity:(DSIdentity *)identity + withCompletion:(DashpayUserInfoCompletionBlock)completion + onCompletionQueue:(dispatch_queue_t)completionQueue; -- (void)retrieveIdentitiesByKeysUntilSuccessWithCompletion:(IdentitiesSuccessCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue; +- (void)searchIdentitiesByDPNSRegisteredIdentityUniqueID:(NSData *)userID + withCompletion:(IdentitiesCompletionBlock)completion; -- (void)retrieveIdentitiesByKeysWithCompletion:(IdentitiesCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue; -- (void)fetchNeededNetworkStateInformationForBlockchainIdentities:(NSArray *)blockchainIdentities withCompletion:(IdentitiesCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue; +- (NSString *)logPrefix; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.m index 3a3171009..b5dd21768 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSIdentitiesManager.m @@ -15,32 +15,42 @@ // limitations under the License. // +#import "DPContract.h" #import "DSIdentitiesManager.h" +#import "DSIdentitiesManager+CoreData.h" +#import "DSAssetLockTransaction.h" #import "DSAuthenticationKeysDerivationPath.h" -#import "DSBlockchainIdentity+Protected.h" +#import "DSIdentity+Protected.h" +#import "DSIdentity+Username.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSChain+Identity.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainManager.h" -#import "DSCreditFundingTransaction.h" -#import "DSDAPIClient.h" -#import "DSDAPIPlatformNetworkService.h" #import "DSDashPlatform.h" +#import "DSDerivationPathFactory.h" #import "DSMerkleBlock.h" #import "DSOptionsManager.h" #import "DSPeerManager.h" #import "DSTransientDashpayUser+Protected.h" #import "DSWallet.h" +#import "DSWallet+Identity.h" #import "NSError+Dash.h" +#import "NSError+Platform.h" #import "NSManagedObject+Sugar.h" #import "NSManagedObjectContext+DSSugar.h" #import "NSString+Dash.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" + +#define ERROR_UNKNOWN_KEYS [NSError errorWithCode:500 localizedDescriptionKey:@"Identity has unknown keys"] +#define ERROR_CONTRACT_SETUP [NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"] @interface DSIdentitiesManager () @property (nonatomic, strong) DSChain *chain; @property (nonatomic, strong) dispatch_queue_t identityQueue; -@property (nonatomic, strong) NSMutableDictionary *foreignBlockchainIdentities; +@property (nonatomic, strong) NSMutableDictionary *foreignIdentities; @property (nonatomic, assign) NSTimeInterval lastSyncedIndentitiesTimestamp; @property (nonatomic, assign) BOOL hasRecentIdentitiesSync; @@ -48,6 +58,10 @@ @interface DSIdentitiesManager () @implementation DSIdentitiesManager +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [Identity Manager] ", self.chain.name]; +} + - (instancetype)initWithChain:(DSChain *)chain { NSParameterAssert(chain); @@ -55,27 +69,15 @@ - (instancetype)initWithChain:(DSChain *)chain { self.chain = chain; _identityQueue = dispatch_queue_create([@"org.dashcore.dashsync.identity" UTF8String], DISPATCH_QUEUE_SERIAL); - self.foreignBlockchainIdentities = [NSMutableDictionary dictionary]; - [self loadExternalBlockchainIdentities]; + [self setup]; +// self.foreignIdentities = [NSMutableDictionary dictionary]; +// [self loadExternalIdentities]; return self; } // MARK: - Loading -- (void)loadExternalBlockchainIdentities { - NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; //shouldn't matter what context is used - - [context performBlockAndWait:^{ - NSArray *externalIdentityEntities = [DSBlockchainIdentityEntity objectsInContext:context matching:@"chain == %@ && isLocal == FALSE", [self.chain chainEntityInContext:context]]; - for (DSBlockchainIdentityEntity *entity in externalIdentityEntities) { - DSBlockchainIdentity *identity = [[DSBlockchainIdentity alloc] initWithBlockchainIdentityEntity:entity]; - if (identity) { - self.foreignBlockchainIdentities[uint256_data(identity.uniqueID)] = identity; - } - } - }]; -} - (BOOL)hasRecentIdentitiesSync { return ([[NSDate date] timeIntervalSince1970] - self.lastSyncedIndentitiesTimestamp < 30); @@ -83,558 +85,467 @@ - (BOOL)hasRecentIdentitiesSync { // MARK: - Wiping -- (void)clearExternalBlockchainIdentities { - self.foreignBlockchainIdentities = [NSMutableDictionary dictionary]; -} +//- (void)clearExternalIdentities { +// self.foreignIdentities = [NSMutableDictionary dictionary]; +//} // MARK: - Identities -- (void)registerForeignBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - NSAssert(!blockchainIdentity.isTransient, @"Dash Identity should no longer be transient"); - @synchronized(self.foreignBlockchainIdentities) { - if (!self.foreignBlockchainIdentities[uint256_data(blockchainIdentity.uniqueID)]) { - [blockchainIdentity saveInitial]; - self.foreignBlockchainIdentities[uint256_data(blockchainIdentity.uniqueID)] = blockchainIdentity; - } - } -} - -- (DSBlockchainIdentity *)foreignBlockchainIdentityWithUniqueId:(UInt256)uniqueId { - return [self foreignBlockchainIdentityWithUniqueId:uniqueId createIfMissing:NO inContext:nil]; -} - -- (DSBlockchainIdentity *)foreignBlockchainIdentityWithUniqueId:(UInt256)uniqueId createIfMissing:(BOOL)addIfMissing inContext:(NSManagedObjectContext *)context { - //foreign blockchain identities are for local blockchain identies' contacts, not for search. - @synchronized(self.foreignBlockchainIdentities) { - DSBlockchainIdentity *foreignBlockchainIdentity = self.foreignBlockchainIdentities[uint256_data(uniqueId)]; - if (foreignBlockchainIdentity) { - NSAssert(context ? [foreignBlockchainIdentity blockchainIdentityEntityInContext:context] : foreignBlockchainIdentity.blockchainIdentityEntity, @"Blockchain identity entity should exist"); - return foreignBlockchainIdentity; - } else if (addIfMissing) { - foreignBlockchainIdentity = [[DSBlockchainIdentity alloc] initWithUniqueId:uniqueId isTransient:FALSE onChain:self.chain]; - [foreignBlockchainIdentity saveInitialInContext:context]; - self.foreignBlockchainIdentities[uint256_data(uniqueId)] = foreignBlockchainIdentity; - return self.foreignBlockchainIdentities[uint256_data(uniqueId)]; - } - return nil; - } -} - -- (NSArray *)unsyncedBlockchainIdentities { - NSMutableArray *unsyncedBlockchainIdentities = [NSMutableArray array]; - for (DSBlockchainIdentity *blockchainIdentity in [self.chain localBlockchainIdentities]) { - if (!blockchainIdentity.registrationCreditFundingTransaction || (blockchainIdentity.registrationCreditFundingTransaction.blockHeight == BLOCK_UNKNOWN_HEIGHT)) { - [unsyncedBlockchainIdentities addObject:blockchainIdentity]; - } else if (self.chain.lastSyncBlockHeight > blockchainIdentity.dashpaySyncronizationBlockHeight) { +//- (void)registerForeignIdentity:(DSIdentity *)identity { +// NSAssert(!identity.isTransient, @"Dash Identity should no longer be transient"); +// @synchronized(self.foreignIdentities) { +// if (!self.foreignIdentities[uint256_data(identity.uniqueID)]) { +// [identity saveInitial]; +// self.foreignIdentities[uint256_data(identity.uniqueID)] = identity; +// } +// } +//} + +//- (DSIdentity *)foreignIdentityWithUniqueId:(UInt256)uniqueId { +// return [self foreignIdentityWithUniqueId:uniqueId createIfMissing:NO inContext:nil]; +//} +// +//- (DSIdentity *)foreignIdentityWithUniqueId:(UInt256)uniqueId +// createIfMissing:(BOOL)addIfMissing +// inContext:(NSManagedObjectContext *)context { +// //foreign identities are for local blockchain identies' contacts, not for search. +// @synchronized(self.foreignIdentities) { +// DSIdentity *foreignIdentity = self.foreignIdentities[uint256_data(uniqueId)]; +// if (foreignIdentity) { +// NSAssert(context ? [foreignIdentity identityEntityInContext:context] : foreignIdentity.identityEntity, @"Identity entity should exist"); +// return foreignIdentity; +// } else if (addIfMissing) { +// foreignIdentity = [[DSIdentity alloc] initWithUniqueId:uniqueId isTransient:FALSE onChain:self.chain]; +// [foreignIdentity saveInitialInContext:context]; +// self.foreignIdentities[uint256_data(uniqueId)] = foreignIdentity; +// return self.foreignIdentities[uint256_data(uniqueId)]; +// } +// return nil; +// } +//} + +- (NSArray *)unsyncedIdentities { + NSMutableArray *unsyncedIdentities = [NSMutableArray array]; + for (DSIdentity *identity in [self.chain localIdentities]) { + if (!identity.registrationAssetLockTransaction || (identity.registrationAssetLockTransaction.blockHeight == BLOCK_UNKNOWN_HEIGHT)) { + DSLog(@"%@: unsynced identity (asset lock tx unknown or has unknown height) %@ %@", self.logPrefix, uint256_hex(identity.registrationAssetLockTransactionHash), identity.registrationAssetLockTransaction); + + [unsyncedIdentities addObject:identity]; + } else if (self.chain.lastSyncBlockHeight > identity.dashpaySyncronizationBlockHeight) { + DSLog(@"%@: unsynced identity (lastSyncBlockHeight (%u) > dashpaySyncronizationBlockHeight %u)", self.logPrefix, self.chain.lastSyncBlockHeight, identity.dashpaySyncronizationBlockHeight); //If they are equal then the blockchain identity is synced //This is because the dashpaySyncronizationBlock represents the last block for the bloom filter used in L1 should be considered valid //That's because it is set at the time with the hash of the last - [unsyncedBlockchainIdentities addObject:blockchainIdentity]; + [unsyncedIdentities addObject:identity]; } } - return unsyncedBlockchainIdentities; + return unsyncedIdentities; } -//TODO: if we get an error or identity not found, better stop the process and start syncing chain -- (void)syncBlockchainIdentitiesWithCompletion:(IdentitiesSuccessCompletionBlock)completion { - [self - retrieveIdentitiesByKeysUntilSuccessWithCompletion:^(NSArray *_Nullable retrievedBlockchainIdentities) { - NSArray *blockchainIdentities = [self unsyncedBlockchainIdentities]; - [self fetchNeededNetworkStateInformationForBlockchainIdentities:blockchainIdentities - withCompletion:^(BOOL success, NSArray *_Nullable blockchainIdentities, NSArray *_Nonnull errors) { - self.lastSyncedIndentitiesTimestamp = [[NSDate date] timeIntervalSince1970]; - if (success) { - if (completion) { - completion(blockchainIdentities); - } - } - } - completionQueue:self.chain.networkingQueue]; - } - completionQueue:self.chain.networkingQueue]; -} +//- (void)syncPlatformWithCompletion:(IdentitiesSuccessCompletionBlock)completion { +// [self syncIdentitiesWithCompletion:^(NSArray *_Nullable identities) { +// [self retrieveAllIdentitiesChainStates:completion]; +// }]; +// +//} -- (void)retrieveAllBlockchainIdentitiesChainStates { - for (DSWallet *wallet in self.chain.wallets) { - [self retrieveAllBlockchainIdentitiesChainStatesForWallet:wallet]; +//TODO: if we get an error or identity not found, better stop the process and start syncing chain +- (void)syncIdentitiesWithCompletion:(IdentitiesSuccessCompletionBlock)completion { + DSLog(@"%@: Sync Identities", self.logPrefix); + if (!self.chain.isEvolutionEnabled) { + if (completion) dispatch_async(self.chain.networkingQueue, ^{ completion(@[]); }); + return; } -} + dispatch_async(self.identityQueue, ^{ + NSArray *wallets = self.chain.wallets; -- (void)retrieveAllBlockchainIdentitiesChainStatesForWallet:(DSWallet *)wallet { - for (DSBlockchainIdentity *identity in [wallet.blockchainIdentities allValues]) { - if (identity.registrationStatus == DSBlockchainIdentityRegistrationStatus_Unknown) { - [identity fetchIdentityNetworkStateInformationWithCompletion:^(BOOL success, BOOL found, NSError *error) { - if (success && found) { - //now lets get dpns info - if (([[DSOptionsManager sharedInstance] syncType] & DSSyncType_DPNS)) { - [identity fetchUsernamesWithCompletion:^(BOOL success, NSError *error){ - - }]; + __block dispatch_group_t keyHashesDispatchGroup = dispatch_group_create(); + __block NSMutableArray *errors = [NSMutableArray array]; + __block NSMutableArray *allIdentities = [NSMutableArray array]; + + for (DSWallet *wallet in wallets) { + uint32_t unusedIndex = [wallet unusedIdentityIndex]; + DSAuthenticationKeysDerivationPath *derivationPath = [[DSDerivationPathFactory sharedInstance] identityECDSAKeysDerivationPathForWallet:wallet]; + const int keysToCheck = 5; + NSMutableDictionary *keyIndexes = [NSMutableDictionary dictionaryWithCapacity:keysToCheck]; + u160 **key_hashes = malloc(keysToCheck * sizeof(u160 *)); + for (int i = 0; i < keysToCheck; i++) { + const NSUInteger indexes[] = {(unusedIndex + i) | BIP32_HARD, 0 | BIP32_HARD}; + NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; + NSData *publicKeyData = [derivationPath publicKeyDataAtIndexPath:indexPath]; + key_hashes[i] = u160_ctor_u(publicKeyData.hash160); + [keyIndexes setObject:@(unusedIndex + i) forKey:publicKeyData]; + } + dispatch_group_enter(keyHashesDispatchGroup); + DRetry *stragegy = DRetryLinear(5); + dash_spv_platform_identity_manager_IdentityValidator *options = DAcceptIdentityNotFound(); + Result_ok_std_collections_Map_keys_u8_arr_20_values_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error *result = dash_spv_platform_identity_manager_IdentitiesManager_monitor_for_key_hashes(self.chain.sharedRuntime, self.chain.sharedIdentitiesObj, Vec_u8_20_ctor(keysToCheck, key_hashes), stragegy, options); + + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: Sync Identities: ERROR %@", self.logPrefix, error); + Result_ok_std_collections_Map_keys_u8_arr_20_values_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error_destroy(result); + [errors addObject:error]; + dispatch_group_leave(keyHashesDispatchGroup); + return; + } + std_collections_Map_keys_u8_arr_20_values_dpp_identity_identity_Identity *ok = result->ok; + NSMutableArray *identities = [NSMutableArray array]; + + for (int j = 0; j < ok->count; j++) { + DIdentity *identity = ok->values[j]; + switch (identity->tag) { + case dpp_identity_identity_Identity_V0: { + dpp_identity_v0_IdentityV0 *identity_v0 = identity->v0; + DMaybeOpaqueKey *maybe_opaque_key = DOpaqueKeyFromIdentityPubKey(identity_v0->public_keys->values[0]); + NSData *publicKeyData = [DSKeyManager publicKeyData:maybe_opaque_key->ok]; + NSNumber *index = [keyIndexes objectForKey:publicKeyData]; + DSIdentity *identityModel = [[DSIdentity alloc] initAtIndex:index.intValue uniqueId:u256_cast(identity_v0->id->_0->_0) inWallet:wallet]; + [identityModel applyIdentity:identity save:NO inContext:nil]; + [identities addObject:identityModel]; + break; } - } - }]; - } else if (identity.registrationStatus == DSBlockchainIdentityRegistrationStatus_Registered) { - if (!identity.currentDashpayUsername) { - if (([[DSOptionsManager sharedInstance] syncType] & DSSyncType_DPNS)) { - [identity fetchUsernamesWithCompletion:^(BOOL success, NSError *error){ - }]; + default: + break; } } + Result_ok_std_collections_Map_keys_u8_arr_20_values_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error_destroy(result); + BOOL success = [wallet registerIdentities:identities verify:YES]; + DSLog(@"%@: Sync Identities: %@", self.logPrefix, DSLocalizedFormat(success ? @"OK (%lu)" : @"Retrieved (%lu) but can't register in wallet", nil, identities.count)); + if (success) { + [allIdentities addObjectsFromArray:identities]; + NSManagedObjectContext *platformContext = [NSManagedObjectContext platformContext]; + [platformContext performBlockAndWait:^{ + for (DSIdentity *identity in identities) { + [identity saveInitialInContext:platformContext]; + } + }]; + } else { + [errors addObject:ERROR_UNKNOWN_KEYS]; + } + dispatch_group_leave(keyHashesDispatchGroup); + } - } + + dispatch_group_notify(keyHashesDispatchGroup, self.chain.networkingQueue, ^{ + NSArray *identities = [self unsyncedIdentities]; + DSLog(@"%@ Sync Identities: unsynced: %@", self.logPrefix, identities); + dispatch_group_t dispatchGroup = dispatch_group_create(); + __block NSMutableArray *errors = [NSMutableArray array]; + for (DSIdentity *identity in identities) { + dispatch_group_enter(dispatchGroup); + [self fetchNeededNetworkStateInformationForIdentity:identity + withCompletion:^(BOOL success, DSIdentity *_Nullable identity, NSError *_Nullable error) { + if (success && identity != nil) { + dispatch_group_leave(dispatchGroup); + } else { + [errors addObject:error]; + } + } + completionQueue:self.identityQueue]; + } + dispatch_group_notify(dispatchGroup, self.chain.networkingQueue, ^{ + self.lastSyncedIndentitiesTimestamp = [[NSDate date] timeIntervalSince1970]; + if (!errors.count && completion) + completion(identities); + }); + + }); + }); } -- (id)searchIdentityByDashpayUsername:(NSString *)name withCompletion:(IdentityCompletionBlock)completion { - return [self searchIdentityByName:name inDomain:@"dash" withCompletion:completion]; +- (void)searchIdentityByDashpayUsername:(NSString *)name + withCompletion:(IdentityCompletionBlock)completion { + [self searchIdentityByName:name + inDomain:@"dash" + withCompletion:completion]; } -- (id)searchIdentityByName:(NSString *)name inDomain:(NSString *)domain withCompletion:(IdentityCompletionBlock)completion { - DSDAPIClient *client = self.chain.chainManager.DAPIClient; - id call = [client.DAPIPlatformNetworkService getDPNSDocumentsForUsernames:@[name] - inDomain:domain - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - __block NSMutableArray *rBlockchainIdentities = [NSMutableArray array]; - for (NSDictionary *document in documents) { - NSData *userIdData = document[@"$ownerId"]; - NSString *normalizedLabel = document[@"normalizedLabel"]; - NSString *domain = document[@"normalizedParentDomainName"]; - DSBlockchainIdentity *identity = [[DSBlockchainIdentity alloc] initWithUniqueId:userIdData.UInt256 isTransient:TRUE onChain:self.chain]; - [identity addUsername:normalizedLabel inDomain:domain status:DSBlockchainIdentityUsernameStatus_Confirmed save:NO registerOnNetwork:NO]; - [rBlockchainIdentities addObject:identity]; - } - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(YES, [rBlockchainIdentities firstObject], nil); - }); - } +- (void)searchIdentityByName:(NSString *)name + inDomain:(NSString *)domain + withCompletion:(IdentityCompletionBlock)completion { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@ Search Identity by name: %@, domain: %@", self.logPrefix, name, domain]; + DSLog(@"%@", debugString); + DMaybeDocumentsMap *result = dash_spv_platform_document_manager_DocumentsManager_dpns_documents_for_username(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, DChar(name)); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: ERROR: %@", debugString, error); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, nil, error); }); + return; } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, nil, error); - }); - } -#if DEBUG - DSLogPrivate(@"Failure in searchIdentityByName %@", error); -#else - DSLog(@"Failure in searchIdentityByName %@", @""); -#endif - }]; - return call; -} + + // TODO: in wallet we have unusuable but cancelable request, so... + __block NSMutableArray *rIdentities = [NSMutableArray array]; + DDocumentsMap *documents = result->ok; + for (int i = 0; i < documents->count; i++) { + DDocument *document = documents->values[i]; + DSLog(@"%@: document[%i]: ", debugString, i); + dash_spv_platform_document_print_document(document); + + if (!document) continue; + NSString *normalizedLabel = DGetTextDocProperty(document, @"normalizedLabel"); + NSString *domain = DGetTextDocProperty(document, @"normalizedParentDomainName"); + DIdentifier *owner_id = document->v0->owner_id; + + DSIdentity *identity = [[DSIdentity alloc] initWithUniqueId:u256_cast(owner_id->_0->_0) isTransient:TRUE onChain:self.chain]; + [identity addUsername:normalizedLabel inDomain:domain status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed_ctor() save:NO registerOnNetwork:NO]; + [rIdentities addObject:identity]; -- (id)fetchProfileForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity withCompletion:(DashpayUserInfoCompletionBlock)completion onCompletionQueue:(dispatch_queue_t)completionQueue { - return [self fetchProfileForBlockchainIdentity:blockchainIdentity retryCount:5 delay:2 delayIncrease:1 withCompletion:completion onCompletionQueue:completionQueue]; + } + DSLog(@"%@: OK: %@", debugString, [rIdentities firstObject]); + if (completion) + dispatch_async(dispatch_get_main_queue(), ^{ + completion(YES, [rIdentities firstObject], nil); + }); } -- (id)fetchProfileForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity - retryCount:(uint32_t)retryCount - delay:(uint32_t)delay - delayIncrease:(uint32_t)delayIncrease - withCompletion:(DashpayUserInfoCompletionBlock)completion - onCompletionQueue:(dispatch_queue_t)completionQueue { +- (void)fetchProfileForIdentity:(DSIdentity *)identity + withCompletion:(DashpayUserInfoCompletionBlock)completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@ Fetch Profile for: %@", self.logPrefix, identity]; + DSLog(@"%@", debugString); DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; if ([dashpayContract contractState] != DPContractState_Registered) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, [NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"]); - }); - } - return nil; + DSLog(@"%@: ERROR: DashPay Contract Not Registered", debugString); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, nil, nil); }); + return; } - DSDAPIClient *client = self.chain.chainManager.DAPIClient; - id call = [client.DAPIPlatformNetworkService getDashpayProfileForUserId:blockchainIdentity.uniqueIDData - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - if (documents.count == 0) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, nil, nil); - }); - } - return; - } - NSDictionary *contactDictionary = documents.firstObject; - - DSTransientDashpayUser *transientDashpayUser = [[DSTransientDashpayUser alloc] initWithDashpayProfileDocument:contactDictionary]; - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, transientDashpayUser, nil); - }); - } + DMaybeDocument *result = dash_spv_platform_document_manager_DocumentsManager_stream_dashpay_profile_for_user_id_using_contract(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, u256_ctor_u(identity.uniqueID), dashpayContract.raw_contract, DRetryDown20(5), DNotFoundAsAnError(), 2000); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: ERROR: %@", debugString, error); + dispatch_async(completionQueue, ^{ completion(NO, nil, error); }); + DMaybeDocumentDtor(result); + return; } - failure:^(NSError *_Nonnull error) { - if (retryCount > 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), self.identityQueue, ^{ - [self fetchProfileForBlockchainIdentity:blockchainIdentity retryCount:retryCount - 1 delay:delay + delayIncrease delayIncrease:delayIncrease withCompletion:completion onCompletionQueue:completionQueue]; - }); - } else { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, error); - }); - } - } - }]; - return call; + if (!result->ok) { + DSLog(@"%@: ERROR: Profile is None", debugString); + if (completion) dispatch_async(completionQueue, ^{ completion(YES, nil, nil); }); + DMaybeDocumentDtor(result); + return; + } + DSTransientDashpayUser *transientDashpayUser = [[DSTransientDashpayUser alloc] initWithDocument:result->ok]; + DSLog(@"%@: OK: %@", debugString, transientDashpayUser); + dispatch_async(completionQueue, ^{ if (completion) completion(YES, transientDashpayUser, nil); }); } -- (id)fetchProfilesForBlockchainIdentities:(NSArray *)blockchainIdentities withCompletion:(DashpayUserInfosCompletionBlock)completion onCompletionQueue:(dispatch_queue_t)completionQueue { - __weak typeof(self) weakSelf = self; - +- (void)fetchProfilesForIdentities:(NSArray *)identityUserIds + withCompletion:(DashpayUserInfosCompletionBlock)completion + onCompletionQueue:(dispatch_queue_t)completionQueue { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@ Fetch Profiles for: %@", self.logPrefix, identityUserIds]; + DSLog(@"%@", debugString); DPContract *dashpayContract = [DSDashPlatform sharedInstanceForChain:self.chain].dashPayContract; if ([dashpayContract contractState] != DPContractState_Registered) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, [NSError errorWithCode:500 localizedDescriptionKey:@"The Dashpay contract is not properly set up"]); - }); - } - return nil; + DSLog(@"%@: ERROR: DashPay Contract Not Registered", debugString); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, nil, ERROR_CONTRACT_SETUP); }); + return; } - NSMutableArray *blockchainIdentityUserIds = [NSMutableArray array]; - for (DSBlockchainIdentity *blockchainIdentity in blockchainIdentities) { - [blockchainIdentityUserIds addObject:blockchainIdentity.uniqueIDData]; + NSUInteger user_ids_count = identityUserIds.count; + u256 **user_ids_values = malloc(sizeof(u256 *) * user_ids_count); + + for (int i = 0; i < user_ids_count; i++) { + NSData *userID = identityUserIds[i]; + user_ids_values[i] = u256_ctor(userID); } - DSDAPIClient *client = self.chain.chainManager.DAPIClient; - id call = [client.DAPIPlatformNetworkService getDashpayProfilesForUserIds:blockchainIdentityUserIds - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, [NSError errorWithCode:500 localizedDescriptionKey:@"Internal memory allocation error"]); - }); - } - return; - } - - NSMutableDictionary *dashpayUserDictionary = [NSMutableDictionary dictionary]; - for (NSDictionary *documentDictionary in documents) { - NSData *userIdData = documentDictionary[@"$ownerId"]; - DSTransientDashpayUser *transientDashpayUser = [[DSTransientDashpayUser alloc] initWithDashpayProfileDocument:documentDictionary]; - [dashpayUserDictionary setObject:transientDashpayUser forKey:userIdData]; - } - __weak typeof(self) weakSelf = self; - if (completion) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; + Vec_u8_32 *user_ids = Vec_u8_32_ctor(user_ids_count, user_ids_values); + DMaybeDocumentsMap *result = dash_spv_platform_document_manager_DocumentsManager_stream_dashpay_profiles_for_user_ids_using_contract(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, user_ids, dashpayContract.raw_contract, DRetryDown20(5), DNotFoundAsAnError(), 2000); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: ERROR: %@", debugString, error); + if (completion) dispatch_async(completionQueue, ^{ completion(NO, nil, error); }); + DMaybeDocumentsMapDtor(result); + return; + } + DDocumentsMap *documents = result->ok; + NSMutableDictionary *dashpayUserDictionary = [NSMutableDictionary dictionary]; + for (int i = 0; i < documents->count; i++) { + DDocument *document = documents->values[i]; + switch (document->tag) { + case dpp_document_Document_V0: { + DSTransientDashpayUser *transientDashpayUser = [[DSTransientDashpayUser alloc] initWithDocument:document]; + [dashpayUserDictionary setObject:transientDashpayUser forKey:NSDataFromPtr(document->v0->owner_id->_0->_0)]; + break; } - dispatch_async(completionQueue, ^{ - completion(YES, dashpayUserDictionary, nil); - }); + default: + break; } } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(NO, nil, error); - }); - } - }]; - return call; + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: OK: %@", debugString, dashpayUserDictionary); + if (completion) dispatch_async(completionQueue, ^{ completion(YES, dashpayUserDictionary, nil); }); } -- (id)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo withCompletion:(IdentitiesCompletionBlock)completion { - return [self searchIdentitiesByDashpayUsernamePrefix:namePrefix startAfter:nil limit:100 queryDashpayProfileInfo:queryDashpayProfileInfo withCompletion:completion]; +- (void)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix + queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo + withCompletion:(IdentitiesCompletionBlock)completion { + [self searchIdentitiesByDashpayUsernamePrefix:namePrefix + startAfter:nil + limit:100 + queryDashpayProfileInfo:queryDashpayProfileInfo + withCompletion:completion]; } -- (id)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix startAfter:(NSData* _Nullable)startAfter limit:(uint32_t)limit queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo withCompletion:(IdentitiesCompletionBlock)completion { - return [self searchIdentitiesByNamePrefix:namePrefix - inDomain:@"dash" - startAfter:startAfter - limit:limit - withCompletion:^(BOOL success, NSArray *_Nullable blockchainIdentities, NSArray *_Nonnull errors) { +- (void)searchIdentitiesByDashpayUsernamePrefix:(NSString *)namePrefix + startAfter:(NSData* _Nullable)startAfter + limit:(uint32_t)limit + queryDashpayProfileInfo:(BOOL)queryDashpayProfileInfo + withCompletion:(IdentitiesCompletionBlock)completion { + [self searchIdentitiesByNamePrefix:namePrefix + startAfter:startAfter + limit:limit + withCompletion:^(BOOL success, NSArray *_Nullable identities, NSArray *_Nonnull errors) { if (errors.count) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(success, blockchainIdentities, errors); - }); - } - } else if (queryDashpayProfileInfo && blockchainIdentities.count) { - __block NSMutableDictionary *blockchainIdentityDictionary = [NSMutableDictionary dictionary]; - for (DSBlockchainIdentity *blockchainIdentity in blockchainIdentities) { - [blockchainIdentityDictionary setObject:blockchainIdentity forKey:blockchainIdentity.uniqueIDData]; + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(success, identities, errors); }); + } else if (queryDashpayProfileInfo && identities.count) { + __block NSMutableDictionary *identityDictionary = [NSMutableDictionary dictionary]; + for (DSIdentity *identity in identities) { + [identityDictionary setObject:identity forKey:identity.uniqueIDData]; } - [self fetchProfilesForBlockchainIdentities:blockchainIdentities - withCompletion:^(BOOL success, NSDictionary *_Nullable dashpayUserInfosByBlockchainIdentityUniqueId, NSError *_Nullable error) { - for (NSData *blockchainIdentityUniqueIdData in dashpayUserInfosByBlockchainIdentityUniqueId) { - DSBlockchainIdentity *blockchainIdentity = blockchainIdentityDictionary[blockchainIdentityUniqueIdData]; - blockchainIdentity.transientDashpayUser = dashpayUserInfosByBlockchainIdentityUniqueId[blockchainIdentityUniqueIdData]; + [self fetchProfilesForIdentities:identityDictionary.allKeys + withCompletion:^(BOOL success, NSDictionary *_Nullable dashpayUserInfosByIdentityUniqueId, NSError *_Nullable error) { + for (NSData *identityUniqueIdData in dashpayUserInfosByIdentityUniqueId) { + DSIdentity *identity = identityDictionary[identityUniqueIdData]; + identity.transientDashpayUser = dashpayUserInfosByIdentityUniqueId[identityUniqueIdData]; } - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(success, blockchainIdentities, errors); - }); - } - } - onCompletionQueue:self.identityQueue]; - } else { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(success, blockchainIdentities, errors); - }); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(success, identities, errors); }); } + onCompletionQueue:self.identityQueue]; + } else if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ completion(success, identities, errors); }); } }]; } -- (id)searchIdentitiesByNamePrefix:(NSString *)namePrefix inDomain:(NSString *)domain startAfter:(NSData* _Nullable)startAfter limit:(uint32_t)limit withCompletion:(IdentitiesCompletionBlock)completion { - DSDAPIClient *client = self.chain.chainManager.DAPIClient; - id call = [client.DAPIPlatformNetworkService searchDPNSDocumentsForUsernamePrefix:namePrefix - inDomain:domain - startAfter:startAfter - limit:limit - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - __block NSMutableDictionary *rBlockchainIdentities = [NSMutableDictionary dictionary]; - for (NSDictionary *document in documents) { - NSData *userIdData = document[@"$ownerId"]; - DSBlockchainIdentity *identity = [rBlockchainIdentities objectForKey:userIdData]; - UInt256 uniqueId = userIdData.UInt256; - if (!identity) { - identity = [self.chain blockchainIdentityForUniqueId:uniqueId foundInWallet:nil includeForeignBlockchainIdentities:YES]; - } - NSString *label = document[@"label"]; - NSString *domain = document[@"normalizedParentDomainName"]; - if (!identity) { - identity = [[DSBlockchainIdentity alloc] initWithUniqueId:uniqueId isTransient:TRUE onChain:self.chain]; - [identity addUsername:label inDomain:domain status:DSBlockchainIdentityUsernameStatus_Confirmed save:NO registerOnNetwork:NO]; - } else { - if (![identity.dashpayUsernames containsObject:label]) { - [identity addUsername:label inDomain:domain status:DSBlockchainIdentityUsernameStatus_Confirmed save:YES registerOnNetwork:NO]; +- (void)searchIdentitiesByNamePrefix:(NSString *)namePrefix + startAfter:(NSData* _Nullable)startAfter + limit:(uint32_t)limit + withCompletion:(IdentitiesCompletionBlock)completion { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@ Search Identities By Name Prefix: %@", self.logPrefix, namePrefix]; + DSLog(@"%@", debugString); + DMaybeDocumentsMap *result = dash_spv_platform_document_manager_DocumentsManager_dpns_documents_for_username_prefix(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, DChar(namePrefix)); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DSLog(@"%@: ERROR: %@", debugString, error); + DMaybeDocumentsMapDtor(result); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, nil, @[error]); }); + return; + } + DDocumentsMap *documents = result->ok; + __block NSMutableDictionary *rIdentities = [NSMutableDictionary dictionary]; + for (int i = 0; i < documents->count; i++) { + DDocument *document = documents->values[i]; + if (!document) continue; + DSLog(@"%@: document[%i]: ", debugString, i); + dash_spv_platform_document_print_document(document); + + switch (document->tag) { + case dpp_document_Document_V0: { + u256 *owner_id = document->v0->owner_id->_0->_0; + NSData *userIdData = NSDataFromPtr(owner_id); + UInt256 uniqueId = u256_cast(owner_id); + DSIdentity *identity = [rIdentities objectForKey:userIdData]; + if (!identity) + identity = [self.chain identityForUniqueId:uniqueId foundInWallet:nil includeForeignIdentities:YES]; + NSString *label = DGetTextDocProperty(document, @"label"); + NSString *domain = DGetTextDocProperty(document, @"normalizedParentDomainName"); + if (!identity) { + identity = [[DSIdentity alloc] initWithUniqueId:uniqueId isTransient:TRUE onChain:self.chain]; + [identity addUsername:label inDomain:domain status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed_ctor() save:NO registerOnNetwork:NO]; + } else if (![identity hasDashpayUsername:label]) { + [identity addUsername:label inDomain:domain status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed_ctor() save:YES registerOnNetwork:NO]; } + [rIdentities setObject:identity forKey:userIdData]; + break; } - - [rBlockchainIdentities setObject:identity forKey:userIdData]; - } - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(YES, [[rBlockchainIdentities allValues] copy], @[]); - }); + default: + break; } } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, nil, @[error]); - }); - } -#if DEBUG - DSLogPrivate(@"Failure in searchIdentitiesByNamePrefix %@", error); -#else - DSLog(@"Failure in searchIdentitiesByNamePrefix %@", @""); -#endif - }]; - return call; + DSLog(@"%@: OK: %@", debugString, rIdentities); + + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(YES, [[rIdentities allValues] copy], @[]); }); } -- (void)searchIdentitiesByDPNSRegisteredBlockchainIdentityUniqueID:(NSData *)userID withCompletion:(IdentitiesCompletionBlock)completion { - DSDAPIClient *client = self.chain.chainManager.DAPIClient; - [client.DAPIPlatformNetworkService getDPNSDocumentsForIdentityWithUserId:userID - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull documents) { - __block NSMutableArray *rBlockchainIdentities = [NSMutableArray array]; - for (NSDictionary *document in documents) { - NSData *userIdData = document[@"$ownerId"]; - NSString *normalizedLabel = document[@"normalizedLabel"]; - NSString *domain = document[@"normalizedParentDomainName"]; - DSBlockchainIdentity *identity = [[DSBlockchainIdentity alloc] initWithUniqueId:userIdData.UInt256 isTransient:TRUE onChain:self.chain]; - [identity addUsername:normalizedLabel inDomain:domain status:DSBlockchainIdentityUsernameStatus_Confirmed save:NO registerOnNetwork:NO]; - [identity fetchIdentityNetworkStateInformationWithCompletion:^(BOOL success, BOOL found, NSError *error){ - - }]; - [rBlockchainIdentities addObject:identity]; - } - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(YES, [rBlockchainIdentities copy], @[]); - }); - } +- (void)searchIdentitiesByDPNSRegisteredIdentityUniqueID:(NSData *)userID + withCompletion:(IdentitiesCompletionBlock)completion { + NSMutableString *debugString = [NSMutableString stringWithFormat:@"%@ Search Identities By DPNS identity id: %@", self.logPrefix, userID.hexString]; + DSLog(@"%@", debugString); + DMaybeDocumentsMap *result = dash_spv_platform_document_manager_DocumentsManager_dpns_documents_for_identity_with_user_id(self.chain.sharedRuntime, self.chain.sharedDocumentsObj, u256_ctor(userID)); + if (result->error) { + NSError *error = [NSError ffi_from_platform_error:result->error]; + DMaybeDocumentsMapDtor(result); + DSLog(@"%@: ERROR: %@", debugString, error); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, nil, @[error]); }); + return; } - failure:^(NSError *_Nonnull error) { - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, nil, @[error]); - }); + DDocumentsMap *documents = result->ok; + __block NSMutableArray *rIdentities = [NSMutableArray array]; + for (int i = 0; i < documents->count; i++) { + DDocument *document = documents->values[i]; + if (!document) continue; + DSLog(@"%@: document[%i]: ", debugString, i); + dash_spv_platform_document_print_document(document); + + switch (document->tag) { + case dpp_document_Document_V0: { + NSString *normalizedLabel = DGetTextDocProperty(document, @"normalizedLabel"); + NSString *domain = DGetTextDocProperty(document, @"normalizedParentDomainName"); + DSIdentity *identity = [[DSIdentity alloc] initWithUniqueId:u256_cast(document->v0->owner_id->_0->_0) isTransient:TRUE onChain:self.chain]; + [identity addUsername:normalizedLabel inDomain:domain status:dash_spv_platform_document_usernames_UsernameStatus_Confirmed_ctor() save:NO registerOnNetwork:NO]; + [identity fetchIdentityNetworkStateInformationWithCompletion:^(BOOL success, BOOL found, NSError *error) {}]; + [rIdentities addObject:identity]; + break; + } + default: + break; } -#if DEBUG - DSLogPrivate(@"Failure in searchIdentitiesByDPNSRegisteredBlockchainIdentityUniqueID %@", error); -#else - DSLog(@"Failure in searchIdentitiesByDPNSRegisteredBlockchainIdentityUniqueID %@", @""); -#endif - }]; + } + DSLog(@"%@: OK: %@", debugString, rIdentities); + DMaybeDocumentsMapDtor(result); + if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(YES, [rIdentities copy], @[]); }); } -- (void)checkCreditFundingTransactionForPossibleNewIdentity:(DSCreditFundingTransaction *)creditFundingTransaction { +- (void)checkAssetLockTransactionForPossibleNewIdentity:(DSAssetLockTransaction *)transaction { uint32_t index; - DSWallet *wallet = [self.chain walletHavingBlockchainIdentityCreditFundingRegistrationHash:creditFundingTransaction.creditBurnPublicKeyHash foundAtIndex:&index]; - + DSWallet *wallet = [self.chain walletHavingIdentityAssetLockRegistrationHash:transaction.creditBurnPublicKeyHash foundAtIndex:&index]; if (!wallet) return; //it's a topup or we are funding an external identity - - DSBlockchainIdentity *blockchainIdentity = [wallet blockchainIdentityForUniqueId:creditFundingTransaction.creditBurnIdentityIdentifier]; - - NSAssert(blockchainIdentity, @"We should have already created the blockchain identity at this point in the transaction manager by calling triggerUpdatesForLocalReferences"); - - - //DSLogPrivate(@"Paused Sync at block %d to gather identity information on %@",block.height,blockchainIdentity.uniqueIdString); - [self fetchNeededNetworkStateInformationForBlockchainIdentity:blockchainIdentity - withCompletion:^(BOOL success, DSBlockchainIdentity *_Nullable blockchainIdentity, NSError *_Nullable error) { - if (success && blockchainIdentity != nil) { - [self chain:self.chain didFinishInChainSyncPhaseFetchingBlockchainIdentityDAPInformation:blockchainIdentity]; - } + DSIdentity *identity = [wallet identityForUniqueId:transaction.creditBurnIdentityIdentifier]; + NSAssert(identity, @"We should have already created the blockchain identity at this point in the transaction manager by calling triggerUpdatesForLocalReferences"); + //DSLogPrivate(@"Paused Sync at block %d to gather identity information on %@", block.height, identity.uniqueIdString); + [self fetchNeededNetworkStateInformationForIdentity:identity + withCompletion:^(BOOL success, DSIdentity *_Nullable identity, NSError *_Nullable error) { + if (success && identity != nil) + [self chain:self.chain didFinishInChainSyncPhaseFetchingIdentityDAPInformation:identity]; } completionQueue:self.chain.networkingQueue]; } -- (void)fetchNeededNetworkStateInformationForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity withCompletion:(IdentityCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue { - [blockchainIdentity fetchNeededNetworkStateInformationWithCompletion:^(DSBlockchainIdentityQueryStep failureStep, NSArray *_Nullable errors) { - if (!failureStep || failureStep == DSBlockchainIdentityQueryStep_NoIdentity) { +- (void)fetchNeededNetworkStateInformationForIdentity:(DSIdentity *)identity + withCompletion:(IdentityCompletionBlock)completion + completionQueue:(dispatch_queue_t)completionQueue { + [identity fetchNeededNetworkStateInformationInContext:[NSManagedObjectContext platformContext] + withCompletion:^(DSIdentityQueryStep failureStep, NSArray *_Nullable errors) { + if (!failureStep || failureStep == DSIdentityQueryStep_NoIdentity) { //if this was never registered no need to retry - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, blockchainIdentity, nil); - }); - } + if (completion) + dispatch_async(completionQueue, ^{ completion(YES, identity, nil); }); } else { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), completionQueue, ^{ - [self fetchNeededNetworkStateInformationForBlockchainIdentity:blockchainIdentity withCompletion:completion completionQueue:completionQueue]; + [self fetchNeededNetworkStateInformationForIdentity:identity + withCompletion:completion + completionQueue:completionQueue]; }); } - }]; -} - -- (void)fetchNeededNetworkStateInformationForBlockchainIdentities:(NSArray *)blockchainIdentities withCompletion:(IdentitiesCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue { - dispatch_group_t dispatchGroup = dispatch_group_create(); - __block NSMutableArray *errors = [NSMutableArray array]; - for (DSBlockchainIdentity *blockchainIdentity in blockchainIdentities) { - dispatch_group_enter(dispatchGroup); - [self fetchNeededNetworkStateInformationForBlockchainIdentity:blockchainIdentity - withCompletion:^(BOOL success, DSBlockchainIdentity *_Nullable blockchainIdentity, NSError *_Nullable error) { - if (success && blockchainIdentity != nil) { - dispatch_group_leave(dispatchGroup); - } else { - [errors addObject:error]; - } - } - completionQueue:self.identityQueue]; - } - dispatch_group_notify(dispatchGroup, completionQueue, ^{ - if (completion) { - completion(!errors.count, blockchainIdentities, errors); - } - }); -} - -- (NSArray *)identitiesFromIdentityDictionaries:(NSArray *)identityDictionaries keyIndexes:(NSDictionary *)keyIndexes forWallet:(DSWallet *)wallet { - NSMutableArray *identities = [NSMutableArray array]; - for (NSDictionary *versionedIdentityDictionary in identityDictionaries) { - NSNumber *version = [versionedIdentityDictionary objectForKey:@(DSPlatformStoredMessage_Version)]; - NSDictionary *identityDictionary = [versionedIdentityDictionary objectForKey:@(DSPlatformStoredMessage_Item)]; - OpaqueKey *key = [DSBlockchainIdentity firstKeyInIdentityDictionary:identityDictionary]; - NSNumber *index = [keyIndexes objectForKey:[DSKeyManager publicKeyData:key]]; - if (index) { - DSBlockchainIdentity *blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index.intValue withIdentityDictionary:identityDictionary version:[version intValue] inWallet:wallet]; - [identities addObject:blockchainIdentity]; - } - } - return identities; -} - -#define RETRIEVE_IDENTITIES_DELAY_INCREMENT 2 - -- (void)retrieveIdentitiesByKeysUntilSuccessWithCompletion:(IdentitiesSuccessCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue { - [self internalRetrieveIdentitiesByKeysUntilSuccessWithDelay:0 withCompletion:completion completionQueue:completionQueue]; -} - -- (void)internalRetrieveIdentitiesByKeysUntilSuccessWithDelay:(uint32_t)delay withCompletion:(IdentitiesSuccessCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue { - [self - retrieveIdentitiesByKeysWithCompletion:^(BOOL success, NSArray *_Nullable blockchainIdentities, NSArray *_Nonnull errors) { - if (!success) { - dispatch_after(delay, self.identityQueue, ^{ - [self internalRetrieveIdentitiesByKeysUntilSuccessWithDelay:delay + RETRIEVE_IDENTITIES_DELAY_INCREMENT withCompletion:completion completionQueue:completionQueue]; - }); - } else if (completion) { - completion(blockchainIdentities); - } - } - completionQueue:completionQueue]; -} - -- (void)retrieveIdentitiesByKeysWithCompletion:(IdentitiesCompletionBlock)completion completionQueue:(dispatch_queue_t)completionQueue { - if (!self.chain.isEvolutionEnabled) { - if (completion) { - dispatch_async(completionQueue, ^{ - completion(YES, @[], @[]); - }); - } - return; } - dispatch_async(self.identityQueue, ^{ - NSArray *wallets = self.chain.wallets; - DSDAPIClient *client = self.chain.chainManager.DAPIClient; - __block dispatch_group_t dispatch_group = dispatch_group_create(); - __block NSMutableArray *errors = [NSMutableArray array]; - __block NSMutableArray *allIdentities = [NSMutableArray array]; - - for (DSWallet *wallet in wallets) { - NSMutableArray *keyHashes = [NSMutableArray array]; - uint32_t unusedIndex = [wallet unusedBlockchainIdentityIndex]; - DSAuthenticationKeysDerivationPath *derivationPath = [DSBlockchainIdentity derivationPathForType:KeyKind_ECDSA forWallet:wallet]; - const int keysToCheck = 5; - NSMutableDictionary *keyIndexes = [NSMutableDictionary dictionary]; - for (int i = 0; i < keysToCheck; i++) { - const NSUInteger indexes[] = {(unusedIndex + i) | BIP32_HARD, 0 | BIP32_HARD}; - NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2]; - OpaqueKey *key = [derivationPath publicKeyAtIndexPath:indexPath]; - NSData *publicKeyData = [DSKeyManager publicKeyData:key]; - [keyHashes addObject:uint160_data(publicKeyData.hash160)]; - [keyIndexes setObject:@(unusedIndex + i) forKey:publicKeyData]; - } - dispatch_group_enter(dispatch_group); - [client.DAPIPlatformNetworkService fetchIdentitiesByKeyHashes:keyHashes - completionQueue:self.identityQueue - success:^(NSArray *_Nonnull identityDictionaries) { - NSArray *identities = [self identitiesFromIdentityDictionaries:identityDictionaries keyIndexes:keyIndexes forWallet:wallet]; - BOOL success = [wallet registerBlockchainIdentities:identities verify:YES]; - if (success) { - [allIdentities addObjectsFromArray:identities]; - NSManagedObjectContext *platformContext = [NSManagedObjectContext platformContext]; - [platformContext performBlockAndWait:^{ - for (DSBlockchainIdentity *identity in identities) { - [identity saveInitialInContext:platformContext]; - } - dispatch_group_leave(dispatch_group); - }]; - } else { - [errors addObject:[NSError errorWithDomain:@"DashPlatform" - code:500 - userInfo:@{NSLocalizedDescriptionKey: - DSLocalizedString(@"Identity has unknown keys", nil)}]]; - dispatch_group_leave(dispatch_group); - } - } - failure:^(NSError *_Nonnull error) { - if (error) { - [errors addObject:error]; - } - dispatch_group_leave(dispatch_group); - }]; - } - - dispatch_group_notify(dispatch_group, completionQueue, ^{ - completion(!errors.count, allIdentities, errors); - }); - }); + onCompletionQueue:dispatch_get_main_queue()]; } // MARK: - DSChainIdentitiesDelegate -- (void)chain:(DSChain *)chain didFinishInChainSyncPhaseFetchingBlockchainIdentityDAPInformation:(DSBlockchainIdentity *)blockchainIdentity { - [self.chain.chainManager chain:chain didFinishInChainSyncPhaseFetchingBlockchainIdentityDAPInformation:blockchainIdentity]; +- (void)chain:(DSChain *)chain didFinishInChainSyncPhaseFetchingIdentityDAPInformation:(DSIdentity *)identity { + [self.chain.chainManager chain:chain didFinishInChainSyncPhaseFetchingIdentityDAPInformation:identity]; } @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.h index fed7b41db..156dd0f26 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.h @@ -16,9 +16,598 @@ // #import -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSChain.h" -#import "DSDerivationPath.h" +#import "NSIndexPath+FFI.h" + +#define NSDataToHeap(data) (^{ \ + uint8_t *ffi_ref = malloc(data.length); \ + memcpy(ffi_ref, data.bytes, data.length); \ + return ffi_ref; \ +}()) + +#define u128 Arr_u8_16 +#define u160 Arr_u8_20 +#define u256 Arr_u8_32 +#define u264 Arr_u8_33 +#define u384 Arr_u8_48 +#define u512 Arr_u8_64 +#define u768 Arr_u8_96 + +#define u128_ctor(data) Arr_u8_16_ctor(data.length, NSDataToHeap(data)) +#define u160_ctor(data) Arr_u8_20_ctor(data.length, NSDataToHeap(data)) +#define u256_ctor(data) Arr_u8_32_ctor(data.length, NSDataToHeap(data)) +#define u264_ctor(data) Arr_u8_33_ctor(data.length, NSDataToHeap(data)) +#define u384_ctor(data) Arr_u8_48_ctor(data.length, NSDataToHeap(data)) +#define u512_ctor(data) Arr_u8_64_ctor(data.length, NSDataToHeap(data)) +#define u768_ctor(data) Arr_u8_96_ctor(data.length, NSDataToHeap(data)) + +#define u128_cast(u) *((UInt128 *)u->values) +#define u160_cast(u) *((UInt160 *)u->values) +#define u256_cast(u) *((UInt256 *)u->values) +#define u384_cast(u) *((UInt384 *)u->values) +#define u512_cast(u) *((UInt512 *)u->values) +#define u768_cast(u) *((UInt768 *)u->values) + +#define u128_hex(u) uint128_hex(*((UInt128 *)u->values)) +#define u160_hex(u) uint160_hex(*((UInt160 *)u->values)) +#define u256_hex(u) uint256_hex(*((UInt256 *)u->values)) +#define u384_hex(u) uint384_hex(*((UInt384 *)u->values)) +#define u512_hex(u) uint512_hex(*((UInt512 *)u->values)) +#define u768_hex(u) uint768_hex(*((UInt768 *)u->values)) + +#define u128_reversed_hex(u) uint128_hex(uint128_reverse(*((UInt128 *)u->values))) +#define u160_reversed_hex(u) uint160_hex(uint160_reverse(*((UInt160 *)u->values))) +#define u256_reversed_hex(u) uint256_hex(uint256_reverse(*((UInt256 *)u->values))) +#define u384_reversed_hex(u) uint384_hex(uint384_reverse(*((UInt384 *)u->values))) +#define u512_reversed_hex(u) uint512_hex(uint512_reverse(*((UInt512 *)u->values))) +#define u768_reversed_hex(u) uint768_hex(uint768_reverse(*((UInt768 *)u->values))) + +#define u8_16_ctor_u(u) (^{ \ + uint8_t (*ffi_ref)[16] = malloc(16 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 16); \ + return ffi_ref; \ +}()) + +#define u8_20_ctor_u(u) (^{ \ + uint8_t (*ffi_ref)[20] = malloc(20 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 20); \ + return ffi_ref; \ +}()) +#define u8_32_ctor_u(u) (^{ \ + uint8_t (*ffi_ref)[32] = malloc(32 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 32); \ + return ffi_ref; \ +}()) + +#define u8_48_ctor_u(u) (^{ \ + uint8_t (*ffi_ref)[48] = malloc(48 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 48); \ + return ffi_ref; \ +}()) + +#define u8_64_ctor_u(u) (^{ \ + uint8_t (*ffi_ref)[64] = malloc(64 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 64); \ + return ffi_ref; \ +}()) + +#define u8_96_ctor_u(u) (^{ \ + uint8_t (*ffi_ref)[96] = malloc(96 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 96); \ + return ffi_ref; \ +}()) + +#define u128_ctor_u(u) (^{ \ + uint8_t *ffi_ref = malloc(16 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 16); \ + return Arr_u8_16_ctor(16, ffi_ref); \ +}()) +#define u160_ctor_u(u) (^{ \ + uint8_t *ffi_ref = malloc(20 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 20); \ + return Arr_u8_20_ctor(20, ffi_ref); \ +}()) +#define u256_ctor_u(u) (^{ \ + uint8_t *ffi_ref = malloc(32 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 32); \ + return Arr_u8_32_ctor(32, ffi_ref); \ +}()) +#define u384_ctor_u(u) (^{ \ + uint8_t *ffi_ref = malloc(48 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 48); \ + return Arr_u8_48_ctor(48, ffi_ref); \ +}()) +#define u512_ctor_u(u) (^{ \ + uint8_t *ffi_ref = malloc(64 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 64); \ + return Arr_u8_64_ctor(64, ffi_ref); \ +}()) +#define u768_ctor_u(u) (^{ \ + uint8_t *ffi_ref = malloc(96 * sizeof(uint8_t)); \ + memcpy(ffi_ref, u.u8, 96); \ + return Arr_u8_96_ctor(96, ffi_ref); \ +}()) + + +#define u128_dtor(ptr) Arr_u8_16_destroy(ptr) +#define u160_dtor(ptr) Arr_u8_20_destroy(ptr) +#define u256_dtor(ptr) Arr_u8_32_destroy(ptr) +#define u264_dtor(ptr) Arr_u8_33_destroy(ptr) +#define u384_dtor(ptr) Arr_u8_48_destroy(ptr) +#define u512_dtor(ptr) Arr_u8_64_destroy(ptr) +#define u768_dtor(ptr) Arr_u8_96_destroy(ptr) + +#define u_is_zero(ptr) ({ \ + BOOL result = YES; \ + for (uintptr_t i = 0; i < ptr->count; i++) { \ + if (ptr->values[i] != 0) { \ + result = NO; \ + break; \ + } \ + } \ + result; \ +}) + +#define slice_ctor(data) Slice_u8_ctor(data.length, NSDataToHeap(data)) +#define slice_u128_ctor_u(u) Slice_u8_ctor(16, u.u8) +#define slice_u160_ctor_u(u) Slice_u8_ctor(20, u.u8) +#define slice_u256_ctor_u(u) Slice_u8_ctor(32, u.u8) +#define slice_u384_ctor_u(u) Slice_u8_ctor(48, u.u8) +#define slice_u512_ctor_u(u) Slice_u8_ctor(64, u.u8) +#define slice_u768_ctor_u(u) Slice_u8_ctor(96, u.u8) + +#define slice_dtor(ptr) Slice_u8_destroy(ptr) + +#define bytes_ctor(data) Vec_u8_ctor(data.length, NSDataToHeap(data)) +#define bytes_dtor(ptr) Vec_u8_destroy(ptr) + +#define DChar(str) (char *) [str UTF8String] +#define DCharDtor(str) str_destroy(str) + + +#define DMNSyncState dash_spv_masternode_processor_models_sync_state_CacheState +#define DMNSyncStateDtor(ptr) dash_spv_masternode_processor_models_sync_state_CacheState_destroy(ptr) +#define DMNSyncStateQueueChanged dash_spv_masternode_processor_models_sync_state_CacheState_QueueChanged +#define DMNSyncStateStoreChanged dash_spv_masternode_processor_models_sync_state_CacheState_StoreChanged +#define DMNSyncStateStubCount dash_spv_masternode_processor_models_sync_state_CacheState_StubCount + +#define MaybePubKey Result_ok_u8_arr_48_err_drive_proof_verifier_error_ContextProviderError +#define MaybePubKeyDtor Result_ok_u8_arr_48_err_drive_proof_verifier_error_ContextProviderError_destroy +#define DDataContract dpp_data_contract_DataContract +#define MaybeDataContract Result_ok_Option_std_sync_Arc_dpp_data_contract_DataContract_err_drive_proof_verifier_error_ContextProviderError +#define MaybeDataContractDtor Result_ok_Option_std_sync_Arc_dpp_data_contract_DataContract_err_drive_proof_verifier_error_ContextProviderError_destroy +#define MaybeSignedData Result_ok_platform_value_types_binary_data_BinaryData_err_dpp_errors_protocol_error_ProtocolError +#define MaybeSignedDataDtor Result_ok_platform_value_types_binary_data_BinaryData_err_dpp_errors_protocol_error_ProtocolError_destroy +#define MaybePlatformActivationHeight Result_ok_dpp_prelude_CoreBlockHeight_err_drive_proof_verifier_error_ContextProviderError + +#define DCoreProviderError dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define DCoreProviderErrorNullResultCtor(message) dash_spv_masternode_processor_processing_core_provider_CoreProviderError_NullResult_ctor(message) +#define MaybeBool Result_ok_bool_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define MaybeLLMQSnapshot Result_ok_dash_spv_masternode_processor_models_snapshot_LLMQSnapshot_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError + +#define DBlock dash_spv_masternode_processor_common_block_Block +#define DBlockCtor(height, hash) dash_spv_masternode_processor_common_block_Block_ctor(height, hash) + +#define DMBlock dash_spv_masternode_processor_common_block_MBlock +#define DMBlockCtor(height, hash, merkle_root) dash_spv_masternode_processor_common_block_MBlock_ctor(height, hash, merkle_root) + +#define DMaybeBlock Result_ok_dash_spv_masternode_processor_common_block_Block_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define DMaybeBlockCtor(ok, err) Result_ok_dash_spv_masternode_processor_common_block_Block_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError_ctor(ok, err) +#define DMaybeBlockDtor(ptr) Result_ok_dash_spv_masternode_processor_common_block_Block_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError_destroy(ptr) + +#define DMaybeMBlock Result_ok_dash_spv_masternode_processor_common_block_MBlock_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError +#define DMaybeMBlockCtor(ok, err) Result_ok_dash_spv_masternode_processor_common_block_MBlock_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError_ctor(ok, err) + +#define DMasternodeList dashcore_sml_masternode_list_MasternodeList +#define DMasternodeListDtor(ptr) dashcore_sml_masternode_list_MasternodeList_destroy(ptr) +#define DMaybeMasternodeList Result_ok_dashcore_sml_masternode_list_MasternodeList_err_dash_spv_masternode_processor_processing_core_provider_CoreProviderError + +#define DMasternodeEntry dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry +#define DMasternodeEntryDtor(ptr) dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry_destroy(ptr) +#define DMasternodeEntryList Vec_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry +#define DMasternodeEntryListCtor(count, list) Vec_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry_ctor(count, list) +#define DMasternodeEntryListDtor(ptr) Vec_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry_destroy(ptr) +#define DMasternodeEntryMapDtor(ptr) std_collections_Map_keys_u8_arr_32_values_dashcore_sml_masternode_list_entry_qualified_masternode_list_entry_QualifiedMasternodeListEntry_destroy(ptr) +#define DLLMQEntry dashcore_sml_quorum_entry_qualified_quorum_entry_QualifiedQuorumEntry +#define DLLMQEntryDtor(ptr) dashcore_sml_quorum_entry_qualified_quorum_entry_QualifiedQuorumEntry_destroy(ptr) + +#define DLLMQEntryList Vec_dash_spv_crypto_llmq_entry_LLMQEntry +#define DLLMQEntryListCtor(count, list) Vec_dash_spv_crypto_llmq_entry_LLMQEntry_ctor(count, list) + +#define DLLMQType dash_spv_crypto_network_llmq_type_LLMQType +#define DLLMQSnapshot dash_spv_masternode_processor_models_snapshot_LLMQSnapshot +#define DKeyError dash_spv_crypto_keys_KeyError +#define DKeyKind dash_spv_crypto_keys_key_KeyKind +#define DKeyKindIndex(kind) dash_spv_crypto_keys_key_KeyKind_index(kind) +#define DKeyKindFromIndex(index) dash_spv_crypto_keys_key_key_kind_from_index(index) +#define DKeyKindDtor(ptr) dash_spv_crypto_keys_key_KeyKind_destroy(ptr) +#define DKeyKindECDSA() dash_spv_crypto_keys_key_KeyKind_ECDSA_ctor() +#define DKeyKindBLS() dash_spv_crypto_keys_key_KeyKind_BLS_ctor() +#define DKeyKindED25519() dash_spv_crypto_keys_key_KeyKind_ED25519_ctor() +#define DKeyKindStoragePrefix(kind) dash_spv_crypto_keys_key_KeyKind_key_storage_prefix(kind) +#define DKeyKindDerivationString(kind) dash_spv_crypto_keys_key_KeyKind_derivation_string(kind) +#define DKeyVerificationResult Result_ok_bool_err_dash_spv_crypto_keys_KeyError +#define DKeyVerificationResultDtor(ptr) Result_ok_bool_err_dash_spv_crypto_keys_KeyError_destroy(ptr) + +#define DMaybeECDSAKey Result_ok_dash_spv_crypto_keys_ecdsa_key_ECDSAKey_err_dash_spv_crypto_keys_KeyError +#define DMaybeECDSAKeyDtor(ptr) Result_ok_dash_spv_crypto_keys_ecdsa_key_ECDSAKey_err_dash_spv_crypto_keys_KeyError_destroy(ptr) +#define DMaybeECDSAKeyWithPrivateKey(key_str, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_key_with_private_key(key_str, chain_type) +#define DECDSAKeyPublicKeyData(key) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_public_key_data(key) +#define DECDSAKeyPublicKeyDataForPrivateKey(key_str, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_public_key_data_for_private_key(key_str, chain_type) +#define DECDSAKeySign(key, data) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_sign(key, data) +#define DECDSAKeyCompactSign(key, data) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_compact_sign(key, data) +#define DECDSAKeyPublicKeyHash(key) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_hash160(key) +#define DECDSAKeyPubAddress(key, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_address_with_public_key_data(key, chain_type) +#define DECDSAKeyWithSecret(data, compressed) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_key_with_secret_data(data, compressed) +#define DECDSAKeyWithPublicKeyData(data) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_key_with_public_key_data(data) +#define DECDSAKeyWithPublicKeyDataEqualTo(key, data) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_public_key_data_equal_to(key, data) +#define DECDSAKeyWithCompactSig(sig, digest) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_key_with_compact_sig(sig, digest) +#define DECDSAKeyFromCompactSig(sig, digest) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_key_recovered_from_compact_sig(sig, digest) +#define DECDSAKeyFromSeedData(data) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_init_with_seed_data(data) +#define DECDSAKeySerializedPrivateKey(key, script) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_serialized_private_key_for_script(key, script) +#define DECDSAKeySerializedPrivateMasterKey(seed, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_serialized_private_master_key_from_seed(seed, chain_type) +#define DECDSAKeySerializedPrivateKeyFromSeedAtU256(seed, path, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_serialized_extended_private_key_from_seed_at_u256_path(seed, path, chain_type) +#define DECDSAKeySerializedPrivateKeyFromBIP38(bip38_key, passphrase, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_serialized_from_bip38_key(bip38_key, passphrase, chain_type) +#define DECDSAKeySerializedAuthPrivateKeyFromSeed(seed, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_serialized_auth_private_key_from_seed_for_chain(seed, chain_type) +#define DECDSAKeyIsValidBIP38(bip38_key) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_is_valid_bip38_key(bip38_key) +#define DECDSAPublicKeyUniqueIdFromDerivedKeyData(secret, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_public_key_unique_id_from_derived_key_data(secret, chain_type) +#define DECDSAKeyProRegTxPayloadCollateralDigest(payload_hash, script_payout, reward, owner_hash, voter_hash, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_pro_reg_tx_payload_collateral_digest(payload_hash, script_payout, reward, owner_hash, voter_hash, chain_type) +#define DECDSAKeyProRegTxVerifyPayloadSig(sig, payload, hash) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_pro_reg_tx_verify_payload_signature(sig, payload, hash) +#define DECDSAKeyContainsSecretKey(sec_key_str, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_contains_secret_key(sec_key_str, chain_type) + +#define DECDSAKeyAddressFromPublicKeyData(data, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_address_from_public_key_data(data, chain_type) +#define DECDSAKeyAddressFromRecoveredCompactSig(sig, digest, chain_type) dash_spv_crypto_keys_ecdsa_key_ECDSAKey_address_from_recovered_compact_sig(sig, digest, chain_type) + +#define DBLSKeyWithPublicKey(key, legacy) dash_spv_crypto_keys_bls_key_BLSKey_key_with_public_key(key, legacy) +#define DBLSKeyWithSeedData(data, legacy) dash_spv_crypto_keys_bls_key_BLSKey_key_with_seed_data(data, legacy) +#define DBLSKeyPublicKeyData(key) dash_spv_crypto_keys_bls_key_BLSKey_public_key_data(key) +#define DBLSKeyPrivateKeyData(key) dash_spv_crypto_keys_bls_key_BLSKey_private_key_data(key) +#define DBLSKeySignData(key, data) dash_spv_crypto_keys_bls_key_BLSKey_sign_data(key, data) +#define DBLSKeyVerify(key, digest, sig) dash_spv_crypto_keys_bls_key_BLSKey_verify(key, digest, sig) +#define DBLSKeyVerifySig(key, legacy, digest, sig) dash_spv_crypto_keys_bls_key_BLSKey_verify_signature(key, legacy, digest, sig) +#define DBLSKeySerializedPubKey(key, legacy) dash_spv_crypto_keys_bls_key_BLSKey_public_key_serialized(key, legacy) + +#define DOpaqueKey dash_spv_crypto_keys_key_OpaqueKey +#define DOpaqueKeyDtor(ptr) dash_spv_crypto_keys_key_OpaqueKey_destroy(ptr) +#define DOpaqueKeyExtendedPublicKeyData(ptr) dash_spv_crypto_keys_key_OpaqueKey_extended_public_key_data(ptr) + +#define DOpaqueKeyEncryptData(prv_key, pub_key, data) dash_spv_crypto_keys_key_OpaqueKey_encrypt_data(prv_key, pub_key, data) +#define DOpaqueKeyEncryptDataUsingIV(prv_key, pub_key, data, iv) dash_spv_crypto_keys_key_OpaqueKey_encrypt_data_using_iv(prv_key, pub_key, data, iv) +#define DOpaqueKeyEncryptDataWithDHKey(key, data) dash_spv_crypto_keys_key_OpaqueKey_encrypt_data_with_dh_key(key, data) +#define DOpaqueKeyDecryptData(prv_key, pub_key, data) dash_spv_crypto_keys_key_OpaqueKey_decrypt_data(prv_key, pub_key, data) +#define DOpaqueKeyDecryptDataUsingIV(prv_key, pub_key, data, iv) dash_spv_crypto_keys_key_OpaqueKey_decrypt_data_using_iv_size(prv_key, pub_key, data, iv) +#define DOpaqueKeyDecryptDataWithDHKey(key, data) dash_spv_crypto_keys_key_OpaqueKey_decrypt_data_with_dh_key(key, data) + +#define DOpaqueKeySign(key, data) dash_spv_crypto_keys_key_OpaqueKey_sign(key, data) +#define DOpaqueKeyPrivateKeyData(key) dash_spv_crypto_keys_key_OpaqueKey_private_key_data(key) +#define DOpaqueKeyPublicKeyData(key) dash_spv_crypto_keys_key_OpaqueKey_public_key_data(key) +#define DOpaqueKeyPublicKeyHash(key) dash_spv_crypto_keys_key_OpaqueKey_hash160(key) +#define DOpaqueKeyHashAndSign(key, data) dash_spv_crypto_keys_key_OpaqueKey_hash_and_sign(key, data) +#define DOpaqueKeyCreateIdentifier(key) dash_spv_crypto_keys_key_OpaqueKey_create_identifier(key) +#define DOpaqueKeyCreateAccountRef(src_key, dst_key, acc_number) dash_spv_crypto_keys_key_OpaqueKey_create_account_reference(src_key, dst_key, acc_number) +#define DOpaqueKeyCreateTxSig(key, input, flags, in_script) dash_spv_crypto_keys_key_OpaqueKey_create_tx_signature(key, input, flags, in_script) +#define DOpaqueKeyHasPrivateKey(key) dash_spv_crypto_keys_key_OpaqueKey_has_private_key(key) +#define DOpaqueKeyForgetPrivateKey(key) dash_spv_crypto_keys_key_OpaqueKey_forget_private_key(key) +#define DOpaqueKeyDerivateTo256WithOffset(key, path, offset) dash_spv_crypto_keys_key_OpaqueKey_public_derive_to_256_path_with_offset(key, path, offset) +#define DOpaqueKeyVerify(key, payload, signature) dash_spv_crypto_keys_key_OpaqueKey_verify(key, payload, signature) +#define DOpaqueKeyCheckPayloadSignature(key, hash) dash_spv_crypto_keys_key_OpaqueKey_check_payload_signature(key, hash) +#define DOpaqueKeySerializedPrivateKey(key, script) dash_spv_crypto_keys_key_OpaqueKey_serialized_private_key_for_script(key, script) +#define DOpaqueKeyPublicKeyDataEqualTo(key, data) dash_spv_crypto_keys_key_OpaqueKey_public_key_data_equal_to(key, data) +#define DOpaqueKeyPublicKeyDataAtIndexPath(key, index_path) dash_spv_crypto_keys_key_OpaqueKey_public_key_data_at_index_path(key, index_path) +#define DOpaqueKeyPublicKeyFromExtPubKeyDataAtIndexPath(key, index_path) dash_spv_crypto_keys_key_OpaqueKey_public_key_from_extended_public_key_data_at_index_path(key, index_path) +#define DOpaqueKeyPrivateKeyDataEqualTo(key, data) dash_spv_crypto_keys_key_OpaqueKey_private_key_data_equal_to(key, data) +#define DOpaqueKeyExtPrivateKeyData(key) dash_spv_crypto_keys_key_OpaqueKey_extended_private_key_data(key) +#define DOpaqueKeySecretKeyString(key) dash_spv_crypto_keys_key_OpaqueKey_secret_key_string(key) +#define DOpaqueKeyHasKind(key, kind) dash_spv_crypto_keys_key_OpaqueKey_has_kind(key, kind) +#define DOpaqueKeyDecrypt(prv_key, pub_key, data) dash_spv_crypto_keys_key_OpaqueKey_decrypt_data_vec(prv_key, pub_key, data) +#define DOpaqueKeyPubAddress(key, chain_type) dash_spv_crypto_keys_key_OpaqueKey_address_with_public_key_data(key, chain_type) +#define DAddressWithPubKeyData(data, chain_type) dash_spv_crypto_util_address_address_with_public_key_data(data, chain_type) +#define DScriptPubKeyForAddress(address, chain_type) dash_spv_apple_bindings_address_addresses_script_pubkey_for_address(address, chain_type) +#define DIsValidDashAddress(address, chain_type) dash_spv_apple_bindings_address_addresses_is_valid_dash_address_for_chain(address, chain_type) +#define DAddressWithScriptPubKeyData(data, chain_type) dash_spv_apple_bindings_address_addresses_address_with_script_pubkey(data, chain_type) +#define DMaybeOpaqueKeyFromSeed(kind, seed) dash_spv_crypto_keys_key_KeyKind_key_with_seed_data(kind, seed) +#define DMaybeDeriveOpaqueKeyFromExtendedPrivateKeyDataForIndexPath(kind, data, index_path) dash_spv_crypto_keys_key_KeyKind_derive_key_from_extended_private_key_data_for_index_path(kind, data, index_path) +#define DMaybeOpaqueKeyWithPrivateKeyData(kind, data) dash_spv_crypto_keys_key_KeyKind_key_with_private_key_data(kind, data) +#define DMaybeOpaqueKeyWithPrivateKey(kind, key_str, data) dash_spv_crypto_keys_key_KeyKind_key_with_private_key(kind, key_str, data) +#define DMaybeOpaqueKeyWithPublicKeyData(kind, data) dash_spv_crypto_keys_key_KeyKind_key_with_public_key_data(kind, data) +#define DMaybeOpaqueKeyInitWithExtendedPublicKeyData(kind, data) dash_spv_crypto_keys_key_KeyKind_key_init_with_extended_public_key_data(kind, data) +#define DMaybeOpaqueKeyWithExtendedPublicKeyData(kind, data) dash_spv_crypto_keys_key_KeyKind_key_with_extended_public_key_data(kind, data) +#define DMaybeOpaqueKeyWithExtendedPrivateKeyData(kind, data) dash_spv_crypto_keys_key_KeyKind_key_with_extended_private_key_data(kind, data) +#define DMaybeOpaqueKeyFromExtendedPublicKeyDataAtU256(kind, data, path) dash_spv_crypto_keys_key_KeyKind_public_key_from_extended_public_key_data_at_index_path_256(kind, data, path) +#define DMaybeOpaquePrivateKeyAtIndexPathWrapped(kind, seed, index_path, derivation_path) dash_spv_crypto_keys_key_KeyKind_private_key_at_index_path_wrapped(kind, seed, index_path, derivation_path) +#define DMaybeOpaquePrivateKeysAtIndexPathsWrapped(kind, seed, index_paths, derivation_path) dash_spv_crypto_keys_key_KeyKind_private_keys_at_index_paths_wrapped(kind, seed, index_paths, derivation_path) +#define DMaybeSerializedOpaquePrivateKeysAtIndexPathsWrapped(kind, seed, index_paths, derivation_path, chain_type) dash_spv_crypto_keys_key_KeyKind_serialized_private_keys_at_index_paths_wrapper(kind, seed, index_paths, derivation_path, chain_type) +#define DOpaqueKeyUsedInTxInputScript(in_script, key, chain_type) dash_spv_crypto_keys_key_maybe_opaque_key_used_in_tx_input_script(in_script, key, chain_type) + +#define DMaybeOpaqueKey Result_ok_dash_spv_crypto_keys_key_OpaqueKey_err_dash_spv_crypto_keys_KeyError +#define DMaybeOpaqueKeys Result_ok_Vec_dash_spv_crypto_keys_key_OpaqueKey_err_dash_spv_crypto_keys_KeyError +#define DMaybeKeyData Result_ok_Vec_u8_err_dash_spv_crypto_keys_KeyError +#define DMaybeKeyString Result_ok_String_err_dash_spv_crypto_keys_KeyError +#define DChainType dash_spv_crypto_network_chain_type_ChainType +#define DDevnetType dash_spv_crypto_network_chain_type_DevnetType +#define DIndexPathU256 dash_spv_crypto_keys_key_IndexPathU256 +#define DMaybeOpaqueKeyDtor(ptr) Result_ok_dash_spv_crypto_keys_key_OpaqueKey_err_dash_spv_crypto_keys_KeyError_destroy(ptr) +#define DMaybeKeyDataDtor(ptr) Result_ok_Vec_u8_err_dash_spv_crypto_keys_KeyError_destroy(ptr) +#define DMaybeKeyStringDtor(ptr) Result_ok_String_err_dash_spv_crypto_keys_KeyError_destroy(ptr) + +#define DIdentityPublicKey dpp_identity_identity_public_key_IdentityPublicKey +#define DIdentifier platform_value_types_identifier_Identifier +#define DRetry dash_spv_platform_util_RetryStrategy +#define DRetryLinear(max_retry) dash_spv_platform_util_RetryStrategy_Linear_ctor(max_retry) +#define DRetryDown20(max_retry) dash_spv_platform_util_RetryStrategy_SlowingDown20Percent_ctor(max_retry) +#define DRetryDown50(max_retry) dash_spv_platform_util_RetryStrategy_SlowingDown50Percent_ctor(max_retry) + +#define DNotFoundAsAnError() dash_spv_platform_document_manager_DocumentValidator_None_ctor() +#define DNotFoundAsNotAnError() dash_spv_platform_document_manager_DocumentValidator_AcceptNotFoundAsNotAnError_ctor() + +#define DProcessingError dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError +#define DQRInfoResult Result_ok_std_collections_BTreeSet_dashcore_hash_types_BlockHash_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError +#define DQRInfoResultDtor(ptr) Result_ok_std_collections_BTreeSet_dashcore_hash_types_BlockHash_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_destroy(ptr) + +#define DMnDiffFromFile(processor, message, protocol_version) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_mn_list_diff_result_from_file(processor, message, protocol_version) +#define DMnDiffFromMessage(proc, message, height, verify) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_process_mn_list_diff_result_from_message(proc, message, height, verify) + +#define DMnEngineDeserializationResult Result_ok_usize_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError +#define DMnEngineDeserializationResultDtor(ptr) Result_ok_usize_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_destroy(ptr) +#define DMnDiffResult Result_Tuple_dashcore_hash_types_BlockHash_dashcore_hash_types_BlockHash_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError +#define DMnDiffResultDtor(ptr) Result_Tuple_dashcore_hash_types_BlockHash_dashcore_hash_types_BlockHash_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_destroy(ptr) + +#define DMasternodeListForBlockHash(processor, block_hash) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_masternode_list_for_block_hash(processor, block_hash) + +#define DMasternodeListByBlockHash(cache, block_hash) dash_spv_masternode_processor_processing_processor_cache_MasternodeProcessorCache_masternode_list_by_block_hash(cache, block_hash) + +#define DMasternodeListReversedProRegTxHashes(list) dashcore_sml_masternode_list_MasternodeList_reversed_pro_reg_tx_hashes_cloned(list) + +#define DProcessorClear(proc) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_clear(proc) + +#define DMasternodeEntryByProRegTxHash(list, hash) dashcore_sml_masternode_list_MasternodeList_masternode_by_pro_reg_tx_hash(list, hash) + +#define NSDataFromPtr(ptr) ptr ? [NSData dataWithBytes:(const void *)ptr->values length:ptr->count] : nil +#define NSStringFromPtr(ptr) ptr ? [NSString stringWithCString:ptr encoding:NSUTF8StringEncoding] : nil + +#define DKnownMasternodeListsCount(proc) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_known_masternode_lists_count(proc) +#define DCurrentMasternodeListBlockHeight(proc) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_current_masternode_list_height(proc) +#define DHeightForBlockHash(proc, hash) dash_spv_masternode_processor_processing_processor_MasternodeProcessor_height_for_block_hash(proc, hash) + +#define DMasternodeEntryVotingAddress(entry, chain_type) dash_spv_masternode_processor_processing_voting_address(entry, chain_type) +#define DMasternodeEntryOperatorPublicKeyAddress(entry, chain_type) dash_spv_masternode_processor_processing_operator_public_key_address(entry, chain_type) +#define DMasternodeEntryEvoNodeAddress(entry, chain_type) dash_spv_masternode_processor_processing_evo_node_address(entry, chain_type) +#define DAddMasternodeList(cache, hash, list) dash_spv_masternode_processor_processing_processor_cache_MasternodeProcessorCache_add_masternode_list(cache, hash, list) +#define DRemoveMasternodeList(cache, hash) dash_spv_masternode_processor_processing_processor_cache_MasternodeProcessorCache_remove_masternode_list(cache, hash) +#define DRemoveMasternodeListsBefore(cache, height) dash_spv_masternode_processor_processing_processor_cache_MasternodeProcessorCache_remove_masternode_lists_before_height(cache, height) + +#define DKeyType dpp_identity_identity_public_key_key_type_KeyType + +#define DBinaryData platform_value_types_binary_data_BinaryData +#define DBinaryDataCtor(ptr) platform_value_types_binary_data_BinaryData_ctor(ptr) +#define DAssetLockProof dpp_identity_state_transition_asset_lock_proof_AssetLockProof +#define DDocumentTypes std_collections_Map_keys_dpp_data_contract_DocumentName_values_dpp_data_contract_document_type_DocumentType +#define DDocument dpp_document_Document +#define DDocumentsMap indexmap_IndexMap_platform_value_types_identifier_Identifier_Option_dpp_document_Document +#define DMaybeDocument Result_ok_Option_dpp_document_Document_err_dash_spv_platform_error_Error +#define DMaybeDocumentDtor(ptr) Result_ok_Option_dpp_document_Document_err_dash_spv_platform_error_Error_destroy(ptr) + +#define DDocumentResult Result_ok_dpp_document_Document_err_dash_spv_platform_error_Error +#define DDocumentResultDtor(ptr) Result_ok_dpp_document_Document_err_dash_spv_platform_error_Error_destroy(ptr) + +#define DMaybeDocumentsMap Result_ok_indexmap_IndexMap_platform_value_types_identifier_Identifier_Option_dpp_document_Document_err_dash_spv_platform_error_Error +#define DMaybeDocumentsMapDtor(ptr) Result_ok_indexmap_IndexMap_platform_value_types_identifier_Identifier_Option_dpp_document_Document_err_dash_spv_platform_error_Error_destroy(ptr) +#define DContactRequest dash_spv_platform_models_contact_request_ContactRequest +#define DContactRequestDtor(ptr) dash_spv_platform_models_contact_request_ContactRequest_destroy(ptr) +#define DContactRequestKind dash_spv_platform_models_contact_request_ContactRequestKind +#define DContactRequests Vec_dash_spv_platform_models_contact_request_ContactRequestKind +#define DMaybeContactRequests Result_ok_Vec_dash_spv_platform_models_contact_request_ContactRequestKind_err_dash_spv_platform_error_Error +#define DMaybeContactRequestsDtor(ptr) Result_ok_Vec_dash_spv_platform_models_contact_request_ContactRequestKind_err_dash_spv_platform_error_Error_destroy(ptr) + +#define DMaybeContract Result_ok_Option_dpp_data_contract_DataContract_err_dash_spv_platform_error_Error +#define DMaybeContractDtor(ptr) Result_ok_Option_dpp_data_contract_DataContract_err_dash_spv_platform_error_Error_destroy(ptr) +#define DIdentity dpp_identity_identity_Identity +#define DMaybeIdentity Result_ok_Option_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error +#define DMaybeIdentityDtor(ptr) Result_ok_Option_dpp_identity_identity_Identity_err_dash_spv_platform_error_Error_destroy(ptr) + +#define DMaybeTransientUser Result_ok_Option_dash_spv_platform_models_transient_dashpay_user_TransientDashPayUser_err_dash_spv_platform_error_Error +#define DMaybeTransientUserDtor(ptr) Result_ok_Option_dash_spv_platform_models_transient_dashpay_user_TransientDashPayUser_err_dash_spv_platform_error_Error_destroy(ptr) + +#define DMaybeStateTransition Result_ok_dpp_state_transition_StateTransition_err_dash_spv_platform_error_Error +#define DMaybeStateTransitionProofResult Result_ok_dpp_state_transition_proof_result_StateTransitionProofResult_err_dash_spv_platform_error_Error +#define DMaybeStateTransitionProofResultDtor(ptr) Result_ok_dpp_state_transition_proof_result_StateTransitionProofResult_err_dash_spv_platform_error_Error_destroy(ptr) + +#define DGetDocProperty(document, prop) dash_spv_platform_document_get_document_property(document, DChar(prop)) +#define DValue platform_value_Value +#define DValueDtor(ptr) platform_value_Value_destroy(ptr) +#define DValuePair Tuple_platform_value_Value_platform_value_Value +#define DValuePairCtor(key, value) Tuple_platform_value_Value_platform_value_Value_ctor(key, value) +#define DValuePairVec Vec_Tuple_platform_value_Value_platform_value_Value +#define DValuePairVecCtor(count, values) Vec_Tuple_platform_value_Value_platform_value_Value_ctor(count, values) +#define DValueMapCtor(value_pair_vec) platform_value_value_map_ValueMap_ctor(value_pair_vec) + +#define DValueVec Vec_platform_value_Value +#define DValueVecCtor(count, values) Vec_platform_value_Value_ctor(count, values) +#define DValueTextPairCtor(key, value) DValuePairCtor(platform_value_Value_Text_ctor(DChar(key)), value) +#define DValueTextTextPairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_Text_ctor(DChar(value))) +#define DValueTextBytesPairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_Bytes_ctor(bytes_ctor(value))) +#define DValueTextBoolPairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_Bool_ctor(value)) +#define DValueTextMapPairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_Map_ctor(value)) +#define DValueTextU32PairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_U32_ctor(value)) +#define DValueTextU64PairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_U64_ctor(value)) +#define DValueTextIdentifierPairCtor(key, value) DValueTextPairCtor(key, platform_value_Value_Identifier_ctor(value)) + +#define DAddSaltForUsername(model, username, salt) dash_spv_platform_identity_model_IdentityModel_add_salt(model, DChar(username), salt) +#define DSaltForUsername(model, username) dash_spv_platform_identity_model_IdentityModel_salt_for_username(model, DChar(username)) + +#define DGetTextDocProperty(document, propertyName) ({ \ + NSString *result = nil; \ + DValue *value = DGetDocProperty((document), (propertyName)); \ + if (value) { \ + result = NSStringFromPtr(value->text); \ + DValueDtor(value); \ + } \ + result; \ +}) +#define DGetBytesDocProperty(document, propertyName) ({ \ + NSData *result = nil; \ + DValue *value = DGetDocProperty((document), (propertyName)); \ + if (value) { \ + result = NSDataFromPtr(value->bytes); \ + DValueDtor(value); \ + } \ + result; \ +}) +#define DGetBytes32DocProperty(document, propertyName) ({ \ + NSData *result = nil; \ + DValue *value = DGetDocProperty((document), (propertyName)); \ + if (value) { \ + result = NSDataFromPtr(value->bytes32); \ + DValueDtor(value); \ + } \ + result; \ +}) +#define DGetIDDocProperty(document, propertyName) ({ \ + NSData *result = nil; \ + DValue *value = DGetDocProperty((document), (propertyName)); \ + if (value) { \ + if (value->identifier && value->identifier->_0) \ + result = NSDataFromPtr(value->identifier->_0); \ + DValueDtor(value); \ + } \ + result; \ +}) + +#define DGetIDDocProperty(document, propertyName) ({ \ + NSData *result = nil; \ + DValue *value = DGetDocProperty((document), (propertyName)); \ + if (value) { \ + if (value->identifier && value->identifier->_0) \ + result = NSDataFromPtr(value->identifier->_0); \ + DValueDtor(value); \ + } \ + result; \ +}) + + +#define DKeyID dpp_identity_identity_public_key_KeyID +#define DIdentityPublicKey dpp_identity_identity_public_key_IdentityPublicKey +#define DIdentityPublicKeysMap std_collections_Map_keys_dpp_identity_identity_public_key_KeyID_values_dpp_identity_identity_public_key_IdentityPublicKey + +#define DAcceptIdentityNotFound() dash_spv_platform_identity_manager_IdentityValidator_AcceptNotFoundAsNotAnError_ctor() +#define DRaiseIdentityNotFound() dash_spv_platform_identity_manager_IdentityValidator_None_ctor() + +#define DOpaqueKeyFromIdentityPubKey(key) dash_spv_platform_identity_manager_opaque_key_from_identity_public_key(key) +#define DOpaqueKeyToKeyTypeIndex(key) dash_spv_platform_identity_manager_opaque_key_to_key_type_index(key) +#define DOpaqueKeyKind(key) dash_spv_crypto_keys_key_OpaqueKey_kind(key) + +#define DDataContract dpp_data_contract_DataContract + +#define DPurpose dpp_identity_identity_public_key_purpose_Purpose +#define DPurposeDtor(ptr) dpp_identity_identity_public_key_purpose_Purpose_destroy(ptr) +#define DPurposeAuth() dpp_identity_identity_public_key_purpose_Purpose_AUTHENTICATION_ctor() +#define DPurposeIndex(ptr) dash_spv_platform_identity_manager_purpose_to_index(ptr) +#define DPurposeFromIndex(index) dash_spv_platform_identity_manager_purpose_from_index(index) + +#define DSecurityLevel dpp_identity_identity_public_key_security_level_SecurityLevel +#define DSecurityLevelDtor(ptr) dpp_identity_identity_public_key_security_level_SecurityLevel_destroy(ptr) +#define DSecurityLevelMaster() dpp_identity_identity_public_key_security_level_SecurityLevel_MASTER_ctor() +#define DSecurityLevelHigh() dpp_identity_identity_public_key_security_level_SecurityLevel_HIGH_ctor() +#define DSecurityLevelIndex(ptr) dash_spv_platform_identity_manager_security_level_to_index(ptr) +#define DSecurityLevelFromIndex(index) dash_spv_platform_identity_manager_security_level_from_index(index) + +#define DIdentityRegistrationPublicKey(index, key) dash_spv_platform_identity_manager_identity_registration_public_key(index, key) +#define DCreateIdentityPubKey(index, key, security_level, purpose) dash_spv_platform_identity_manager_identity_public_key(index, key, security_level, purpose) +#define DUsernameStatus dash_spv_platform_document_usernames_UsernameStatus +#define DUsernameStatusDtor(ptr) dash_spv_platform_document_usernames_UsernameStatus_destroy(ptr) +#define DUsernameStatusCallback Fn_ARGS_std_os_raw_c_void_dash_spv_platform_document_usernames_UsernameStatus_RTRN_ +#define DUsernameStatusFromIndex(index) dash_spv_platform_document_usernames_username_status_from_index(index) +#define DUsernameStatusIndex(status) dash_spv_platform_document_usernames_username_status_to_index(status) +#define DUsernameAdd(model, username, domain, status) dash_spv_platform_identity_model_IdentityModel_add_username(model, DChar(username), DChar(domain), status); +#define DMaybeIdentityBalance Result_ok_Option_u64_err_dash_spv_platform_error_Error +#define DMaybeIdentityBalanceDtor(ptr) Result_ok_Option_u64_err_dash_spv_platform_error_Error_destroy(ptr) +#define DIdentityKeyStatus dash_spv_platform_identity_model_IdentityKeyStatus +#define DIdentityKeyStatusDtor(ptr) dash_spv_platform_identity_model_IdentityKeyStatus_destroy(ptr) +#define DIdentityRegistrationStatus dash_spv_platform_identity_model_IdentityRegistrationStatus +#define DIdentityRegistrationStatusDtor(ptr) dash_spv_platform_identity_model_IdentityRegistrationStatus_destroy(ptr) +#define DIdentityRegistrationStatusIndex(ptr) dash_spv_platform_identity_model_IdentityModel_registration_status_index(ptr) +#define DIdentityRegistrationStatusFromIndex(index) dash_spv_platform_identity_model_IdentityRegistrationStatus_from_index(index) +#define DIdentityRegistrationStatusRegistered() dash_spv_platform_identity_model_IdentityRegistrationStatus_Registered_ctor() + +#define DKeyInfo dash_spv_platform_identity_model_KeyInfo +#define DKeyInfoDtor(ptr) dash_spv_platform_identity_model_KeyInfo_destroy(ptr) +#define DKeyInfoDictionaries std_collections_Map_keys_u32_values_dash_spv_platform_identity_model_KeyInfo +#define DKeyInfoDictionariesDtor(ptr) std_collections_Map_keys_u32_values_dash_spv_platform_identity_model_KeyInfo_destroy(ptr) +#define DKeyInfoAtIndex(model, index) dash_spv_platform_identity_model_IdentityModel_key_info_at_index(model, index) +#define DIdentityModelSetStatus(model, status) dash_spv_platform_identity_model_IdentityModel_set_registration_status(model, status) +#define DGetKeyInfoDictionaries(model) dash_spv_platform_identity_model_IdentityModel_key_info_dictionaries(model) +#define DGetRegisteredKeyInfoDictionaries(model) dash_spv_platform_identity_model_IdentityModel_registered_key_info_dictionaries(model) +#define DIdentityKeyStatusFromIndex(index) dash_spv_platform_identity_model_IdentityKeyStatus_from_index(index) +#define DIdentityKeyStatusToIndex(status) dash_spv_platform_identity_model_IdentityKeyStatus_to_index(status) + +#define DIdentityKeyStatusRegistered() dash_spv_platform_identity_model_IdentityKeyStatus_Registered_ctor() + +#define DUsernameStatuses std_collections_Map_keys_String_values_dash_spv_platform_identity_model_UsernameStatusInfo +#define DUsernameStatusesDtor(ptr) std_collections_Map_keys_String_values_dash_spv_platform_identity_model_UsernameStatusInfo_destroy(ptr) + +#define DBLSSignature dashcore_bls_sig_utils_BLSSignature +#define DBLSSignatureCtor(sig) dashcore_bls_sig_utils_BLSSignature_ctor(sig) + +#define DChainLock dashcore_ephemerealdata_chain_lock_ChainLock +#define DChainLockCtor(height, block_hash, sig) dashcore_ephemerealdata_chain_lock_ChainLock_ctor(height, block_hash, sig) +#define DChainLockBlockHeight(ptr) dashcore_ephemerealdata_chain_lock_ChainLock_get_block_height(ptr) +#define DChainLockSignature(ptr) dashcore_ephemerealdata_chain_lock_ChainLock_get_signature(ptr) +#define DChainLockDtor(ptr) dashcore_ephemerealdata_chain_lock_ChainLock_destroy(ptr) +#define DInstantLock dashcore_ephemerealdata_instant_lock_InstantLock +#define DInstantLockCtor(version, inputs, txid, cycle_hash, sig) dashcore_ephemerealdata_instant_lock_InstantLock_ctor(version, inputs, txid, cycle_hash, sig) +#define DInstantLockDtor(ptr) dashcore_ephemerealdata_instant_lock_InstantLock_destroy(ptr) +#define DMaybeInstantLock Result_ok_dashcore_ephemerealdata_instant_lock_InstantLock_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError +#if (defined(DASHCORE_MESSAGE_VERIFICATION)) +#define DMessageVerificationResult Result_ok_bool_err_dashcore_sml_message_verification_error_MessageVerificationError +#define DMessageVerificationResultDtor(ptr) Result_ok_bool_err_dashcore_sml_message_verification_error_MessageVerificationError_destroy(ptr) +#endif + +#define DScriptBuf dashcore_blockdata_script_owned_ScriptBuf +#define DScriptBufCtor(bytes) dashcore_blockdata_script_owned_ScriptBuf_ctor(bytes) +#define DScriptBufDtor(ptr) dashcore_blockdata_script_owned_ScriptBuf_destroy(ptr) + +#define DTxIn dashcore_blockdata_transaction_txin_TxIn +#define DTxInCtor(prev_output, script_sig, sequence) dashcore_blockdata_transaction_txin_TxIn_ctor(prev_output, script_sig, sequence, dashcore_blockdata_witness_Witness_ctor(bytes_ctor([NSData data]), 0, 0)) +#define DTxInDtor(ptr) dashcore_blockdata_transaction_txin_TxIn_destroy(ptr) +#define DTxInputs Vec_dashcore_blockdata_transaction_txin_TxIn +#define DTxInputsCtor(count, values) Vec_dashcore_blockdata_transaction_txin_TxIn_ctor(count, values) +#define DTxInputsDtor(ptr) Vec_dashcore_blockdata_transaction_txin_TxIn_destroy(ptr) + +#define DTxOut dashcore_blockdata_transaction_txout_TxOut +#define DTxOutCtor(amount, script) dashcore_blockdata_transaction_txout_TxOut_ctor(amount, script) +#define DTxOutDtor(ptr) dashcore_blockdata_transaction_txout_TxOut_destroy(ptr) +#define DTxOutputs Vec_dashcore_blockdata_transaction_txout_TxOut +#define DTxOutputsCtor(count, values) Vec_dashcore_blockdata_transaction_txout_TxOut_ctor(count, values) +#define DTxOutputsDtor(ptr) Vec_dashcore_blockdata_transaction_txout_TxOut_destroy(ptr) + +#define DTxid dashcore_hash_types_Txid +#define DTxidCtor(data) dashcore_hash_types_Txid_ctor(data) +#define DTxidDtor(ptr) dashcore_hash_types_Txid_destroy(ptr) + +#define DOutPoint dashcore_blockdata_transaction_outpoint_OutPoint +#define DOutPointCtor(txid, vout) dashcore_blockdata_transaction_outpoint_OutPoint_ctor(txid, vout) +#define DOutPointCtorU(u, i) DOutPointCtor(DTxidCtor(u256_ctor_u(u)), i) +#define DOutPointFromUTXO(utxo) DOutPointCtorU(utxo.hash, (uint32_t) utxo.n) +#define DOutPointFromMessage(msg) dash_spv_masternode_processor_processing_outpoint_from_message(msg) +#define DOutPointDtor(ptr) dashcore_blockdata_transaction_outpoint_OutPoint_destroy(ptr) +#define DOutPoints Vec_dashcore_blockdata_transaction_outpoint_OutPoint +#define DOutPointsCtor(count, values) Vec_dashcore_blockdata_transaction_outpoint_OutPoint_ctor(count, values) + +#define DBalance dash_spv_coinjoin_models_balance_Balance +#define DBalanceCtor(myTrusted, myUntrustedPending, myImmature, watchOnlyTrusted, watchOnlyUntrustedPending, watchOnlyImmature, anonymized, denominatedTrusted, denominatedUntrustedPending) dash_spv_coinjoin_models_balance_Balance_ctor(myTrusted, myUntrustedPending, myImmature, watchOnlyTrusted, watchOnlyUntrustedPending, watchOnlyImmature, anonymized, denominatedTrusted, denominatedUntrustedPending) +#define DBalanceDtor(ptr) dash_spv_coinjoin_models_balance_Balance_destroy(ptr) + +#define DTransaction dashcore_blockdata_transaction_Transaction +#define DTransactionCtor(version, lock_time, inputs, outputs, special_payload) dashcore_blockdata_transaction_Transaction_ctor(version, lock_time, inputs, outputs, special_payload); +#define DTransactionDtor(ptr) dashcore_blockdata_transaction_Transaction_destroy(ptr) + +#define DSocketAddrFrom(ip, port) dash_spv_masternode_processor_processing_socket_addr_v4_ctor(ip, port) +#define DSocketAddrIp(addr) dash_spv_masternode_processor_processing_socket_addr_ip(addr) +#define DSocketAddrPort(addr) dash_spv_masternode_processor_processing_socket_addr_port(addr) + +#define DBlockHash dashcore_hash_types_BlockHash +#define DCycleHash dashcore_hash_types_CycleHash NS_ASSUME_NONNULL_BEGIN @@ -29,77 +618,77 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithChain:(DSChain *)chain; -+ (BOOL)hasPrivateKey:(OpaqueKey *)key; -+ (NSString *)secretKeyHexString:(OpaqueKey *)key; -+ (OpaqueKey *_Nullable)keyWithPrivateKeyString:(NSString *)key ofKeyType:(KeyKind)keyType forChainType:(ChainType)chainType; -+ (OpaqueKey *_Nullable)keyWithPrivateKeyData:(NSData *)data ofType:(KeyKind)keyType; -+ (OpaqueKey *_Nullable)keyWithPublicKeyData:(NSData *)data ofType:(KeyKind)keyType; -+ (OpaqueKey *_Nullable)keyWithExtendedPublicKeyData:(NSData *)data ofType:(KeyKind)keyType; -+ (BOOL)keysPublicKeyDataIsEqual:(OpaqueKey *)key1 key2:(OpaqueKey *)key2; -+ (NSData *)signMesasageDigest:(OpaqueKey *)key digest:(UInt256)digest; -+ (BOOL)verifyMessageDigest:(OpaqueKey *)key digest:(UInt256)digest signature:(NSData *)signature; - -+ (OpaqueKey *_Nullable)privateKeyAtIndexPath:(KeyKind)keyType indexes:(UInt256 *)indexes hardened:(BOOL *)hardened length:(NSUInteger)length indexPath:(NSIndexPath *)indexPath fromSeed:(NSData *)seed; -+ (OpaqueKey *_Nullable)publicKeyAtIndexPath:(OpaqueKey *)key indexPath:(NSIndexPath *)indexPath; -+ (NSData *_Nullable)publicKeyDataAtIndexPath:(OpaqueKey *)key indexPath:(NSIndexPath *)indexPath; - -+ (NSData *)privateKeyData:(OpaqueKey *)key; -+ (NSData *)publicKeyData:(OpaqueKey *)key; -+ (NSData *)extendedPrivateKeyData:(OpaqueKey *)key; -+ (NSData *)extendedPublicKeyData:(OpaqueKey *)key; - -+ (OpaqueKey *_Nullable)deriveKeyFromExtenedPrivateKeyDataAtIndexPath:(NSData *_Nullable)data indexPath:(NSIndexPath *)indexPath forKeyType:(KeyKind)keyType; -+ (OpaqueKey *_Nullable)keyPublicDeriveTo256Bit:(DSDerivationPath *)parentPath childIndexes:(UInt256 *)childIndexes childHardened:(BOOL *)childHardened length:(NSUInteger)length; - -+ (NSString *)serializedPrivateKey:(OpaqueKey *)key chainType:(ChainType)chainType; - -+ (NSString *)addressForKey:(OpaqueKey *)key forChainType:(ChainType)chainType; -+ (NSString *)addressWithPublicKeyData:(NSData *)data forChain:(nonnull DSChain *)chain; -+ (NSString *_Nullable)addressWithScriptPubKey:(NSData *)script forChain:(nonnull DSChain *)chain; -+ (NSString *_Nullable)addressWithScriptSig:(NSData *)script forChain:(nonnull DSChain *)chain; -+ (NSString *)addressFromHash160:(UInt160)hash forChain:(nonnull DSChain *)chain; -+ (BOOL)isValidDashAddress:(NSString *)address forChain:(nonnull DSChain *)chain; -+ (NSData *)scriptPubKeyForAddress:(NSString *)address forChain:(nonnull DSChain *)chain; - -+ (UInt160)ecdsaKeyPublicKeyHashFromSecret:(NSString *)secret forChainType:(ChainType)chainType; - -+ (NSString *_Nullable)ecdsaKeyAddressFromPublicKeyData:(NSData *)data forChainType:(ChainType)chainType; -- (NSString *)ecdsaKeyPublicKeyUniqueIDFromDerivedKeyData:(UInt256)secret forChainType:(ChainType)chainType; -- (NSString *)keyRecoveredFromCompactSig:(NSData *)signature andMessageDigest:(UInt256)md; -+ (NSData *_Nullable)compactSign:(DSDerivationPath *)derivationPath fromSeed:(NSData *)seed atIndexPath:(NSIndexPath *)indexPath digest:(UInt256)digest; -+ (ECDSAKey *)ecdsaKeyWithPrivateKey:(NSString *)key forChainType:(ChainType)chainType; -+ (NSString *)blsPublicKeySerialize:(OpaqueKey *)key legacy:(BOOL)legacy; -+ (NSString *_Nullable)ecdsaKeyWithBIP38Key:(NSString *)key passphrase:(NSString *)passphrase forChainType:(ChainType)chainType; ++ (BOOL)hasPrivateKey:(DOpaqueKey *)key; ++ (NSString *)secretKeyHexString:(DOpaqueKey *)key; ++ (DMaybeOpaqueKey *_Nullable)keyWithPrivateKeyData:(NSData *)data ofType:(DKeyKind *)keyType; ++ (DMaybeOpaqueKey *_Nullable)keyWithPublicKeyData:(NSData *)data ofType:(DKeyKind *)keyType; ++ (DMaybeOpaqueKey *_Nullable)keyWithExtendedPublicKeyData:(NSData *)data ofType:(DKeyKind *)keyType; ++ (BOOL)keysPublicKeyDataIsEqual:(DOpaqueKey *)key1 key2:(DOpaqueKey *)key2; ++ (NSData *)signMesasageDigest:(DOpaqueKey *)key digest:(UInt256)digest; ++ (BOOL)verifyMessageDigest:(DOpaqueKey *)key digest:(UInt256)digest signature:(NSData *)signature; + ++ (DMaybeOpaqueKey *_Nullable)publicKeyAtIndexPath:(DOpaqueKey *)key indexPath:(NSIndexPath *)indexPath; ++ (NSData *_Nullable)publicKeyDataAtIndexPath:(DOpaqueKey *)key indexPath:(NSIndexPath *)indexPath; + ++ (NSData *)privateKeyData:(DOpaqueKey *)key; ++ (NSData *)publicKeyData:(DOpaqueKey *)key; ++ (NSData *)extendedPrivateKeyData:(DOpaqueKey *)key; ++ (NSData *)extendedPublicKeyData:(DOpaqueKey *)key; + ++ (NSString *)serializedPrivateKey:(DOpaqueKey *)key + chainType:(DChainType *)chainType; + ++ (NSString *)addressForKey:(DOpaqueKey *)key + forChainType:(DChainType *)chainType; ++ (NSString *)addressWithPublicKeyData:(NSData *)data + forChain:(nonnull DSChain *)chain; ++ (NSString *_Nullable)addressWithScriptPubKey:(NSData *)script + forChain:(nonnull DSChain *)chain; ++ (NSString *_Nullable)addressWithScriptSig:(NSData *)script + forChain:(nonnull DSChain *)chain; ++ (NSString *)addressFromHash160:(UInt160)hash + forChain:(nonnull DSChain *)chain; ++ (BOOL)isValidDashAddress:(NSString *)address + forChain:(nonnull DSChain *)chain; ++ (NSData *)scriptPubKeyForAddress:(NSString *)address + forChain:(nonnull DSChain *)chain; + ++ (NSString *_Nullable)ecdsaKeyAddressFromPublicKeyData:(NSData *)data + forChainType:(DChainType *)chainType; +- (NSString *)ecdsaKeyPublicKeyUniqueIDFromDerivedKeyData:(UInt256)secret + forChainType:(DChainType *)chainType; +- (NSString *)keyRecoveredFromCompactSig:(NSData *)signature + andMessageDigest:(UInt256)md; ++ (NSData *_Nullable)compactSign:(DSDerivationPath *)derivationPath + fromSeed:(NSData *)seed + atIndexPath:(NSIndexPath *)indexPath + digest:(UInt256)digest; ++ (NSString *)blsPublicKeySerialize:(DOpaqueKey *)key + legacy:(BOOL)legacy; ++ (NSString *_Nullable)ecdsaKeyWithBIP38Key:(NSString *)key + passphrase:(NSString *)passphrase + forChainType:(DChainType *)chainType; + (BOOL)isValidDashBIP38Key:(NSString *)key; -+ (OpaqueKey *_Nullable)keyDeprecatedExtendedPublicKeyFromSeed:(NSData *)seed indexes:(UInt256 *)indexes hardened:(BOOL *)hardened length:(NSUInteger)length; + (NSString *)NSStringFrom:(char *)c_string; -+ (NSData *)NSDataFrom:(ByteArray)byte_array; -+ (NSString *)localizedKeyType:(OpaqueKey *)key; ++ (NSData *)NSDataFrom:(Vec_u8 *)byte_array; ++ (NSString *)localizedKeyType:(DOpaqueKey *)key; + (UInt256)x11:(NSData *)data; + (UInt256)blake3:(NSData *)data; -+ (NSData *)encryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey; -+ (NSData *)encryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey usingIV:(NSData *)iv; -+ (NSData *)decryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey; -+ (NSData *)decryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey usingIVSize:(NSUInteger)ivSize; ++ (NSData *)encryptData:(NSData *)data secretKey:(DOpaqueKey *)secretKey publicKey:(DOpaqueKey *)publicKey; ++ (NSData *)encryptData:(NSData *)data secretKey:(DOpaqueKey *)secretKey publicKey:(DOpaqueKey *)publicKey usingIV:(NSData *)iv; ++ (NSData *)decryptData:(NSData *)data secretKey:(DOpaqueKey *)secretKey publicKey:(DOpaqueKey *)publicKey; ++ (NSData *)decryptData:(NSData *)data secretKey:(DOpaqueKey *)secretKey publicKey:(DOpaqueKey *)publicKey usingIVSize:(NSUInteger)ivSize; -+ (NSData *)encryptData:(NSData *)data withDHKey:(OpaqueKey *)dhKey; -+ (NSData *)decryptData:(NSData *)data withDHKey:(OpaqueKey *)dhKey; - -+ (NSString *)keyStoragePrefix:(KeyKind)keyType; ++ (NSData *)encryptData:(NSData *)data withDHKey:(DOpaqueKey *)dhKey; ++ (NSData *)decryptData:(NSData *)data withDHKey:(DOpaqueKey *)dhKey; /// Transactions + (BOOL)verifyProRegTXPayloadSignature:(NSData *)signature payload:(NSData *)payload ownerKeyHash:(UInt160)ownerKeyHash; -+ (NSData *)proRegTXPayloadCollateralDigest:(NSData *)payload - scriptPayout:(NSData *)scriptPayout - reward:(uint16_t)reward - ownerKeyHash:(UInt160)ownerKeyHash - voterKeyHash:(UInt160)voterKeyHash - chainType:(ChainType)chainType; - -+ (NSString *_Nullable)devnetIdentifierFor:(ChainType)chainType; + ++ (NSString *_Nullable)devnetIdentifierFor:(DChainType *)chainType; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.m index de71bac40..0f8c75ced 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSKeyManager.m @@ -15,18 +15,18 @@ // limitations under the License. // -#import "dash_shared_core.h" -#import "DSChain.h" +#import "DSChain+Params.h" #import "DSDerivationPath+Protected.h" #import "DSKeyManager.h" #import "NSIndexPath+FFI.h" +#import "NSString+Dash.h" + // Main purpose of this class is to organize work with rust bindings for keys and internal cache @interface DSKeyManager () @property (nonatomic, strong) DSChain *chain; -@property (nonatomic, assign, nullable) KeysCache *keysCache; @end @@ -36,309 +36,310 @@ @implementation DSKeyManager - (instancetype)initWithChain:(DSChain *)chain { NSParameterAssert(chain); if (!(self = [super init])) return nil; - _keysCache = [DSKeyManager createKeysCache]; DSLog(@"[%@] DSKeyManager.initWithChain: %@: ", chain.name, chain); return self; } -- (void)dealloc { - [DSKeyManager destroyKeysCache:self.keysCache]; -} - -+ (KeysCache *)createKeysCache { - return keys_create_cache(); -} - -+ (void)destroyKeysCache:(KeysCache *)cache { - keys_destroy_cache(cache); -} - -+ (BOOL)hasPrivateKey:(OpaqueKey *)key { - return key_has_private_key(key); ++ (BOOL)hasPrivateKey:(DOpaqueKey *)key { + return DOpaqueKeyHasPrivateKey(key); } -+ (BOOL)keysPublicKeyDataIsEqual:(OpaqueKey *)key1 key2:(OpaqueKey *)key2 { ++ (BOOL)keysPublicKeyDataIsEqual:(DOpaqueKey *)key1 key2:(DOpaqueKey *)key2 { if (key1 == NULL || key2 == NULL) return false; - return keys_public_key_data_is_equal(key1, key2); + return DOpaqueKeyPublicKeyDataEqualTo(key1, DOpaqueKeyPublicKeyData(key2)); } -+ (NSString *)secretKeyHexString:(OpaqueKey *)key { - return [DSKeyManager NSStringFrom:key_secret_key_string(key)]; ++ (NSString *)secretKeyHexString:(DOpaqueKey *)key { + return [DSKeyManager NSStringFrom:DOpaqueKeySecretKeyString(key)]; } -+ (OpaqueKey *_Nullable)keyWithPrivateKeyData:(NSData *)data ofType:(KeyKind)keyType { - return key_create_with_private_key_data(data.bytes, data.length, (int16_t) keyType); ++ (DMaybeOpaqueKey *_Nullable)keyWithPrivateKeyData:(NSData *)data ofType:(DKeyKind *)keyType { + Slice_u8 *slice = slice_ctor(data); + DMaybeOpaqueKey *result = DMaybeOpaqueKeyWithPrivateKeyData(keyType, slice); + return result; } -+ (OpaqueKey *_Nullable)keyWithPublicKeyData:(NSData *)data ofType:(KeyKind)keyType { - return key_create_with_public_key_data(data.bytes, data.length, (int16_t) keyType); ++ (DMaybeOpaqueKey *_Nullable)keyWithPublicKeyData:(NSData *)data ofType:(DKeyKind *)keyType { + Slice_u8 *slice = slice_ctor(data); + DMaybeOpaqueKey *result = DMaybeOpaqueKeyWithPublicKeyData(keyType, slice); + return result; } -+ (OpaqueKey *_Nullable)keyWithExtendedPublicKeyData:(NSData *)data ofType:(KeyKind)keyType { - return key_create_from_extended_public_key_data(data.bytes, data.length, (int16_t) keyType); ++ (DMaybeOpaqueKey *_Nullable)keyWithExtendedPublicKeyData:(NSData *)data ofType:(DKeyKind *)keyType { + Slice_u8 *slice = slice_ctor(data); + DMaybeOpaqueKey *result = DMaybeOpaqueKeyWithExtendedPrivateKeyData(keyType, slice); + return result; } -+ (NSData *)signMesasageDigest:(OpaqueKey *)key digest:(UInt256)digest { - return [DSKeyManager NSDataFrom:key_sign_message_digest(key, digest.u8)]; ++ (NSData *)signMesasageDigest:(DOpaqueKey *)key digest:(UInt256)digest { + Slice_u8 *digest_slice = slice_u256_ctor_u(digest); + NSData *signature = [DSKeyManager NSDataFrom:DOpaqueKeySign(key, digest_slice)]; + return signature; } -+ (BOOL)verifyMessageDigest:(OpaqueKey *)key digest:(UInt256)digest signature:(NSData *)signature { - return key_verify_message_digest(key, digest.u8, signature.bytes, signature.length); ++ (BOOL)verifyMessageDigest:(DOpaqueKey *)key digest:(UInt256)digest signature:(NSData *)signature { + Slice_u8 *message_digest = slice_u256_ctor_u(digest); + Slice_u8 *sig = slice_ctor(signature); + DKeyVerificationResult *result = DOpaqueKeyVerify(key, message_digest, sig); + BOOL verified = result->ok && result->ok[0]; + DKeyVerificationResultDtor(result); + return verified; } -+ (OpaqueKey *_Nullable)privateKeyAtIndexPath:(KeyKind)keyType indexes:(UInt256 *)indexes hardened:(BOOL *)hardened length:(NSUInteger)length indexPath:(NSIndexPath *)indexPath fromSeed:(NSData *)seed { - NSParameterAssert(indexPath); - NSParameterAssert(seed); - if (!seed || !indexPath) return nil; - if (!length) return nil; //there needs to be at least 1 length - IndexPathData *index_path = [indexPath ffi_malloc]; - OpaqueKey *key = key_private_key_at_index_path(seed.bytes, seed.length, (int16_t) keyType, index_path, (const uint8_t *) indexes, hardened, length); - [NSIndexPath ffi_free:index_path]; - return key; -} - -+ (OpaqueKey *_Nullable)keyPublicDeriveTo256Bit:(DSDerivationPath *)parentPath childIndexes:(UInt256 *)childIndexes childHardened:(BOOL *)childHardened length:(NSUInteger)length { - OpaqueKey *key = key_public_derive_to_256bit(parentPath.extendedPublicKey, (const uint8_t *) childIndexes, childHardened, length, parentPath.length); - return key; -} - -+ (OpaqueKey *_Nullable)publicKeyAtIndexPath:(OpaqueKey *)key indexPath:(NSIndexPath *)indexPath { ++ (DMaybeOpaqueKey *_Nullable)publicKeyAtIndexPath:(DOpaqueKey *)key indexPath:(NSIndexPath *)indexPath { if (key == NULL) return nil; - IndexPathData *index_path = [indexPath ffi_malloc]; - OpaqueKey *key_at_index_path = key_public_key_at_index_path(key, index_path); - [NSIndexPath ffi_free:index_path]; - return key_at_index_path; + Vec_u32 *index_path = [NSIndexPath ffi_to:indexPath]; + return DOpaqueKeyPublicKeyFromExtPubKeyDataAtIndexPath(key, index_path); } -+ (NSData *_Nullable)publicKeyDataAtIndexPath:(OpaqueKey *)key indexPath:(NSIndexPath *)indexPath { ++ (NSData *_Nullable)publicKeyDataAtIndexPath:(DOpaqueKey *)key indexPath:(NSIndexPath *)indexPath { if (key == NULL) return nil; - IndexPathData *index_path = [indexPath ffi_malloc]; - ByteArray byte_array = key_public_key_data_at_index_path(key, index_path); - [NSIndexPath ffi_free:index_path]; - return [DSKeyManager NSDataFrom:byte_array]; + DMaybeKeyData *maybe_data = DOpaqueKeyPublicKeyDataAtIndexPath(key, [NSIndexPath ffi_to:indexPath]); + NSData *data = NSDataFromPtr(maybe_data->ok); + DMaybeKeyDataDtor(maybe_data); + return data; } -+ (NSString *)serializedPrivateKey:(OpaqueKey *)key chainType:(ChainType)chainType { - char *c_string = key_serialized_private_key_for_chain(key, chainType); ++ (NSString *)serializedPrivateKey:(DOpaqueKey *)key chainType:(DChainType *)chainType { + uint8_t priv_key = dash_spv_crypto_network_chain_type_ChainType_script_priv_key(chainType); + char *c_string = DOpaqueKeySerializedPrivateKey(key, priv_key); return [DSKeyManager NSStringFrom:c_string]; } -+ (NSString *)addressForKey:(OpaqueKey *)key forChainType:(ChainType)chainType { - char *c_string = key_address_for_key(key, chainType); ++ (NSString *)addressForKey:(DOpaqueKey *)key forChainType:(DChainType *)chainType { + char *c_string = DOpaqueKeyPubAddress(key, chainType); return [DSKeyManager NSStringFrom:c_string]; } + (NSString *)addressWithPublicKeyData:(NSData *)data forChain:(nonnull DSChain *)chain { - char *c_string = key_address_with_public_key_data(data.bytes, data.length, chain.chainType); + char *c_string = DAddressWithPubKeyData(slice_ctor(data), chain.chainType); return [DSKeyManager NSStringFrom:c_string]; } + (NSString *)addressFromHash160:(UInt160)hash forChain:(nonnull DSChain *)chain { - char *c_string = address_from_hash160(hash.u8, chain.chainType); + u160 *h = u160_ctor_u(hash); + char *c_string = dash_spv_apple_bindings_address_addresses_address_from_hash160(h, chain.chainType); return [DSKeyManager NSStringFrom:c_string]; } + (NSString *_Nullable)addressWithScriptPubKey:(NSData *)script forChain:(nonnull DSChain *)chain { - char *c_string = address_with_script_pubkey(script.bytes, script.length, chain.chainType); + Vec_u8 *vec = bytes_ctor(script); + char *c_string = DAddressWithScriptPubKeyData(vec, chain.chainType); return [DSKeyManager NSStringFrom:c_string]; } + (NSString *_Nullable)addressWithScriptSig:(NSData *)script forChain:(nonnull DSChain *)chain { - char *c_string = address_with_script_sig(script.bytes, script.length, chain.chainType); + Vec_u8 *vec = bytes_ctor(script); + char *c_string = dash_spv_apple_bindings_address_addresses_address_with_script_sig(vec, chain.chainType); return [DSKeyManager NSStringFrom:c_string]; } + (BOOL)isValidDashAddress:(NSString *)address forChain:(nonnull DSChain *)chain { - return is_valid_dash_address_for_chain([address UTF8String], chain.chainType); + return DIsValidDashAddress(DChar(address), chain.chainType); } + (NSData *)scriptPubKeyForAddress:(NSString *)address forChain:(nonnull DSChain *)chain { - return [DSKeyManager NSDataFrom:script_pubkey_for_address([address UTF8String], chain.chainType)]; + Vec_u8 *vec = DScriptPubKeyForAddress(DChar(address), chain.chainType); + return [DSKeyManager NSDataFrom:vec]; } -+ (NSData *)privateKeyData:(OpaqueKey *)key { - ByteArray arr = key_private_key_data(key); - return [DSKeyManager NSDataFrom:arr]; -} - -+ (NSData *)publicKeyData:(OpaqueKey *)key { - ByteArray arr = key_public_key_data(key); - return [DSKeyManager NSDataFrom:arr]; ++ (NSData *)privateKeyData:(DOpaqueKey *)key { + DMaybeKeyData *result = DOpaqueKeyPrivateKeyData(key); + NSData *data = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return data; } -+ (NSData *)extendedPrivateKeyData:(OpaqueKey *)key { - ByteArray arr = key_extended_private_key_data(key); - return [DSKeyManager NSDataFrom:arr]; ++ (NSData *)publicKeyData:(DOpaqueKey *)key { + Vec_u8 *vec = DOpaqueKeyPublicKeyData(key); + NSData *data = [DSKeyManager NSDataFrom:vec]; + return data; } -+ (NSData *)extendedPublicKeyData:(OpaqueKey *)key { - ByteArray arr = key_extended_public_key_data(key); - return [DSKeyManager NSDataFrom:arr]; ++ (NSData *)extendedPrivateKeyData:(DOpaqueKey *)key { + Result_ok_dash_spv_crypto_util_sec_vec_SecVec_err_dash_spv_crypto_keys_KeyError *result = DOpaqueKeyExtPrivateKeyData(key); + if (result->error) { + return NULL; + } + Vec_u8 *bytes = dash_spv_crypto_util_sec_vec_SecVec_to_vec(result->ok); + NSData *data = NSDataFromPtr(bytes); + Result_ok_dash_spv_crypto_util_sec_vec_SecVec_err_dash_spv_crypto_keys_KeyError_destroy(result); + return data; } - -+ (OpaqueKey *_Nullable)deriveKeyFromExtenedPrivateKeyDataAtIndexPath:(NSData *_Nullable)data - indexPath:(NSIndexPath *)indexPath - forKeyType:(KeyKind)keyType { - if (!data) return nil; - NSUInteger idxs[[indexPath length]]; - [indexPath getIndexes:idxs]; - OpaqueKey *key = key_derive_key_from_extened_private_key_data_for_index_path(data.bytes, data.length, (int16_t) keyType, idxs, indexPath.length); - return key; ++ (NSData *)extendedPublicKeyData:(DOpaqueKey *)key { + DMaybeKeyData *result = DOpaqueKeyExtendedPublicKeyData(key); + NSData *data = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return data; } -+ (UInt160)ecdsaKeyPublicKeyHashFromSecret:(NSString *)secret forChainType:(ChainType)chainType { - return [DSKeyManager NSDataFrom:ecdsa_public_key_hash_from_secret([secret UTF8String], chainType)].UInt160; -} -+ (NSString *_Nullable)ecdsaKeyAddressFromPublicKeyData:(NSData *)data forChainType:(ChainType)chainType { - return [DSKeyManager NSStringFrom:ecdsa_address_from_public_key_data(data.bytes, data.length, chainType)]; ++ (NSString *_Nullable)ecdsaKeyAddressFromPublicKeyData:(NSData *)data forChainType:(DChainType *)chainType { + char *addr = DECDSAKeyAddressFromPublicKeyData(slice_ctor(data), chainType); + return [DSKeyManager NSStringFrom:addr]; } -- (NSString *)ecdsaKeyPublicKeyUniqueIDFromDerivedKeyData:(UInt256)secret forChainType:(ChainType)chainType { - uint64_t unque_id = ecdsa_public_key_unique_id_from_derived_key_data(secret.u8, 32, chainType); - return [NSString stringWithFormat:@"%0llx", unque_id]; +- (NSString *)ecdsaKeyPublicKeyUniqueIDFromDerivedKeyData:(UInt256)secret forChainType:(DChainType *)chainType { + Slice_u8 *slice = slice_u256_ctor_u(secret); + uint64_t unique_id = DECDSAPublicKeyUniqueIdFromDerivedKeyData(slice, chainType); + return [NSString stringWithFormat:@"%0llx", unique_id]; } - (NSString *)keyRecoveredFromCompactSig:(NSData *)signature andMessageDigest:(UInt256)md { - return [DSKeyManager NSStringFrom:address_for_ecdsa_key_recovered_from_compact_sig(signature.bytes, signature.length, md.u8, self.chain.chainType)]; -} - -+ (NSData *_Nullable)compactSign:(DSDerivationPath *)derivationPath fromSeed:(NSData *)seed atIndexPath:(NSIndexPath *)indexPath digest:(UInt256)digest { - OpaqueKey *key = [derivationPath privateKeyAtIndexPath:indexPath fromSeed:seed]; - // TODO: wrong need to sign opaque? - NSData *data = [DSKeyManager NSDataFrom:key_ecdsa_compact_sign(key->ecdsa, digest.u8)]; - processor_destroy_opaque_key(key); + Slice_u8 *slice = slice_ctor(signature); + u256 *digest = u256_ctor_u(md); + DMaybeKeyString *result = DECDSAKeyAddressFromRecoveredCompactSig(slice, digest, self.chain.chainType); + NSString *addr = NSStringFromPtr(result->ok); + DMaybeKeyStringDtor(result); + return addr; +} + ++ (NSData *_Nullable)compactSign:(DSDerivationPath *)derivationPath + fromSeed:(NSData *)seed + atIndexPath:(NSIndexPath *)indexPath + digest:(UInt256)digest { + DMaybeOpaqueKey *key = [derivationPath privateKeyAtIndexPath:indexPath fromSeed:seed]; + NSData *data = NULL; + if (key->ok) { + Slice_u8 *slice = slice_u256_ctor_u(digest); + Vec_u8 *bytes = DOpaqueKeySign(key->ok, slice); + data = NSDataFromPtr(bytes); + bytes_dtor(bytes); + } + DMaybeOpaqueKeyDtor(key); return data; } -+ (ECDSAKey *)ecdsaKeyWithPrivateKey:(NSString *)key forChainType:(ChainType)chainType { - return key_ecdsa_with_private_key([key UTF8String], chainType); -} - -+ (NSString *)blsPublicKeySerialize:(OpaqueKey *)key legacy:(BOOL)legacy { - BLSKey *bls; - if (key->tag == OpaqueKey_BLSBasic) - bls = key->bls_basic; - else - bls = key->bls_legacy; - return uint384_hex([DSKeyManager NSDataFrom:key_bls_serialize(bls, legacy)].UInt384); ++ (NSString *)blsPublicKeySerialize:(DOpaqueKey *)key legacy:(BOOL)legacy { + DMaybeKeyString *result = DBLSKeySerializedPubKey(key->bls, legacy); + NSString *keySerialized = NSStringFromPtr(result->ok); + DMaybeKeyStringDtor(result); + return keySerialized; } -+ (NSString *_Nullable)ecdsaKeyWithBIP38Key:(NSString *)key passphrase:(NSString *)passphrase forChainType:(ChainType)chainType { - return [DSKeyManager NSStringFrom:key_ecdsa_with_bip38_key([key UTF8String], [passphrase UTF8String], chainType)]; ++ (NSString *_Nullable)ecdsaKeyWithBIP38Key:(NSString *)key + passphrase:(NSString *)passphrase + forChainType:(DChainType *)chainType { + DMaybeKeyString *result = DECDSAKeySerializedPrivateKeyFromBIP38(DChar(key), DChar(passphrase), chainType); + NSString *keySerialized = NSStringFromPtr(result->ok); + DMaybeKeyStringDtor(result); + return keySerialized; } + (BOOL)isValidDashBIP38Key:(NSString *)key { - return key_is_valid_bip38_key([key UTF8String]); -} - -+ (OpaqueKey *_Nullable)keyWithPrivateKeyString:(NSString *)key ofKeyType:(KeyKind)keyType forChainType:(ChainType)chainType { - return key_with_private_key([key UTF8String], keyType, chainType); -} - -+ (OpaqueKey *_Nullable)keyDeprecatedExtendedPublicKeyFromSeed:(NSData *)seed indexes:(UInt256 *)indexes hardened:(BOOL *)hardened length:(NSUInteger)length { - OpaqueKey *key = deprecated_incorrect_extended_public_key_from_seed(seed.bytes, seed.length, (const uint8_t *) indexes, hardened, length); - return key; + return DECDSAKeyIsValidBIP38(DChar(key)); } + (NSString *)NSStringFrom:(char *)c_string { - if (c_string == NULL) { - return nil; - } else { - NSString *address = [NSString stringWithUTF8String:c_string]; - processor_destroy_string(c_string); - return address; + NSString *address = NULL; + if (c_string != NULL) { + address = NSStringFromPtr(c_string); + DCharDtor(c_string); } + return address; } -+ (NSData *)NSDataFrom:(ByteArray)byte_array { - if (byte_array.ptr == NULL && byte_array.len == 0) { ++ (NSData *)NSDataFrom:(Vec_u8 *)byte_array { + if (byte_array->values == NULL && byte_array->count == 0) { return nil; } else { - NSData *data = [NSData dataWithBytes:(const void *)byte_array.ptr length:byte_array.len]; - processor_destroy_byte_array(byte_array.ptr, byte_array.len); + NSData *data = NSDataFromPtr(byte_array); + bytes_dtor(byte_array); return data; } } -+ (NSString *)keyStoragePrefix:(KeyKind)keyType { - switch (keyType) { - case KeyKind_ECDSA: return @""; - case KeyKind_BLS: return @"_BLS_"; - case KeyKind_BLSBasic: return @"_BLS_B_"; - case KeyKind_ED25519: return @"_ED25519_"; - } -} - -+ (NSString *)localizedKeyType:(OpaqueKey *)key { ++ (NSString *)localizedKeyType:(DOpaqueKey *)key { switch (key->tag) { - case OpaqueKey_ECDSA: return DSLocalizedString(@"ECDSA", nil); - case OpaqueKey_BLSLegacy: return DSLocalizedString(@"BLS (Legacy)", nil); - case OpaqueKey_BLSBasic: return DSLocalizedString(@"BLS (Basic)", nil); - case OpaqueKey_ED25519: return DSLocalizedString(@"ED25519", nil); + case dash_spv_crypto_keys_key_OpaqueKey_ECDSA: return DSLocalizedString(@"ECDSA", nil); + case dash_spv_crypto_keys_key_OpaqueKey_BLS: return DSLocalizedString(@"BLS", nil); + case dash_spv_crypto_keys_key_OpaqueKey_ED25519: return DSLocalizedString(@"ED25519", nil); default: return DSLocalizedString(@"Unknown Key Type", nil); } } + /// Crypto + (UInt256)x11:(NSData *)data { - return [DSKeyManager NSDataFrom:processor_x11(data.bytes, data.length)].UInt256; + u256 *result = dash_spv_crypto_x11(slice_ctor(data)); + NSData *hash = NSDataFromPtr(result); + u256_dtor(result); + return hash.UInt256; } + (UInt256)blake3:(NSData *)data { - return [DSKeyManager NSDataFrom:processor_blake3(data.bytes, data.length)].UInt256; -} - -+ (NSData *)encryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey { - ByteArray result = key_encrypt_data(data.bytes, data.length, secretKey, publicKey); - return [DSKeyManager NSDataFrom:result]; -} - -+ (NSData *)encryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey usingIV:(NSData *)iv { - ByteArray result = key_encrypt_data_using_iv(data.bytes, data.length, secretKey, publicKey, iv.bytes, iv.length); - return [DSKeyManager NSDataFrom:result]; -} - -+ (NSData *)decryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey { - ByteArray result = key_decrypt_data(data.bytes, data.length, secretKey, publicKey); - return [DSKeyManager NSDataFrom:result]; -} - -+ (NSData *)decryptData:(NSData *)data secretKey:(OpaqueKey *)secretKey publicKey:(OpaqueKey *)publicKey usingIVSize:(NSUInteger)ivSize { - ByteArray result = key_decrypt_data_using_iv_size(data.bytes, data.length, secretKey, publicKey, ivSize); - return [DSKeyManager NSDataFrom:result]; -} - -+ (NSData *)encryptData:(NSData *)data withDHKey:(OpaqueKey *)dhKey { - ByteArray result = key_encrypt_data_with_dh_key(data.bytes, data.length, dhKey); - return [DSKeyManager NSDataFrom:result]; -} - -+ (NSData *)decryptData:(NSData *)data withDHKey:(OpaqueKey *)dhKey { - ByteArray result = key_decrypt_data_with_dh_key(data.bytes, data.length, dhKey); - return [DSKeyManager NSDataFrom:result]; -} - - - - -+ (BOOL)verifyProRegTXPayloadSignature:(NSData *)signature payload:(NSData *)payload ownerKeyHash:(UInt160)ownerKeyHash { - return pro_reg_tx_verify_payload_signature(signature.bytes, signature.length, payload.bytes, payload.length, ownerKeyHash.u8); -} - -+ (NSData *)proRegTXPayloadCollateralDigest:(NSData *)payload - scriptPayout:(NSData *)scriptPayout - reward:(uint16_t)reward - ownerKeyHash:(UInt160)ownerKeyHash - voterKeyHash:(UInt160)voterKeyHash - chainType:(ChainType)chainType { - ByteArray result = pro_reg_tx_payload_collateral_digest(payload.bytes, payload.length, scriptPayout.bytes, scriptPayout.length, reward, ownerKeyHash.u8, voterKeyHash.u8, chainType); - return [DSKeyManager NSDataFrom:result]; -} - -+ (NSString *_Nullable)devnetIdentifierFor:(ChainType)chainType { - return [DSKeyManager NSStringFrom:devnet_identifier_for_chain_type(chainType)]; + u256 *result = dash_spv_crypto_blake3(slice_ctor(data)); + NSData *hash = NSDataFromPtr(result); + u256_dtor(result); + return hash.UInt256; +} + ++ (NSData *)encryptData:(NSData *)data + secretKey:(DOpaqueKey *)secretKey + publicKey:(DOpaqueKey *)publicKey { + DMaybeKeyData *result = DOpaqueKeyEncryptData(secretKey, publicKey, slice_ctor(data)); + NSData *encrypted = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return encrypted; +} + ++ (NSData *)encryptData:(NSData *)data + secretKey:(DOpaqueKey *)secretKey + publicKey:(DOpaqueKey *)publicKey + usingIV:(NSData *)iv { + DMaybeKeyData *result = DOpaqueKeyEncryptDataUsingIV(secretKey, publicKey, slice_ctor(data), bytes_ctor(iv)); + NSData *encrypted = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return encrypted; +} + ++ (NSData *)decryptData:(NSData *)data + secretKey:(DOpaqueKey *)secretKey + publicKey:(DOpaqueKey *)publicKey { + DMaybeKeyData *result = DOpaqueKeyDecryptData(secretKey, publicKey, slice_ctor(data)); + NSData *decrypted = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return decrypted; +} + ++ (NSData *)decryptData:(NSData *)data + secretKey:(DOpaqueKey *)secretKey + publicKey:(DOpaqueKey *)publicKey + usingIVSize:(NSUInteger)ivSize { + DMaybeKeyData *result = DOpaqueKeyDecryptDataUsingIV(secretKey, publicKey, slice_ctor(data), ivSize); + NSData *decrypted = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return decrypted; +} + ++ (NSData *)encryptData:(NSData *)data + withDHKey:(DOpaqueKey *)dhKey { + DMaybeKeyData *result = DOpaqueKeyEncryptDataWithDHKey(dhKey, bytes_ctor(data)); + NSData *encrypted = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return encrypted; +} + ++ (NSData *)decryptData:(NSData *)data + withDHKey:(DOpaqueKey *)dhKey { + DMaybeKeyData *result = DOpaqueKeyDecryptDataWithDHKey(dhKey, bytes_ctor(data)); + NSData *decrypted = NSDataFromPtr(result->ok); + DMaybeKeyDataDtor(result); + return decrypted; +} + ++ (BOOL)verifyProRegTXPayloadSignature:(NSData *)signature + payload:(NSData *)payload + ownerKeyHash:(UInt160)ownerKeyHash { + Slice_u8 *sig = slice_ctor(signature); + Slice_u8 *pld = slice_ctor(payload); + u160 *hash = u160_ctor_u(ownerKeyHash); + return DECDSAKeyProRegTxVerifyPayloadSig(sig, pld, hash); +} + ++ (NSString *_Nullable)devnetIdentifierFor:(DChainType *)chainType { + return [DSKeyManager NSStringFrom:dash_spv_crypto_network_chain_type_ChainType_devnet_identifier(chainType)]; } @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.h b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.h index 4ceb012a7..fdee452d3 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.h @@ -16,7 +16,7 @@ // #import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSDerivationPath.h" #import "DSLocalMasternode.h" #import "DSMasternodeManager.h" @@ -57,17 +57,20 @@ NS_ASSUME_NONNULL_BEGIN fundsWalletIndex:(uint32_t)fundsWalletIndex inOperatorWallet:(DSWallet *_Nullable)operatorWallet operatorWalletIndex:(uint32_t)operatorWalletIndex - operatorPublicKey:(OpaqueKey *)operatorPublicKey + operatorPublicKey:(DOpaqueKey *)operatorPublicKey inOwnerWallet:(DSWallet *_Nullable)ownerWallet ownerWalletIndex:(uint32_t)ownerWalletIndex - ownerPrivateKey:(OpaqueKey *)ownerPrivateKey + ownerPrivateKey:(DOpaqueKey *)ownerPrivateKey inVotingWallet:(DSWallet *_Nullable)votingWallet votingWalletIndex:(uint32_t)votingWalletIndex - votingKey:(OpaqueKey *)votingKey + votingKey:(DOpaqueKey *)votingKey inPlatformNodeWallet:(DSWallet *_Nullable)platformNodeWallet platformNodeWalletIndex:(uint32_t)platformNodeWalletIndex - platformNodeKey:(OpaqueKey *)platformNodeKey; + platformNodeKey:(DOpaqueKey *)platformNodeKey; - (DSLocalMasternode *_Nullable)localMasternodeFromProviderRegistrationTransaction:(DSProviderRegistrationTransaction *)providerRegistrationTransaction save:(BOOL)save; -- (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry claimedWithOwnerWallet:(DSWallet *)wallet ownerKeyIndex:(uint32_t)ownerKeyIndex; +- (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DMasternodeEntry *)simplifiedMasternodeEntry + claimedWithOwnerWallet:(DSWallet *)wallet + ownerKeyIndex:(uint32_t)ownerKeyIndex + onChain:(DSChain *)chain; - (DSLocalMasternode *_Nullable)localMasternodeHavingProviderRegistrationTransactionHash:(UInt256)providerRegistrationTransactionHash; - (DSLocalMasternode *_Nullable)localMasternodeUsingIndex:(uint32_t)index atDerivationPath:(DSDerivationPath *)derivationPath; - (NSArray *_Nullable)localMasternodesPreviouslyUsingIndex:(uint32_t)index atDerivationPath:(DSDerivationPath *)derivationPath; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m index c17220628..114d5b8b6 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+LocalMasternode.m @@ -17,22 +17,16 @@ #import "DSChain+Protected.h" #import "DSChain.h" +#import "DSChain+Wallet.h" #import "DSChainManager+Protected.h" #import "DSLocalMasternode+Protected.h" #import "DSMasternodeManager+LocalMasternode.h" -#import "DSSimplifiedMasternodeEntry.h" #import NSString const *localMasternodesDictionaryKey = @"localMasternodesDictionaryKey"; -@interface DSMasternodeManager () -@property (nonatomic, strong) NSMutableDictionary *localMasternodesDictionaryByRegistrationTransactionHash; -@end - @implementation DSMasternodeManager (LocalMasternode) -//@dynamic localMasternodesDictionaryByRegistrationTransactionHash; - - (void)setLocalMasternodesDictionaryByRegistrationTransactionHash:(NSMutableDictionary *)dictionary { objc_setAssociatedObject(self, &localMasternodesDictionaryKey, dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @@ -49,7 +43,13 @@ - (void)setLocalMasternodesDictionaryByRegistrationTransactionHash:(NSMutableDic - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress onPort:(uint32_t)port inWallet:(DSWallet *)wallet { NSParameterAssert(wallet); - return [self createNewMasternodeWithIPAddress:ipAddress onPort:port inFundsWallet:wallet inOperatorWallet:wallet inOwnerWallet:wallet inVotingWallet:wallet inPlatformNodeWallet:wallet]; + return [self createNewMasternodeWithIPAddress:ipAddress + onPort:port + inFundsWallet:wallet + inOperatorWallet:wallet + inOwnerWallet:wallet + inVotingWallet:wallet + inPlatformNodeWallet:wallet]; } - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress @@ -59,8 +59,13 @@ - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress inOwnerWallet:(DSWallet *)ownerWallet inVotingWallet:(DSWallet *)votingWallet inPlatformNodeWallet:(DSWallet *)platformNodeWallet { - DSLocalMasternode *localMasternode = [[DSLocalMasternode alloc] initWithIPAddress:ipAddress onPort:port inFundsWallet:fundsWallet inOperatorWallet:operatorWallet inOwnerWallet:ownerWallet inVotingWallet:votingWallet inPlatformNodeWallet:platformNodeWallet]; - return localMasternode; + return [[DSLocalMasternode alloc] initWithIPAddress:ipAddress + onPort:port + inFundsWallet:fundsWallet + inOperatorWallet:operatorWallet + inOwnerWallet:ownerWallet + inVotingWallet:votingWallet + inPlatformNodeWallet:platformNodeWallet]; } - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress @@ -75,8 +80,18 @@ - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress votingWalletIndex:(uint32_t)votingWalletIndex inPlatformNodeWallet:(DSWallet *_Nullable)platformNodeWallet platformNodeWalletIndex:(uint32_t)platformNodeWalletIndex { - DSLocalMasternode *localMasternode = [[DSLocalMasternode alloc] initWithIPAddress:ipAddress onPort:port inFundsWallet:fundsWallet fundsWalletIndex:fundsWalletIndex inOperatorWallet:operatorWallet operatorWalletIndex:operatorWalletIndex inOwnerWallet:ownerWallet ownerWalletIndex:ownerWalletIndex inVotingWallet:votingWallet votingWalletIndex:votingWalletIndex inPlatformNodeWallet:platformNodeWallet platformNodeWalletIndex:platformNodeWalletIndex]; - return localMasternode; + return [[DSLocalMasternode alloc] initWithIPAddress:ipAddress + onPort:port + inFundsWallet:fundsWallet + fundsWalletIndex:fundsWalletIndex + inOperatorWallet:operatorWallet + operatorWalletIndex:operatorWalletIndex + inOwnerWallet:ownerWallet + ownerWalletIndex:ownerWalletIndex + inVotingWallet:votingWallet + votingWalletIndex:votingWalletIndex + inPlatformNodeWallet:platformNodeWallet + platformNodeWalletIndex:platformNodeWalletIndex]; } - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress @@ -85,62 +100,93 @@ - (DSLocalMasternode *)createNewMasternodeWithIPAddress:(UInt128)ipAddress fundsWalletIndex:(uint32_t)fundsWalletIndex inOperatorWallet:(DSWallet *_Nullable)operatorWallet operatorWalletIndex:(uint32_t)operatorWalletIndex - operatorPublicKey:(OpaqueKey *)operatorPublicKey + operatorPublicKey:(DOpaqueKey *)operatorPublicKey inOwnerWallet:(DSWallet *_Nullable)ownerWallet ownerWalletIndex:(uint32_t)ownerWalletIndex - ownerPrivateKey:(OpaqueKey *)ownerPrivateKey + ownerPrivateKey:(DOpaqueKey *)ownerPrivateKey inVotingWallet:(DSWallet *_Nullable)votingWallet votingWalletIndex:(uint32_t)votingWalletIndex - votingKey:(OpaqueKey *)votingKey + votingKey:(DOpaqueKey *)votingKey inPlatformNodeWallet:(DSWallet *_Nullable)platformNodeWallet platformNodeWalletIndex:(uint32_t)platformNodeWalletIndex - platformNodeKey:(OpaqueKey *)platformNodeKey { - DSLocalMasternode *localMasternode = [[DSLocalMasternode alloc] initWithIPAddress:ipAddress onPort:port inFundsWallet:fundsWallet fundsWalletIndex:fundsWalletIndex inOperatorWallet:operatorWallet operatorWalletIndex:operatorWalletIndex inOwnerWallet:ownerWallet ownerWalletIndex:ownerWalletIndex inVotingWallet:votingWallet votingWalletIndex:votingWalletIndex inPlatformNodeWallet:platformNodeWallet platformNodeWalletIndex:platformNodeWalletIndex]; - - if (operatorWalletIndex == UINT32_MAX && operatorPublicKey) { + platformNodeKey:(DOpaqueKey *)platformNodeKey { + DSLocalMasternode *localMasternode = [[DSLocalMasternode alloc] initWithIPAddress:ipAddress + onPort:port + inFundsWallet:fundsWallet + fundsWalletIndex:fundsWalletIndex + inOperatorWallet:operatorWallet + operatorWalletIndex:operatorWalletIndex + inOwnerWallet:ownerWallet + ownerWalletIndex:ownerWalletIndex + inVotingWallet:votingWallet + votingWalletIndex:votingWalletIndex + inPlatformNodeWallet:platformNodeWallet + platformNodeWalletIndex:platformNodeWalletIndex]; + + if (operatorWalletIndex == UINT32_MAX && operatorPublicKey) [localMasternode forceOperatorPublicKey:operatorPublicKey]; - } - - if (ownerWalletIndex == UINT32_MAX && ownerPrivateKey) { + if (ownerWalletIndex == UINT32_MAX && ownerPrivateKey) [localMasternode forceOwnerPrivateKey:ownerPrivateKey]; - } - - if (votingWalletIndex == UINT32_MAX && votingKey) { + if (votingWalletIndex == UINT32_MAX && votingKey) [localMasternode forceVotingKey:votingKey]; - } - - if (platformNodeWalletIndex == UINT32_MAX && platformNodeKey) { + if (platformNodeWalletIndex == UINT32_MAX && platformNodeKey) [localMasternode forcePlatformNodeKey:platformNodeKey]; - } - return localMasternode; } -- (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry claimedWithOwnerWallet:(DSWallet *)ownerWallet ownerKeyIndex:(uint32_t)ownerKeyIndex { +- (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DMasternodeEntry *)simplifiedMasternodeEntry + claimedWithOwnerWallet:(DSWallet *)ownerWallet + ownerKeyIndex:(uint32_t)ownerKeyIndex + onChain:(DSChain *)chain { NSParameterAssert(simplifiedMasternodeEntry); NSParameterAssert(ownerWallet); - - DSLocalMasternode *localMasternode = [self localMasternodeHavingProviderRegistrationTransactionHash:simplifiedMasternodeEntry.providerRegistrationTransactionHash]; - + dashcore_sml_masternode_list_entry_MasternodeListEntry *entry = simplifiedMasternodeEntry->masternode_list_entry; + u256 *pro_reg_tx_hash = dashcore_hash_types_ProTxHash_inner(entry->pro_reg_tx_hash); + DSLocalMasternode *localMasternode = [self localMasternodeHavingProviderRegistrationTransactionHash:u256_cast(pro_reg_tx_hash)]; + u256_dtor(pro_reg_tx_hash); if (localMasternode) return localMasternode; - + u160 *key_id_voting = dashcore_hash_types_PubkeyHash_inner(entry->key_id_voting); uint32_t votingIndex; - DSWallet *votingWallet = [simplifiedMasternodeEntry.chain walletHavingProviderVotingAuthenticationHash:simplifiedMasternodeEntry.keyIDVoting foundAtIndex:&votingIndex]; - + DSWallet *votingWallet = [chain walletHavingProviderVotingAuthenticationHash:u160_cast(key_id_voting) foundAtIndex:&votingIndex]; + u160_dtor(key_id_voting); + UInt384 operatorPublicKey = u384_cast(entry->operator_public_key->_0); uint32_t operatorIndex; - DSWallet *operatorWallet = [simplifiedMasternodeEntry.chain walletHavingProviderOperatorAuthenticationKey:simplifiedMasternodeEntry.operatorPublicKey foundAtIndex:&operatorIndex]; - - uint32_t platformNodeIndex; - DSWallet *platformNodeWallet = [simplifiedMasternodeEntry.chain walletHavingPlatformNodeAuthenticationHash:simplifiedMasternodeEntry.platformNodeID foundAtIndex:&platformNodeIndex]; - - if (votingWallet || operatorWallet) { - return [[DSLocalMasternode alloc] initWithIPAddress:simplifiedMasternodeEntry.address onPort:simplifiedMasternodeEntry.port inFundsWallet:nil fundsWalletIndex:0 inOperatorWallet:operatorWallet operatorWalletIndex:operatorIndex inOwnerWallet:ownerWallet ownerWalletIndex:ownerKeyIndex inVotingWallet:votingWallet votingWalletIndex:votingIndex inPlatformNodeWallet:platformNodeWallet platformNodeWalletIndex:platformNodeIndex]; - } else { - return nil; + DSWallet *operatorWallet = [chain walletHavingProviderOperatorAuthenticationKey:operatorPublicKey foundAtIndex:&operatorIndex]; + UInt160 platformNodeID = UINT160_ZERO; + switch (entry->mn_type->tag) { + case dashcore_sml_masternode_list_entry_EntryMasternodeType_HighPerformance: { + u160 *platform_node_id = dashcore_hash_types_PubkeyHash_inner(entry->mn_type->high_performance.platform_node_id); + platformNodeID = u160_cast(platform_node_id); + u160_dtor(platform_node_id); + break; + } + default: + break; } + uint32_t platformNodeIndex; + DSWallet *platformNodeWallet = [chain walletHavingPlatformNodeAuthenticationHash:platformNodeID foundAtIndex:&platformNodeIndex]; + + u128 *ip_address = DSocketAddrIp(entry->service_address); + UInt128 ipAddress = u128_cast(ip_address); + uint16_t port = DSocketAddrPort(entry->service_address); + return votingWallet || operatorWallet + ? [[DSLocalMasternode alloc] initWithIPAddress:ipAddress + onPort:port + inFundsWallet:nil + fundsWalletIndex:0 + inOperatorWallet:operatorWallet + operatorWalletIndex:operatorIndex + inOwnerWallet:ownerWallet + ownerWalletIndex:ownerKeyIndex + inVotingWallet:votingWallet + votingWalletIndex:votingIndex + inPlatformNodeWallet:platformNodeWallet + platformNodeWalletIndex:platformNodeIndex] + : nil; } -- (DSLocalMasternode *)localMasternodeFromProviderRegistrationTransaction:(DSProviderRegistrationTransaction *)providerRegistrationTransaction save:(BOOL)save { +- (DSLocalMasternode *)localMasternodeFromProviderRegistrationTransaction:(DSProviderRegistrationTransaction *)providerRegistrationTransaction + save:(BOOL)save { NSParameterAssert(providerRegistrationTransaction); //First check to see if we have a local masternode for this provider registration hash @@ -165,8 +211,7 @@ - (DSLocalMasternode *)localMasternodeFromProviderRegistrationTransaction:(DSPro } - (DSLocalMasternode *)localMasternodeHavingProviderRegistrationTransactionHash:(UInt256)providerRegistrationTransactionHash { - DSLocalMasternode *localMasternode = self.localMasternodesDictionaryByRegistrationTransactionHash[uint256_data(providerRegistrationTransactionHash)]; - return localMasternode; + return self.localMasternodesDictionaryByRegistrationTransactionHash[uint256_data(providerRegistrationTransactionHash)]; } - (DSLocalMasternode *)localMasternodeUsingIndex:(uint32_t)index atDerivationPath:(DSDerivationPath *)derivationPath { @@ -202,23 +247,20 @@ - (DSLocalMasternode *)localMasternodeUsingIndex:(uint32_t)index atDerivationPat - (NSArray *)localMasternodesPreviouslyUsingIndex:(uint32_t)index atDerivationPath:(DSDerivationPath *)derivationPath { NSParameterAssert(derivationPath); - if (derivationPath.reference == DSDerivationPathReference_ProviderFunds || derivationPath.reference == DSDerivationPathReference_ProviderOwnerKeys) { + if (derivationPath.reference == DSDerivationPathReference_ProviderFunds || derivationPath.reference == DSDerivationPathReference_ProviderOwnerKeys) return nil; - } NSMutableArray *localMasternodes = [NSMutableArray array]; for (DSLocalMasternode *localMasternode in self.localMasternodesDictionaryByRegistrationTransactionHash.allValues) { switch (derivationPath.reference) { case DSDerivationPathReference_ProviderOperatorKeys: - if (localMasternode.operatorKeysWallet == derivationPath.wallet && [localMasternode.previousOperatorWalletIndexes containsIndex:index]) { + if (localMasternode.operatorKeysWallet == derivationPath.wallet && [localMasternode.previousOperatorWalletIndexes containsIndex:index]) [localMasternodes addObject:localMasternode]; - } break; case DSDerivationPathReference_ProviderVotingKeys: - if (localMasternode.votingKeysWallet == derivationPath.wallet && [localMasternode.previousVotingWalletIndexes containsIndex:index]) { + if (localMasternode.votingKeysWallet == derivationPath.wallet && [localMasternode.previousVotingWalletIndexes containsIndex:index]) [localMasternodes addObject:localMasternode]; - } break; default: break; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Mndiff.h b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Mndiff.h deleted file mode 100644 index e94622df3..000000000 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Mndiff.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "dash_shared_core.h" -#import "DSChain.h" -#import "DSMasternodeProcessorContext.h" -#import "DSMasternodeList.h" -#import "DSMasternodeManager.h" -#import "DSMnDiffProcessingResult.h" -#import "DSQRInfoProcessingResult.h" -#import "DSQuorumEntry.h" -#import "DSSimplifiedMasternodeEntry.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface DSMasternodeManager (Mndiff) - -/// Rust FFI callbacks -MasternodeList *getMasternodeListByBlockHash(uint8_t (*block_hash)[32], const void *context); -bool saveMasternodeList(uint8_t (*block_hash)[32], MasternodeList *masternode_list, const void *context); -void destroyMasternodeList(MasternodeList *masternode_list); -void destroyU8(uint8_t *block_hash); -uint32_t getBlockHeightByHash(uint8_t (*block_hash)[32], const void *context); -uint8_t *getBlockHashByHeight(uint32_t block_height, const void *context); -uint8_t *getMerkleRootByHash(uint8_t (*block_hash)[32], const void *context); -LLMQSnapshot *getLLMQSnapshotByBlockHash(uint8_t (*block_hash)[32], const void *context); -bool saveLLMQSnapshot(uint8_t (*block_hash)[32], LLMQSnapshot *snapshot, const void *context); -void destroyLLMQSnapshot(LLMQSnapshot *snapshot); -void addInsightForBlockHash(uint8_t (*block_hash)[32], const void *context); -ProcessingError shouldProcessDiffWithRange(uint8_t (*base_block_hash)[32], uint8_t (*block_hash)[32], const void *context); - -+ (MasternodeProcessor *)registerProcessor; -+ (void)unregisterProcessor:(MasternodeProcessor *)processor; - -+ (MasternodeProcessorCache *)createProcessorCache; -+ (void)destroyProcessorCache:(MasternodeProcessorCache *)processorCache; - -- (DSMnDiffProcessingResult *)processMasternodeDiffFromFile:(NSData *)message protocolVersion:(uint32_t)protocolVersion withContext:(DSMasternodeProcessorContext *)context; - -- (void)processMasternodeDiffWith:(NSData *)message context:(DSMasternodeProcessorContext *)context completion:(void (^)(DSMnDiffProcessingResult *result))completion; -- (void)processQRInfoWith:(NSData *)message context:(DSMasternodeProcessorContext *)context completion:(void (^)(DSQRInfoProcessingResult *result))completion; - -- (void)clearProcessorCache; -- (void)removeMasternodeListFromCacheAtBlockHash:(UInt256)blockHash; -- (void)removeSnapshotFromCacheAtBlockHash:(UInt256)blockHash; - -@end - - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Mndiff.m b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Mndiff.m deleted file mode 100644 index 1d1955269..000000000 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Mndiff.m +++ /dev/null @@ -1,297 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "DSBlock.h" -#import "DSBlockOperation.h" -#import "DSChain+Protected.h" -#import "DSChainManager.h" -#import "DSInsightManager.h" -#import "DSMasternodeList+Mndiff.h" -#import "DSMasternodeManager+Mndiff.h" -#import "DSMasternodeManager+Protected.h" -#import "DSMerkleBlock.h" -#import "DSQuorumEntry+Mndiff.h" -#import "DSQuorumSnapshot+Mndiff.h" -#import "DSSimplifiedMasternodeEntry+Mndiff.h" -#import "NSData+Dash.h" - -#define AS_OBJC(context) ((__bridge DSMasternodeProcessorContext *)(context)) -#define AS_RUST(context) ((__bridge void *)(context)) - -@implementation DSMasternodeManager (Mndiff) - - -/// -/// MARK: Rust FFI callbacks -/// -MasternodeList *getMasternodeListByBlockHash(uint8_t (*block_hash)[32], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - MasternodeList *c_list = NULL; - @synchronized (context) { - DSMasternodeList *list = [AS_OBJC(context) masternodeListForBlockHash:blockHash]; - if (list) { - c_list = [list ffi_malloc]; - } - } - processor_destroy_block_hash(block_hash); - return c_list; -} - -bool saveMasternodeList(uint8_t (*block_hash)[32], MasternodeList *masternode_list, const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - BOOL saved = NO; - @synchronized (context) { - DSMasternodeProcessorContext *processorContext = AS_OBJC(context); - DSMasternodeList *masternodeList = [DSMasternodeList masternodeListWith:masternode_list onChain:processorContext.chain]; - saved = [processorContext saveMasternodeList:masternodeList forBlockHash:blockHash]; - } - processor_destroy_block_hash(block_hash); - processor_destroy_masternode_list(masternode_list); - return saved; -} - -void destroyMasternodeList(MasternodeList *masternode_list) { - [DSMasternodeList ffi_free:masternode_list]; -} - -void destroyU8(uint8_t *block_hash) { // big uint - if (block_hash) { - free(block_hash); - } -} - -uint32_t getBlockHeightByHash(uint8_t (*block_hash)[32], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - uint32_t block_height = UINT32_MAX; - @synchronized (context) { - block_height = [AS_OBJC(context) blockHeightForBlockHash:blockHash]; - } - processor_destroy_block_hash(block_hash); - return block_height; -} - -uint8_t *getBlockHashByHeight(uint32_t block_height, const void *context) { - uint8_t (*block_hash)[32] = NULL; - @synchronized (context) { - DSBlock *block = [AS_OBJC(context) blockForBlockHeight:block_height]; - if (block) { - block_hash = uint256_malloc(block.blockHash); - } - } - return (uint8_t *)block_hash; -} - - -uint8_t *getMerkleRootByHash(uint8_t (*block_hash)[32], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - uint8_t (*merkle_root)[32] = NULL; - @synchronized (context) { - UInt256 merkleRoot = [AS_OBJC(context) merkleRootForBlockHash:blockHash]; - merkle_root = uint256_malloc(merkleRoot); - } - processor_destroy_block_hash(block_hash); - return (uint8_t *)merkle_root; -} - -LLMQSnapshot *getLLMQSnapshotByBlockHash(uint8_t (*block_hash)[32], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - LLMQSnapshot *c_snapshot = NULL; - @synchronized (context) { - DSQuorumSnapshot *snapshot = [AS_OBJC(context) quorumSnapshotForBlockHash:blockHash]; - if (snapshot) { - c_snapshot = [snapshot ffi_malloc]; - } - } - processor_destroy_block_hash(block_hash); - return c_snapshot; -} - - -bool saveLLMQSnapshot(uint8_t (*block_hash)[32], LLMQSnapshot *snapshot, const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - BOOL saved = NO; - @synchronized (context) { - saved = [AS_OBJC(context) saveQuorumSnapshot:[DSQuorumSnapshot quorumSnapshotWith:snapshot forBlockHash:blockHash]]; - } - processor_destroy_block_hash(block_hash); - processor_destroy_llmq_snapshot(snapshot); - return saved; -} -void destroyLLMQSnapshot(LLMQSnapshot *snapshot) { - [DSQuorumSnapshot ffi_free:snapshot]; -} - -uint8_t *getCLSignatureByBlockHash(uint8_t (*block_hash)[32], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - uint8_t (*cl_signature)[96] = NULL; - @synchronized (context) { - NSData *signature = [AS_OBJC(context) CLSignatureForBlockHash:blockHash]; - if (signature) { - cl_signature = uint768_malloc(signature.UInt768); - } - } - processor_destroy_block_hash(block_hash); - return (uint8_t *)cl_signature; -} -bool saveCLSignature(uint8_t (*block_hash)[32], uint8_t (*cl_signature)[96], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - UInt768 clSignature = *((UInt768 *)cl_signature); - BOOL saved = NO; - @synchronized (context) { - saved = [AS_OBJC(context) saveCLSignature:blockHash signature:clSignature]; - } - processor_destroy_block_hash(block_hash); - processor_destroy_cl_signature(cl_signature); - return saved; -} - -void addInsightForBlockHash(uint8_t (*block_hash)[32], const void *context) { - UInt256 blockHash = *((UInt256 *)block_hash); - @synchronized (context) { - [AS_OBJC(context) blockUntilGetInsightForBlockHash:blockHash]; - } - processor_destroy_block_hash(block_hash); -} - -ProcessingError shouldProcessDiffWithRange(uint8_t (*base_block_hash)[32], uint8_t (*block_hash)[32], const void *context) { - UInt256 baseBlockHash = *((UInt256 *)base_block_hash); - UInt256 blockHash = *((UInt256 *)block_hash); - processor_destroy_block_hash(base_block_hash); - processor_destroy_block_hash(block_hash); - ProcessingError error = ProcessingError_None; - @synchronized (context) { - error = [AS_OBJC(context) shouldProcessDiffWithRange:baseBlockHash blockHash:blockHash]; - } - return error; -} - -/// -/// MARK: Registering/unregistering processor (which is responsible for callback processing) -/// - -+ (MasternodeProcessor *)registerProcessor { - return register_processor( - getMerkleRootByHash, - getBlockHeightByHash, - getBlockHashByHeight, - getLLMQSnapshotByBlockHash, - saveLLMQSnapshot, - getCLSignatureByBlockHash, - saveCLSignature, - getMasternodeListByBlockHash, - saveMasternodeList, - destroyMasternodeList, - addInsightForBlockHash, - destroyU8, - destroyLLMQSnapshot, - shouldProcessDiffWithRange); -} - -+ (void)unregisterProcessor:(MasternodeProcessor *)processor { - unregister_processor(processor); -} - -/// -/// MARK: Creating/destroying opaque cache (which is important for storing some of the results between processing sessions) -/// - -+ (MasternodeProcessorCache *)createProcessorCache { - return processor_create_cache(); -} - -+ (void)destroyProcessorCache:(MasternodeProcessorCache *)processorCache { - processor_destroy_cache(processorCache); -} - - -/// -/// MARK: Call processing methods -/// -- (void)processMasternodeDiffWith:(NSData *)message context:(DSMasternodeProcessorContext *)context completion:(void (^)(DSMnDiffProcessingResult *result))completion { - NSAssert(self.processor, @"processMasternodeDiffMessage: No processor created"); - DSLog(@"[%@] processMasternodeDiffWith: %@", context.chain.name, context); - MNListDiffResult *result = process_mnlistdiff_from_message(message.bytes, - message.length, - context.chain.chainType, - context.useInsightAsBackup, - context.isFromSnapshot, - context.peer ? context.peer.version : context.chain.protocolVersion, - self.processor, - self.processorCache, - AS_RUST(context)); - DSMnDiffProcessingResult *processingResult = [DSMnDiffProcessingResult processingResultWith:result onChain:context.chain]; - processor_destroy_mnlistdiff_result(result); - completion(processingResult); -} - -- (void)processQRInfoWith:(NSData *)message context:(DSMasternodeProcessorContext *)context completion:(void (^)(DSQRInfoProcessingResult *result))completion { - NSAssert(self.processor, @"processQRInfoMessage: No processor created"); - NSAssert(self.processorCache, @"processQRInfoMessage: No processorCache created"); - DSLog(@"[%@] processQRInfoWith: %@", context.chain.name, context); - QRInfoResult *result = process_qrinfo_from_message(message.bytes, - message.length, - context.chain.chainType, - context.useInsightAsBackup, - context.isFromSnapshot, - context.chain.isRotatedQuorumsPresented, - context.peer ? context.peer.version : context.chain.protocolVersion, - self.processor, - self.processorCache, - AS_RUST(context)); - DSQRInfoProcessingResult *processingResult = [DSQRInfoProcessingResult processingResultWith:result onChain:context.chain]; - processor_destroy_qr_info_result(result); - completion(processingResult); -} - -- (DSMnDiffProcessingResult *)processMasternodeDiffFromFile:(NSData *)message protocolVersion:(uint32_t)protocolVersion withContext:(DSMasternodeProcessorContext *)context { - NSAssert(self.processor, @"processMasternodeDiffMessage: No processor created"); - DSLog(@"[%@] processMasternodeDiffMessage: %@", context.chain.name, context); - MNListDiffResult *result = NULL; - @synchronized (context) { - result = process_mnlistdiff_from_message( - message.bytes, - message.length, - context.chain.chainType, - context.useInsightAsBackup, - context.isFromSnapshot, - protocolVersion, - self.processor, - self.processorCache, - AS_RUST(context)); - } - DSMnDiffProcessingResult *processingResult = [DSMnDiffProcessingResult processingResultWith:result onChain:context.chain]; - processor_destroy_mnlistdiff_result(result); - return processingResult; -} - -- (void)clearProcessorCache { - NSAssert(self.processorCache, @"clearProcessorCache: No processorCache created"); - processor_clear_cache(self.processorCache); -} - -- (void)removeMasternodeListFromCacheAtBlockHash:(UInt256)blockHash { - NSAssert(self.processorCache, @"removeMasternodeListFromCacheAtBlockHash: No processorCache created"); - processor_remove_masternode_list_from_cache_for_block_hash((const uint8_t *) blockHash.u8, self.processorCache); -} - -- (void)removeSnapshotFromCacheAtBlockHash:(UInt256)blockHash { - NSAssert(self.processorCache, @"removeSnapshotFromCacheAtBlockHash: No processorCache created"); - processor_remove_llmq_snapshot_from_cache_for_block_hash((const uint8_t *) blockHash.u8, self.processorCache); -} - - -@end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h index e9ab3ff59..2f6240a26 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager+Protected.h @@ -23,11 +23,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#import "DSMasternodeProcessorContext.h" #import "DSMasternodeManager.h" -#import "DSMnDiffProcessingResult.h" #import "DSOperationQueue.h" -#import "DSQRInfoProcessingResult.h" NS_ASSUME_NONNULL_BEGIN @@ -35,19 +32,29 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithChain:(DSChain *_Nonnull)chain; - (void)setUp; -- (void)loadFileDistributedMasternodeLists; +- (BOOL)restoreState; + +//- (void)loadFileDistributedMasternodeLists; - (void)wipeMasternodeInfo; - (void)getRecentMasternodeList; - (void)getCurrentMasternodeListWithSafetyDelay:(uint32_t)safetyDelay; -- (void)getMasternodeListsForBlockHashes:(NSOrderedSet *)blockHashes; - (void)peer:(DSPeer *)peer relayedMasternodeDiffMessage:(NSData *)message; - (void)peer:(DSPeer *)peer relayedQuorumRotationInfoMessage:(NSData *)message; -- (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry claimedWithOwnerWallet:(DSWallet *)wallet ownerKeyIndex:(uint32_t)ownerKeyIndex; +- (DSLocalMasternode *)localMasternodeFromSimplifiedMasternodeEntry:(DMasternodeEntry *)simplifiedMasternodeEntry + claimedWithOwnerWallet:(DSWallet *)wallet + ownerKeyIndex:(uint32_t)ownerKeyIndex + onChain:(DSChain *)chain; -+ (void)saveMasternodeList:(DSMasternodeList *)masternodeList toChain:(DSChain *)chain havingModifiedMasternodes:(NSDictionary *)modifiedMasternodes createUnknownBlocks:(BOOL)createUnknownBlocks inContext:(NSManagedObjectContext *)context completion:(void (^)(NSError *error))completion; +//+ (nullable NSError *)saveMasternodeList:(DMasternodeList *)masternodeList +// toChain:(DSChain *)chain +// havingModifiedMasternodes:(DMasternodeEntryMap *)modifiedMasternodes +// createUnknownBlocks:(BOOL)createUnknownBlocks +// inContext:(NSManagedObjectContext *)context; - (BOOL)isMasternodeListOutdated; +- (BOOL)processRequestFromFileForBlockHash:(UInt256)blockHash; +- (void)issueWithMasternodeListFromPeer:(DSPeer *)peer; @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h index 6259fb70b..7f6694faf 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.h @@ -24,83 +24,83 @@ // THE SOFTWARE. #import "DSChain.h" -#import "DSMasternodeListService.h" -#import "DSMasternodeListStore.h" -#import "DSMasternodeListDiffService.h" -#import "DSQuorumRotationService.h" -#import "DSQuorumSnapshot.h" +#import "DSKeyManager.h" +//#import "DSMasternodeListStore.h" #import "DSPeer.h" #import NS_ASSUME_NONNULL_BEGIN +FOUNDATION_EXPORT NSString *const DSCurrentMasternodeListDidChangeNotification; +FOUNDATION_EXPORT NSString *const DSMasternodeListDidChangeNotification; +FOUNDATION_EXPORT NSString *const DSMasternodeManagerNotificationMasternodeListKey; +FOUNDATION_EXPORT NSString *const DSQuorumListDidChangeNotification; #define MASTERNODE_COST 100000000000 +#define CHAINLOCK_ACTIVATION_HEIGHT 1088640 -@class DSPeer, DSChain, DSSimplifiedMasternodeEntry, DSWallet, DSLocalMasternode, DSProviderRegistrationTransaction, DSQuorumEntry, DSMasternodeList, DSInstantSendTransactionLock, DSMasternodeListService, DSQuorumRotationService, DSMasternodeListDiffService; +@class DSChain, DSWallet, DSLocalMasternode, DSProviderRegistrationTransaction, DSInstantSendTransactionLock, DSMasternodeListService, DSQuorumRotationService, DSMasternodeListDiffService, DSPeer; -@interface DSMasternodeManager : NSObject +@interface DSMasternodeManager : NSObject @property (nonatomic, readonly, nonnull) DSChain *chain; @property (nonatomic, readonly) NSUInteger simplifiedMasternodeEntryCount; @property (nonatomic, readonly) NSUInteger activeQuorumsCount; -@property (nonatomic, readonly) NSArray *recentMasternodeLists; @property (nonatomic, readonly) NSUInteger knownMasternodeListsCount; -@property (nonatomic, readonly) uint32_t earliestMasternodeListBlockHeight; +//@property (nonatomic, readonly) uint32_t earliestMasternodeListBlockHeight; @property (nonatomic, readonly) uint32_t lastMasternodeListBlockHeight; -@property (nonatomic, readonly) DSMasternodeList *currentMasternodeList; +@property (nonatomic, readonly) DMasternodeList *currentMasternodeList; @property (nonatomic, readonly) NSUInteger masternodeListRetrievalQueueCount; @property (nonatomic, readonly) NSUInteger masternodeListRetrievalQueueMaxAmount; -@property (nonatomic, readonly) BOOL hasMasternodeListCurrentlyBeingSaved; @property (nonatomic, readonly) BOOL currentMasternodeListIsInLast24Hours; -@property (nonatomic, readonly) DSMasternodeListStore *store; +//@property (nonatomic, readonly) DSMasternodeListStore *store; @property (nonatomic, readonly) DSMasternodeListDiffService *masternodeListDiffService; @property (nonatomic, readonly) DSQuorumRotationService *quorumRotationService; - -@property (nonatomic, readonly, nullable) MasternodeProcessor *processor; -@property (nonatomic, readonly, nullable) MasternodeProcessorCache *processorCache; @property (nonatomic, assign, readonly) uint32_t rotatedQuorumsActivationHeight; +@property (nonatomic, readonly) BOOL isSyncing; - (instancetype)init NS_UNAVAILABLE; - (uint32_t)heightForBlockHash:(UInt256)blockhash; - (BOOL)hasCurrentMasternodeListInLast30Days; -- (DSSimplifiedMasternodeEntry *)masternodeHavingProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash; +- (DMasternodeEntry *)masternodeHavingProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash; +- (DMasternodeEntry *)masternodeAtLocation:(UInt128)IPAddress port:(uint32_t)port; - (BOOL)hasMasternodeAtLocation:(UInt128)IPAddress port:(uint32_t)port; -- (DSQuorumEntry *_Nullable)quorumEntryForInstantSendRequestID:(UInt256)requestID withBlockHeightOffset:(uint32_t)blockHeightOffset; -- (DSQuorumEntry *_Nullable)quorumEntryForChainLockRequestID:(UInt256)requestID withBlockHeightOffset:(uint32_t)blockHeightOffset; -- (DSQuorumEntry *_Nullable)quorumEntryForChainLockRequestID:(UInt256)requestID forBlockHeight:(uint32_t)blockHeight; -- (DSQuorumEntry *_Nullable)quorumEntryForPlatformHavingQuorumHash:(UInt256)quorumHash forBlockHeight:(uint32_t)blockHeight; - -- (DSMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash withBlockHeightLookup:(uint32_t (^_Nullable)(UInt256 blockHash))blockHeightLookup; -- (DSMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash; +//- (DLLMQEntry *_Nullable)quorumEntryForInstantSendRequestID:(UInt256)requestID +// withBlockHeightOffset:(uint32_t)blockHeightOffset; +//- (DLLMQEntry *_Nullable)quorumEntryForChainLockRequestID:(UInt256)requestID +// withBlockHeightOffset:(uint32_t)blockHeightOffset; +//- (DLLMQEntry *_Nullable)quorumEntryForChainLockRequestID:(UInt256)requestID +// forBlockHeight:(uint32_t)blockHeight; +//- (DLLMQEntry *_Nullable)quorumEntryForPlatformHavingQuorumHash:(UInt256)quorumHash +// forBlockHeight:(uint32_t)blockHeight; -/// Rust helpers -- (DSQuorumSnapshot *_Nullable)quorumSnapshotForBlockHeight:(uint32_t)blockHeight; -- (DSQuorumSnapshot *_Nullable)quorumSnapshotForBlockHash:(UInt256)blockHash; -- (BOOL)saveQuorumSnapshot:(DSQuorumSnapshot *)snapshot; -- (BOOL)saveMasternodeList:(DSMasternodeList *)masternodeList forBlockHash:(UInt256)blockHash; - -- (NSData *_Nullable)CLSignatureForBlockHeight:(uint32_t)blockHeight; -- (NSData *_Nullable)CLSignatureForBlockHash:(UInt256)blockHash; -- (BOOL)saveCLSignature:(NSData *)blockHashData signatureData:(NSData *)signatureData; +- (DMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash + withBlockHeightLookup:(uint32_t (^_Nullable)(UInt256 blockHash))blockHeightLookup; +- (DMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash; - (void)startSync; - (void)stopSync; -- (BOOL)requestMasternodeListForBlockHeight:(uint32_t)blockHeight error:(NSError *_Nullable *_Nullable)error; -- (BOOL)requestMasternodeListForBlockHash:(UInt256)blockHash; +//- (BOOL)requestMasternodeListForBlockHeight:(uint32_t)blockHeight +// error:(NSError *_Nullable *_Nullable)error; /// Returns current masternode list -- (DSMasternodeList *_Nullable)reloadMasternodeLists; -- (DSMasternodeList *_Nullable)reloadMasternodeListsWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (void)destroyProcessors; +- (void)reloadMasternodeLists; +//- (DMasternodeList *_Nullable)reloadMasternodeListsWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; + +- (void)checkPingTimesForCurrentMasternodeListInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *errors))completion; + + +- (void)masternodeListServiceEmptiedRetrievalQueue:(DSMasternodeListService *)service; + +- (BOOL)hasBlockForBlockHash:(NSData *)blockHashData; -- (void)checkPingTimesForCurrentMasternodeListInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *errors))completion; +- (NSSet *)blockHashesUsedByMasternodeLists; -- (UInt256)buildLLMQHashFor:(DSQuorumEntry *)quorum; @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m index 92cf5bd40..5a784119c 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSMasternodeManager.m @@ -24,123 +24,101 @@ // THE SOFTWARE. #import "DSMasternodeManager.h" +#import "DSChain+Checkpoint.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" -#import "DSChainLock.h" #import "DSChainManager+Protected.h" #import "DSCheckpoint.h" -#import "DSGetMNListDiffRequest.h" -#import "DSGetQRInfoRequest.h" -#import "DSMasternodeProcessorContext.h" +#import "DSLocalMasternodeEntity+CoreDataClass.h" #import "DSMasternodeListService+Protected.h" -#import "DSMasternodeListStore+Protected.h" -#import "DSMasternodeManager+LocalMasternode.h" -#import "DSMasternodeManager+Mndiff.h" -#import "DSMasternodeManager+Protected.h" +#import "DSMasternodeListDiffService.h" +#import "DSQuorumRotationService.h" #import "DSMerkleBlock.h" -#import "DSMnDiffProcessingResult.h" -#import "DSOperationQueue.h" +#import "DSMerkleBlockEntity+CoreDataClass.h" #import "DSOptionsManager.h" -#import "DSPeer.h" #import "DSPeerManager+Protected.h" -#import "DSQRInfoProcessingResult.h" -#import "DSSimplifiedMasternodeEntry.h" #import "DSTransactionManager+Protected.h" +#import "NSArray+Dash.h" #import "NSError+Dash.h" +#import "NSError+Platform.h" +#import "NSSet+Dash.h" +#import "NSObject+Notification.h" -#define SAVE_MASTERNODE_DIFF_TO_FILE (0 && DEBUG) -#define DSFullLog(FORMAT, ...) printf("%s\n", [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]) +#define ENGINE_STORAGE_LOCATION(chain) [NSString stringWithFormat:@"MNL_ENGINE_%@.dat", chain.name] + +#define SAVE_MASTERNODE_DIFF_TO_FILE (1 && DEBUG) +#define SAVE_ERROR_STATE (1 && DEBUG) @interface DSMasternodeManager () @property (nonatomic, strong) DSChain *chain; -@property (nonatomic, strong) DSMasternodeListStore *store; @property (nonatomic, strong) DSMasternodeListDiffService *masternodeListDiffService; @property (nonatomic, strong) DSQuorumRotationService *quorumRotationService; @property (nonatomic, assign) NSTimeInterval timeIntervalForMasternodeRetrievalSafetyDelay; - -@property (nonatomic, assign, nullable) MasternodeProcessor *processor; -@property (nonatomic, assign, nullable) MasternodeProcessorCache *processorCache; - @property (nonatomic, assign) uint32_t rotatedQuorumsActivationHeight; @property (nonatomic, strong) dispatch_group_t processingGroup; @property (nonatomic, strong) dispatch_queue_t processingQueue; @property (nonatomic, strong) dispatch_source_t masternodeListTimer; +@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; @property (nonatomic) BOOL isSyncing; +@property (nonatomic) BOOL isRestored; + @end @implementation DSMasternodeManager -- (void)dealloc { - [self destroyProcessors]; -} - -- (void)destroyProcessors { - [DSMasternodeManager unregisterProcessor:self.processor]; - [DSMasternodeManager destroyProcessorCache:self.processorCache]; - _processor = nil; - _processorCache = nil; -} - -- (BOOL)hasCurrentMasternodeListInLast30Days { - return self.currentMasternodeList && [[NSDate date] timeIntervalSince1970] - [self.chain timestampForBlockHeight:self.currentMasternodeList.height] < DAY_TIME_INTERVAL * 30; -} - - (instancetype)initWithChain:(DSChain *)chain { NSParameterAssert(chain); if (!(self = [super init])) return nil; _chain = chain; - _store = [[DSMasternodeListStore alloc] initWithChain:chain]; - self.masternodeListDiffService = [[DSMasternodeListDiffService alloc] initWithChain:chain store:_store delegate:self]; - self.quorumRotationService = [[DSQuorumRotationService alloc] initWithChain:chain store:_store delegate:self]; + self.masternodeListDiffService = [[DSMasternodeListDiffService alloc] initWithChain:chain]; + self.quorumRotationService = [[DSQuorumRotationService alloc] initWithChain:chain]; _rotatedQuorumsActivationHeight = UINT32_MAX; - _processor = [DSMasternodeManager registerProcessor]; - _processorCache = [DSMasternodeManager createProcessorCache]; _processingGroup = dispatch_group_create(); _processingQueue = dispatch_queue_create([[NSString stringWithFormat:@"org.dashcore.dashsync.processing.%@", uint256_data(self.chain.genesisHash).shortHexString] UTF8String], DISPATCH_QUEUE_SERIAL); + self.managedObjectContext = chain.chainManagedObjectContext; return self; } -#pragma mark - DSMasternodeListServiceDelegate - -- (DSMasternodeList *__nullable)masternodeListSerivceDidRequestFileFromBlockHash:(DSMasternodeListService *)service blockHash:(UInt256)blockHash { - return [self processRequestFromFileForBlockHash:blockHash]; +- (MasternodeProcessor *)processor { + return self.chain.sharedProcessorObj; } -- (void)masternodeListSerivceExceededMaxFailuresForMasternodeList:(DSMasternodeListService *)service blockHash:(UInt256)blockHash { - [self removeOutdatedMasternodeListsBeforeBlockHash:blockHash]; +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [MasternodeManager] ", self.chain.name]; } -- (void)masternodeListSerivceEmptiedRetrievalQueue:(DSMasternodeListService *)service { - if (![self.masternodeListDiffService retrievalQueueCount]) { - if (![self.quorumRotationService retrievalQueueCount]) - [self removeOutdatedMasternodeListsBeforeBlockHash:self.store.lastQueriedBlockHash]; - [self.chain.chainManager chainFinishedSyncingMasternodeListsAndQuorums:self.chain]; - } +- (BOOL)hasCurrentMasternodeListInLast30Days { + DMasternodeList *list = self.currentMasternodeList; + BOOL has = list && [[NSDate date] timeIntervalSince1970] - [self.chain timestampForBlockHeight:list->known_height] < DAY_TIME_INTERVAL * 30; + if (list) + DMasternodeListDtor(list); + return has; } -// MARK: - Helpers +#pragma mark - DSMasternodeListServiceDelegate -- (NSArray *)recentMasternodeLists { - return [self.store recentMasternodeLists]; -} -- (NSUInteger)knownMasternodeListsCount { - return [self.store knownMasternodeListsCount]; +- (void)masternodeListServiceEmptiedRetrievalQueue:(DSMasternodeListService *)service { + [self.chain.chainManager chainFinishedSyncingMasternodeListsAndQuorums:self.chain]; } -- (uint32_t)earliestMasternodeListBlockHeight { - return [self.store earliestMasternodeListBlockHeight]; + +// MARK: - Helpers + +- (NSUInteger)knownMasternodeListsCount { + return DKnownMasternodeListsCount(self.processor); } - (uint32_t)lastMasternodeListBlockHeight { - return [self.store lastMasternodeListBlockHeight]; + return DCurrentMasternodeListBlockHeight(self.processor); } - (uint32_t)heightForBlockHash:(UInt256)blockhash { - return [self.store heightForBlockHash:blockhash]; + return [self.chain heightForBlockHash:blockhash]; } - (BOOL)isMasternodeListOutdated { @@ -148,26 +126,28 @@ - (BOOL)isMasternodeListOutdated { return lastHeight == UINT32_MAX || lastHeight < self.chain.lastTerminalBlockHeight - 8; } -- (DSSimplifiedMasternodeEntry *)masternodeHavingProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash { +- (DMasternodeEntry *)masternodeHavingProviderRegistrationTransactionHash:(NSData *)providerRegistrationTransactionHash { NSParameterAssert(providerRegistrationTransactionHash); - return [self.currentMasternodeList.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash objectForKey:providerRegistrationTransactionHash]; + dashcore_hash_types_ProTxHash *pro_tx_hash = dashcore_hash_types_ProTxHash_ctor(u256_ctor(providerRegistrationTransactionHash)); + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_current_masternode_list_masternode_with_pro_reg_tx_hash(self.processor, pro_tx_hash); } - (NSUInteger)simplifiedMasternodeEntryCount { - return [self.currentMasternodeList masternodeCount]; + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_current_masternode_list_masternode_count(self.processor); } - (NSUInteger)activeQuorumsCount { - return [self.currentMasternodeList quorumsCount]; + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_current_masternode_list_quorum_count(self.processor); } - (BOOL)hasMasternodeAtLocation:(UInt128)IPAddress port:(uint32_t)port { - for (DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry in [self.currentMasternodeList.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash allValues]) { - if (uint128_eq(simplifiedMasternodeEntry.address, IPAddress) && simplifiedMasternodeEntry.port == port) { - return YES; - } - } - return NO; + u128 *addr = u128_ctor_u(IPAddress); + BOOL result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_has_masternode_at_location(self.processor, addr, port); + return result; +} +- (DMasternodeEntry *)masternodeAtLocation:(UInt128)IPAddress port:(uint32_t)port { + SocketAddr *addr = DSocketAddrFrom(u128_ctor_u(IPAddress), port); + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_masternode_at_location(self.processor, addr); } - (NSUInteger)masternodeListRetrievalQueueCount { @@ -179,11 +159,13 @@ - (NSUInteger)masternodeListRetrievalQueueMaxAmount { } - (BOOL)currentMasternodeListIsInLast24Hours { - if (!self.currentMasternodeList) { - return NO; - } - DSBlock *block = [self.chain blockForBlockHash:self.currentMasternodeList.blockHash]; - if (!block) return FALSE; + DMasternodeList *list = self.currentMasternodeList; + if (!list) return NO; + u256 *block_hash = dashcore_hash_types_BlockHash_inner(list->block_hash); + DSBlock *block = [self.chain blockForBlockHash:u256_cast(block_hash)]; + u256_dtor(block_hash); + DMasternodeListDtor(list); + if (!block) return NO; NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; NSTimeInterval delta = currentTimestamp - block.timestamp; return fabs(delta) < DAY_TIME_INTERVAL; @@ -192,159 +174,165 @@ - (BOOL)currentMasternodeListIsInLast24Hours { // MARK: - Set Up and Tear Down -- (void)setUp { - __weak typeof(self) weakSelf = self; - [self.store setUp:^(DSMasternodeList * _Nonnull masternodeList) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; +- (NSData *)readFromDisk:(NSString *)location { + NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; + NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; + NSString *filePath = [bundle pathForResource:location ofType:@"dat"]; + return filePath ? [NSData dataWithContentsOfFile:filePath] : nil; +} +- (void)writeToDisk:(NSString *)location data:(NSData *)data { + DSLog(@"%@ •-• File %@ saved", self.logPrefix, location); + [data saveToFile:location inDirectory:NSCachesDirectory]; +} + +- (BOOL)restoreEngine { + NSData *engineBytes = [self readFromDisk:ENGINE_STORAGE_LOCATION(self.chain)]; + if (engineBytes) { + Slice_u8 *bytes = slice_ctor(engineBytes); + DMnEngineDeserializationResult *result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_deserialize_engine(self.processor, bytes); + BOOL success = !result->error; + if (success) { + DSLog(@"%@ restoreEngine: Ok: (%lu bytes)", self.logPrefix, result->ok[0]); + } else { + DSLog(@"%@ restoreEngine: Error: %@", self.logPrefix, [NSError ffi_from_processing_error:result->error]); } - strongSelf.masternodeListDiffService.currentMasternodeList = masternodeList; - }]; - [self loadFileDistributedMasternodeLists]; -} - -- (DSMasternodeList *_Nullable)reloadMasternodeLists { - return [self reloadMasternodeListsWithBlockHeightLookup:nil]; -} - -- (DSMasternodeList *_Nullable)reloadMasternodeListsWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - [self clearProcessorCache]; - [self.store removeAllMasternodeLists]; - return [self.store loadMasternodeListsWithBlockHeightLookup:blockHeightLookup]; -} - -- (DSMasternodeList *)currentMasternodeList { - if (!self.chain.isRotatedQuorumsPresented) { - return self.masternodeListDiffService.currentMasternodeList; + DMnEngineDeserializationResultDtor(result); + return success; + } else { - UInt256 lastMnlistDiffBlockHash = self.masternodeListDiffService.currentMasternodeList.blockHash; - UInt256 lastQrInfoDiffBlockHash = self.quorumRotationService.currentMasternodeList.blockHash; - return [self heightForBlockHash:lastMnlistDiffBlockHash] > [self heightForBlockHash:lastQrInfoDiffBlockHash] ? self.masternodeListDiffService.currentMasternodeList : self.quorumRotationService.currentMasternodeList; + return NO; } } -- (void)loadFileDistributedMasternodeLists { - BOOL syncMasternodeLists = [[DSOptionsManager sharedInstance] syncType] & DSSyncType_MasternodeList; - BOOL useCheckpointMasternodeLists = [[DSOptionsManager sharedInstance] useCheckpointMasternodeLists]; - if (!syncMasternodeLists || - !useCheckpointMasternodeLists || - self.currentMasternodeList) { - return; - } +- (BOOL)restoreFromCheckpoint { DSCheckpoint *checkpoint = [self.chain lastCheckpointHavingMasternodeList]; - if (!checkpoint || - self.chain.lastTerminalBlockHeight < checkpoint.height || - [self masternodeListForBlockHash:checkpoint.blockHash withBlockHeightLookup:nil]) { - return; - } - DSMasternodeList *masternodeList = [self processRequestFromFileForBlockHash:checkpoint.blockHash]; - if (masternodeList) { - self.masternodeListDiffService.currentMasternodeList = masternodeList; + if (!checkpoint || !checkpoint.masternodeListName || [checkpoint.masternodeListName isEqualToString:@""]) + return NO; + NSData *message = [self readFromDisk:checkpoint.masternodeListName]; + if (!message) + return NO; + Slice_u8 *message_slice = slice_ctor(message); + DMnDiffResult *result = DMnDiffFromMessage(self.processor, message_slice, nil, false); + + if (result->error) { + NSError *error = [NSError ffi_from_processing_error:result->error]; + DSLog(@"%@ processRequestFromFileForBlockHash Error: %@", self.logPrefix, error); + DMnDiffResultDtor(result); + return NO; } + DMnDiffResultDtor(result); + return YES; } -- (DSMasternodeList *)loadMasternodeListAtBlockHash:(NSData *)blockHash withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - return [self.store loadMasternodeListAtBlockHash:blockHash withBlockHeightLookup:blockHeightLookup]; -} +- (BOOL)restoreState { + BOOL restored = [self restoreEngine]; + if (!restored) { + DSLog(@"%@ No Engine Stored", self.logPrefix); + // TODO: checkpoints don't work anymore, since old protocol version support was dropped +// restored = [self restoreFromCheckpoint]; +// if (!restored) +// DSLog(@"%@ No Checkpoint Stored", self.logPrefix); + } + if (restored) { + DMasternodeList *current_list = [self currentMasternodeList]; + DSLog(@"%@ Engine restored: %u/%u", self.logPrefix, restored, current_list ? current_list->known_height : 0); -- (void)wipeMasternodeInfo { - DSLog(@"[%@] wipeMasternodeInfo", self.chain.name); - [self clearProcessorCache]; - [self.store removeAllMasternodeLists]; - [self.masternodeListDiffService cleanAllLists]; - [self.quorumRotationService cleanAllLists]; - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSMasternodeListDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSQuorumListDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); + [self notify:DSCurrentMasternodeListDidChangeNotification userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSMasternodeManagerNotificationMasternodeListKey: current_list ? [NSValue valueWithPointer:current_list] : [NSNull null] + }]; + self.isRestored = YES; + } + return restored; } -// MARK: - LLMQ Snapshot List Helpers -- (DSQuorumSnapshot *_Nullable)quorumSnapshotForBlockHeight:(uint32_t)blockHeight { - DSBlock *block = [self.chain blockAtHeight:blockHeight]; - if (!block) { - DSLog(@"[%@] No block for snapshot at height: %ul: ", self.chain.name, blockHeight); - return nil; +- (NSSet *)blockHashesUsedByMasternodeLists { + std_collections_Map_keys_u32_values_u8_arr_32 *result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_known_block_hashes(self.processor); + NSMutableSet *blockHashes = [NSMutableSet setWithCapacity:result->count]; + for (int i = 0; i < result->count; i++) { + [blockHashes addObject:NSDataFromPtr(result->values[i])]; } - return [self.store.cachedQuorumSnapshots objectForKey:uint256_data(block.blockHash)]; + std_collections_Map_keys_u32_values_u8_arr_32_destroy(result); + return blockHashes; } -- (DSQuorumSnapshot *_Nullable)quorumSnapshotForBlockHash:(UInt256)blockHash { - return [self.store.cachedQuorumSnapshots objectForKey:uint256_data(blockHash)]; +- (void)setUp { + [self restoreState]; + [DSLocalMasternodeEntity loadLocalMasternodesInContext:self.managedObjectContext onChainEntity:[self.chain chainEntityInContext:self.managedObjectContext]]; } -- (BOOL)saveQuorumSnapshot:(DSQuorumSnapshot *)snapshot { - [self.store.cachedQuorumSnapshots setObject:snapshot forKey:uint256_data(snapshot.blockHash)]; - return YES; +- (void)reloadMasternodeLists { + DProcessorClear(self.processor); + [self.chain.chainManager notifyMasternodeSyncStateChange:UINT32_MAX storedCount:0]; + [self restoreState]; } -// MARK: - ChainLock Signature List Helpers -- (NSData *_Nullable)CLSignatureForBlockHeight:(uint32_t)blockHeight { - DSBlock *block = [self.chain blockAtHeight:blockHeight]; - if (!block) { - DSLog(@"[%@] No block for snapshot at height: %ul: ", self.chain.name, blockHeight); - return nil; +- (BOOL)hasBlockForBlockHash:(NSData *)blockHashData { + UInt256 blockHash = blockHashData.UInt256; + BOOL hasBlock = [self.chain blockForBlockHash:blockHash] != nil; + if (!hasBlock) { + hasBlock = [DSMerkleBlockEntity hasBlocksWithHash:blockHash inContext:self.managedObjectContext]; + } + if (!hasBlock && self.chain.isTestnet) { + //We can trust insight if on testnet + [self.chain blockUntilGetInsightForBlockHash:blockHash]; + hasBlock = !![[self.chain insightVerifiedBlocksByHashDictionary] objectForKey:blockHashData]; } - return [self.store.cachedCLSignatures objectForKey:uint256_data(block.blockHash)]; + return hasBlock; } -- (NSData *_Nullable)CLSignatureForBlockHash:(UInt256)blockHash { - NSData *cachedSig = [self.store.cachedCLSignatures objectForKey:uint256_data(blockHash)]; - if (!cachedSig) { - DSChainLock *chainLock = [self.chain.chainManager chainLockForBlockHash:blockHash]; - if (chainLock) { - return uint768_data(chainLock.signature); - } - } - return cachedSig; +- (DMasternodeList *)currentMasternodeList { + return dash_spv_masternode_processor_processing_processor_MasternodeProcessor_current_masternode_list(self.processor); } -- (BOOL)saveCLSignature:(NSData *)blockHashData signatureData:(NSData *)signatureData { - [self.store.cachedCLSignatures setObject:signatureData forKey:blockHashData]; - return YES; +- (void)wipeMasternodeInfo { + DSLog(@"%@ wipeMasternodeInfo", self.logPrefix); + DProcessorClear(self.processor); + [self.masternodeListDiffService cleanAllLists]; + [self.quorumRotationService cleanAllLists]; + [self.chain.chainManager notifyMasternodeSyncStateChange:UINT32_MAX storedCount:0]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSMasternodeListDidChangeNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain + }]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSQuorumListDidChangeNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain + }]; + }); } // MARK: - Masternode List Helpers -- (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); - [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; -} - -- (DSMasternodeList *)masternodeListForBlockHash:(UInt256)blockHash { +- (DMasternodeList *)masternodeListForBlockHash:(UInt256)blockHash { return [self masternodeListForBlockHash:blockHash withBlockHeightLookup:nil]; } -- (DSMasternodeList *)masternodeListForBlockHash:(UInt256)blockHash withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - return [self.store masternodeListForBlockHash:blockHash withBlockHeightLookup:blockHeightLookup]; -} - -- (DSMasternodeList *)masternodeListBeforeBlockHash:(UInt256)blockHash { - return [self.store masternodeListBeforeBlockHash:blockHash]; +- (DMasternodeList *)masternodeListForBlockHash:(UInt256)blockHash + withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { + u256 *block_hash = u256_ctor_u(blockHash); + DMasternodeList *list = DMasternodeListForBlockHash(self.processor, block_hash); + return list; } // MARK: - Requesting Masternode List - (void)startSync { - DSLog(@"[%@] [DSMasternodeManager] startSync", self.chain.name); + DSLog(@"%@ [Start]", self.logPrefix); self.isSyncing = YES; + if (!self.isRestored) + [self restoreState]; [self getRecentMasternodeList]; } - (void)stopSync { - DSLog(@"[%@] [DSMasternodeManager] stopSync", self.chain.name); + DSLog(@"%@ [Stop]", self.logPrefix); self.isSyncing = NO; [self cancelMasternodeListTimer]; + if (self.chain.isRotatedQuorumsPresented) { [self.quorumRotationService stop]; } @@ -352,14 +340,17 @@ - (void)stopSync { } - (void)getRecentMasternodeList { - DSLog(@"[%@] getRecentMasternodeList at tip", self.chain.name); - [self.masternodeListDiffService getRecentMasternodeList]; - if (self.chain.isRotatedQuorumsPresented) { - [self.quorumRotationService getRecentMasternodeList]; + DSLog(@"%@ getRecentMasternodeList at tip (qr %u)", self.logPrefix, self.chain.isRotatedQuorumsPresented); + DSMerkleBlock *merkleBlock = [self.chain blockFromChainTip:0]; + if (!merkleBlock) { + // sometimes it happens while rescan + DSLog(@"%@ getRecentMasternodeList: (no block exist) for tip", self.logPrefix); + return; } - + [self.quorumRotationService getRecent:merkleBlock.blockHash]; } + // the safety delay checks to see if this was called in the last n seconds. - (void)getCurrentMasternodeListWithSafetyDelay:(uint32_t)safetyDelay { self.timeIntervalForMasternodeRetrievalSafetyDelay = [[NSDate date] timeIntervalSince1970]; @@ -386,77 +377,25 @@ - (void)cancelMasternodeListTimer { } } } -- (void)getMasternodeListsForBlockHashes:(NSOrderedSet *)blockHashes { - [self.masternodeListDiffService populateRetrievalQueueWithBlockHashes:blockHashes]; -} - -- (BOOL)requestMasternodeListForBlockHeight:(uint32_t)blockHeight error:(NSError **)error { - DSMerkleBlock *merkleBlock = [self.chain blockAtHeight:blockHeight]; - if (!merkleBlock) { - if (error) { - *error = [NSError errorWithCode:600 localizedDescriptionKey:@"Unknown block"]; - } - return FALSE; - } - [self requestMasternodeListForBlockHash:merkleBlock.blockHash]; - return TRUE; -} - -- (BOOL)requestMasternodeListForBlockHash:(UInt256)blockHash { - self.store.lastQueriedBlockHash = blockHash; - NSData *blockHashData = uint256_data(blockHash); - @synchronized (self.store.masternodeListQueriesNeedingQuorumsValidated) { - [self.store.masternodeListQueriesNeedingQuorumsValidated addObject:blockHashData]; - } - // this is safe - [self getMasternodeListsForBlockHashes:[NSOrderedSet orderedSetWithObject:blockHashData]]; - return TRUE; -} -- (DSMasternodeList *__nullable)processRequestFromFileForBlockHash:(UInt256)blockHash { +- (BOOL)processRequestFromFileForBlockHash:(UInt256)blockHash { DSCheckpoint *checkpoint = [self.chain checkpointForBlockHash:blockHash]; - if (!checkpoint || !checkpoint.masternodeListName || [checkpoint.masternodeListName isEqualToString:@""]) { - return nil; - } - NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; - NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; - NSString *masternodeListName = checkpoint.masternodeListName; - NSString *filePath = [bundle pathForResource:masternodeListName ofType:@"dat"]; - if (!filePath) { - return nil; - } - NSData *message = [NSData dataWithContentsOfFile:filePath]; - - if (!message) { - return NULL; - } - - MerkleBlockFinder blockFinder = ^DSMerkleBlock *(UInt256 blockHash) { - return [self.chain blockForBlockHash:blockHash]; - }; - DSMasternodeProcessorContext *context = [self createDiffMessageContext:NO isFromSnapshot:YES isDIP0024:NO peer:nil merkleRootLookup:^UInt256(UInt256 blockHash) { - return blockFinder(blockHash).merkleRoot; - }]; - DSMnDiffProcessingResult *result = [self processMasternodeDiffFromFile:message protocolVersion:[checkpoint protocolVersion] withContext:context]; + if (!checkpoint || !checkpoint.masternodeListName || [checkpoint.masternodeListName isEqualToString:@""]) + return NO; + NSData *message = [self readFromDisk:checkpoint.masternodeListName]; + if (!message) + return NO; + Slice_u8 *message_slice = slice_ctor(message); + DMnDiffResult *result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_process_mn_list_diff_result_from_message(self.processor, message_slice, nil, false); - __block DSMerkleBlock *block = blockFinder(blockHash); - if (![result isValid]) { - DSLog(@"[%@] Invalid File for block at height %u with merkleRoot %@ (foundCoinbase %@ | validQuorums %@ | rootMNListValid %@ | rootQuorumListValid %@)", self.chain.name, block.height, uint256_hex(block.merkleRoot), result.foundCoinbase?@"Yes":@"No", result.validQuorums?@"Yes":@"No", result.rootMNListValid?@"Yes":@"No", result.rootQuorumListValid?@"Yes":@"No"); - return NULL; - } - // valid Coinbase might be false if no merkle block - if (block && !result.validCoinbase) { - DSLog(@"[%@] Invalid Coinbase for block at height %u with merkleRoot %@", self.chain.name, block.height, uint256_hex(block.merkleRoot)); - return NULL; + if (result->error) { + NSError *error = [NSError ffi_from_processing_error:result->error]; + DSLog(@"%@ processRequestFromFileForBlockHash Error: %@", self.logPrefix, error); + DMnDiffResultDtor(result); + return NO; } - DSMasternodeList *masternodeList = result.masternodeList; - [self.store saveMasternodeList:masternodeList - addedMasternodes:result.addedMasternodes - modifiedMasternodes:result.modifiedMasternodes - completion:^(NSError *_Nonnull error) { - DSLog(@"[%@] MNL Saved from file", self.chain.name); - }]; - return masternodeList; + DMnDiffResultDtor(result); + return YES; } @@ -481,425 +420,207 @@ - (DSBlock *)lastBlockForBlockHash:(UInt256)blockHash fromPeer:(DSPeer *)peer { return lastBlock; } -- (NSString *)logListSet:(NSOrderedSet *)list { - NSString *str = @"\n"; - for (NSData *blockHashData in list) { - str = [str stringByAppendingString:[NSString stringWithFormat:@"•••• -> %d: %@,\n", [self heightForBlockHash:blockHashData.UInt256], blockHashData.hexString]]; - } - return str; -} - -- (void)removeOutdatedMasternodeListsBeforeBlockHash:(UInt256)blockHash { - DSMasternodeList *qrinfoMasternodeList = self.quorumRotationService.masternodeListAtH4C; - if (!qrinfoMasternodeList) { - qrinfoMasternodeList = self.quorumRotationService.masternodeListAtH3C; - } - DSMasternodeList *diffMasternodeList = self.masternodeListDiffService.currentMasternodeList; - uint32_t heightToDelete = UINT32_MAX; - if (diffMasternodeList) { - heightToDelete = diffMasternodeList.height; - NSData *oldestHashInDiffQueue = [self.masternodeListDiffService.retrievalQueue firstObject]; - if (oldestHashInDiffQueue) { - uint32_t oldestHeight = [self heightForBlockHash:oldestHashInDiffQueue.UInt256]; - if (heightToDelete > oldestHeight) { - heightToDelete = oldestHeight; - } - } - } else { - // Don't remove if we didn't get updates from mnlistdiff - return; - } - if (qrinfoMasternodeList) { - if (heightToDelete > qrinfoMasternodeList.height) { - heightToDelete = qrinfoMasternodeList.height; - } - NSData *oldestHashInQRInfoQueue = [self.quorumRotationService.retrievalQueue firstObject]; - if (oldestHashInQRInfoQueue) { - uint32_t oldestHeight = [self heightForBlockHash:oldestHashInQRInfoQueue.UInt256]; - if (heightToDelete > oldestHeight) { - heightToDelete = oldestHeight; - } - } +- (void)issueWithMasternodeListFromPeer:(DSPeer *)peer { + [self.chain.chainManager chain:self.chain badMasternodeListReceivedFromPeer:peer]; + NSArray *faultyPeers = [[NSUserDefaults standardUserDefaults] arrayForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; + if (faultyPeers.count >= MAX_FAULTY_DML_PEERS) { + DSLog(@"%@ Exceeded max failures for masternode list, starting from scratch", self.logPrefix); + //no need to remove local masternodes + [self.masternodeListDiffService cleanListsRetrievalQueue]; + [self.quorumRotationService cleanListsRetrievalQueue]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; + [self.chain.masternodeManager getRecentMasternodeList]; } else { - // Don't remove if we didn't get updates from qrinfo - return; - } - if (heightToDelete > 0 && heightToDelete != UINT32_MAX) { - DSLog(@"[%@] --> removeOldMasternodeLists (removeOutdatedMasternodeListsBeforeBlockHash): %u (%u, %u)", self.chain.name, heightToDelete, diffMasternodeList.height, qrinfoMasternodeList.height); - uint32_t h = heightToDelete - 50; - NSDictionary *lists = [[self.store masternodeListsByBlockHash] copy]; - for (NSData *proRegTxHashData in lists) { - DSMasternodeList *list = lists[proRegTxHashData]; - if (list.height < h) { - [self removeMasternodeListFromCacheAtBlockHash:list.blockHash]; - } - } - [self.store removeOldMasternodeLists:heightToDelete]; - } -} - -- (void)processMasternodeListDiffResult:(DSMnDiffProcessingResult *)result forPeer:(DSPeer *)peer skipPresenceInRetrieval:(BOOL)skipPresenceInRetrieval completion:(void (^)(void))completion { - DSMasternodeList *masternodeList = result.masternodeList; - DSLog(@"[%@] •••• processMasternodeListDiffResult: isValid: %d validCoinbase: %d", self.chain.name, [result isValid], result.validCoinbase); - if ([self.masternodeListDiffService shouldProcessDiffResult:result skipPresenceInRetrieval:skipPresenceInRetrieval]) { - NSOrderedSet *neededMissingMasternodeLists = result.neededMissingMasternodeLists; - DSLog(@"[%@] •••• processMasternodeListDiffResult: missingMasternodeLists: %@", self.chain.name, [self logListSet:neededMissingMasternodeLists]); - UInt256 masternodeListBlockHash = masternodeList.blockHash; - NSData *masternodeListBlockHashData = uint256_data(masternodeListBlockHash); - BOOL hasAwaitingQuorumValidation; - @synchronized (self.store.masternodeListQueriesNeedingQuorumsValidated) { - hasAwaitingQuorumValidation = [self.store.masternodeListQueriesNeedingQuorumsValidated containsObject:masternodeListBlockHashData]; - } - - if ([neededMissingMasternodeLists count] && hasAwaitingQuorumValidation) { - [self.masternodeListDiffService removeFromRetrievalQueue:masternodeListBlockHashData]; - [self processMissingMasternodeLists:neededMissingMasternodeLists forMasternodeList:masternodeList]; - completion(); - } else { - if (uint256_eq(self.store.lastQueriedBlockHash, masternodeListBlockHash)) { - self.masternodeListDiffService.currentMasternodeList = masternodeList; - @synchronized (self.store.masternodeListQueriesNeedingQuorumsValidated) { - [self.store.masternodeListQueriesNeedingQuorumsValidated removeObject:masternodeListBlockHashData]; - } - } - DSLog(@"[%@] ••• updateStoreWithMasternodeList: %u: %@ (%@)", self.chain.name, masternodeList.height, uint256_hex(masternodeListBlockHash), uint256_reverse_hex(masternodeListBlockHash)); - [self updateStoreWithProcessingResult:masternodeList result:result completion:^(NSError *error) { - if ([result hasRotatedQuorumsForChain:self.chain] && !self.chain.isRotatedQuorumsPresented) { - uint32_t masternodeListBlockHeight = [self heightForBlockHash:masternodeListBlockHash]; - DSLog(@"[%@] •••• processMasternodeListDiffResult: rotated quorums are presented at height %u: %@, so we'll switch into consuming qrinfo", self.chain.name, masternodeListBlockHeight, uint256_hex(masternodeListBlockHash)); - self.chain.isRotatedQuorumsPresented = YES; - self.rotatedQuorumsActivationHeight = masternodeListBlockHeight; - if (self.isSyncing) { - [self.quorumRotationService addToRetrievalQueue:masternodeListBlockHashData]; - [self.quorumRotationService dequeueMasternodeListRequest]; - } - } - if (self.isSyncing) - [self.masternodeListDiffService updateAfterProcessingMasternodeListWithBlockHash:masternodeListBlockHashData fromPeer:peer]; - completion(); - }]; + if (!faultyPeers) { + faultyPeers = @[peer.location]; + } else if (![faultyPeers containsObject:peer.location]) { + faultyPeers = [faultyPeers arrayByAddingObject:peer.location]; } - } else { - [self.masternodeListDiffService issueWithMasternodeListFromPeer:peer]; - completion(); + [[NSUserDefaults standardUserDefaults] setObject:faultyPeers forKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; + DSLog(@"%@ Failure %lu for masternode list from peer: %@", self.logPrefix, (unsigned long)faultyPeers.count, peer); + [self.quorumRotationService dequeueMasternodeListRequest]; } + [self.chain.chainManager notify:DSMasternodeListDiffValidationErrorNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; } -- (void)processQRInfoResult:(DSQRInfoProcessingResult *)result forPeer:(DSPeer *)peer completion:(void (^)(void))completion { - - DSMnDiffProcessingResult *mnListDiffResultAtTip = result.mnListDiffResultAtTip; - DSMnDiffProcessingResult *mnListDiffResultAtH = result.mnListDiffResultAtH; - DSMnDiffProcessingResult *mnListDiffResultAtHC = result.mnListDiffResultAtHC; - DSMnDiffProcessingResult *mnListDiffResultAtH2C = result.mnListDiffResultAtH2C; - DSMnDiffProcessingResult *mnListDiffResultAtH3C = result.mnListDiffResultAtH3C; - DSMnDiffProcessingResult *mnListDiffResultAtH4C = result.mnListDiffResultAtH4C; - DSLog(@"[%@] •••• processQRInfoResult tip: %d", self.chain.name, [mnListDiffResultAtTip isValid]); - - NSOrderedSet *missingMasternodeListsAtTip = mnListDiffResultAtTip.neededMissingMasternodeLists; - NSOrderedSet *missingMasternodeListsAtH = mnListDiffResultAtH.neededMissingMasternodeLists; - NSOrderedSet *missingMasternodeListsAtHC = mnListDiffResultAtHC.neededMissingMasternodeLists; - NSOrderedSet *missingMasternodeListsAtH2C = mnListDiffResultAtH2C.neededMissingMasternodeLists; - NSOrderedSet *missingMasternodeListsAtH3C = mnListDiffResultAtH3C.neededMissingMasternodeLists; - NSOrderedSet *missingMasternodeListsAtH4C = mnListDiffResultAtH4C.neededMissingMasternodeLists; - - NSMutableOrderedSet *missingMasternodeLists = [NSMutableOrderedSet orderedSet]; - [missingMasternodeLists addObjectsFromArray:[missingMasternodeListsAtTip array]]; - [missingMasternodeLists addObjectsFromArray:[missingMasternodeListsAtH array]]; - [missingMasternodeLists addObjectsFromArray:[missingMasternodeListsAtHC array]]; - [missingMasternodeLists addObjectsFromArray:[missingMasternodeListsAtH2C array]]; - [missingMasternodeLists addObjectsFromArray:[missingMasternodeListsAtH3C array]]; - [missingMasternodeLists addObjectsFromArray:[missingMasternodeListsAtH4C array]]; - DSLog(@"[%@] •••• processQRInfoResult: missingMasternodeLists: %@", self.chain.name, [self logListSet:missingMasternodeLists]); - - DSMasternodeList *masternodeListAtTip = mnListDiffResultAtTip.masternodeList; - DSMasternodeList *masternodeListAtH = mnListDiffResultAtH.masternodeList; - DSMasternodeList *masternodeListAtHC = mnListDiffResultAtHC.masternodeList; - DSMasternodeList *masternodeListAtH2C = mnListDiffResultAtH2C.masternodeList; - DSMasternodeList *masternodeListAtH3C = mnListDiffResultAtH3C.masternodeList; - DSMasternodeList *masternodeListAtH4C = mnListDiffResultAtH4C.masternodeList; - self.quorumRotationService.masternodeListAtTip = masternodeListAtTip; - self.quorumRotationService.masternodeListAtH = masternodeListAtH; - self.quorumRotationService.masternodeListAtHC = masternodeListAtHC; - self.quorumRotationService.masternodeListAtH2C = masternodeListAtH2C; - self.quorumRotationService.masternodeListAtH3C = masternodeListAtH3C; - self.quorumRotationService.masternodeListAtH4C = masternodeListAtH4C; - UInt256 blockHashAtTip = masternodeListAtTip.blockHash; - UInt256 blockHashAtH = masternodeListAtH.blockHash; - UInt256 blockHashAtHC = masternodeListAtHC.blockHash; - UInt256 blockHashAtH2C = masternodeListAtH2C.blockHash; - UInt256 blockHashAtH3C = masternodeListAtH3C.blockHash; - UInt256 blockHashAtH4C = masternodeListAtH4C.blockHash; - NSData *blockHashDataAtTip = uint256_data(blockHashAtTip); - NSData *blockHashDataAtH = uint256_data(blockHashAtH); - NSData *blockHashDataAtHC = uint256_data(blockHashAtHC); - NSData *blockHashDataAtH2C = uint256_data(blockHashAtH2C); - NSData *blockHashDataAtH3C = uint256_data(blockHashAtH3C); - NSData *blockHashDataAtH4C = uint256_data(blockHashAtH4C); - NSMutableSet *masternodeListQueriesNeedingQuorumsValidated; - @synchronized (self.store.masternodeListQueriesNeedingQuorumsValidated) { - masternodeListQueriesNeedingQuorumsValidated = self.store.masternodeListQueriesNeedingQuorumsValidated; - } - if (![self.quorumRotationService shouldProcessDiffResult:mnListDiffResultAtH4C skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else if (![missingMasternodeListsAtH4C count] || ![masternodeListQueriesNeedingQuorumsValidated containsObject:blockHashDataAtH4C]) { - [self updateStoreWithProcessingResult:masternodeListAtH4C result:mnListDiffResultAtH4C completion:^(NSError *error) {}]; - } - if (![self.quorumRotationService shouldProcessDiffResult:mnListDiffResultAtH3C skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else if (![missingMasternodeListsAtH3C count] || ![masternodeListQueriesNeedingQuorumsValidated containsObject:blockHashDataAtH3C]) { - [self updateStoreWithProcessingResult:masternodeListAtH3C result:mnListDiffResultAtH3C completion:^(NSError *error) {}]; - } - if (![self.quorumRotationService shouldProcessDiffResult:mnListDiffResultAtH2C skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else if (![missingMasternodeListsAtH2C count] || ![masternodeListQueriesNeedingQuorumsValidated containsObject:blockHashDataAtH2C]) { - [self updateStoreWithProcessingResult:masternodeListAtH2C result:mnListDiffResultAtH2C completion:^(NSError *error) {}]; - } - if (![self.quorumRotationService shouldProcessDiffResult:mnListDiffResultAtHC skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else if (![missingMasternodeListsAtHC count] || ![masternodeListQueriesNeedingQuorumsValidated containsObject:blockHashDataAtHC]) { - [self updateStoreWithProcessingResult:masternodeListAtHC result:mnListDiffResultAtHC completion:^(NSError *error) {}]; - } - if (![self.quorumRotationService shouldProcessDiffResult:mnListDiffResultAtH skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else if (![missingMasternodeListsAtH count] || ![masternodeListQueriesNeedingQuorumsValidated containsObject:blockHashDataAtH]) { - [self updateStoreWithProcessingResult:masternodeListAtH result:mnListDiffResultAtH completion:^(NSError *error) {}]; - } - if (![self.quorumRotationService shouldProcessDiffResult:mnListDiffResultAtTip skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else { - if ([missingMasternodeListsAtTip count] && [masternodeListQueriesNeedingQuorumsValidated containsObject:blockHashDataAtTip]) { - [self.quorumRotationService removeFromRetrievalQueue:blockHashDataAtTip]; - [self processMissingMasternodeLists:missingMasternodeLists forMasternodeList:masternodeListAtTip]; - } else { - if (uint256_eq(self.store.lastQueriedBlockHash, blockHashAtTip)) { - self.quorumRotationService.currentMasternodeList = masternodeListAtTip; - @synchronized (self.store.masternodeListQueriesNeedingQuorumsValidated) { - [self.store.masternodeListQueriesNeedingQuorumsValidated removeObject:blockHashDataAtTip]; - } - } - [self updateStoreWithProcessingResult:masternodeListAtTip result:mnListDiffResultAtTip completion:^(NSError *error) {}]; - [self.quorumRotationService updateAfterProcessingMasternodeListWithBlockHash:blockHashDataAtTip fromPeer:peer]; - } - } - [self.store saveQuorumSnapshot:result.snapshotAtHC completion:^(NSError * _Nonnull error) {}]; - [self.store saveQuorumSnapshot:result.snapshotAtH2C completion:^(NSError * _Nonnull error) {}]; - [self.store saveQuorumSnapshot:result.snapshotAtH3C completion:^(NSError * _Nonnull error) {}]; - [self.store saveQuorumSnapshot:result.snapshotAtH4C completion:^(NSError * _Nonnull error) {}]; - - for (DSMnDiffProcessingResult *diffResult in result.mnListDiffList) { - DSLog(@"[%@] •••• -> processed qrinfo +++ %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:diffResult.baseBlockHash], [self heightForBlockHash:diffResult.blockHash], uint256_hex(diffResult.baseBlockHash), uint256_hex(diffResult.blockHash)); - DSMasternodeList *diffMasternodeList = diffResult.masternodeList; - UInt256 diffBlockHash = diffMasternodeList.blockHash; - NSData *diffBlockHashData = uint256_data(diffBlockHash); - if (![self.quorumRotationService shouldProcessDiffResult:diffResult skipPresenceInRetrieval:YES]) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - } else if (![diffResult.neededMissingMasternodeLists count] || ![masternodeListQueriesNeedingQuorumsValidated containsObject:diffBlockHashData]) { - [self updateStoreWithProcessingResult:diffMasternodeList result:diffResult completion:^(NSError *error) {}]; - } - } - for (DSQuorumSnapshot *snapshot in result.snapshotList) { - [self.store saveQuorumSnapshot:snapshot completion:^(NSError * _Nonnull error) {}]; - } - - [self.store.activeQuorums unionOrderedSet:result.lastQuorumPerIndex]; -} - -- (void)processMissingMasternodeLists:(NSOrderedSet *)neededMissingMasternodeLists forMasternodeList:(DSMasternodeList *)masternodeList { - UInt256 masternodeListBlockHash = masternodeList.blockHash; - NSData *masternodeListBlockHashData = uint256_data(masternodeListBlockHash); - self.store.masternodeListAwaitingQuorumValidation = masternodeList; - NSMutableOrderedSet *neededMasternodeLists = [neededMissingMasternodeLists mutableCopy]; - [neededMasternodeLists addObject:masternodeListBlockHashData]; //also get the current one again - if (self.isSyncing) - [self getMasternodeListsForBlockHashes:neededMasternodeLists]; -} -- (void)updateStoreWithProcessingResult:(DSMasternodeList *)masternodeList result:(DSMnDiffProcessingResult *)result completion:(void (^)(NSError *error))completion { - if (uint256_eq(self.store.masternodeListAwaitingQuorumValidation.blockHash, masternodeList.blockHash)) { - self.store.masternodeListAwaitingQuorumValidation = nil; - } - [self.store.cachedCLSignatures addEntriesFromDictionary:result.clSignatures]; - [self.store saveMasternodeList:masternodeList - addedMasternodes:result.addedMasternodes - modifiedMasternodes:result.modifiedMasternodes - completion:^(NSError *error) { - completion(error); - if (!error || !([self.masternodeListDiffService retrievalQueueCount] + [self.quorumRotationService retrievalQueueCount])) { //if it is 0 then we most likely have wiped chain info - return; - } - [self wipeMasternodeInfo]; - if (self.isSyncing) { - dispatch_async(self.chain.networkingQueue, ^{ - [self getRecentMasternodeList]; - }); - } - }]; -} - (void)peer:(DSPeer *)peer relayedMasternodeDiffMessage:(NSData *)message { - DSLog(@"[%@: %@:%d] •••• -> received mnlistdiff: %@", self.chain.name, peer.host, peer.port, uint256_hex(message.SHA256)); + DSLog(@"%@ [%@:%d] mnlistdiff: received: %@", self.logPrefix, peer.host, peer.port, uint256_hex(message.SHA256)); @synchronized (self.masternodeListDiffService) { self.masternodeListDiffService.timedOutAttempt = 0; } dispatch_async(self.processingQueue, ^{ dispatch_group_enter(self.processingGroup); - DSMasternodeProcessorContext *ctx = [self createDiffMessageContext:self.chain.isTestnet isFromSnapshot:NO isDIP0024:NO peer:peer merkleRootLookup:^UInt256(UInt256 blockHash) { - DSBlock *lastBlock = [self lastBlockForBlockHash:blockHash fromPeer:peer]; - if (!lastBlock) { - [self.masternodeListDiffService issueWithMasternodeListFromPeer:peer]; - DSLog(@"[%@] Last Block missing", self.chain.name); - return UINT256_ZERO; + Slice_u8 *message_slice = slice_ctor(message); + + DMnDiffResult *result = DMnDiffFromMessage(self.processor, message_slice, nil, true); + + if (result->error) { + NSError *error = [NSError ffi_from_processing_error:result->error]; + DSLog(@"%@ mnlistdiff: Error: %@", self.logPrefix, error.description); + switch (result->error->tag) { + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_MissingLists: + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_LocallyStored: + break; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_InvalidResult: + [self.masternodeListDiffService cleanRequestsInRetrieval]; + DSLog(@"%@ mnlistdiff: InvalidResult -> dequeueMasternodeListRequest (mn)", self.chain.name); + [self.masternodeListDiffService dequeueMasternodeListRequest]; + default: + [self issueWithMasternodeListFromPeer:peer]; + break; } - 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)); - 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]; - #endif - if (result.errorStatus) { - DSLog(@"[%@] Processing status: %ul", self.chain.name, result.errorStatus); - dispatch_group_leave(self.processingGroup); - return; - } - [self processMasternodeListDiffResult:result forPeer:peer skipPresenceInRetrieval:NO completion:^{ - dispatch_group_leave(self.processingGroup); - }]; - }]; +//#if SAVE_MASTERNODE_DIFF_TO_FILE +// NSString *fileName = [NSString stringWithFormat:@"MNL_ERR__%d.dat", peer.version]; +// DSLog(@"%@ •-• File %@ saved", self.logPrefix, fileName); +// [message saveToFile:fileName inDirectory:NSCachesDirectory]; +//#endif + + DMnDiffResultDtor(result); + dispatch_group_leave(self.processingGroup); + return; + } + if (self.isSyncing) { + u256 *block_hash = dashcore_hash_types_BlockHash_inner(result->ok->o_1); + NSData *blockHashData = NSDataFromPtr(block_hash); + u256_dtor(block_hash); + [self.masternodeListDiffService removeFromRetrievalQueue:blockHashData]; + [self.masternodeListDiffService dequeueMasternodeListRequest]; + if (![self.masternodeListDiffService retrievalQueueCount]) + [self.chain.chainManager.transactionManager checkWaitingForQuorums]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; + +// [self.masternodeListDiffService updateAfterProcessingMasternodeListWithBlockHash:masternodeListBlockHashData fromPeer:peer]; + } +//#if SAVE_MASTERNODE_DIFF_TO_FILE +// u256 *base_block_hash = result->ok->o_0; +// uint32_t base_block_height = DHeightForBlockHash(self.processor, base_block_hash); +// uint32_t block_height = DHeightForBlockHash(self.processor, block_hash); +// NSString *fileName = [NSString stringWithFormat:@"MNL_%@_%@__%d.dat", @(base_block_height), @(block_height), peer.version]; +// DSLog(@"%@ •-• File %@ saved", self.logPrefix, fileName); +// [message saveToFile:fileName inDirectory:NSCachesDirectory]; +//#endif +// DMnDiffResultDtor(result); + DMnDiffResultDtor(result); + + dispatch_group_leave(self.processingGroup); }); } -- (void)peer:(DSPeer *)peer relayedQuorumRotationInfoMessage:(NSData *)message { - DSLog(@"[%@: %@:%d] •••• -> received qrinfo: %@", self.chain.name, peer.host, peer.port, uint256_hex(message.SHA256)); - @synchronized (self.quorumRotationService) { - self.quorumRotationService.timedOutAttempt = 0; - } - dispatch_async(self.processingQueue, ^{ - dispatch_group_enter(self.processingGroup); - MerkleRootFinder merkleRootLookup = ^UInt256(UInt256 blockHash) { - DSBlock *lastBlock = [self lastBlockForBlockHash:blockHash fromPeer:peer]; - if (!lastBlock) { - [self.quorumRotationService issueWithMasternodeListFromPeer:peer]; - DSLog(@"[%@] Last Block missing", self.chain.name); - return UINT256_ZERO; - } - return lastBlock.merkleRoot; - }; - DSMasternodeProcessorContext *ctx = [self createDiffMessageContext:self.chain.isTestnet isFromSnapshot:NO isDIP0024:YES peer:peer merkleRootLookup:merkleRootLookup]; - [self processQRInfoWith:message context:ctx completion:^(DSQRInfoProcessingResult * _Nonnull result) { - if (result.errorStatus) { - DSLog(@"[%@] •••• Processing status: %u", self.chain.name, result.errorStatus); +- (void)tryToProcessQrInfo:(DSPeer *)peer message:(NSData *)message attempt:(uint8_t)attempt { + // uint32_t protocol_version = peer ? peer.version : self.chain.protocolVersion; + __block NSUInteger numOfAttempt = attempt; + dispatch_async(self.processingQueue, ^{ + dispatch_group_enter(self.processingGroup); + Slice_u8 *slice_msg = slice_ctor(message); + + DQRInfoResult *result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_process_qr_info_result_from_message(self.processor, slice_msg, true, true); + if (result->error) { + NSError *error = [NSError ffi_from_processing_error:result->error]; + DSLog(@"%@ qrinfo: Error: %@", self.logPrefix, error); + switch (result->error->tag) { + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_InvalidResult: + [self issueWithMasternodeListFromPeer:peer]; + break; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_LocallyStored: + [self.quorumRotationService cleanListsRetrievalQueue]; + break; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_UnknownBlockHash: + break; + case dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_QuorumValidationError: + switch (result->error->quorum_validation_error->tag) { + case dashcore_sml_quorum_validation_error_QuorumValidationError_RequiredBlockNotPresent: { +// DBlockHash *unknown_block_hash = result->error->quorum_validation_error->required_block_not_present; + // TODO: it can be tip so we can wait for 300ms and try again + if (attempt < 3) { + sleep(10); + numOfAttempt++; + dispatch_group_leave(self.processingGroup); + [self tryToProcessQrInfo:peer message:message attempt:attempt]; + } + break; + } + default: + break; + } + break; + default: + break; + } + #if SAVE_MASTERNODE_DIFF_TO_FILE + NSString *fileName = [NSString stringWithFormat:@"QRINFO_ERR_%d.dat", peer.version]; + DSLog(@"%@ •-• File %@ saved", self.logPrefix, fileName); + [message saveToFile:fileName inDirectory:NSCachesDirectory]; + #endif + #if SAVE_ERROR_STATE + Result_ok_Vec_u8_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError *bincode = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_serialize_engine(self.processor); + if (bincode->error) { + NSError *error = [NSError ffi_from_processing_error:bincode->error]; + DSLog(@"%@ Engine: Error: %@", self.logPrefix, error); + } else { + [self writeToDisk:ENGINE_STORAGE_LOCATION(self.chain) data:NSDataFromPtr(bincode->ok)]; + } + Result_ok_Vec_u8_err_dash_spv_masternode_processor_processing_processor_processing_error_ProcessingError_destroy(bincode); + #endif + + DQRInfoResultDtor(result); 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)); - DSLog(@"[%@] •••• -> processed qrinfo h %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:result.mnListDiffResultAtH.baseBlockHash], [self heightForBlockHash:result.mnListDiffResultAtH.blockHash], uint256_hex(result.mnListDiffResultAtH.baseBlockHash), uint256_hex(result.mnListDiffResultAtH.blockHash)); - DSLog(@"[%@] •••• -> processed qrinfo h-c %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:result.mnListDiffResultAtHC.baseBlockHash], [self heightForBlockHash:result.mnListDiffResultAtHC.blockHash], uint256_hex(result.mnListDiffResultAtHC.baseBlockHash), uint256_hex(result.mnListDiffResultAtHC.blockHash)); - DSLog(@"[%@] •••• -> processed qrinfo h-2c %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:result.mnListDiffResultAtH2C.baseBlockHash], [self heightForBlockHash:result.mnListDiffResultAtH2C.blockHash], uint256_hex(result.mnListDiffResultAtH2C.baseBlockHash), uint256_hex(result.mnListDiffResultAtH2C.blockHash)); - DSLog(@"[%@] •••• -> processed qrinfo h-3c %u..%u %@ .. %@", self.chain.name, [self heightForBlockHash:result.mnListDiffResultAtH3C.baseBlockHash], [self heightForBlockHash:result.mnListDiffResultAtH3C.blockHash], uint256_hex(result.mnListDiffResultAtH3C.baseBlockHash), uint256_hex(result.mnListDiffResultAtH3C.blockHash)); - 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)); + std_collections_BTreeSet_dashcore_hash_types_BlockHash *missed_hashes = result->ok; + + if (missed_hashes->count > 0) { + NSArray *missedHashes = [NSArray ffi_from_block_hash_btree_set:missed_hashes]; + [self.masternodeListDiffService addToRetrievalQueueArray:missedHashes]; } - 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]; - #endif - [self processQRInfoResult:result forPeer:peer completion:^{ - dispatch_group_leave(self.processingGroup); - }]; - }]; - }); -} - -- (DSMasternodeProcessorContext *)createDiffMessageContext:(BOOL)useInsightAsBackup isFromSnapshot:(BOOL)isFromSnapshot isDIP0024:(BOOL)isDIP0024 peer:(DSPeer *_Nullable)peer merkleRootLookup:(MerkleRootFinder)merkleRootLookup { - DSMasternodeProcessorContext *mndiffContext = [[DSMasternodeProcessorContext alloc] init]; - [mndiffContext setUseInsightAsBackup:useInsightAsBackup]; - [mndiffContext setIsFromSnapshot:isFromSnapshot]; - [mndiffContext setIsDIP0024:isDIP0024]; - [mndiffContext setChain:self.chain]; - [mndiffContext setPeer:peer]; - [mndiffContext setMasternodeListLookup:^DSMasternodeList *(UInt256 blockHash) { - return [self masternodeListForBlockHash:blockHash withBlockHeightLookup:nil]; - }]; - [mndiffContext setBlockHeightLookup:^uint32_t(UInt256 blockHash) { - return [self heightForBlockHash:blockHash]; - }]; - [mndiffContext setMerkleRootLookup:merkleRootLookup]; - return mndiffContext; -} -- (BOOL)hasMasternodeListCurrentlyBeingSaved { - return [self.store hasMasternodeListCurrentlyBeingSaved]; + //#if SAVE_MASTERNODE_DIFF_TO_FILE + // u256 *base_block_hash = result->ok->o_0; + // uint32_t base_block_height = DHeightForBlockHash(self.processor, base_block_hash); + // uint32_t block_height = DHeightForBlockHash(self.processor, block_hash); + // NSString *fileName = [NSString stringWithFormat:@"QRINFO_%@_%@__%d.dat", @(base_block_height), @(block_height), peer.version]; + // DSLog(@"%@ •-• File %@ saved", self.logPrefix, fileName); + // [message saveToFile:fileName inDirectory:NSCachesDirectory]; + //#endif + // [self.quorumRotationService updateAfterProcessingMasternodeListWithBlockHash:NSDataFromPtr(block_hash) fromPeer:peer]; + + [self.quorumRotationService cleanListsRetrievalQueue]; + [self.quorumRotationService dequeueMasternodeListRequest]; + if (missed_hashes->count == 0) + [self.chain.chainManager.transactionManager checkWaitingForQuorums]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; + + + DQRInfoResultDtor(result); + dispatch_group_leave(self.processingGroup); + }); } -+ (void)saveMasternodeList:(DSMasternodeList *)masternodeList - toChain:(DSChain *)chain - havingModifiedMasternodes:(NSDictionary *)modifiedMasternodes - createUnknownBlocks:(BOOL)createUnknownBlocks - inContext:(NSManagedObjectContext *)context - completion:(void (^)(NSError *error))completion { - [DSMasternodeListStore saveMasternodeList:masternodeList - toChain:chain - havingModifiedMasternodes:modifiedMasternodes - createUnknownBlocks:createUnknownBlocks - inContext:context - completion:completion]; -} - -// MARK: - Quorums - -- (DSQuorumEntry *)quorumEntryForChainLockRequestID:(UInt256)requestID withBlockHeightOffset:(uint32_t)blockHeightOffset { - DSMerkleBlock *merkleBlock = [self.chain blockFromChainTip:blockHeightOffset]; - return [self quorumEntryForChainLockRequestID:requestID forMerkleBlock:merkleBlock]; -} - -- (DSQuorumEntry *)quorumEntryForChainLockRequestID:(UInt256)requestID forBlockHeight:(uint32_t)blockHeight { - DSMerkleBlock *merkleBlock = [self.chain blockAtHeight:blockHeight]; - return [self quorumEntryForChainLockRequestID:requestID forMerkleBlock:merkleBlock]; -} - -- (DSQuorumEntry *)quorumEntryForChainLockRequestID:(UInt256)requestID forMerkleBlock:(DSMerkleBlock *)merkleBlock { - return [self.store quorumEntryForChainLockRequestID:requestID forMerkleBlock:merkleBlock]; -} - -- (DSQuorumEntry *)quorumEntryForInstantSendRequestID:(UInt256)requestID withBlockHeightOffset:(uint32_t)blockHeightOffset { - return [self.store quorumEntryForInstantSendRequestID:requestID forMerkleBlock:[self.chain blockFromChainTip:blockHeightOffset]]; -} - -- (DSQuorumEntry *)quorumEntryForPlatformHavingQuorumHash:(UInt256)quorumHash forBlockHeight:(uint32_t)blockHeight { - return [self.store quorumEntryForPlatformHavingQuorumHash:quorumHash forBlockHeight:blockHeight]; +- (void)peer:(DSPeer *)peer relayedQuorumRotationInfoMessage:(NSData *)message { + DSLog(@"%@ [%@:%d] qrinfo: received: %@", self.logPrefix, peer.host, peer.port, uint256_hex(message.SHA256)); + @synchronized (self.quorumRotationService) { + self.quorumRotationService.timedOutAttempt = 0; + } + + [self tryToProcessQrInfo:peer message:message attempt:0]; } // MARK: - Meta information -- (void)checkPingTimesForCurrentMasternodeListInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *errors))completion { - __block NSArray *entries = self.currentMasternodeList.simplifiedMasternodeEntries; - [self.chain.chainManager.DAPIClient checkPingTimesForMasternodes:entries - completion:^(NSMutableDictionary *_Nonnull pingTimes, NSMutableDictionary *_Nonnull errors) { - [self.store savePlatformPingInfoForEntries:entries inContext:context]; - if (completion != nil) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(pingTimes, errors); - }); - } - }]; - -} +- (void)checkPingTimesForCurrentMasternodeListInContext:(NSManagedObjectContext *)context + withCompletion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *errors))completion { + // TODO: this is not supported yet +// __block NSArray *entries = self.currentMasternodeList.simplifiedMasternodeEntries; +// [self.chain.chainManager.DAPIClient checkPingTimesForMasternodes:entries +// completion:^(NSMutableDictionary *_Nonnull pingTimes, NSMutableDictionary *_Nonnull errors) { +// [self.store savePlatformPingInfoForEntries:entries inContext:context]; +// if (completion != nil) { +// dispatch_async(dispatch_get_main_queue(), ^{ +// completion(pingTimes, errors); +// }); +// } +// }]; -- (UInt256)buildLLMQHashFor:(DSQuorumEntry *)quorum { - uint32_t workHeight = [self heightForBlockHash:quorum.quorumHash] - 8; - if (workHeight >= chain_core20_activation_height(self.chain.chainType)) { - NSData *bestCLSignature = [self CLSignatureForBlockHeight:workHeight]; - return [DSKeyManager NSDataFrom:quorum_build_llmq_hash_v20(quorum.llmqType, workHeight, bestCLSignature.bytes)].UInt256; - } else { - 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 5852a45d1..33e7f71cf 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager+Protected.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager+Protected.h @@ -41,7 +41,7 @@ typedef NS_ENUM(NSUInteger, DSPeerManagerDesiredState) @property (nonatomic, readonly) NSUInteger connectFailures, misbehavingCount, maxConnectCount; @property (nonatomic, readonly) NSSet *connectedPeers; @property (nonatomic, readonly) DSPeerManagerDesiredState desiredState; -@property (nonatomic, readonly) DSMasternodeList *masternodeList; +@property (nonatomic, readonly) DMasternodeList *masternodeList; - (void)peerMisbehaving:(DSPeer *)peer errorMessage:(NSString *)errorMessage; - (void)chainSyncStopped; @@ -54,7 +54,8 @@ typedef NS_ENUM(NSUInteger, DSPeerManagerDesiredState) - (instancetype)initWithChain:(DSChain *)chain; -- (void)useMasternodeList:(DSMasternodeList *)masternodeList withConnectivityNonce:(uint64_t)connectivityNonce; +- (void)useMasternodeList:(DMasternodeList *)masternodeList + withConnectivityNonce:(uint64_t)connectivityNonce; - (void)clearRegisteredPeers; - (void)registerPeerAtLocation:(UInt128)IPAddress port:(uint32_t)port dapiPort:(uint32_t)dapiPort; diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.h index 174409623..9064eb275 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.h @@ -75,7 +75,9 @@ typedef NS_ENUM(uint16_t, DSDisconnectReason) @property (nonatomic, readonly) NSArray *registeredDevnetPeers; @property (nonatomic, readonly) NSArray *registeredDevnetPeerServices; @property (nullable, nonatomic, readonly) NSString *trustedPeerHost; +@property (nonatomic, readonly) BOOL shouldSendDsq; +- (DSPeer *)peerForLocation:(UInt128)IPAddress port:(uint16_t)port; - (DSPeerStatus)statusForLocation:(UInt128)IPAddress port:(uint32_t)port; - (DSPeerType)typeForLocation:(UInt128)IPAddress port:(uint32_t)port; - (void)setTrustedPeerHost:(NSString *_Nullable)host; @@ -91,6 +93,10 @@ typedef NS_ENUM(uint16_t, DSDisconnectReason) - (void)sendRequest:(DSMessageRequest *)request; +// MARK: CoinJoin + +- (void)shouldSendDsq:(BOOL)shouldSendDsq; + @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 cdfb882d0..4e7c1dfc3 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSPeerManager.m @@ -26,10 +26,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +//#import "dash_spv_apple_bindings.h" #import "DSAccount.h" #import "DSBackgroundManager.h" #import "DSBloomFilter.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainManager+Protected.h" #import "DSDerivationPath.h" @@ -37,7 +40,6 @@ #import "DSGovernanceObject.h" #import "DSGovernanceSyncManager.h" #import "DSGovernanceVote.h" -#import "DSMasternodeList.h" #import "DSMasternodeManager.h" #import "DSMerkleBlock.h" #import "DSMerkleBlockEntity+CoreDataClass.h" @@ -57,6 +59,7 @@ #import "NSError+Dash.h" #import "NSManagedObject+Sugar.h" #import "NSString+Bitcoin.h" +#import "DSSendCoinJoinQueue.h" #import #import @@ -77,6 +80,11 @@ #define SYNC_COUNT_INFO @"SYNC_COUNT_INFO" +#define ERROR_NO_PEERS [NSError errorWithCode:1 localizedDescriptionKey:@"No peers found"] +#define ERROR_SYNC_TIMEOUT [NSError errorWithCode:500 descriptionKey:DSLocalizedString(@"Synchronization Timeout", @"An error message for notifying that chain sync has timed out")] +#define ERROR_NO_SERVICE(host) [NSError errorWithCode:500 descriptionKey:DSLocalizedFormat(@"Node at host %@ does not service network", nil, host)] +#define ERROR_NO_BLOOM(host) [NSError errorWithCode:500 descriptionKey:DSLocalizedFormat(@"Node at host %@ does not support bloom filtering", nil, host)] + @interface DSPeerManager () @property (nonatomic, strong) NSMutableOrderedSet *peers; @@ -89,7 +97,7 @@ @interface DSPeerManager () @property (nonatomic, strong) DSChain *chain; @property (nonatomic, assign) DSPeerManagerDesiredState desiredState; @property (nonatomic, assign) uint64_t masternodeListConnectivityNonce; -@property (nonatomic, strong) DSMasternodeList *masternodeList; +@property (nonatomic, assign) DMasternodeList *masternodeList; @property (nonatomic, readonly) dispatch_queue_t networkingQueue; @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; @@ -127,6 +135,8 @@ - (instancetype)initWithChain:(DSChain *)chain { - (void)dealloc { [NSObject cancelPreviousPerformRequestsWithTarget:self]; if (self.walletAddedObserver) [[NSNotificationCenter defaultCenter] removeObserver:self.walletAddedObserver]; + if (_masternodeList) + DMasternodeListDtor(_masternodeList); } - (dispatch_queue_t)networkingQueue { @@ -192,19 +202,21 @@ - (NSString *)downloadPeerName { return [self.downloadPeer.host stringByAppendingFormat:@":%d", self.downloadPeer.port]; } -- (NSArray *)dnsSeeds { - switch (self.chain.chainType.tag) { - case ChainType_MainNet: - return MAINNET_DNS_SEEDS; - case ChainType_TestNet: - return TESTNET_DNS_SEEDS; - case ChainType_DevNet: - return nil; //no dns seeds for devnets - default: - break; - } - return nil; -} +//- (NSArray *)dnsSeeds { +// struct Vec_String *vec = dash_spv_crypto_network_chain_type_ChainType_dns_seeds(self.chain.chainType); +// +// switch (self.chain.chainType.tag) { +// case ChainType_MainNet: +// return MAINNET_DNS_SEEDS; +// case ChainType_TestNet: +// return TESTNET_DNS_SEEDS; +// case ChainType_DevNet: +// return nil; //no dns seeds for devnets +// default: +// break; +// } +// return nil; +//} // MARK: - Peers + (DSPeer *)peerFromString:(NSString *)string forChain:(DSChain *)chain { @@ -243,6 +255,17 @@ - (void)clearPeers:(DSDisconnectReason)reason { _peers = nil; } } +- (NSArray *)peers:(uint32_t)peerCount withConnectivityNonce:(uint64_t)connectivityNonce { + Vec_Tuple_Arr_u8_16_u16 *vec = dash_spv_masternode_processor_processing_peer_addresses_with_connectivity_nonce(self.masternodeList, connectivityNonce, peerCount); + + NSMutableArray *mArray = [NSMutableArray array]; + for (int i = 0; i < vec->count; i++) { + Tuple_Arr_u8_16_u16 *pair = vec->values[i]; + [mArray addObject:[[DSPeer alloc] initWithAddress:u128_cast(pair->o_0) andPort:pair->o_1 onChain:self.chain]]; + } + Vec_Tuple_Arr_u8_16_u16_destroy(vec); + return mArray; +} - (NSMutableOrderedSet *)peers { if (_fixedPeer) return [NSMutableOrderedSet orderedSetWithObject:_fixedPeer]; @@ -269,7 +292,7 @@ - (NSMutableOrderedSet *)peers { [_peers addObjectsFromArray:[self registeredDevnetPeers]]; if (self.masternodeList) { - NSArray *masternodePeers = [self.masternodeList peers:8 withConnectivityNonce:self.masternodeListConnectivityNonce]; + NSArray *masternodePeers = [self peers:8 withConnectivityNonce:self.masternodeListConnectivityNonce]; [_peers addObjectsFromArray:masternodePeers]; } @@ -278,7 +301,7 @@ - (NSMutableOrderedSet *)peers { } if (self.masternodeList) { - NSArray *masternodePeers = [self.masternodeList peers:500 withConnectivityNonce:self.masternodeListConnectivityNonce]; + NSArray *masternodePeers = [self peers:500 withConnectivityNonce:self.masternodeListConnectivityNonce]; [_peers addObjectsFromArray:masternodePeers]; [self sortPeers]; return _peers; @@ -287,20 +310,22 @@ - (NSMutableOrderedSet *)peers { // DNS peer discovery NSTimeInterval now = [NSDate timeIntervalSince1970]; NSMutableArray *peers = [NSMutableArray arrayWithObject:[NSMutableArray array]]; - NSArray *dnsSeeds = [self dnsSeeds]; + Vec_String *dns_seeds = dash_spv_crypto_network_chain_type_ChainType_dns_seeds(self.chain.chainType); if (_peers.count < PEER_MAX_CONNECTIONS || ((DSPeer *)_peers[PEER_MAX_CONNECTIONS - 1]).timestamp + 3 * DAY_TIME_INTERVAL < now) { - while (peers.count < dnsSeeds.count) [peers addObject:[NSMutableArray array]]; + while (peers.count < dns_seeds->count) [peers addObject:[NSMutableArray array]]; } if (peers.count > 0) { - if ([dnsSeeds count]) { + if (dns_seeds->count) { dispatch_apply(peers.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) { NSString *servname = @(self.chain.standardPort).stringValue; struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, 0, 0, 0, NULL, NULL}, *servinfo, *p; UInt128 addr = {.u32 = {0, 0, CFSwapInt32HostToBig(0xffff), 0}}; + + char *dns_seed = dns_seeds->values[i]; - DSLog(@"[%@] [DSPeerManager] DNS lookup %@", self.chain.name, [dnsSeeds objectAtIndex:i]); - NSString *dnsSeed = [dnsSeeds objectAtIndex:i]; + DSLog(@"[%@] [DSPeerManager] DNS lookup %s", self.chain.name, dns_seed); + NSString *dnsSeed = [NSString stringWithUTF8String:dns_seed]; if (getaddrinfo([dnsSeed UTF8String], servname.UTF8String, &hints, &servinfo) == 0) { for (p = servinfo; p != NULL; p = p->ai_next) { if (p->ai_family == AF_INET) { @@ -325,7 +350,7 @@ - (NSMutableOrderedSet *)peers { freeaddrinfo(servinfo); } else { - DSLog(@"[%@] [DSPeerManager] failed getaddrinfo for %@", self.chain.name, dnsSeeds[i]); + DSLog(@"[%@] [DSPeerManager] failed getaddrinfo for %s", self.chain.name, dns_seed); } }); } @@ -334,6 +359,7 @@ - (NSMutableOrderedSet *)peers { if (![self.chain isMainnet] && ![self.chain isTestnet]) { [self sortPeers]; + Vec_String_destroy(dns_seeds); return _peers; } // if DNS peer discovery fails, fall back on a hard coded list of peers (list taken from satoshi client) @@ -375,7 +401,7 @@ - (NSMutableOrderedSet *)peers { [self sortPeers]; } - + Vec_String_destroy(dns_seeds); return _peers; } } @@ -409,7 +435,7 @@ - (void)peerMisbehaving:(DSPeer *)peer errorMessage:(NSString *)errorMessage { _peers = nil; } - [peer disconnectWithError:[NSError errorWithCode:500 localizedDescriptionKey:errorMessage]]; + [peer disconnectWithError:ERROR_500(errorMessage)]; DSLog(@"[%@] [DSPeerManager] peerMisbehaving -> peerManager::connect", self.chain.name); [self connect]; } @@ -640,14 +666,15 @@ - (NSArray *)registeredDevnetPeerServices { // MARK: - Using Masternode List for connectivitity -- (void)useMasternodeList:(DSMasternodeList *)masternodeList withConnectivityNonce:(uint64_t)connectivityNonce { +- (void)useMasternodeList:(DMasternodeList *)masternodeList + withConnectivityNonce:(uint64_t)connectivityNonce { self.masternodeList = masternodeList; self.masternodeListConnectivityNonce = connectivityNonce; BOOL connected = self.connected; - NSArray *peers = [masternodeList peers:500 withConnectivityNonce:connectivityNonce]; + NSArray *peers = [self peers:500 withConnectivityNonce:connectivityNonce]; @synchronized(self) { if (!_peers) { @@ -696,7 +723,10 @@ - (void)connect { DSLog(@"[%@] [DSPeerManager] connect", self.chain.name); self.desiredState = DSPeerManagerDesiredState_Connected; 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.chain syncsBlockchain] && ![self.chain canConstructAFilter]) { + DSLog(@"[%@] [DSPeerManager] failed to connect: check that wallet is created", self.chain.name); + 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) { @@ -762,10 +792,9 @@ - (void)connect { [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 object:nil - userInfo:@{@"error": error, DSChainManagerNotificationChainKey: self.chain}]; + userInfo:@{@"error": ERROR_NO_PEERS, DSChainManagerNotificationChainKey: self.chain}]; }); } }); @@ -802,7 +831,7 @@ - (void)syncTimeout { } }); - [self disconnectDownloadPeerForError:[NSError errorWithCode:500 descriptionKey:DSLocalizedString(@"Synchronization Timeout", @"An error message for notifying that chain sync has timed out")] withCompletion:nil]; + [self disconnectDownloadPeerForError:ERROR_SYNC_TIMEOUT withCompletion:nil]; } - (void)restartSyncTimeout:(NSTimeInterval)afterDelay { [self cancelSyncTimeout]; @@ -832,13 +861,13 @@ - (void)peerConnected:(DSPeer *)peer { // drop peers that don't carry full blocks, or aren't synced yet // TODO: XXXX does this work with 0.11 pruned nodes? if (!(peer.services & SERVICES_NODE_NETWORK) || peer.lastBlockHeight + 10 < self.chain.lastSyncBlockHeight) { - [peer disconnectWithError:[NSError errorWithCode:500 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Node at host %@ does not service network", nil), peer.host]]]; + [peer disconnectWithError:ERROR_NO_SERVICE(peer.host)]; return; } // drop peers that don't support SPV filtering if (peer.version >= 70206 && !(peer.services & SERVICES_NODE_BLOOM)) { - [peer disconnectWithError:[NSError errorWithCode:500 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Node at host %@ does not support bloom filtering", nil), peer.host]]]; + [peer disconnectWithError:ERROR_NO_BLOOM(peer.host)]; return; } @@ -880,6 +909,11 @@ - (void)peerConnected:(DSPeer *)peer { if (!self.masternodeList) { [peer sendGetaddrMessage]; // request a list of other dash peers } + + if (self.shouldSendDsq) { + [peer sendRequest:[DSSendCoinJoinQueue requestWithShouldSend:true]]; + } + dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil @@ -1060,5 +1094,14 @@ - (void)sendRequest:(DSMessageRequest *)request { [self.downloadPeer sendRequest:request]; } +// MARK: CoinJoin + +- (void)shouldSendDsq:(BOOL)shouldSendDsq { + for (DSPeer *peer in self.connectedPeers) { + DSSendCoinJoinQueue *request = [DSSendCoinJoinQueue requestWithShouldSend:shouldSendDsq]; + [peer sendRequest:request]; + } + _shouldSendDsq = shouldSendDsq; +} @end diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSSporkManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSSporkManager.m index bba224268..f92c01ffe 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSSporkManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSSporkManager.m @@ -24,6 +24,7 @@ // THE SOFTWARE. #import "DSSporkManager.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataProperties.h" #import "DSChainManager+Protected.h" diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.h b/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.h index 886cda483..a029bd5f7 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.h +++ b/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.h @@ -77,7 +77,13 @@ typedef void (^DSTransactionRequestRelayCompletionBlock)(DSTransaction *tx, DSPa - (void)publishTransaction:(DSTransaction *)transaction completion:(void (^)(NSError * _Nullable error))completion; -- (void)confirmPaymentRequest:(DSPaymentRequest *)paymentRequest usingUserBlockchainIdentity:(DSBlockchainIdentity *_Nullable)blockchainIdentity fromAccount:(DSAccount *)account acceptInternalAddress:(BOOL)acceptInternalAddress acceptReusingAddress:(BOOL)acceptReusingAddress addressIsFromPasteboard:(BOOL)addressIsFromPasteboard requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingConfirmationPrompt +- (void)confirmPaymentRequest:(DSPaymentRequest *)paymentRequest + usingUserIdentity:(DSIdentity *_Nullable)identity + fromAccount:(DSAccount *)account + acceptInternalAddress:(BOOL)acceptInternalAddress + acceptReusingAddress:(BOOL)acceptReusingAddress + addressIsFromPasteboard:(BOOL)addressIsFromPasteboard +requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingConfirmationPrompt keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge @@ -86,7 +92,7 @@ typedef void (^DSTransactionRequestRelayCompletionBlock)(DSTransaction *tx, DSPa publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock; -- (void)signAndPublishTransaction:(DSTransaction *)tx createdFromProtocolRequest:(DSPaymentProtocolRequest *)protocolRequest fromAccount:(DSAccount *)account toAddress:(NSString *)address requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingConfirmationPrompt promptMessage:(NSString *_Nullable)promptMessage forAmount:(uint64_t)amount keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion signedCompletion:(DSTransactionSigningCompletionBlock)signedCompletion publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion requestRelayCompletion:(DSTransactionRequestRelayCompletionBlock _Nullable)requestRelayCompletion errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock; +- (void)signAndPublishTransaction:(DSTransaction *)tx createdFromProtocolRequest:(DSPaymentProtocolRequest *)protocolRequest fromAccount:(DSAccount *)account toAddress:(NSString *)address requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingConfirmationPrompt promptMessage:(NSString *_Nullable)promptMessage forAmount:(uint64_t)amount keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication mixedOnly:(BOOL)mixedOnly requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion signedCompletion:(DSTransactionSigningCompletionBlock)signedCompletion publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion requestRelayCompletion:(DSTransactionRequestRelayCompletionBlock _Nullable)requestRelayCompletion errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock; - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(uint64_t)requestedAmount fromAccount:(DSAccount *)account addressIsFromPasteboard:(BOOL)addressIsFromPasteboard requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication @@ -98,7 +104,7 @@ typedef void (^DSTransactionRequestRelayCompletionBlock)(DSTransaction *tx, DSPa requestRelayCompletion:(DSTransactionRequestRelayCompletionBlock _Nullable)requestRelayCompletion errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock; -- (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(uint64_t)requestedAmount fromAccount:(DSAccount *)account acceptInternalAddress:(BOOL)acceptInternalAddress acceptReusingAddress:(BOOL)acceptReusingAddress addressIsFromPasteboard:(BOOL)addressIsFromPasteboard acceptUncertifiedPayee:(BOOL)acceptUncertifiedPayee requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt +- (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(uint64_t)requestedAmount fromAccount:(DSAccount *)account acceptInternalAddress:(BOOL)acceptInternalAddress acceptReusingAddress:(BOOL)acceptReusingAddress addressIsFromPasteboard:(BOOL)addressIsFromPasteboard acceptUncertifiedPayee:(BOOL)acceptUncertifiedPayee mixedOnly:(BOOL)mixedOnly requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge diff --git a/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.m b/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.m index 9c2cd630a..bfb818c52 100644 --- a/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.m +++ b/DashSync/shared/Models/Managers/Chain Managers/DSTransactionManager.m @@ -27,19 +27,20 @@ #import "DSAccount.h" #import "DSAuthenticationManager.h" #import "DSBlock.h" -#import "DSBlockchainIdentity+Protected.h" -#import "DSBlockchainIdentityRegistrationTransition.h" +#import "DSIdentity+Protected.h" #import "DSBloomFilter.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Transaction.h" +#import "DSChain+Wallet.h" #import "DSChainLock.h" #import "DSChainManager+Protected.h" -#import "DSCreditFundingTransaction.h" -#import "DSDAPIPlatformNetworkService.h" +#import "DSChainManager+Transactions.h" #import "DSError.h" #import "DSEventManager.h" +#import "DSGapLimit.h" #import "DSIdentitiesManager.h" #import "DSInstantSendTransactionLock.h" -#import "DSMasternodeList.h" #import "DSMasternodeManager+Protected.h" #import "DSMerkleBlock.h" #import "DSOptionsManager.h" @@ -52,7 +53,8 @@ #import "DSTransactionEntity+CoreDataClass.h" #import "DSTransactionHashEntity+CoreDataClass.h" #import "DSTransactionInput.h" -#import "DSTransition.h" +#import "DSCoinJoinManager.h" +#import "DSWallet+Identity.h" #import "DSWallet+Protected.h" #import "NSData+Dash.h" #import "NSDate+Utils.h" @@ -72,6 +74,12 @@ #define SAVE_MAX_TRANSACTIONS_INFO (DEBUG && 0) #define DEBUG_CHAIN_LOCKS_WAITING_FOR_QUORUMS (DEBUG && 0) +#define ERROR_NOT_SIGNED [NSError errorWithCode:401 localizedDescriptionKey:@"Dash transaction not signed"] +#define ERROR_SIGNING [NSError errorWithCode:401 localizedDescriptionKey:@"Error signing transaction"] +#define ERROR_NOT_CONNECTED [NSError errorWithCode:-1009 localizedDescriptionKey:@"Not connected to the Dash network"] +#define ERROR_TIMEOUT [NSError errorWithCode:DASH_PEER_TIMEOUT_CODE localizedDescriptionKey:@"Transaction canceled, network timeout"] +#define ERROR_DOUBLE_SPEND [NSError errorWithCode:401 localizedDescriptionKey:@"Double spend"] + @interface DSTransactionManager () @property (nonatomic, strong) NSMutableDictionary *txRelays, *txRequests; @@ -104,6 +112,11 @@ @interface DSTransactionManager () @implementation DSTransactionManager +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [DSTransactionManager] ", self.chain.name]; +} + + - (instancetype)initWithChain:(id)chain { if (!(self = [super init])) return nil; _chain = chain; @@ -185,14 +198,14 @@ - (void)publishTransaction:(DSTransaction *)transaction completion:(void (^)(NSE if ([transaction transactionTypeRequiresInputs] && !transaction.isSigned) { if (completion) { [[DSEventManager sharedEventManager] saveEvent:@"transaction_manager:not_signed"]; - completion([NSError errorWithCode:401 localizedDescriptionKey:@"Dash transaction not signed"]); + completion(ERROR_NOT_SIGNED); } return; } else if (!self.peerManager.connected && self.peerManager.connectFailures >= MAX_CONNECT_FAILURES) { if (completion) { [[DSEventManager sharedEventManager] saveEvent:@"transaction_manager:not_connected"]; - completion([NSError errorWithCode:-1009 localizedDescriptionKey:@"Not connected to the Dash network"]); + completion(ERROR_NOT_CONNECTED); } return; @@ -212,6 +225,13 @@ - (void)publishTransaction:(DSTransaction *)transaction completion:(void (^)(NSE dispatch_async(self.chainManager.chain.networkingQueue, ^{ [self performSelector:@selector(txTimeout:) withObject:hash afterDelay:PROTOCOL_TIMEOUT]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil + userInfo:@{DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction}]; + }); for (DSPeer *p in peers) { if (p.status != DSPeerStatus_Connected) continue; @@ -279,7 +299,11 @@ - (void)removeUnrelayedTransactionsFromPeer:(DSPeer *)peer { for (DSTransaction *transaction in transactionsSet) { if (transaction.blockHeight != TX_UNCONFIRMED) continue; hash = uint256_obj(transaction.txHash); +#if DEBUG + DSLogPrivate(@"[%@] checking published callback %@ -> %@", self.chain.name, uint256_reverse_hex(transaction.txHash), self.publishedCallback[hash] ? @"OK" : @"no callback"); +#else DSLog(@"[%@] checking published callback -> %@", self.chain.name, self.publishedCallback[hash] ? @"OK" : @"no callback"); +#endif if (self.publishedCallback[hash] != NULL) continue; DSLog(@"[%@] transaction relays count %lu, transaction requests count %lu", self.chain.name, (unsigned long)[self.txRelays[hash] count], (unsigned long)[self.txRequests[hash] count]); DSAccount *account = [self.chain firstAccountThatCanContainTransaction:transaction]; @@ -291,6 +315,9 @@ - (void)removeUnrelayedTransactionsFromPeer:(DSPeer *)peer { NSAssert(FALSE, @"This probably needs more implementation work, if you are here now is the time to do it."); continue; } + + BOOL updateTransaction = NO; + if ([self.txRelays[hash] count] == 0 && [self.txRequests[hash] count] == 0) { // if this is for a transaction we sent, and it wasn't already known to be invalid, notify user of failure if (!rescan && account && [account amountSentByTransaction:transaction] > 0 && [account transactionIsValid:transaction]) { @@ -325,7 +352,7 @@ - (void)removeUnrelayedTransactionsFromPeer:(DSPeer *)peer { DSLog(@"[%@] removing transaction ", self.chain.name); #endif [transactionsToBeRemoved addObject:transaction]; - + updateTransaction = YES; } else if ([self.txRelays[hash] count] < self.peerManager.maxConnectCount) { // set timestamp 0 to mark as unverified #if DEBUG @@ -336,6 +363,17 @@ - (void)removeUnrelayedTransactionsFromPeer:(DSPeer *)peer { [self.chain setBlockHeight:TX_UNCONFIRMED andTimestamp:0 forTransactionHashes:@[hash]]; + updateTransaction = YES; + } + + if (updateTransaction) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil + userInfo:@{DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction, + DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(NO)}}]; + }); } } @@ -418,7 +456,7 @@ - (void)txTimeout:(NSValue *)txHash { if (callback) { [[DSEventManager sharedEventManager] saveEvent:@"transaction_manager:tx_canceled_timeout"]; - callback([NSError errorWithCode:DASH_PEER_TIMEOUT_CODE localizedDescriptionKey:@"Transaction canceled, network timeout"]); + callback(ERROR_TIMEOUT); } }); } @@ -447,6 +485,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u acceptReusingAddress:NO addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:NO + mixedOnly:NO requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest @@ -458,7 +497,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u errorNotificationBlock:errorNotificationBlock]; } -- (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(uint64_t)requestedAmount fromAccount:(DSAccount *)account acceptInternalAddress:(BOOL)acceptInternalAddress acceptReusingAddress:(BOOL)acceptReusingAddress addressIsFromPasteboard:(BOOL)addressIsFromPasteboard acceptUncertifiedPayee:(BOOL)acceptUncertifiedPayee +- (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(uint64_t)requestedAmount fromAccount:(DSAccount *)account acceptInternalAddress:(BOOL)acceptInternalAddress acceptReusingAddress:(BOOL)acceptReusingAddress addressIsFromPasteboard:(BOOL)addressIsFromPasteboard acceptUncertifiedPayee:(BOOL)acceptUncertifiedPayee mixedOnly:(BOOL)mixedOnly requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest @@ -506,7 +545,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u NSString *challengeAction = DSLocalizedString(@"Ignore", nil); challenge( challengeTitle, challengeMessage, challengeAction, ^{ - [self confirmProtocolRequest:protoReq forAmount:requestedAmount fromAccount:account acceptInternalAddress:YES acceptReusingAddress:acceptReusingAddress addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:acceptUncertifiedPayee requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; + [self confirmProtocolRequest:protoReq forAmount:requestedAmount fromAccount:account acceptInternalAddress:YES acceptReusingAddress:acceptReusingAddress addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:acceptUncertifiedPayee mixedOnly:mixedOnly requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; }, ^{ additionalInfoRequest(DSRequestingAdditionalInfo_CancelOrChangeAmount); @@ -528,6 +567,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u acceptReusingAddress:YES addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:acceptUncertifiedPayee + mixedOnly:mixedOnly requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest @@ -549,7 +589,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u NSString *challengeAction = DSLocalizedString(@"Ignore", nil); challenge( challengeTitle, challengeMessage, challengeAction, ^{ - [self confirmProtocolRequest:protoReq forAmount:requestedAmount fromAccount:account acceptInternalAddress:acceptInternalAddress acceptReusingAddress:acceptReusingAddress addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:YES requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; + [self confirmProtocolRequest:protoReq forAmount:requestedAmount fromAccount:account acceptInternalAddress:acceptInternalAddress acceptReusingAddress:acceptReusingAddress addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:YES mixedOnly:mixedOnly requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; }, ^{ additionalInfoRequest(DSRequestingAdditionalInfo_CancelOrChangeAmount); @@ -561,9 +601,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u return; } else if (amount < TX_MIN_OUTPUT_AMOUNT) { NSString *errorTitle = DSLocalizedString(@"Couldn't make payment", nil); - NSString *errorMessage = [NSString stringWithFormat: - DSLocalizedString(@"Dash payments can't be less than %@", nil), - [priceManager stringForDashAmount:TX_MIN_OUTPUT_AMOUNT]]; + NSString *errorMessage = DSLocalizedFormat(@"Dash payments can't be less than %@", nil, [priceManager stringForDashAmount:TX_MIN_OUTPUT_AMOUNT]); NSString *localizedDescription = [NSString stringWithFormat:@"%@\n%@", errorTitle, errorMessage]; NSError *error = [NSError errorWithDomain:DSErrorDomain code:DSErrorPaymentAmountLessThenMinOutputAmount @@ -572,9 +610,7 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u return; } else if (outputTooSmall) { NSString *errorTitle = DSLocalizedString(@"Couldn't make payment", nil); - NSString *errorMessage = [NSString stringWithFormat: - DSLocalizedString(@"Dash transaction outputs can't be less than %@", nil), - [priceManager stringForDashAmount:TX_MIN_OUTPUT_AMOUNT]]; + NSString *errorMessage = DSLocalizedFormat(@"Dash transaction outputs can't be less than %@", nil, [priceManager stringForDashAmount:TX_MIN_OUTPUT_AMOUNT]); NSString *localizedDescription = [NSString stringWithFormat:@"%@\n%@", errorTitle, errorMessage]; NSError *error = [NSError errorWithDomain:DSErrorDomain code:DSErrorPaymentTransactionOutputTooSmall @@ -582,15 +618,23 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u errorNotificationBlock(error, errorTitle, errorMessage, YES); return; } + + DSCoinControl *coinControl = nil; + + if (mixedOnly) { + coinControl = [[DSCoinJoinManager sharedInstanceForChain:self.chain] selectCoinJoinUTXOs]; + } if (requestedAmount == 0) { tx = [account transactionForAmounts:protoReq.details.outputAmounts toOutputScripts:protoReq.details.outputScripts - withFee:YES]; + withFee:YES + coinControl:coinControl]; } else if (amount <= account.balance) { tx = [account transactionForAmounts:@[@(requestedAmount)] toOutputScripts:@[protoReq.details.outputScripts.firstObject] - withFee:YES]; + withFee:YES + coinControl:coinControl]; } if (tx) { @@ -629,33 +673,59 @@ - (void)confirmProtocolRequest:(DSPaymentProtocolRequest *)protoReq forAmount:(u if (transactionCreationCompletion(tx, suggestedPrompt, amount, fee, address ? @[address] : @[], isSecure)) { CFRunLoopPerformBlock([[NSRunLoop mainRunLoop] getCFRunLoop], kCFRunLoopCommonModes, ^{ - [self signAndPublishTransaction:tx createdFromProtocolRequest:protoReq fromAccount:account toAddress:address requiresSpendingAuthenticationPrompt:YES promptMessage:suggestedPrompt forAmount:amount keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; + [self signAndPublishTransaction:tx createdFromProtocolRequest:protoReq fromAccount:account toAddress:address requiresSpendingAuthenticationPrompt:YES promptMessage:suggestedPrompt forAmount:amount keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication mixedOnly:mixedOnly requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; }); } } -- (void)signAndPublishTransaction:(DSTransaction *)tx createdFromProtocolRequest:(DSPaymentProtocolRequest *)protocolRequest fromAccount:(DSAccount *)account toAddress:(NSString *)address requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt promptMessage:(NSString *)promptMessage forAmount:(uint64_t)amount keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion signedCompletion:(DSTransactionSigningCompletionBlock)signedCompletion publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion requestRelayCompletion:(DSTransactionRequestRelayCompletionBlock)requestRelayCompletion errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock { +- (void)signAndPublishTransaction:(DSTransaction *)tx createdFromProtocolRequest:(DSPaymentProtocolRequest *)protocolRequest fromAccount:(DSAccount *)account toAddress:(NSString *)address requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt promptMessage:(NSString *)promptMessage forAmount:(uint64_t)amount keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication mixedOnly:(BOOL)mixedOnly requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion signedCompletion:(DSTransactionSigningCompletionBlock)signedCompletion publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion requestRelayCompletion:(DSTransactionRequestRelayCompletionBlock)requestRelayCompletion errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock { DSAuthenticationManager *authenticationManager = [DSAuthenticationManager sharedInstance]; __block BOOL previouslyWasAuthenticated = authenticationManager.didAuthenticate; if (!tx) { // tx is nil if there were insufficient wallet funds if (authenticationManager.didAuthenticate) { //the fee puts us over the limit - [self insufficientFundsForTransactionCreatedFromProtocolRequest:protocolRequest fromAccount:account forAmount:amount toAddress:address requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; + [self insufficientFundsForTransactionCreatedFromProtocolRequest:protocolRequest + fromAccount:account + forAmount:amount + toAddress:address + requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt + keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication + mixedOnly:mixedOnly + requestingAdditionalInfo:additionalInfoRequest + presentChallenge:challenge + transactionCreationCompletion:transactionCreationCompletion + signedCompletion:signedCompletion + publishedCompletion:publishedCompletion + requestRelayCompletion:requestRelayCompletion + errorNotificationBlock:errorNotificationBlock]; } else { [authenticationManager seedWithPrompt:promptMessage forWallet:account.wallet forAmount:amount forceAuthentication:NO completion:^(NSData *_Nullable seed, BOOL cancelled) { - if (seed) { - //the fee puts us over the limit - [self insufficientFundsForTransactionCreatedFromProtocolRequest:protocolRequest fromAccount:account forAmount:amount toAddress:address requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:requestRelayCompletion errorNotificationBlock:errorNotificationBlock]; - } else { - additionalInfoRequest(DSRequestingAdditionalInfo_CancelOrChangeAmount); - } - if (!previouslyWasAuthenticated && !keepAuthenticatedIfErrorAfterAuthentication) [authenticationManager deauthenticate]; - }]; + if (seed) { + //the fee puts us over the limit + [self insufficientFundsForTransactionCreatedFromProtocolRequest:protocolRequest + fromAccount:account + forAmount:amount + toAddress:address + requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt + keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication + mixedOnly:mixedOnly + requestingAdditionalInfo:additionalInfoRequest + presentChallenge:challenge + transactionCreationCompletion:transactionCreationCompletion + signedCompletion:signedCompletion + publishedCompletion:publishedCompletion + requestRelayCompletion:requestRelayCompletion + errorNotificationBlock:errorNotificationBlock]; + } else { + additionalInfoRequest(DSRequestingAdditionalInfo_CancelOrChangeAmount); + } + if (!previouslyWasAuthenticated && !keepAuthenticatedIfErrorAfterAuthentication) [authenticationManager deauthenticate]; + }]; } } else { NSString *displayedPrompt = promptMessage; @@ -681,7 +751,7 @@ - (void)signAndPublishTransaction:(DSTransaction *)tx createdFromProtocolRequest if (!signedTransaction || !tx.isSigned) { if (!previouslyWasAuthenticated && !keepAuthenticatedIfErrorAfterAuthentication) [authenticationManager deauthenticate]; dispatch_async(dispatch_get_main_queue(), ^{ - signedCompletion(tx, [NSError errorWithCode:401 localizedDescriptionKey:@"Error signing transaction"], NO); + signedCompletion(tx, ERROR_SIGNING, NO); }); return; } @@ -752,10 +822,7 @@ - (void)publishSignedTransaction:(DSTransaction *)tx createdFromProtocolRequest: completion:^(DSPaymentProtocolACK *ack, NSError *error) { if (error) { dispatch_async(dispatch_get_main_queue(), ^{ - NSString *errorTitle = [NSString - stringWithFormat: - DSLocalizedString(@"Error from payment request server %@", nil), - protocolRequest.details.paymentURL]; + NSString *errorTitle = DSLocalizedFormat(@"Error from payment request server %@", nil, protocolRequest.details.paymentURL); NSString *errorMessage = error.localizedDescription; NSString *localizedDescription = [NSString stringWithFormat:@"%@\n%@", @@ -800,6 +867,7 @@ - (void)publishSignedTransaction:(DSTransaction *)tx createdFromProtocolRequest: - (void)insufficientFundsForTransactionCreatedFromProtocolRequest:(DSPaymentProtocolRequest *)protocolRequest fromAccount:(DSAccount *)account forAmount:(uint64_t)requestedSendAmount toAddress:(NSString *)address requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication + mixedOnly:(BOOL)mixedOnly requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest presentChallenge:(DSTransactionChallengeBlock)challenge transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion @@ -816,9 +884,9 @@ - (void)insufficientFundsForTransactionCreatedFromProtocolRequest:(DSPaymentProt if (amount > 0 && amount < requestedSendAmount) { NSString *challengeTitle = DSLocalizedString(@"Insufficient funds for Dash network fee", nil); - NSString *challengeMessage = [NSString stringWithFormat:DSLocalizedString(@"Reduce payment amount by\n%@ (%@)?", nil), + NSString *challengeMessage = DSLocalizedFormat(@"Reduce payment amount by\n%@ (%@)?", nil, [manager stringForDashAmount:requestedSendAmount - amount], - [manager localCurrencyStringForDashAmount:requestedSendAmount - amount]]; + [manager localCurrencyStringForDashAmount:requestedSendAmount - amount]); NSString *reduceString = [NSString stringWithFormat:@"%@ (%@)", [manager stringForDashAmount:amount - requestedSendAmount], @@ -834,6 +902,7 @@ - (void)insufficientFundsForTransactionCreatedFromProtocolRequest:(DSPaymentProt acceptReusingAddress:YES addressIsFromPasteboard:NO acceptUncertifiedPayee:YES + mixedOnly:mixedOnly requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest @@ -864,16 +933,22 @@ - (void)insufficientFundsForTransactionCreatedFromProtocolRequest:(DSPaymentProt } } -- (void)confirmPaymentRequest:(DSPaymentRequest *)paymentRequest usingUserBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity fromAccount:(DSAccount *)account acceptInternalAddress:(BOOL)acceptInternalAddress acceptReusingAddress:(BOOL)acceptReusingAddress addressIsFromPasteboard:(BOOL)addressIsFromPasteboard requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt +- (void)confirmPaymentRequest:(DSPaymentRequest *)paymentRequest + usingUserIdentity:(DSIdentity *)identity + fromAccount:(DSAccount *)account + acceptInternalAddress:(BOOL)acceptInternalAddress + acceptReusingAddress:(BOOL)acceptReusingAddress + addressIsFromPasteboard:(BOOL)addressIsFromPasteboard +requiresSpendingAuthenticationPrompt:(BOOL)requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:(BOOL)keepAuthenticatedIfErrorAfterAuthentication - requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest - presentChallenge:(DSTransactionChallengeBlock)challenge - transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion - signedCompletion:(DSTransactionSigningCompletionBlock)signedCompletion - publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion - errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock { - DSPaymentProtocolRequest *protocolRequest = [paymentRequest protocolRequestForBlockchainIdentity:blockchainIdentity onAccount:account inContext:[NSManagedObjectContext viewContext]]; - [self confirmProtocolRequest:protocolRequest forAmount:paymentRequest.amount fromAccount:account acceptInternalAddress:acceptInternalAddress acceptReusingAddress:acceptReusingAddress addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:NO requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:nil errorNotificationBlock:errorNotificationBlock]; + requestingAdditionalInfo:(DSTransactionCreationRequestingAdditionalInfoBlock)additionalInfoRequest + presentChallenge:(DSTransactionChallengeBlock)challenge +transactionCreationCompletion:(DSTransactionCreationCompletionBlock)transactionCreationCompletion + signedCompletion:(DSTransactionSigningCompletionBlock)signedCompletion + publishedCompletion:(DSTransactionPublishedCompletionBlock)publishedCompletion + errorNotificationBlock:(DSTransactionErrorNotificationBlock)errorNotificationBlock { + DSPaymentProtocolRequest *protocolRequest = [paymentRequest protocolRequestForIdentity:identity onAccount:account inContext:[NSManagedObjectContext viewContext]]; + [self confirmProtocolRequest:protocolRequest forAmount:paymentRequest.amount fromAccount:account acceptInternalAddress:acceptInternalAddress acceptReusingAddress:acceptReusingAddress addressIsFromPasteboard:addressIsFromPasteboard acceptUncertifiedPayee:NO mixedOnly:NO requiresSpendingAuthenticationPrompt:requiresSpendingAuthenticationPrompt keepAuthenticatedIfErrorAfterAuthentication:keepAuthenticatedIfErrorAfterAuthentication requestingAdditionalInfo:additionalInfoRequest presentChallenge:challenge transactionCreationCompletion:transactionCreationCompletion signedCompletion:signedCompletion publishedCompletion:publishedCompletion requestRelayCompletion:nil errorNotificationBlock:errorNotificationBlock]; } // MARK: - Mempools Sync @@ -943,18 +1018,18 @@ - (void)fetchMempoolFromNetwork { } } -// MARK: - TransactionFetching - -- (void)fetchTransactionWithDAPIForTransactionHash:(UInt256)transactionHash success:(void (^)(DSTransaction *transaction))success - failure:(void (^)(NSError *error))failure { - DSDAPIPlatformNetworkService *networkService = self.chainManager.DAPIClient.DAPIPlatformNetworkService; - [networkService getTransactionById:uint256_hex(transactionHash) - success:^(NSDictionary *_Nonnull transactionDictionary) { - //[DSTransaction transactionWithMessage:<#(nonnull NSData *)#> - // onChain:<#(nonnull DSChain *)#>]} - } - failure:failure]; -} +//// MARK: - TransactionFetching +// +//- (void)fetchTransactionWithDAPIForTransactionHash:(UInt256)transactionHash success:(void (^)(DSTransaction *transaction))success +// failure:(void (^)(NSError *error))failure { +// DSDAPIPlatformNetworkService *networkService = self.chainManager.DAPIClient.DAPIPlatformNetworkService; +// [networkService getTransactionById:uint256_hex(transactionHash) +// success:^(NSDictionary *_Nonnull transactionDictionary) { +// //[DSTransaction transactionWithMessage:<#(nonnull NSData *)#> +// // onChain:<#(nonnull DSChain *)#>]} +// } +// failure:failure]; +//} - (void)fetchTransactionHavingHash:(UInt256)transactionHash { for (DSPeer *peer in self.peerManager.connectedPeers) { @@ -994,29 +1069,7 @@ - (void)updateTransactionsBloomFilter { // the transaction likely consumed one or more wallet addresses, so check that at least the next // unused addresses are still matched by the bloom filter - NSMutableArray *allAddressesArray = [NSMutableArray array]; - - for (DSWallet *wallet in self.chain.wallets) { - // every time a new wallet address is added, the bloom filter has to be rebuilt, and each address is only used for - // one transaction, so here we generate some spare addresses to avoid rebuilding the filter each time a wallet - // transaction is encountered during the blockchain download - [wallet registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_EXTERNAL unusedAccountGapLimit:SEQUENCE_UNUSED_GAP_LIMIT_EXTERNAL dashpayGapLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INCOMING internal:NO error:nil]; - [wallet registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INTERNAL unusedAccountGapLimit:SEQUENCE_GAP_LIMIT_INTERNAL dashpayGapLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INCOMING internal:YES error:nil]; - NSSet *addresses = [wallet.allReceiveAddresses setByAddingObjectsFromSet:wallet.allChangeAddresses]; - [allAddressesArray addObjectsFromArray:[addresses allObjects]]; - - [allAddressesArray addObjectsFromArray:[wallet providerOwnerAddresses]]; - [allAddressesArray addObjectsFromArray:[wallet providerVotingAddresses]]; - [allAddressesArray addObjectsFromArray:[wallet providerOperatorAddresses]]; - [allAddressesArray addObjectsFromArray:[wallet platformNodeAddresses]]; - } - - for (DSFundsDerivationPath *derivationPath in self.chain.standaloneDerivationPaths) { - [derivationPath registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_EXTERNAL internal:NO error:nil]; - [derivationPath registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INTERNAL internal:YES error:nil]; - NSArray *addresses = [derivationPath.allReceiveAddresses arrayByAddingObjectsFromArray:derivationPath.allChangeAddresses]; - [allAddressesArray addObjectsFromArray:addresses]; - } + NSArray *allAddressesArray = [self.chain newAddressesForBloomFilter]; for (NSString *address in allAddressesArray) { NSData *hash = address.addressToHash160; @@ -1099,22 +1152,24 @@ - (DSTransaction *)peer:(DSPeer *)peer requestedTransaction:(UInt256)txHash { if (callback && !isTransactionValid) { [self.publishedTx removeObjectForKey:hash]; - error = [NSError errorWithCode:401 localizedDescriptionKey:@"Double spend"]; + error = ERROR_DOUBLE_SPEND; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification + object:nil + userInfo:@{DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction, + DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(NO)}}]; + }); } else if (transaction) { for (DSAccount *account in accounts) { - if (![account transactionForHash:txHash]) { - if ([account registerTransaction:transaction saveImmediately:YES]) { - [[NSManagedObjectContext chainContext] ds_saveInBlock]; - } - } + if (![account transactionForHash:txHash] && [account registerTransaction:transaction saveImmediately:YES]) + [[NSManagedObjectContext chainContext] ds_saveInBlock]; } } dispatch_async(self.chainManager.chain.networkingQueue, ^{ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(txTimeout:) object:hash]; - dispatch_async(dispatch_get_main_queue(), ^{ - if (callback) callback(error); - }); + dispatch_async(dispatch_get_main_queue(), ^{ if (callback) callback(error); }); }); // [peer sendPingMessageWithPongHandler:^(BOOL success) { // check if peer will relay the transaction back @@ -1276,35 +1331,18 @@ - (void)peer:(DSPeer *)peer relayedTransaction:(DSTransaction *)transaction inBl } } - BOOL isNewBlockchainIdentity = FALSE; - DSBlockchainIdentity *blockchainIdentity = nil; + BOOL isNewIdentity = FALSE; if (![transaction isMemberOfClass:[DSTransaction class]]) { //it's a special transaction BOOL registered = YES; //default to yes - if (![transaction isKindOfClass:[DSCreditFundingTransaction class]]) { - registered = [self.chain registerSpecialTransaction:transaction saveImmediately:block ? NO : YES]; - } +// if (![transaction isKindOfClass:[DSAssetLockTransaction class]]) { + registered = [self.chain registerSpecialTransaction:transaction saveImmediately:!block]; +// } if (registered) { - if ([transaction isKindOfClass:[DSCreditFundingTransaction class]]) { - DSCreditFundingTransaction *creditFundingTransaction = (DSCreditFundingTransaction *)transaction; - uint32_t index; - DSWallet *wallet = [self.chain walletHavingBlockchainIdentityCreditFundingRegistrationHash:creditFundingTransaction.creditBurnPublicKeyHash foundAtIndex:&index]; - - if (wallet) { - blockchainIdentity = [wallet blockchainIdentityForUniqueId:transaction.creditBurnIdentityIdentifier]; - } - - if (!blockchainIdentity) { - [self.chain triggerUpdatesForLocalReferences:transaction]; - if (wallet) { - blockchainIdentity = [wallet blockchainIdentityForUniqueId:transaction.creditBurnIdentityIdentifier]; - if (blockchainIdentity) isNewBlockchainIdentity = TRUE; - } - } else if (blockchainIdentity && !blockchainIdentity.registrationCreditFundingTransaction) { - blockchainIdentity.registrationCreditFundingTransactionHash = creditFundingTransaction.txHash; - } + if ([transaction isKindOfClass:[DSAssetLockTransaction class]]) { + isNewIdentity = registered; } else { [self.chain triggerUpdatesForLocalReferences:transaction]; } @@ -1344,12 +1382,23 @@ - (void)peer:(DSPeer *)peer relayedTransaction:(DSTransaction *)transaction inBl addedNewAccount = TRUE; // New account was created dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSAccountNewAccountFromTransactionNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSChainManagerNotificationWalletKey: account.wallet, DSChainManagerNotificationAccountKey: account}]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSAccountNewAccountFromTransactionNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSChainManagerNotificationWalletKey: account.wallet, + DSChainManagerNotificationAccountKey: account + }]; }); } else { // This means we were not authenticated, we should post a notification dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSAccountNewAccountShouldBeAddedFromTransactionNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSChainManagerNotificationWalletKey: account.wallet}]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSAccountNewAccountShouldBeAddedFromTransactionNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSChainManagerNotificationWalletKey: account.wallet + }]; }); } break; @@ -1358,10 +1407,10 @@ - (void)peer:(DSPeer *)peer relayedTransaction:(DSTransaction *)transaction inBl // keep track of how many peers have or relay a tx, this indicates how likely the tx is to confirm if (callback || !peer || (!syncing && ![self.txRelays[hash] containsObject:peer])) { - if (!self.txRelays[hash]) self.txRelays[hash] = [NSMutableSet set]; - if (peer) { + if (!self.txRelays[hash]) + self.txRelays[hash] = [NSMutableSet set]; + if (peer) [self.txRelays[hash] addObject:peer]; - } if (callback) [self.publishedCallback removeObjectForKey:hash]; for (DSAccount *account in accountsAcceptingTransaction) { @@ -1369,8 +1418,8 @@ - (void)peer:(DSPeer *)peer relayedTransaction:(DSTransaction *)transaction inBl [account transactionForHash:transaction.txHash].blockHeight == TX_UNCONFIRMED && [account transactionForHash:transaction.txHash].timestamp == 0) { [account setBlockHeight:TX_UNCONFIRMED - andTimestamp:[NSDate timeIntervalSince1970] - forTransactionHashes:@[hash]]; // set timestamp when tx is verified + andTimestamp:[NSDate timeIntervalSince1970] + forTransactionHashes:@[hash]]; // set timestamp when tx is verified } } @@ -1383,11 +1432,21 @@ - (void)peer:(DSPeer *)peer relayedTransaction:(DSTransaction *)transaction inBl dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSTransactionManagerNotificationTransactionKey: transaction, - DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(YES)}}]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionReceivedNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSWalletBalanceDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction, + DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(YES)} + }]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionReceivedNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain + }]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSWalletBalanceDidChangeNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain + }]; if (callback) callback(nil); }); @@ -1396,26 +1455,24 @@ - (void)peer:(DSPeer *)peer relayedTransaction:(DSTransaction *)transaction inBl dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSTransactionManagerNotificationTransactionKey: transaction, - DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(YES)}}]; + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction, + DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(YES)}}]; [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionReceivedNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; }); } [self.nonFalsePositiveTransactions addObject:hash]; - if (peer) { + if (peer) [self.txRequests[hash] removeObject:peer]; - } - - - if (block && [transaction isKindOfClass:[DSCreditFundingTransaction class]] && blockchainIdentity && isNewBlockchainIdentity) { - NSTimeInterval walletCreationTime = [blockchainIdentity.wallet walletCreationTime]; - if ((walletCreationTime == BIP39_WALLET_UNKNOWN_CREATION_TIME || walletCreationTime == BIP39_CREATION_TIME) && blockchainIdentity.wallet.defaultBlockchainIdentity == blockchainIdentity) { - [blockchainIdentity.wallet setGuessedWalletCreationTime:self.chain.lastSyncBlockTimestamp - HOUR_TIME_INTERVAL - (DAY_TIME_INTERVAL / arc4random() % DAY_TIME_INTERVAL)]; - } - [self.identitiesManager checkCreditFundingTransactionForPossibleNewIdentity:(DSCreditFundingTransaction *)transaction]; + if (block && [transaction isKindOfClass:[DSAssetLockTransaction class]] && isNewIdentity) { +// NSTimeInterval walletCreationTime = [identity.wallet walletCreationTime]; +// if ((walletCreationTime == BIP39_WALLET_UNKNOWN_CREATION_TIME || walletCreationTime == BIP39_CREATION_TIME) && identity.wallet.defaultIdentity == identity) { +// [identity.wallet setGuessedWalletCreationTime:self.chain.lastSyncBlockTimestamp - HOUR_TIME_INTERVAL - (DAY_TIME_INTERVAL / arc4random() % DAY_TIME_INTERVAL)]; +// } +// [self.identitiesManager checkAssetLockTransactionForPossibleNewIdentity:(DSAssetLockTransaction *)transaction]; [self destroyTransactionsBloomFilter]; //We want to destroy it temporarily, while we wait for L2, no matter what the block should not be saved and needs to be refetched } else if (addedNewAccount) { [self destroyTransactionsBloomFilter]; @@ -1448,9 +1505,11 @@ - (void)peer:(DSPeer *)peer rejectedTransaction:(UInt256)txHash withCode:(uint8_ dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSTransactionManagerNotificationTransactionKey: transaction, - DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(NO)}}]; + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction, + DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationTransactionAcceptedStatusKey: @(NO)} + }]; [[NSNotificationCenter defaultCenter] postNotificationName:DSWalletBalanceDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; #if TARGET_OS_IOS #if DEBUG @@ -1504,8 +1563,8 @@ - (void)peer:(DSPeer *)peer relayedInstantSendTransactionLock:(DSInstantSendTran //NSValue *transactionHashValue = uint256_obj(instantSendTransactionLock.transactionHash); DSTransaction *transaction = nil; DSWallet *wallet = nil; - DSAccount *account = [self.chain firstAccountForTransactionHash:instantSendTransactionLock.transactionHash transaction:&transaction wallet:&wallet]; - + NSData *transactionHashData = instantSendTransactionLock.transactionHashData; + DSAccount *account = [self.chain firstAccountForTransactionHash:transactionHashData.UInt256 transaction:&transaction wallet:&wallet]; if (account && transaction && transaction.instantSendReceived) { return; //no point to retrieve the instant send lock if we already have it } @@ -1513,12 +1572,11 @@ - (void)peer:(DSPeer *)peer relayedInstantSendTransactionLock:(DSInstantSendTran BOOL verified = [instantSendTransactionLock verifySignature]; #if DEBUG - DSLogPrivate(@"[%@: %@:%d] relayed instant send transaction lock %@ %@", self.chain.name, peer.host, peer.port, verified ? @"Verified" : @"Not Verified", uint256_reverse_hex(instantSendTransactionLock.transactionHash)); + DSLogPrivate(@"[%@: %@:%d] relayed instant send transaction lock %@ %@", self.chain.name, peer.host, peer.port, verified ? @"Verified" : @"Not Verified", transactionHashData.hexString); #else DSLog(@"[%@: %@:%d] relayed instant send transaction lock %@ %@", self.chain.name, peer.host, peer.port, verified ? @"Verified" : @"Not Verified", @""); #endif - if (account && transaction) { [transaction setInstantSendReceivedWithInstantSendLock:instantSendTransactionLock]; [instantSendTransactionLock saveInitial]; @@ -1530,12 +1588,12 @@ - (void)peer:(DSPeer *)peer relayedInstantSendTransactionLock:(DSInstantSendTran DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationInstantSendTransactionLockKey: instantSendTransactionLock, DSTransactionManagerNotificationInstantSendTransactionLockVerifiedKey: @(verified)}}]; }); } else { - [self.instantSendLocksWaitingForTransactions setObject:instantSendTransactionLock forKey:uint256_data(instantSendTransactionLock.transactionHash)]; + [self.instantSendLocksWaitingForTransactions setObject:instantSendTransactionLock forKey:transactionHashData]; } - if (!verified && !instantSendTransactionLock.intendedQuorum) { + if (!verified /*&& !instantSendTransactionLock.intendedQuorumPublicKey*/) { //the quorum hasn't been retrieved yet - [self.instantSendLocksWaitingForQuorums setObject:instantSendTransactionLock forKey:uint256_data(instantSendTransactionLock.transactionHash)]; + [self.instantSendLocksWaitingForQuorums setObject:instantSendTransactionLock forKey:transactionHashData]; } } @@ -1545,47 +1603,41 @@ - (void)checkInstantSendLocksWaitingForQuorums { if (self.instantSendLocksWaitingForTransactions[transactionHashData]) continue; DSInstantSendTransactionLock *instantSendTransactionLock = self.instantSendLocksWaitingForQuorums[transactionHashData]; BOOL verified = [instantSendTransactionLock verifySignature]; + NSData *isTransactionHashData = instantSendTransactionLock.transactionHashData; if (verified) { DSLogPrivate(@"[%@] Verified %@", self.chain.name, instantSendTransactionLock); [instantSendTransactionLock saveSignatureValid]; DSTransaction *transaction = nil; DSWallet *wallet = nil; - DSAccount *account = [self.chain firstAccountForTransactionHash:instantSendTransactionLock.transactionHash transaction:&transaction wallet:&wallet]; + + DSAccount *account = [self.chain firstAccountForTransactionHash:isTransactionHashData.UInt256 transaction:&transaction wallet:&wallet]; if (account && transaction) { [transaction setInstantSendReceivedWithInstantSendLock:instantSendTransactionLock]; } - [self.instantSendLocksWaitingForQuorums removeObjectForKey:uint256_data(instantSendTransactionLock.transactionHash)]; + [self.instantSendLocksWaitingForQuorums removeObjectForKey:isTransactionHashData]; if (account && transaction) { dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:DSTransactionManagerTransactionStatusDidChangeNotification object:nil - userInfo:@{DSChainManagerNotificationChainKey: self.chain, - DSTransactionManagerNotificationTransactionKey: transaction, - DSTransactionManagerNotificationTransactionChangesKey: @{DSTransactionManagerNotificationInstantSendTransactionLockKey: instantSendTransactionLock, DSTransactionManagerNotificationInstantSendTransactionLockVerifiedKey: @(verified)}}]; + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSTransactionManagerNotificationTransactionKey: transaction, + DSTransactionManagerNotificationTransactionChangesKey: @{ + DSTransactionManagerNotificationInstantSendTransactionLockKey: instantSendTransactionLock, + DSTransactionManagerNotificationInstantSendTransactionLockVerifiedKey: @(verified) + } + }]; }); } } else { DSTransaction *transaction = nil; DSWallet *wallet = nil; - DSAccount *account = [self.chain firstAccountForTransactionHash:instantSendTransactionLock.transactionHash transaction:&transaction wallet:&wallet]; - + DSAccount *account = [self.chain firstAccountForTransactionHash:isTransactionHashData.UInt256 transaction:&transaction wallet:&wallet]; // Either there is no account or no transaction that means the transaction was not meant for our wallet or the transaction is confirmed // Which means the instant send lock no longer needs verification. - if (!account || !transaction || transaction.confirmed) { - [self.instantSendLocksWaitingForQuorums removeObjectForKey:uint256_data(instantSendTransactionLock.transactionHash)]; - } -#if DEBUG - DSMasternodeList *masternodeList = nil; - DSQuorumEntry *quorum = [instantSendTransactionLock findSigningQuorumReturnMasternodeList:&masternodeList]; - if (quorum && masternodeList) { - NSArray *quorumEntries = [masternodeList quorumEntriesRankedForInstantSendRequestID:[instantSendTransactionLock requestID]]; - NSUInteger index = [quorumEntries indexOfObject:quorum]; - DSLog(@"[%@] Quorum %@ found at index %lu for masternodeList at height %lu", self.chain.name, quorum, (unsigned long)index, (unsigned long)masternodeList.height); - DSLog(@"[%@] Quorum entries are %@", self.chain.name, quorumEntries); - } - DSLog(@"[%@] Could not verify %@", self.chain.name, instantSendTransactionLock); -#endif + if (!account || !transaction || transaction.confirmed) + [self.instantSendLocksWaitingForQuorums removeObjectForKey:isTransactionHashData]; } } } @@ -1599,7 +1651,7 @@ - (void)peer:(DSPeer *)peer relayedHeader:(DSMerkleBlock *)block { if (!self.chain.needsInitialTerminalHeadersSync && (self.chain.earliestWalletCreationTime < block.timestamp + DAY_TIME_INTERVAL * 2) && !self.chainManager.chainSynchronizationFingerprint) { - DSLog(@"[%@: %@:%d] ignoring header %@", self.chain.name, peer.host, peer.port, uint256_hex(block.blockHash)); + DSLog(@"[%@: %@:%d] ignoring header %@ (%u %fl %@)", self.chain.name, peer.host, peer.port, uint256_hex(block.blockHash), self.chain.needsInitialTerminalHeadersSync, self.chain.earliestWalletCreationTime, self.chainManager.chainSynchronizationFingerprint); return; } @@ -1702,10 +1754,13 @@ - (void)peer:(DSPeer *)peer relayedTooManyOrphanBlocks:(NSUInteger)orphanBlockCo - (void)peer:(DSPeer *)peer relayedChainLock:(DSChainLock *)chainLock { BOOL verified = [chainLock verifySignature]; + UInt256 clBlockHash = chainLock.blockHash; + UInt256 clBlockHashRev = uint256_reverse(clBlockHash); + DSLog(@"[%@: %@:%d] relayed chain lock %@", self.chain.name, peer.host, peer.port, uint256_hex(clBlockHash)); - DSLog(@"[%@: %@:%d] relayed chain lock %@", self.chain.name, peer.host, peer.port, uint256_reverse_hex(chainLock.blockHash)); - - DSMerkleBlock *block = [self.chain blockForBlockHash:chainLock.blockHash]; + DSMerkleBlock *block = [self.chain blockForBlockHash:clBlockHash]; + if (!block) + block = [self.chain blockForBlockHash:clBlockHashRev]; if (block) { [self.chain addChainLock:chainLock]; @@ -1715,12 +1770,13 @@ - (void)peer:(DSPeer *)peer relayedChainLock:(DSChainLock *)chainLock { [[NSNotificationCenter defaultCenter] postNotificationName:DSChainBlockWasLockedNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSChainNotificationBlockKey: block}]; }); } else { - [self.chainLocksWaitingForMerkleBlocks setObject:chainLock forKey:uint256_data(chainLock.blockHash)]; + DSLog(@"[%@: %@:%d] no block for chain lock %@", self.chain.name, peer.host, peer.port, uint256_hex(clBlockHash)); + [self.chainLocksWaitingForMerkleBlocks setObject:chainLock forKey:uint256_data(clBlockHash)]; } - if (!verified && !chainLock.intendedQuorum) { + if (!verified /*&& !chainLock.intendedQuorumPublicKey*/) { //the quorum hasn't been retrieved yet - [self.chainLocksWaitingForQuorums setObject:chainLock forKey:uint256_data(chainLock.blockHash)]; + [self.chainLocksWaitingForQuorums setObject:chainLock forKey:uint256_data(clBlockHash)]; } } @@ -1733,23 +1789,28 @@ - (void)checkChainLocksWaitingForQuorums { if (verified) { DSLog(@"[%@] Verified %@", self.chain.name, chainLock); [chainLock saveSignatureValid]; - DSMerkleBlock *block = [self.chain blockForBlockHash:chainLock.blockHash]; + DSMerkleBlock *block = [self.chain blockForBlockHash:chainLock.blockHashData.UInt256]; [self.chainLocksWaitingForQuorums removeObjectForKey:chainLockHashData]; dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSChainBlockWasLockedNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSChainNotificationBlockKey: block}]; + [[NSNotificationCenter defaultCenter] postNotificationName:DSChainBlockWasLockedNotification + object:nil + userInfo:@{ + DSChainManagerNotificationChainKey: self.chain, + DSChainNotificationBlockKey: block + }]; }); } else { -#if DEBUG_CHAIN_LOCKS_WAITING_FOR_QUORUMS - DSMasternodeList *masternodeList = nil; - DSQuorumEntry *quorum = [chainLock findSigningQuorumReturnMasternodeList:&masternodeList]; - if (quorum && masternodeList) { - NSArray *quorumEntries = [masternodeList quorumEntriesRankedForInstantSendRequestID:[chainLock requestID]]; - NSUInteger index = [quorumEntries indexOfObject:quorum]; - DSLog(@"[%@] Quorum %@ found at index %lu for masternodeList at height %lu", self.chain.name, quorum, (unsigned long)index, (unsigned long)masternodeList.height); - DSLog(@"[%@] Quorum entries are %@", self.chain.name, quorumEntries); - } - DSLog(@"[%@] Could not verify %@", self.chain.name, chainLock); -#endif +//#if DEBUG_CHAIN_LOCKS_WAITING_FOR_QUORUMS +// DSMasternodeList *masternodeList = nil; +// DSQuorumEntry *quorum = [chainLock findSigningQuorumReturnMasternodeList:&masternodeList]; +// if (quorum && masternodeList) { +// NSArray *quorumEntries = [masternodeList quorumEntriesRankedForInstantSendRequestID:[chainLock requestID]]; +// NSUInteger index = [quorumEntries indexOfObject:quorum]; +// DSLog(@"[%@] Quorum %@ found at index %lu for masternodeList at height %lu", self.chain.name, quorum, (unsigned long)index, (unsigned long)masternodeList.height); +// DSLog(@"[%@] Quorum entries are %@", self.chain.name, quorumEntries); +// } +// DSLog(@"[%@] Could not verify %@", self.chain.name, chainLock); +//#endif } } } diff --git a/DashSync/shared/Models/Managers/Service Managers/Auth/DSAuthenticationManager.m b/DashSync/shared/Models/Managers/Service Managers/Auth/DSAuthenticationManager.m index 4fd1e78a5..0e4ec65ed 100644 --- a/DashSync/shared/Models/Managers/Service Managers/Auth/DSAuthenticationManager.m +++ b/DashSync/shared/Models/Managers/Service Managers/Auth/DSAuthenticationManager.m @@ -28,7 +28,9 @@ #import "DSAccount.h" #import "DSBIP39Mnemonic.h" #import "DSBiometricsAuthenticator.h" +#import "DSChain+Checkpoint.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainsManager.h" #import "DSCheckpoint.h" #import "DSDerivationPath.h" @@ -545,7 +547,7 @@ - (void)userLockedOut { if (failCount < MAX_FAIL_COUNT) { NSTimeInterval wait = [self lockoutWaitTime]; NSString *waitString = [NSString waitTimeFromNow:wait]; - message = [NSString stringWithFormat:DSLocalizedString(@"Try again in %@", nil), waitString]; + message = DSLocalizedFormat(@"Try again in %@", nil, waitString); } else { message = DSLocalizedString(@"No attempts remaining", nil); } @@ -871,9 +873,7 @@ - (void)performAuthenticationPrecheck:(void (^)(BOOL shouldContinueAuthenticatio return; } else { //no longer locked out, give the user a try - attemptsMessage = [NSString localizedStringWithFormat: - DSLocalizedString(@"%ld attempt(s) remaining", @"#bc-ignore!"), - (long)(MAX_FAIL_COUNT - failCount)]; + attemptsMessage = DSLocalizedFormat(@"%ld attempt(s) remaining", @"#bc-ignore!", (long)(MAX_FAIL_COUNT - failCount)); } } diff --git a/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.h b/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.h index 73edc5492..699a9d506 100644 --- a/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.h +++ b/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.h @@ -28,6 +28,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)blockForBlockHash:(UInt256)blockHash onChain:(DSChain *)chain completion:(void (^)(DSBlock *_Nullable block, NSError *_Nullable error))completion; +- (void)blockForBlockHeight:(uint32_t)blockHeight onChain:(DSChain *)chain + completion:(void (^)(DSBlock *_Nullable block, NSError *_Nullable error))completion; - (void)queryInsightForTransactionWithHash:(UInt256)transactionHash onChain:(DSChain *)chain completion:(void (^)(DSTransaction *transaction, NSError *error))completion; diff --git a/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.m b/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.m index 77ec21af0..a219f6996 100644 --- a/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.m +++ b/DashSync/shared/Models/Managers/Service Managers/DSInsightManager.m @@ -8,6 +8,7 @@ #import "DSInsightManager.h" #import "DSBlock.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSMerkleBlock.h" #import "DSTransactionFactory.h" #import "NSData+Dash.h" @@ -106,6 +107,48 @@ - (void)blockForBlockHash:(UInt256)blockHash onChain:(DSChain *)chain completion NSString *insightURL = [chain isMainnet] ? INSIGHT_URL : TESTNET_INSIGHT_URL; [self queryInsight:insightURL forBlockWithHash:reversedBlockHash onChain:chain completion:completion]; } +- (void)blockForBlockHeight:(uint32_t)blockHeight onChain:(DSChain *)chain completion:(void (^)(DSBlock *block, NSError *error))completion { + NSAssert(blockHeight != UINT32_MAX, @"blockHeight must be set"); + NSParameterAssert(chain); + NSString *insightURL = [chain isMainnet] ? INSIGHT_URL : TESTNET_INSIGHT_URL; + NSParameterAssert(insightURL); + NSString *path = [[insightURL stringByAppendingPathComponent:BLOCK_PATH] stringByAppendingPathComponent:[@(blockHeight) stringValue]]; + NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:path] + cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:20.0]; + req.HTTPMethod = @"GET"; + DSLogPrivate(@"%@ GET: %@", req.URL.absoluteString, + [[NSString alloc] initWithData:req.HTTPBody + encoding:NSUTF8StringEncoding]); + + [[[NSURLSession sharedSession] dataTaskWithRequest:req + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + completion(nil, error); + return; + } + + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + + if (error) { + DSLogPrivate(@"Error decoding response (%@) %@", req.URL.absoluteString, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); + return; + } + NSNumber *version = json[@"version"]; + NSData *blockHash = [json[@"hash"] hexToData]; + NSData *previousBlockHash = [json[@"previousblockhash"] hexToData]; + NSData *merkleRoot = [json[@"merkleroot"] hexToData]; + NSNumber *timestamp = json[@"time"]; + NSString *targetString = json[@"bits"]; + NSData *chainWork = [json[@"chainwork"] hexToData]; + NSNumber *height = json[@"height"]; + DSBlock *block = [[DSBlock alloc] initWithVersion:[version unsignedIntValue] blockHash:blockHash.reverse.UInt256 prevBlock:previousBlockHash.reverse.UInt256 timestamp:timestamp.unsignedIntValue merkleRoot:merkleRoot.reverse.UInt256 target:[targetString.hexToData UInt32AtOffset:0] chainWork:chainWork.reverse.UInt256 height:height.unsignedIntValue onChain:chain]; + + completion(block, nil); + }] resume]; + +} - (void)queryInsight:(NSString *)insightURL forBlockWithHash:(UInt256)blockHash onChain:(DSChain *)chain completion:(void (^)(DSBlock *block, NSError *error))completion { NSParameterAssert(insightURL); @@ -130,8 +173,7 @@ - (void)queryInsight:(NSString *)insightURL forBlockWithHash:(UInt256)blockHash if (error) { DSLogPrivate(@"Error decoding response %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - completion(nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), - req.URL.host]]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); return; } NSNumber *version = json[@"version"]; @@ -148,6 +190,8 @@ - (void)queryInsight:(NSString *)insightURL forBlockWithHash:(UInt256)blockHash }] resume]; } + + - (void)queryInsightForTransactionWithHash:(UInt256)transactionHash onChain:(DSChain *)chain completion:(void (^)(DSTransaction *transaction, NSError *error))completion { NSParameterAssert(chain); @@ -193,8 +237,7 @@ - (void)queryInsight:(NSString *)insightURL forTransactionWithHash:(UInt256)tran if (error) { DSLogPrivate(@"Error decoding response %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - completion(nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), - req.URL.host]]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); return; } NSString *rawTxString = json[@"rawtx"]; @@ -232,8 +275,7 @@ - (void)findExistingAddresses:(NSArray *)addresses forInsightURL:(NSString *)ins if (error || ![json isKindOfClass:[NSDictionary class]] || ![json objectForKey:@"items"]) { DSLogPrivate(@"Error decoding response %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - completion(nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), - req.URL.host]]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); return; } NSMutableSet *existingAddresses = [NSMutableSet set]; @@ -289,8 +331,7 @@ - (void)utxos:(NSString *)insightURL forAddresses:(NSArray *)addresses if (error || ![json isKindOfClass:[NSArray class]]) { DSLogPrivate(@"Error decoding response %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - completion(nil, nil, nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), - req.URL.host]]); + completion(nil, nil, nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); return; } @@ -312,8 +353,7 @@ - (void)utxos:(NSString *)insightURL forAddresses:(NSArray *)addresses ![utxo[@"scriptPubKey"] isKindOfClass:[NSString class]] || ![utxo[@"scriptPubKey"] hexToData] || (![utxo[@"duffs"] isKindOfClass:[NSNumber class]] && ![utxo[@"satoshis"] isKindOfClass:[NSNumber class]] && !amount)) { - completion(nil, nil, nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), - req.URL.host]]); + completion(nil, nil, nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); return; } diff --git a/DashSync/shared/Models/Managers/Service Managers/DSOptionsManager.h b/DashSync/shared/Models/Managers/Service Managers/DSOptionsManager.h index df52c504c..b8aa796c6 100644 --- a/DashSync/shared/Models/Managers/Service Managers/DSOptionsManager.h +++ b/DashSync/shared/Models/Managers/Service Managers/DSOptionsManager.h @@ -23,11 +23,11 @@ typedef NS_ENUM(NSUInteger, DSSyncType) DSSyncType_GovernanceVotes = 1 << 5, DSSyncType_GovernanceVoting = DSSyncType_Governance | DSSyncType_MasternodeList, DSSyncType_Sporks = 1 << 6, - DSSyncType_BlockchainIdentities = 1 << 7, + DSSyncType_Identities = 1 << 7, DSSyncType_DPNS = 1 << 8, DSSyncType_Dashpay = 1 << 9, DSSyncType_MultiAccountAutoDiscovery = 1 << 10, - DSSyncType_Default = DSSyncType_SPV | DSSyncType_Mempools | DSSyncType_VerifiedMasternodeList | DSSyncType_Sporks | DSSyncType_BlockchainIdentities | DSSyncType_DPNS | DSSyncType_Dashpay | DSSyncType_MultiAccountAutoDiscovery, + DSSyncType_Default = DSSyncType_SPV | DSSyncType_Mempools | DSSyncType_VerifiedMasternodeList | DSSyncType_Sporks | DSSyncType_Identities | DSSyncType_DPNS | DSSyncType_Dashpay | DSSyncType_MultiAccountAutoDiscovery, DSSyncType_NeedsWalletSyncType = DSSyncType_BaseSPV | DSSyncType_FullBlocks, DSSyncType_GetsNewBlocks = DSSyncType_SPV | DSSyncType_FullBlocks, }; diff --git a/DashSync/shared/Models/Managers/Service Managers/DSPriceManager.m b/DashSync/shared/Models/Managers/Service Managers/DSPriceManager.m index badf8fbb4..0cc2cc920 100644 --- a/DashSync/shared/Models/Managers/Service Managers/DSPriceManager.m +++ b/DashSync/shared/Models/Managers/Service Managers/DSPriceManager.m @@ -52,7 +52,7 @@ #import "NSData+Dash.h" #import "NSDate+Utils.h" #import "NSString+Dash.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #define TICKER_REFRESH_TIME 60.0 #define VOLATILE_RATES_CUTTOFF_PERIOD 7 * 24 * 60 * 60 // 7 Days @@ -292,7 +292,7 @@ - (void)updatePrices { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSNumber *lastPrice = strongSelf.pricesByCode[strongSelf.localCurrencyCode].price; NSInteger lastRetrievalTime = [defaults integerForKey:LAST_RATES_RETRIEVAL_TIME]; - NSInteger now = [[NSDate date] timeIntervalSince1970]; + NSInteger now = [NSDate timeIntervalSince1970]; [defaults setObject:plainPricesByCode forKey:PRICESBYCODE_KEY]; [defaults setInteger:now forKey:LAST_RATES_RETRIEVAL_TIME]; diff --git a/DashSync/shared/Models/Managers/Service Managers/DSVersionManager.m b/DashSync/shared/Models/Managers/Service Managers/DSVersionManager.m index 9c0810762..3ac7eff8b 100644 --- a/DashSync/shared/Models/Managers/Service Managers/DSVersionManager.m +++ b/DashSync/shared/Models/Managers/Service Managers/DSVersionManager.m @@ -9,6 +9,7 @@ #import "DSAccount.h" #import "DSAuthenticationManager+UpdateSecureTime.h" #import "DSBIP39Mnemonic.h" +#import "DSChain+Wallet.h" #import "DSChainManager.h" #import "DSChainsManager.h" #import "DSDerivationPathFactory.h" diff --git a/DashSync/shared/Models/Masternode/DSLocalMasternode.h b/DashSync/shared/Models/Masternode/DSLocalMasternode.h index 447c5b9fa..61c0ac00c 100644 --- a/DashSync/shared/Models/Masternode/DSLocalMasternode.h +++ b/DashSync/shared/Models/Masternode/DSLocalMasternode.h @@ -6,7 +6,7 @@ // #import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "DSKeyManager.h" #import NS_ASSUME_NONNULL_BEGIN @@ -88,23 +88,23 @@ typedef NS_ENUM(NSUInteger, DSLocalMasternodeStatus) - (void)saveInContext:(NSManagedObjectContext *)context; // BLS -- (OpaqueKey *_Nullable)operatorKeyFromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *_Nullable)operatorKeyFromSeed:(NSData *)seed; // ECDSA -- (OpaqueKey *_Nullable)ownerKeyFromSeed:(NSData *)seed; -- (OpaqueKey *_Nullable)votingKeyFromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *_Nullable)ownerKeyFromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *_Nullable)votingKeyFromSeed:(NSData *)seed; // ED25519 -- (OpaqueKey *_Nullable)platformNodeKeyFromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *_Nullable)platformNodeKeyFromSeed:(NSData *)seed; - (NSString *)operatorKeyStringFromSeed:(NSData *)seed; - (NSString *_Nullable)ownerKeyStringFromSeed:(NSData *)seed; - (NSString *_Nullable)votingKeyStringFromSeed:(NSData *)seed; - (NSString *_Nullable)platformNodeKeyStringFromSeed:(NSData *)seed; -- (BOOL)forceOperatorPublicKey:(OpaqueKey *)operatorPublicKey; -- (BOOL)forceOwnerPrivateKey:(OpaqueKey *)ownerPrivateKey; +- (BOOL)forceOperatorPublicKey:(DOpaqueKey *)operatorPublicKey; +- (BOOL)forceOwnerPrivateKey:(DOpaqueKey *)ownerPrivateKey; //the voting key can either be private or public key -- (BOOL)forceVotingKey:(OpaqueKey *)votingKey; -- (BOOL)forcePlatformNodeKey:(OpaqueKey *)platformNodeKey; +- (BOOL)forceVotingKey:(DOpaqueKey *)votingKey; +- (BOOL)forcePlatformNodeKey:(DOpaqueKey *)platformNodeKey; @end diff --git a/DashSync/shared/Models/Masternode/DSLocalMasternode.m b/DashSync/shared/Models/Masternode/DSLocalMasternode.m index 695931daa..bb7a1070b 100644 --- a/DashSync/shared/Models/Masternode/DSLocalMasternode.m +++ b/DashSync/shared/Models/Masternode/DSLocalMasternode.m @@ -9,9 +9,12 @@ #import "DSAccount.h" #import "DSAuthenticationKeysDerivationPath.h" #import "DSAuthenticationManager.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainEntity+CoreDataProperties.h" #import "DSChainManager.h" +#import "DSGapLimit.h" #import "DSLocalMasternodeEntity+CoreDataClass.h" #import "DSMasternodeHoldingsDerivationPath.h" #import "DSMasternodeManager.h" @@ -56,7 +59,7 @@ @interface DSLocalMasternode () @property (nonatomic, strong) NSMutableArray *providerUpdateServiceTransactions; @property (nonatomic, strong) NSMutableArray *providerUpdateRevocationTransactions; -@property (nonatomic, assign, readonly) OpaqueKey *ownerPrivateKey; +@property (nonatomic, assign, readonly) DOpaqueKey *ownerPrivateKey; @end @@ -179,27 +182,27 @@ - (void)registerInAssociatedWallets { [self.platformNodeKeysWallet registerPlatformNode:self]; } -- (BOOL)forceOperatorPublicKey:(OpaqueKey *)operatorPublicKey { +- (BOOL)forceOperatorPublicKey:(DOpaqueKey *)operatorPublicKey { if (self.operatorWalletIndex != UINT32_MAX) return NO; [self.ownerKeysWallet registerMasternodeOperator:self withOperatorPublicKey:operatorPublicKey]; return YES; } -- (BOOL)forceOwnerPrivateKey:(OpaqueKey *)ownerPrivateKey { +- (BOOL)forceOwnerPrivateKey:(DOpaqueKey *)ownerPrivateKey { if (self.ownerWalletIndex != UINT32_MAX) return NO; - if (!key_ecdsa_has_private_key(ownerPrivateKey->ecdsa)) return NO; + if (!DOpaqueKeyHasPrivateKey(ownerPrivateKey)) return NO; [self.ownerKeysWallet registerMasternodeOwner:self withOwnerPrivateKey:ownerPrivateKey]; return YES; } //the voting key can either be private or public key -- (BOOL)forceVotingKey:(OpaqueKey *)votingKey { +- (BOOL)forceVotingKey:(DOpaqueKey *)votingKey { if (self.votingWalletIndex != UINT32_MAX) return NO; [self.ownerKeysWallet registerMasternodeVoter:self withVotingKey:votingKey]; return YES; } -- (BOOL)forcePlatformNodeKey:(OpaqueKey *)platformNodeKey { +- (BOOL)forcePlatformNodeKey:(DOpaqueKey *)platformNodeKey { if (self.platformNodeWalletIndex != UINT32_MAX) return NO; [self.platformNodeKeysWallet registerPlatformNode:self withKey:platformNodeKey]; return YES; @@ -293,67 +296,71 @@ - (NSString *)operatorPayoutAddress { // MARK: - Getting key from seed -- (OpaqueKey *)operatorKeyFromSeed:(NSData *)seed { - DSAuthenticationKeysDerivationPath *providerOperatorKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self.operatorKeysWallet]; - return [providerOperatorKeysDerivationPath privateKeyForHash160:[[NSData dataWithUInt384:self.providerRegistrationTransaction.operatorKey] hash160] fromSeed:seed]; +- (DMaybeOpaqueKey *)operatorKeyFromSeed:(NSData *)seed { + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self.operatorKeysWallet]; + return [path privateKeyForHash160:[[NSData dataWithUInt384:self.providerRegistrationTransaction.operatorKey] hash160] fromSeed:seed]; } -- (OpaqueKey *)ownerKeyFromSeed:(NSData *)seed { +- (DMaybeOpaqueKey *)ownerKeyFromSeed:(NSData *)seed { if (!self.ownerKeysWallet || !seed) { return nil; } - DSAuthenticationKeysDerivationPath *providerOwnerKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self.ownerKeysWallet]; - return [providerOwnerKeysDerivationPath privateKeyForHash160:self.providerRegistrationTransaction.ownerKeyHash fromSeed:seed]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self.ownerKeysWallet]; + return [path privateKeyForHash160:self.providerRegistrationTransaction.ownerKeyHash fromSeed:seed]; } -- (OpaqueKey *)votingKeyFromSeed:(NSData *)seed { +- (DMaybeOpaqueKey *)votingKeyFromSeed:(NSData *)seed { if (!self.votingKeysWallet || !seed) { return nil; } - DSAuthenticationKeysDerivationPath *providerVotingKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self.votingKeysWallet]; - return [providerVotingKeysDerivationPath privateKeyForHash160:self.providerRegistrationTransaction.votingKeyHash fromSeed:seed]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self.votingKeysWallet]; + return [path privateKeyForHash160:self.providerRegistrationTransaction.votingKeyHash fromSeed:seed]; } -- (OpaqueKey *)platformNodeKeyFromSeed:(NSData *)seed { +- (DMaybeOpaqueKey *)platformNodeKeyFromSeed:(NSData *)seed { if (!self.platformNodeKeysWallet || !seed) { return nil; } - DSAuthenticationKeysDerivationPath *platformNodeKeysDerivationPath = [DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self.platformNodeKeysWallet]; - return [platformNodeKeysDerivationPath privateKeyForHash160:self.providerRegistrationTransaction.platformNodeID fromSeed:seed]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self.platformNodeKeysWallet]; + return [path privateKeyForHash160:self.providerRegistrationTransaction.platformNodeID fromSeed:seed]; } // MARK: - Getting key string from seed - (NSString *)operatorKeyStringFromSeed:(NSData *)seed { - OpaqueKey *key = [self operatorKeyFromSeed:seed]; - return [DSKeyManager secretKeyHexString:key]; + DMaybeOpaqueKey *key = [self operatorKeyFromSeed:seed]; + if (!key || !key->ok) return nil; + // TODO: free memory + return [DSKeyManager secretKeyHexString:key->ok]; } - (NSString *)ownerKeyStringFromSeed:(NSData *)seed { - OpaqueKey *key = [self ownerKeyFromSeed:seed]; - if (!key) return nil; - return [DSKeyManager secretKeyHexString:key]; + DMaybeOpaqueKey *key = [self ownerKeyFromSeed:seed]; + if (!key || !key->ok) return nil; + // TODO: free memory + return [DSKeyManager secretKeyHexString:key->ok]; } - (NSString *)votingKeyStringFromSeed:(NSData *)seed { - OpaqueKey *key = [self votingKeyFromSeed:seed]; - if (!key) return nil; - return [DSKeyManager secretKeyHexString:key]; + DMaybeOpaqueKey *key = [self votingKeyFromSeed:seed]; + if (!key || !key->ok) return nil; + // TODO: free memory + return [DSKeyManager secretKeyHexString:key->ok]; } - (NSString *)platformNodeKeyStringFromSeed:(NSData *)seed { - OpaqueKey *key = [self platformNodeKeyFromSeed:seed]; - if (!key) return nil; - // TODO: cleanup - return [DSKeyManager secretKeyHexString:key]; + DMaybeOpaqueKey *key = [self platformNodeKeyFromSeed:seed]; + if (!key || !key->ok) return nil; + // TODO: free memory + return [DSKeyManager secretKeyHexString:key->ok]; } // MARK: - Getting public key data - (NSData *)operatorPublicKeyData { if (self.operatorKeysWallet) { - DSAuthenticationKeysDerivationPath *providerOperatorKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self.operatorKeysWallet]; - return [providerOperatorKeysDerivationPath publicKeyDataForHash160:[[NSData dataWithUInt384:self.providerRegistrationTransaction.operatorKey] hash160]]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self.operatorKeysWallet]; + return [path publicKeyDataForHash160:[[NSData dataWithUInt384:self.providerRegistrationTransaction.operatorKey] hash160]]; } else { return [NSData data]; } @@ -361,8 +368,8 @@ - (NSData *)operatorPublicKeyData { - (NSData *)ownerPublicKeyData { if (self.ownerKeysWallet) { - DSAuthenticationKeysDerivationPath *providerOwnerKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self.ownerKeysWallet]; - return [providerOwnerKeysDerivationPath publicKeyDataForHash160:self.providerRegistrationTransaction.ownerKeyHash]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self.ownerKeysWallet]; + return [path publicKeyDataForHash160:self.providerRegistrationTransaction.ownerKeyHash]; } else { return [NSData data]; } @@ -370,8 +377,8 @@ - (NSData *)ownerPublicKeyData { - (NSData *)votingPublicKeyData { if (self.votingKeysWallet) { - DSAuthenticationKeysDerivationPath *providerVotingKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self.votingKeysWallet]; - return [providerVotingKeysDerivationPath publicKeyDataForHash160:self.providerRegistrationTransaction.votingKeyHash]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self.votingKeysWallet]; + return [path publicKeyDataForHash160:self.providerRegistrationTransaction.votingKeyHash]; } else { return [NSData data]; } @@ -379,8 +386,8 @@ - (NSData *)votingPublicKeyData { - (NSData *)platformNodePublicKeyData { if (self.platformNodeKeysWallet) { - DSAuthenticationKeysDerivationPath *platformNodeKeysDerivationPath = [DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self.platformNodeKeysWallet]; - return [platformNodeKeysDerivationPath publicKeyDataForHash160:self.providerRegistrationTransaction.platformNodeID]; + DSAuthenticationKeysDerivationPath *path = [DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self.platformNodeKeysWallet]; + return [path publicKeyDataForHash160:self.providerRegistrationTransaction.platformNodeID]; } else { return [NSData data]; } @@ -417,7 +424,7 @@ - (void)registrationTransactionFundedByAccount:(DSAccount *)fundingAccount toAdd if (self.status != DSLocalMasternodeStatus_New) return; char s[INET6_ADDRSTRLEN]; NSString *ipAddressString = @(inet_ntop(AF_INET, &self.ipAddress.u32[3], s, sizeof(s))); - NSString *question = [NSString stringWithFormat:DSLocalizedString(@"Are you sure you would like to register a masternode at %@:%d?", nil), ipAddressString, self.port]; + NSString *question = DSLocalizedFormat(@"Are you sure you would like to register a masternode at %@:%d?", nil, ipAddressString, self.port); [[DSAuthenticationManager sharedInstance] seedWithPrompt:question forWallet:fundingAccount.wallet forAmount:MASTERNODE_COST @@ -447,28 +454,28 @@ - (void)registrationTransactionFundedByAccount:(DSAccount *)fundingAccount toAdd } // TODO: how do we know which version we should use: (self.version == 2 /*BLS Basic*/ && self.providerType == 1) // based on protocol_version? - OpaqueKey *platformNodeKey; + DMaybeOpaqueKey *platformNodeKey; if (self.platformNodeWalletIndex == UINT32_MAX) { - self.platformNodeWalletIndex = (uint32_t)[platformNodeKeysDerivationPath firstUnusedIndex]; + self.platformNodeWalletIndex = [platformNodeKeysDerivationPath firstUnusedIndex]; platformNodeKey = [platformNodeKeysDerivationPath firstUnusedPrivateKeyFromSeed:seed]; } else { - platformNodeKey = [platformNodeKeysDerivationPath privateKeyAtIndex:self.platformNodeWalletIndex fromSeed:seed]; + platformNodeKey = [platformNodeKeysDerivationPath privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.platformNodeWalletIndex] fromSeed:seed]; } - UInt256 platformNodeHash = [DSKeyManager publicKeyData:platformNodeKey].SHA256; + UInt256 platformNodeHash = [DSKeyManager publicKeyData:platformNodeKey->ok].SHA256; UInt160 platformNodeID = *(UInt160 *)&platformNodeHash; - OpaqueKey *ownerKey; + DMaybeOpaqueKey *ownerKey; if (self.ownerWalletIndex == UINT32_MAX) { - self.ownerWalletIndex = (uint32_t)[providerOwnerKeysDerivationPath firstUnusedIndex]; + self.ownerWalletIndex = [providerOwnerKeysDerivationPath firstUnusedIndex]; ownerKey = [providerOwnerKeysDerivationPath firstUnusedPrivateKeyFromSeed:seed]; } else { - ownerKey = [providerOwnerKeysDerivationPath privateKeyAtIndex:self.ownerWalletIndex fromSeed:seed]; + ownerKey = [providerOwnerKeysDerivationPath privateKeyAtIndexPath:[NSIndexPath indexPathWithIndex:self.ownerWalletIndex] fromSeed:seed]; } UInt160 votingKeyHash; - UInt160 ownerKeyHash = [DSKeyManager publicKeyData:ownerKey].hash160; + UInt160 ownerKeyHash = [DSKeyManager publicKeyData:ownerKey->ok].hash160; if ([fundingAccount.wallet.chain.chainManager.sporkManager deterministicMasternodeListEnabled]) { DSAuthenticationKeysDerivationPath *providerVotingKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self.votingKeysWallet]; @@ -476,10 +483,10 @@ - (void)registrationTransactionFundedByAccount:(DSAccount *)fundingAccount toAdd [providerVotingKeysDerivationPath generateExtendedPublicKeyFromSeed:seed storeUnderWalletUniqueId:self.votingKeysWallet.uniqueIDString]; } if (self.votingWalletIndex == UINT32_MAX) { - self.votingWalletIndex = (uint32_t)[providerVotingKeysDerivationPath firstUnusedIndex]; + self.votingWalletIndex = [providerVotingKeysDerivationPath firstUnusedIndex]; votingKeyHash = [providerVotingKeysDerivationPath firstUnusedPublicKey].hash160; } else { - votingKeyHash = [providerVotingKeysDerivationPath publicKeyDataAtIndex:self.votingWalletIndex].hash160; + votingKeyHash = [providerVotingKeysDerivationPath publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:self.votingWalletIndex]].hash160; } } else { votingKeyHash = ownerKeyHash; @@ -488,15 +495,15 @@ - (void)registrationTransactionFundedByAccount:(DSAccount *)fundingAccount toAdd UInt384 operatorKey; if (self.operatorWalletIndex == UINT32_MAX) { - self.operatorWalletIndex = (uint32_t)[providerOperatorKeysDerivationPath firstUnusedIndex]; + self.operatorWalletIndex = [providerOperatorKeysDerivationPath firstUnusedIndex]; operatorKey = [providerOperatorKeysDerivationPath firstUnusedPublicKey].UInt384; } else { - operatorKey = [providerOperatorKeysDerivationPath publicKeyDataAtIndex:self.operatorWalletIndex].UInt384; + operatorKey = [providerOperatorKeysDerivationPath publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:self.operatorWalletIndex]].UInt384; } - uint16_t operatorKeyVersion = providerOperatorKeysDerivationPath.signingAlgorithm == KeyKind_BLS ? 1 : 2; + uint16_t operatorKeyVersion = DKeyKindIndex(providerOperatorKeysDerivationPath.signingAlgorithm) == dash_spv_crypto_keys_key_KeyKind_BLS ? 1 : 2; DSProviderRegistrationTransaction *providerRegistrationTransaction = [[DSProviderRegistrationTransaction alloc] initWithProviderRegistrationTransactionVersion:2 type:0 mode:0 collateralOutpoint:collateral ipAddress:self.ipAddress port:self.port ownerKeyHash:ownerKeyHash operatorKey:operatorKey operatorKeyVersion:operatorKeyVersion votingKeyHash:votingKeyHash platformNodeID:platformNodeID operatorReward:0 scriptPayout:script onChain:fundingAccount.wallet.chain]; if (dsutxo_is_zero(collateral)) { - NSString *holdingAddress = [providerFundsDerivationPath receiveAddress]; + NSString *holdingAddress = [providerFundsDerivationPath registerAddressesWithSettings:[DSGapLimit single]].lastObject; NSData *scriptPayout = [DSKeyManager scriptPubKeyForAddress:holdingAddress forChain:self.holdingKeysWallet.chain]; [fundingAccount updateTransaction:providerRegistrationTransaction forAmounts:@[@(MASTERNODE_COST)] toOutputScripts:@[scriptPayout] withFee:YES]; @@ -522,7 +529,7 @@ - (void)updateTransactionFundedByAccount:(DSAccount *)fundingAccount toIPAddress if (self.status != DSLocalMasternodeStatus_Registered) return; char s[INET6_ADDRSTRLEN]; NSString *ipAddressString = @(inet_ntop(AF_INET, &ipAddress.u32[3], s, sizeof(s))); - NSString *question = [NSString stringWithFormat:DSLocalizedString(@"Are you sure you would like to update this masternode to %@:%d?", nil), ipAddressString, self.port]; + NSString *question = DSLocalizedFormat(@"Are you sure you would like to update this masternode to %@:%d?", nil, ipAddressString, self.port); [[DSAuthenticationManager sharedInstance] seedWithPrompt:question forWallet:fundingAccount.wallet forAmount:0 @@ -536,11 +543,12 @@ - (void)updateTransactionFundedByAccount:(DSAccount *)fundingAccount toIPAddress DSAuthenticationKeysDerivationPath *providerOperatorKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self.operatorKeysWallet]; NSAssert(self.providerRegistrationTransaction, @"There must be a providerRegistrationTransaction linked here"); - OpaqueKey *operatorKey = [providerOperatorKeysDerivationPath privateKeyForHash160:[[NSData dataWithUInt384:self.providerRegistrationTransaction.operatorKey] hash160] fromSeed:seed]; + DMaybeOpaqueKey *operatorKey = [providerOperatorKeysDerivationPath privateKeyForHash160:[[NSData dataWithUInt384:self.providerRegistrationTransaction.operatorKey] hash160] fromSeed:seed]; DSProviderUpdateServiceTransaction *providerUpdateServiceTransaction = [[DSProviderUpdateServiceTransaction alloc] initWithProviderUpdateServiceTransactionVersion:1 providerTransactionHash:self.providerRegistrationTransaction.txHash ipAddress:ipAddress port:port scriptPayout:scriptPayout onChain:fundingAccount.wallet.chain]; [fundingAccount updateTransaction:providerUpdateServiceTransaction forAmounts:@[] toOutputScripts:@[] withFee:YES]; - [providerUpdateServiceTransaction signPayloadWithKey:operatorKey]; + [providerUpdateServiceTransaction signPayloadWithKey:operatorKey->ok]; + DMaybeOpaqueKeyDtor(operatorKey); // TODO: what about platformNodeKey? //there is no need to sign the payload here. completion(providerUpdateServiceTransaction); @@ -549,7 +557,7 @@ - (void)updateTransactionFundedByAccount:(DSAccount *)fundingAccount toIPAddress - (void)updateTransactionFundedByAccount:(DSAccount *)fundingAccount changeOperator:(UInt384)operatorKey changeVotingKeyHash:(UInt160)votingKeyHash changePayoutAddress:(NSString *_Nullable)payoutAddress completion:(void (^_Nullable)(DSProviderUpdateRegistrarTransaction *providerUpdateRegistrarTransaction))completion { if (self.status != DSLocalMasternodeStatus_Registered) return; - NSString *question = [NSString stringWithFormat:DSLocalizedString(@"Are you sure you would like to update this masternode to pay to %@?", nil), payoutAddress]; + NSString *question = DSLocalizedFormat(@"Are you sure you would like to update this masternode to pay to %@?", nil, payoutAddress); [[DSAuthenticationManager sharedInstance] seedWithPrompt:question forWallet:fundingAccount.wallet forAmount:0 @@ -562,13 +570,14 @@ - (void)updateTransactionFundedByAccount:(DSAccount *)fundingAccount changeOpera NSData *scriptPayout = payoutAddress == nil ? [NSData data] : [DSKeyManager scriptPubKeyForAddress:payoutAddress forChain:fundingAccount.wallet.chain]; DSAuthenticationKeysDerivationPath *providerOwnerKeysDerivationPath = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self.ownerKeysWallet]; NSAssert(self.providerRegistrationTransaction, @"There must be a providerRegistrationTransaction linked here"); - OpaqueKey *ownerKey = [providerOwnerKeysDerivationPath privateKeyForHash160:self.providerRegistrationTransaction.ownerKeyHash fromSeed:seed]; + DMaybeOpaqueKey *ownerKey = [providerOwnerKeysDerivationPath privateKeyForHash160:self.providerRegistrationTransaction.ownerKeyHash fromSeed:seed]; DSProviderUpdateRegistrarTransaction *providerUpdateRegistrarTransaction = [[DSProviderUpdateRegistrarTransaction alloc] initWithProviderUpdateRegistrarTransactionVersion:1 providerTransactionHash:self.providerRegistrationTransaction.txHash mode:0 operatorKey:operatorKey votingKeyHash:votingKeyHash scriptPayout:scriptPayout onChain:fundingAccount.wallet.chain]; [fundingAccount updateTransaction:providerUpdateRegistrarTransaction forAmounts:@[] toOutputScripts:@[] withFee:YES]; - [providerUpdateRegistrarTransaction signPayloadWithKey:ownerKey]; + [providerUpdateRegistrarTransaction signPayloadWithKey:ownerKey->ok]; + DMaybeOpaqueKeyDtor(ownerKey); //there is no need to sign the payload here. diff --git a/DashSync/shared/Models/Masternode/DSMasternodeList+Mndiff.m b/DashSync/shared/Models/Masternode/DSMasternodeList+Mndiff.m deleted file mode 100644 index 7eb40bdd4..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeList+Mndiff.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "BigIntTypes.h" -#import "DSMasternodeList+Mndiff.h" -#import "DSQuorumEntry+Mndiff.h" -#import "DSSimplifiedMasternodeEntry+Mndiff.h" -#import "NSData+Dash.h" - -@implementation DSMasternodeList (Mndiff) - -+ (instancetype)masternodeListWith:(MasternodeList *)list onChain:(DSChain *)chain { - uintptr_t masternodes_count = list->masternodes_count; - NSDictionary *masternodes = [DSSimplifiedMasternodeEntry simplifiedEntriesWith:list->masternodes count:masternodes_count onChain:chain]; - NSDictionary *> *quorums = [DSQuorumEntry entriesWithMap:list->llmq_type_maps count:list->llmq_type_maps_count onChain:chain]; - UInt256 masternodeMerkleRoot = list->masternode_merkle_root ? *((UInt256 *)list->masternode_merkle_root) : UINT256_ZERO; - UInt256 quorumMerkleRoot = list->llmq_merkle_root ? *((UInt256 *)list->llmq_merkle_root) : UINT256_ZERO; - UInt256 blockHash = *((UInt256 *)list->block_hash); - return [self masternodeListWithSimplifiedMasternodeEntriesDictionary:masternodes - quorumEntriesDictionary:quorums - atBlockHash:blockHash - atBlockHeight:list->known_height - withMasternodeMerkleRootHash:masternodeMerkleRoot - withQuorumMerkleRootHash:quorumMerkleRoot - onChain:chain]; -} - -- (MasternodeList *)ffi_malloc { - NSDictionary *> *quorums = [self quorums]; - NSDictionary *masternodes = [self simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash]; - uintptr_t quorum_type_maps_count = quorums.count; - uintptr_t masternodes_count = masternodes.count; - MasternodeList *masternode_list = malloc(sizeof(MasternodeList)); - LLMQMap **quorum_type_maps = malloc(quorum_type_maps_count * sizeof(LLMQMap *)); - int i = 0; - int j = 0; - for (NSNumber *type in quorums) { - NSDictionary *quorumEntries = quorums[type]; - uintptr_t quorum_maps_count = quorumEntries.count; - LLMQMap *quorums_map = malloc(sizeof(LLMQMap)); - LLMQEntry **quorums_of_type = malloc(quorum_maps_count * sizeof(LLMQEntry *)); - j = 0; - for (NSData *hash in quorumEntries) { - quorums_of_type[j++] = [quorumEntries[hash] ffi_malloc]; - } - quorums_map->llmq_type = (uint8_t)[type unsignedIntegerValue]; - quorums_map->count = quorum_maps_count; - quorums_map->values = quorums_of_type; - quorum_type_maps[i++] = quorums_map; - } - masternode_list->llmq_type_maps = quorum_type_maps; - masternode_list->llmq_type_maps_count = quorum_type_maps_count; - MasternodeEntry **masternodes_values = malloc(masternodes_count * sizeof(MasternodeEntry *)); - i = 0; - for (NSData *hash in masternodes) { - masternodes_values[i++] = [masternodes[hash] ffi_malloc]; - } - masternode_list->masternodes = masternodes_values; - masternode_list->masternodes_count = masternodes_count; - masternode_list->block_hash = uint256_malloc([self blockHash]); - masternode_list->known_height = [self height]; - masternode_list->masternode_merkle_root = uint256_malloc([self masternodeMerkleRoot]); - masternode_list->llmq_merkle_root = uint256_malloc([self quorumMerkleRoot]); - return masternode_list; -} - -+ (void)ffi_free:(MasternodeList *)list { - if (!list) return; - free(list->block_hash); - if (list->masternodes_count > 0) { - for (int i = 0; i < list->masternodes_count; i++) { - [DSSimplifiedMasternodeEntry ffi_free:list->masternodes[i]]; - } - } - if (list->masternodes) - free(list->masternodes); - if (list->llmq_type_maps_count > 0) { - for (int i = 0; i < list->llmq_type_maps_count; i++) { - LLMQMap *map = list->llmq_type_maps[i]; - for (int j = 0; j < map->count; j++) { - [DSQuorumEntry ffi_free:map->values[j]]; - } - if (map->values) - free(map->values); - free(map); - } - } - if (list->llmq_type_maps) - free(list->llmq_type_maps); - if (list->masternode_merkle_root) - free(list->masternode_merkle_root); - if (list->llmq_merkle_root) - free(list->llmq_merkle_root); - free(list); -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeList.h b/DashSync/shared/Models/Masternode/DSMasternodeList.h deleted file mode 100644 index b51a51b1f..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeList.h +++ /dev/null @@ -1,89 +0,0 @@ -// -// DSMasternodeList.h -// DashSync -// -// Created by Sam Westrich on 5/20/19. -// - -#import "BigIntTypes.h" -#import "DSQuorumEntry.h" -#import "dash_shared_core.h" -#import - -#define MASTERNODE_LIST_ADDED_NODES @"MASTERNODE_LIST_ADDED_NODES" -#define MASTERNODE_LIST_ADDED_VALIDITY @"MASTERNODE_LIST_ADDED_VALIDITY" -#define MASTERNODE_LIST_REMOVED_VALIDITY @"MASTERNODE_LIST_REMOVED_VALIDITY" -#define MASTERNODE_LIST_REMOVED_NODES @"MASTERNODE_LIST_REMOVED_NODES" - -NS_ASSUME_NONNULL_BEGIN - -@class DSSimplifiedMasternodeEntry, DSChain, DSQuorumEntry, DSPeer; - -@interface DSMasternodeList : NSObject - -@property (nonatomic, readonly) NSArray *simplifiedMasternodeEntries; -@property (nonatomic, readonly) NSArray *providerTxOrderedHashes; -@property (nonatomic, readonly) NSDictionary *> *quorums; -@property (nonatomic, readonly) UInt256 blockHash; -@property (nonatomic, readonly) uint32_t height; -@property (nonatomic, readonly) UInt256 masternodeMerkleRoot; -@property (nonatomic, readonly) UInt256 quorumMerkleRoot; -@property (nonatomic, readonly) NSUInteger quorumsCount; -@property (nonatomic, readonly) NSUInteger validQuorumsCount; -@property (nonatomic, readonly) uint64_t masternodeCount; -@property (nonatomic, readonly) uint64_t validMasternodeCount; -@property (nonatomic, readonly) DSChain *chain; -@property (nonatomic, readonly) NSArray *reversedRegistrationTransactionHashes; -@property (nonatomic, readonly) NSDictionary *simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash; - -+ (instancetype)masternodeListWithSimplifiedMasternodeEntries:(NSArray *)simplifiedMasternodeEntries quorumEntries:(NSArray *)quorumEntries atBlockHash:(UInt256)blockHash atBlockHeight:(uint32_t)blockHeight withMasternodeMerkleRootHash:(UInt256)masternodeMerkleRootHash withQuorumMerkleRootHash:(UInt256)quorumMerkleRootHash onChain:(DSChain *)chain; -+ (instancetype)masternodeListWithSimplifiedMasternodeEntriesDictionary:(NSDictionary *)simplifiedMasternodeEntries quorumEntriesDictionary:(NSDictionary *> *)quorumEntries atBlockHash:(UInt256)blockHash atBlockHeight:(uint32_t)blockHeight withMasternodeMerkleRootHash:(UInt256)masternodeMerkleRootHash withQuorumMerkleRootHash:(UInt256)quorumMerkleRootHash onChain:(DSChain *)chain; - -- (NSDictionary *)scoreDictionaryForQuorumModifier:(UInt256)quorumModifier atBlockHeight:(uint32_t)blockHeight; -- (NSArray *)scoresForQuorumModifier:(UInt256)quorumModifier atBlockHeight:(uint32_t)blockHeight; - -- (NSUInteger)validQuorumsCountOfType:(LLMQType)type; -- (NSDictionary *)quorumsOfType:(LLMQType)type; -- (NSUInteger)quorumsCountOfType:(LLMQType)type; - -- (NSArray *)validMasternodesForQuorumModifier:(UInt256)quorumModifier quorumCount:(NSUInteger)quorumCount; - -- (NSArray *)allMasternodesForQuorumModifier:(UInt256)quorumModifier quorumCount:(NSUInteger)quorumCount blockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (NSArray *)validMasternodesForQuorumModifier:(UInt256)quorumModifier quorumCount:(NSUInteger)quorumCount blockHeight:(uint32_t)blockHeight; - -- (UInt256)calculateMasternodeMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (NSDictionary *)compare:(DSMasternodeList *)other; -- (NSDictionary *)compare:(DSMasternodeList *)other blockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (NSDictionary *)compareWithPrevious:(DSMasternodeList *)other; -- (NSDictionary *)compareWithPrevious:(DSMasternodeList *)other blockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (NSDictionary *)listOfChangedNodesComparedTo:(DSMasternodeList *)previous; -- (NSDictionary *)compare:(DSMasternodeList *)other usingOurString:(NSString *)ours usingTheirString:(NSString *)theirs blockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (DSQuorumEntry *_Nullable)quorumEntryForLockRequestID:(UInt256)requestID ofQuorumType:(LLMQType)quorumType; -- (DSQuorumEntry *_Nullable)quorumEntryForPlatformWithQuorumHash:(UInt256)quorumHash; - -- (NSArray *)quorumEntriesRankedForInstantSendRequestID:(UInt256)requestID; - -- (NSArray *)peers:(uint32_t)peerCount withConnectivityNonce:(uint64_t)connectivityNonce; - -- (UInt256)masternodeMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (NSArray *)hashesForMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (NSDictionary *)hashDictionaryForMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (NSDictionary *)toDictionaryUsingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (DSSimplifiedMasternodeEntry *)masternodeForRegistrationHash:(UInt256)registrationHash; - -- (DSMasternodeList *)mergedWithMasternodeList:(DSMasternodeList *)masternodeList; -- (BOOL)hasUnverifiedNonRotatedQuorums; -- (BOOL)hasUnverifiedRotatedQuorums; -- (void)saveToJsonFile:(NSString *)fileName; -- (void)saveToJsonFileExtended:(NSString *)fileName; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSMasternodeList.m b/DashSync/shared/Models/Masternode/DSMasternodeList.m deleted file mode 100644 index 1d388a407..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeList.m +++ /dev/null @@ -1,718 +0,0 @@ -// -// DSMasternodeList.m -// DashSync -// -// Created by Sam Westrich on 5/20/19. -// - -#import "DSMasternodeList.h" -#import "BigIntTypes.h" -#import "DSChain.h" -#import "DSMerkleBlock.h" -#import "DSMutableOrderedDataKeyDictionary.h" -#import "DSPeer.h" -#import "DSQuorumEntry.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "NSData+DSHash.h" -#import "NSData+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" - -// from https://en.bitcoin.it/wiki/Protocol_specification#Merkle_Trees -// Merkle trees are binary trees of hashes. Merkle trees in bitcoin use a double SHA-256, the SHA-256 hash of the -// SHA-256 hash of something. If, when forming a row in the tree (other than the root of the tree), it would have an odd -// number of elements, the final double-hash is duplicated to ensure that the row has an even number of hashes. First -// form the bottom row of the tree with the ordered double-SHA-256 hashes of the byte streams of the transactions in the -// block. Then the row above it consists of half that number of hashes. Each entry is the double-SHA-256 of the 64-byte -// concatenation of the corresponding two hashes below it in the tree. This procedure repeats recursively until we reach -// a row consisting of just a single double-hash. This is the merkle root of the tree. -// -// from https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki#Partial_Merkle_branch_format -// The encoding works as follows: we traverse the tree in depth-first order, storing a bit for each traversed node, -// signifying whether the node is the parent of at least one matched leaf txid (or a matched txid itself). In case we -// are at the leaf level, or this bit is 0, its merkle node hash is stored, and its children are not explored further. -// Otherwise, no hash is stored, but we recurse into both (or the only) child branch. During decoding, the same -// depth-first traversal is performed, consuming bits and hashes as they written during encoding. -// -// example tree with three transactions, where only tx2 is matched by the bloom filter: -// -// merkleRoot -// / \ -// m1 m2 -// / \ / \ -// tx1 tx2 tx3 tx3 -// -// flag bits (little endian): 00001011 [merkleRoot = 1, m1 = 1, tx1 = 0, tx2 = 1, m2 = 0, byte padding = 000] -// hashes: [tx1, tx2, m2] - -@interface DSMasternodeList () - -@property (nonatomic, strong) NSMutableDictionary *mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash; -@property (nonatomic, strong) DSChain *chain; -@property (nonatomic, assign) UInt256 blockHash; -@property (nonatomic, assign) UInt256 masternodeMerkleRoot; -@property (nonatomic, assign) UInt256 quorumMerkleRoot; -@property (nonatomic, assign) uint32_t knownHeight; -@property (nonatomic, strong) NSMutableDictionary *> *mQuorums; - -@end - -@implementation DSMasternodeList - -+ (instancetype)masternodeListWithSimplifiedMasternodeEntries:(NSArray *)simplifiedMasternodeEntries quorumEntries:(NSArray *)quorumEntries atBlockHash:(UInt256)blockHash atBlockHeight:(uint32_t)blockHeight withMasternodeMerkleRootHash:(UInt256)masternodeMerkleRootHash withQuorumMerkleRootHash:(UInt256)quorumMerkleRootHash onChain:(DSChain *)chain { - NSMutableDictionary *masternodeDictionary = [NSMutableDictionary dictionary]; - for (DSSimplifiedMasternodeEntry *entry in simplifiedMasternodeEntries) { - masternodeDictionary[uint256_data(entry.providerRegistrationTransactionHash).reverse] = entry; - } - NSMutableDictionary *quorumDictionary = [NSMutableDictionary dictionary]; - for (DSQuorumEntry *entry in quorumEntries) { - NSMutableDictionary *quorumDictionaryForType = [quorumDictionary objectForKey:@(entry.llmqType)]; - if (!quorumDictionaryForType) { - quorumDictionaryForType = [NSMutableDictionary dictionary]; - quorumDictionary[@(entry.llmqType)] = quorumDictionaryForType; - } - quorumDictionaryForType[uint256_data(entry.quorumHash)] = entry; - } - return [[self alloc] initWithSimplifiedMasternodeEntriesDictionary:masternodeDictionary quorumEntriesDictionary:quorumDictionary atBlockHash:blockHash atBlockHeight:blockHeight withMasternodeMerkleRootHash:masternodeMerkleRootHash withQuorumMerkleRootHash:quorumMerkleRootHash onChain:chain]; -} - -+ (instancetype)masternodeListWithSimplifiedMasternodeEntriesDictionary:(NSDictionary *)simplifiedMasternodeEntries quorumEntriesDictionary:(NSDictionary *> *)quorumEntries atBlockHash:(UInt256)blockHash atBlockHeight:(uint32_t)blockHeight withMasternodeMerkleRootHash:(UInt256)masternodeMerkleRootHash withQuorumMerkleRootHash:(UInt256)quorumMerkleRootHash onChain:(DSChain *)chain { - return [[self alloc] initWithSimplifiedMasternodeEntriesDictionary:simplifiedMasternodeEntries quorumEntriesDictionary:quorumEntries atBlockHash:blockHash atBlockHeight:blockHeight withMasternodeMerkleRootHash:masternodeMerkleRootHash withQuorumMerkleRootHash:quorumMerkleRootHash onChain:chain]; -} - -- (instancetype)initWithSimplifiedMasternodeEntriesDictionary:(NSDictionary *)simplifiedMasternodeEntries quorumEntriesDictionary:(NSDictionary *> *)quorumEntries atBlockHash:(UInt256)blockHash atBlockHeight:(uint32_t)blockHeight withMasternodeMerkleRootHash:(UInt256)masternodeMerkleRootHash withQuorumMerkleRootHash:(UInt256)quorumMerkleRootHash onChain:(DSChain *)chain { - NSParameterAssert(chain); - - if (!(self = [super init])) return nil; - self.masternodeMerkleRoot = masternodeMerkleRootHash; - self.quorumMerkleRoot = quorumMerkleRootHash; - self.knownHeight = blockHeight; - self.chain = chain; - self.blockHash = blockHash; - self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash = [simplifiedMasternodeEntries mutableCopy]; - self.mQuorums = [quorumEntries mutableCopy]; - return self; -} - -- (UInt256)masternodeMerkleRoot { - if (uint256_is_zero(_masternodeMerkleRoot)) { - return [self masternodeMerkleRootWithBlockHeightLookup:^uint32_t(UInt256 blockHash) { - return [self.chain heightForBlockHash:blockHash]; - }]; - } - return _masternodeMerkleRoot; -} - -- (UInt256)masternodeMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - if (uint256_is_zero(_masternodeMerkleRoot)) { - self.masternodeMerkleRoot = [self calculateMasternodeMerkleRootWithBlockHeightLookup:blockHeightLookup]; - } - return _masternodeMerkleRoot; -} - -- (NSArray *)providerTxOrderedHashes { - NSArray *proTxHashes = [self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash allKeys]; - proTxHashes = [proTxHashes sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = *(UInt256 *)((NSData *)obj1).bytes; - UInt256 hash2 = *(UInt256 *)((NSData *)obj2).bytes; - return uint256_sup(hash1, hash2) ? NSOrderedDescending : NSOrderedAscending; - }]; - return proTxHashes; -} - -- (NSArray *)hashesForMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSArray *proTxHashes = [self providerTxOrderedHashes]; - NSMutableArray *simplifiedMasternodeListByRegistrationTransactionHashHashes = [NSMutableArray array]; - uint32_t height = blockHeightLookup(self.blockHash); - if (height == UINT32_MAX) { - DSLog(@"Block height lookup queried an unknown block %@", uint256_hex(self.blockHash)); - return nil; //this should never happen - } - for (NSData *proTxHash in proTxHashes) { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = [self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash objectForKey:proTxHash]; - UInt256 simplifiedMasternodeEntryHash = [simplifiedMasternodeEntry simplifiedMasternodeEntryHashAtBlockHeight:height]; - [simplifiedMasternodeListByRegistrationTransactionHashHashes addObject:uint256_data(simplifiedMasternodeEntryHash)]; - } - return simplifiedMasternodeListByRegistrationTransactionHashHashes; -} - -- (NSDictionary *)hashDictionaryForMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSArray *proTxHashes = [self providerTxOrderedHashes]; - - NSMutableDictionary *simplifiedMasternodeListByRegistrationTransactionHashHashes = [NSMutableDictionary dictionary]; - uint32_t height = blockHeightLookup(self.blockHash); - if (height == UINT32_MAX) { - DSLog(@"Block height lookup queried an unknown block %@", uint256_hex(self.blockHash)); - return nil; //this should never happen - } - for (NSData *proTxHash in proTxHashes) { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = [self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash objectForKey:proTxHash]; - UInt256 simplifiedMasternodeEntryHash = [simplifiedMasternodeEntry simplifiedMasternodeEntryHashAtBlockHeight:height]; - simplifiedMasternodeListByRegistrationTransactionHashHashes[proTxHash] = uint256_data(simplifiedMasternodeEntryHash); - } - return simplifiedMasternodeListByRegistrationTransactionHashHashes; -} - -- (UInt256)calculateMasternodeMerkleRootWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSArray *hashes = [self hashesForMerkleRootWithBlockHeightLookup:blockHeightLookup]; - if (hashes == nil || hashes.count == 0) { - return UINT256_ZERO; - } - NSData *data = [NSData merkleRootFromHashes:hashes]; - if (data == nil || data.length == 0) { - return UINT256_ZERO; - } - return [data UInt256]; -} - -- (UInt256)quorumMerkleRoot { - if (uint256_is_zero(_quorumMerkleRoot)) { - NSMutableArray *llmqCommitmentHashes = [NSMutableArray array]; - for (NSNumber *number in self.mQuorums) { - for (DSQuorumEntry *quorumEntry in [self.mQuorums[number] allValues]) { - [llmqCommitmentHashes addObject:uint256_data(quorumEntry.quorumEntryHash)]; - } - } - NSArray *sortedLlmqHashes = [llmqCommitmentHashes sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = uint256_reverse([(NSData *)obj1 UInt256]); - UInt256 hash2 = uint256_reverse([(NSData *)obj2 UInt256]); - return uint256_sup(hash1, hash2) ? NSOrderedDescending : NSOrderedAscending; - }]; - self.quorumMerkleRoot = [[NSData merkleRootFromHashes:sortedLlmqHashes] UInt256]; - } - return _quorumMerkleRoot; -} - - -- (DSMutableOrderedDataKeyDictionary *)calculateScores:(UInt256)modifier { - NSMutableDictionary *scores = [NSMutableDictionary dictionary]; - for (NSData *registrationTransactionHash in self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[registrationTransactionHash]; - if (uint256_is_zero(simplifiedMasternodeEntry.confirmedHash)) { - continue; - } - NSMutableData *data = [NSMutableData data]; - [data appendData:[NSData dataWithUInt256:simplifiedMasternodeEntry.confirmedHashHashedWithProviderRegistrationTransactionHash].reverse]; - [data appendData:[NSData dataWithUInt256:modifier].reverse]; - UInt256 score = data.SHA256; - scores[[NSData dataWithUInt256:score]] = simplifiedMasternodeEntry; - } - DSMutableOrderedDataKeyDictionary *rankedScores = [[DSMutableOrderedDataKeyDictionary alloc] initWithMutableDictionary:scores keyAscending:YES]; - [rankedScores addIndex:@"providerRegistrationTransactionHash"]; - return rankedScores; -} - -- (UInt256)masternodeScore:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry modifier:(UInt256)modifier atBlockHeight:(uint32_t)blockHeight { - NSParameterAssert(simplifiedMasternodeEntry); - - if (uint256_is_zero([simplifiedMasternodeEntry confirmedHashAtBlockHeight:blockHeight])) { - return UINT256_ZERO; - } - NSMutableData *data = [NSMutableData data]; - [data appendData:[NSData dataWithUInt256:[simplifiedMasternodeEntry confirmedHashHashedWithProviderRegistrationTransactionHashAtBlockHeight:blockHeight]]]; - [data appendData:[NSData dataWithUInt256:modifier]]; - return data.SHA256; -} - -- (NSDictionary *)scoreDictionaryForQuorumModifier:(UInt256)quorumModifier atBlockHeight:(uint32_t)blockHeight { - NSMutableDictionary *scoreDictionary = [NSMutableDictionary dictionary]; - for (NSData *registrationTransactionHash in self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[registrationTransactionHash]; - UInt256 score = [self masternodeScore:simplifiedMasternodeEntry modifier:quorumModifier atBlockHeight:blockHeight]; - if (uint256_is_zero(score)) continue; - scoreDictionary[[NSData dataWithUInt256:score]] = simplifiedMasternodeEntry; - } - return scoreDictionary; -} - -- (NSArray *)scoresForQuorumModifier:(UInt256)quorumModifier atBlockHeight:(uint32_t)blockHeight { - NSDictionary *scoreDictionary = [self scoreDictionaryForQuorumModifier:quorumModifier atBlockHeight:blockHeight]; - NSArray *scores = [[scoreDictionary allKeys] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = *(UInt256 *)((NSData *)obj1).bytes; - UInt256 hash2 = *(UInt256 *)((NSData *)obj2).bytes; - return uint256_sup(hash1, hash2) ? NSOrderedAscending : NSOrderedDescending; - }]; - return scores; -} - -- (NSArray *)validMasternodesForQuorumModifier:(UInt256)quorumModifier quorumCount:(NSUInteger)quorumCount { - return [self validMasternodesForQuorumModifier:quorumModifier - quorumCount:quorumCount - blockHeight:^uint32_t(UInt256 blockHash) { - DSMerkleBlock *block = [self.chain blockForBlockHash:blockHash]; - if (!block) { - DSLog(@"Unknown block %@", uint256_reverse_hex(blockHash)); - NSAssert(block, @"block should be known"); - } - return block.height; - }(self.blockHash)]; -} - -- (NSArray *)allMasternodesForQuorumModifier:(UInt256)quorumModifier quorumCount:(NSUInteger)quorumCount blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - uint32_t blockHeight = blockHeightLookup(self.blockHash); - NSDictionary *scoreDictionary = [self scoreDictionaryForQuorumModifier:quorumModifier atBlockHeight:blockHeight]; - NSArray *scores = [[scoreDictionary allKeys] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = *(UInt256 *)((NSData *)obj1).bytes; - UInt256 hash2 = *(UInt256 *)((NSData *)obj2).bytes; - return uint256_sup(hash1, hash2) ? NSOrderedAscending : NSOrderedDescending; - }]; - NSMutableArray *masternodes = [NSMutableArray array]; - NSUInteger masternodesInListCount = self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash.count; - for (int i = 0; i < masternodesInListCount && i < scores.count; i++) { - NSData *score = scores[i]; - DSSimplifiedMasternodeEntry *masternode = scoreDictionary[score]; - [masternodes addObject:masternode]; - } - return masternodes; -} - -- (NSArray *)validMasternodesForQuorumModifier:(UInt256)quorumModifier quorumCount:(NSUInteger)quorumCount blockHeight:(uint32_t)blockHeight { - NSDictionary *scoreDictionary = [self scoreDictionaryForQuorumModifier:quorumModifier atBlockHeight:blockHeight]; - NSArray *scores = [[scoreDictionary allKeys] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = *(UInt256 *)((NSData *)obj1).bytes; - UInt256 hash2 = *(UInt256 *)((NSData *)obj2).bytes; - return uint256_sup(hash1, hash2) ? NSOrderedAscending : NSOrderedDescending; - }]; - NSMutableArray *masternodes = [NSMutableArray array]; - NSUInteger masternodesInListCount = self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash.count; - for (int i = 0; i < masternodesInListCount && i < scores.count; i++) { - NSData *score = scores[i]; - DSSimplifiedMasternodeEntry *masternode = scoreDictionary[score]; - if ([masternode isValidAtBlockHeight:blockHeight]) { - [masternodes addObject:masternode]; - } - if (masternodes.count == quorumCount) break; - } - return masternodes; -} - -- (NSArray *)simplifiedMasternodeEntries { - return self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash.allValues; -} - -- (NSArray *)reversedRegistrationTransactionHashes { - return self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash.allKeys; -} - -- (uint64_t)masternodeCount { - return [self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash count]; -} - -- (uint64_t)validMasternodeCount { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isValid == TRUE"]; - return [[self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash allValues] filteredArrayUsingPredicate:predicate].count; -} - -- (NSUInteger)quorumsCount { - NSUInteger count = 0; - for (NSNumber *type in self.mQuorums) { - count += self.mQuorums[type].count; - } - return count; -} - -- (NSUInteger)quorumsCountOfType:(LLMQType)type { - return self.mQuorums[@(type)].count; -} - -- (NSDictionary *)quorumsOfType:(LLMQType)type { - return self.mQuorums[@(type)]; -} - -- (NSUInteger)validQuorumsCount { - NSUInteger count = 0; - for (NSNumber *type in self.mQuorums) { - for (NSData *quorumHashData in self.mQuorums[type]) { - DSQuorumEntry *quorum = self.mQuorums[type][quorumHashData]; - if (quorum.verified) count++; - } - } - return count; -} - -- (NSUInteger)validQuorumsCountOfType:(LLMQType)type { - NSUInteger count = 0; - for (NSData *quorumHashData in self.mQuorums[@(type)]) { - DSQuorumEntry *quorum = self.mQuorums[@(type)][quorumHashData]; - if (quorum.verified) count++; - } - return count; -} - - -- (NSDictionary *)quorums { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - NSMutableDictionary *> *q = [self.mQuorums copy]; - for (NSNumber *number in q) { - dictionary[number] = [q objectForKey:number]; - } - return [dictionary copy]; -} - -- (NSDictionary *)simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash { - return [self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash copy]; -} - -- (uint32_t)height { - if (!self.knownHeight || self.knownHeight == UINT32_MAX) { - self.knownHeight = [self.chain heightForBlockHash:self.blockHash]; - } - return self.knownHeight; -} - -/* - - (BOOL)validateQuorumsWithMasternodeLists:(NSDictionary *)masternodeLists { - for (DSQuorumEntry *quorum in self.quorums) { - BOOL verified = quorum.verified; - if (!verified) { - DSMasternodeList *quorumMasternodeList = masternodeLists[uint256_data(quorum.quorumHash)]; - BOOL valid = [quorum validateWithMasternodeList:quorumMasternodeList]; - if (!valid) return FALSE; - } - } - return TRUE; -} -*/ - -- (void)saveToJsonFile:(NSString *)fileName { -// return; - NSMutableArray *json_nodes = [NSMutableArray arrayWithCapacity:self.masternodeCount]; - NSArray *proTxHashes = [self providerTxOrderedHashes]; - for (NSData *proTxHash in proTxHashes) { - DSSimplifiedMasternodeEntry *entry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[proTxHash]; - NSString *json_node = [NSString stringWithFormat:@"{\n\"proRegTxHash\": \"%@\", \n\"confirmedHash\": \"%@\", \n\"service\": \"%@\", \n\"pubKeyOperator\": \"%@\", \n\"votingAddress\": \"%@\", \n\"isValid\": %s, \n\"updateHeight\": %@, \n\"knownConfirmedAtHeight\": %@\n}", - uint256_hex(entry.providerRegistrationTransactionHash), - uint256_hex(entry.confirmedHash), - uint128_hex(entry.address), - uint384_hex(entry.operatorPublicKey), - uint160_data(entry.keyIDVoting).base58String, - entry.isValid ? "true" : "false", @(entry.updateHeight), @(entry.knownConfirmedAtHeight)]; - [json_nodes addObject:json_node]; - } - NSMutableArray *json_quorums = [NSMutableArray arrayWithCapacity:self.quorumsCount]; - NSArray *llmqTypes = [[self.mQuorums allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSNumber* n1, NSNumber* n2) { - return [n1 compare:n2]; - }]; - for (NSNumber *llmqType in llmqTypes) { - NSMutableDictionary *quorumsForMasternodeType = self.mQuorums[llmqType]; - NSArray *llmqHashes = [quorumsForMasternodeType allKeys]; - llmqHashes = [llmqHashes sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = *(UInt256 *)((NSData *)obj1).bytes; - UInt256 hash2 = *(UInt256 *)((NSData *)obj2).bytes; - return uint256_sup(hash1, hash2) ? NSOrderedDescending : NSOrderedAscending; - }]; - for (NSData *quorumHash in llmqHashes) { - DSQuorumEntry *entry = quorumsForMasternodeType[quorumHash]; - NSString *json_quorum = [NSString stringWithFormat:@"{\n\"version\": %@, \n\"llmqType\": %@, \n\"quorumHash\": \"%@\", \n\"quorumIndex\": %@, \n\"signersCount\": %@, \n\"signers\": \"%@\", \n\"validMembersCount\": %@, \n\"validMembers\": \"%@\", \n\"quorumPublicKey\": \"%@\", \n\"quorumVvecHash\": \"%@\", \n\"quorumSig\": \"%@\", \n\"membersSig\": \"%@\"\n}", - @(entry.version), - @(entry.llmqType), - uint256_hex(entry.quorumHash), - @(entry.quorumIndex), - @(entry.signersCount), - [entry signersBitset].hexString, - @(entry.validMembersCount), - [entry validMembersBitset].hexString, - uint384_hex(entry.quorumPublicKey), - uint256_hex(entry.quorumVerificationVectorHash), - uint768_hex(entry.quorumThresholdSignature), - uint768_hex(entry.allCommitmentAggregatedSignature)]; - - [json_quorums addObject:json_quorum]; - } - } - NSString *nodes = [NSString stringWithFormat:@"\n\"mnList\": [%@]", [json_nodes componentsJoinedByString:@","]]; - NSString *quorums = [NSString stringWithFormat:@"\n\"newQuorums\": [%@]", [json_quorums componentsJoinedByString:@","]]; - NSString *list = [NSString stringWithFormat:@"{\n\"blockHash\":\"%@\", \n\"knownHeight\":%@, \n\"masternodeMerkleRoot\":\"%@\", \n\"quorumMerkleRoot\":\"%@\", \n%@, \n%@\n}", uint256_hex(self.blockHash), @(self.knownHeight), uint256_hex(self.masternodeMerkleRoot), uint256_hex(self.quorumMerkleRoot), nodes, quorums]; - NSData* data = [list dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]; - [data saveToFile:fileName inDirectory:NSCachesDirectory]; - DSLog(@"•-• File %@ saved", fileName); -} - -- (void)saveToJsonFileExtended:(NSString *)fileName { - NSMutableArray *json_nodes = [NSMutableArray arrayWithCapacity:self.masternodeCount]; - NSArray *proTxHashes = [self providerTxOrderedHashes]; - for (NSData *proTxHash in proTxHashes) { - DSSimplifiedMasternodeEntry *entry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[proTxHash]; - NSMutableArray *json_prev_public_keys = [NSMutableArray arrayWithCapacity:entry.previousOperatorPublicKeys.count]; - for (DSBlock *block in entry.previousOperatorPublicKeys) { - [json_prev_public_keys addObject:[NSString stringWithFormat:@"{\n\"block_height\":%u, \n\"block_hash\":\"%@\", \n\"public_key\":\"%@\"\n}", block.height, uint256_hex(block.blockHash), ((NSData *)entry.previousOperatorPublicKeys[block]).hexString]]; - } - NSMutableArray *json_prev_entry_hashes = [NSMutableArray arrayWithCapacity:entry.previousSimplifiedMasternodeEntryHashes.count]; - for (DSBlock *block in entry.previousSimplifiedMasternodeEntryHashes) { - [json_prev_entry_hashes addObject:[NSString stringWithFormat:@"{\n\"block_height\":%u, \n\"block_hash\":\"%@\", \n\"entry_hash\":\"%@\"\n}", block.height, uint256_hex(block.blockHash), ((NSData *)entry.previousSimplifiedMasternodeEntryHashes[block]).hexString]]; - } - NSMutableArray *json_prev_validities = [NSMutableArray arrayWithCapacity:entry.previousValidity.count]; - for (DSBlock *block in entry.previousValidity) { - [json_prev_validities addObject:[NSString stringWithFormat:@"{\n\"block_height\":%u, \n\"block_hash\":\"%@\", \n\"is_valid\":\%s\n}", block.height, uint256_hex(block.blockHash), ((NSNumber *) entry.previousValidity[block]).boolValue ? "true" : "false"]]; - } - - NSString *json_node = [NSString stringWithFormat:@"{\n\"provider_registration_transaction_hash\": \"%@\", \n\"confirmed_hash\": \"%@\", \n\"confirmed_hash_hashed_with_provider_registration_transaction_hash\": \"%@\", \n\"socket_address\": {\n\"ip_address\":\"%@\",\n\"port\":%@\n}, \n\"operator_public_key\": \"%@\", \n\"previous_operator_public_keys\": [\n%@\n], \n\"previous_entry_hashes\": [\n%@\n], \n\"previous_validity\": [\n%@\n], \n\"known_confirmed_at_height\": %@, \n\"update_height\": %@, \n\"key_id_voting\": \"%@\", \n\"isValid\": %s, \n\"entry_hash\": \"%@\"\n}", - uint256_hex(entry.providerRegistrationTransactionHash), - uint256_hex(entry.confirmedHash), - uint256_hex(entry.confirmedHashHashedWithProviderRegistrationTransactionHash), - uint128_hex(entry.address), - @(entry.port), - uint384_hex(entry.operatorPublicKey), - [NSString stringWithFormat:@"%@", [json_prev_public_keys componentsJoinedByString:@","]], - [NSString stringWithFormat:@"%@", [json_prev_entry_hashes componentsJoinedByString:@","]], - [NSString stringWithFormat:@"%@", [json_prev_validities componentsJoinedByString:@","]], - @(entry.knownConfirmedAtHeight), - @(entry.updateHeight), - uint160_data(entry.keyIDVoting).base58String, - entry.isValid ? "true" : "false", - uint256_hex(entry.simplifiedMasternodeEntryHash)]; - [json_nodes addObject:json_node]; - } - NSMutableArray *json_quorums = [NSMutableArray arrayWithCapacity:self.quorumsCount]; - NSArray *llmqTypes = [[self.mQuorums allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSNumber* n1, NSNumber* n2) { - return [n1 compare:n2]; - }]; - for (NSNumber *llmqType in llmqTypes) { - NSMutableDictionary *quorumsForMasternodeType = self.mQuorums[llmqType]; - NSArray *llmqHashes = [quorumsForMasternodeType allKeys]; - llmqHashes = [llmqHashes sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - UInt256 hash1 = *(UInt256 *)((NSData *)obj1).bytes; - UInt256 hash2 = *(UInt256 *)((NSData *)obj2).bytes; - return uint256_sup(hash1, hash2) ? NSOrderedDescending : NSOrderedAscending; - }]; - for (NSData *quorumHash in llmqHashes) { - DSQuorumEntry *entry = quorumsForMasternodeType[quorumHash]; - NSString *json_quorum = [NSString stringWithFormat:@"{\n\"version\": %@, \n\"llmqType\": %@, \n\"quorumHash\": \"%@\", \n\"quorumIndex\": %@, \n\"signersCount\": %@, \n\"signers\": \"%@\", \n\"validMembersCount\": %@, \n\"validMembers\": \"%@\", \n\"quorumPublicKey\": \"%@\", \n\"quorumVvecHash\": \"%@\", \n\"quorumSig\": \"%@\", \n\"membersSig\": \"%@\"\n}", - @(entry.version), - @(entry.llmqType), - uint256_hex(entry.quorumHash), - @(entry.quorumIndex), - @(entry.signersCount), - [entry signersBitset].hexString, - @(entry.validMembersCount), - [entry validMembersBitset].hexString, - uint384_hex(entry.quorumPublicKey), - uint256_hex(entry.quorumVerificationVectorHash), - uint768_hex(entry.quorumThresholdSignature), - uint768_hex(entry.allCommitmentAggregatedSignature)]; - - [json_quorums addObject:json_quorum]; - } - } - NSString *nodes = [NSString stringWithFormat:@"\n\"mnList\": [%@]", [json_nodes componentsJoinedByString:@","]]; - NSString *quorums = [NSString stringWithFormat:@"\n\"newQuorums\": [%@]", [json_quorums componentsJoinedByString:@","]]; - NSString *list = [NSString stringWithFormat:@"{\n\"blockHash\":\"%@\", \n\"knownHeight\":%@, \n\"masternodeMerkleRoot\":\"%@\", \n\"quorumMerkleRoot\":\"%@\", \n%@, \n%@\n}", uint256_hex(self.blockHash), @(self.knownHeight), uint256_hex(self.masternodeMerkleRoot), uint256_hex(self.quorumMerkleRoot), nodes, quorums]; - NSData* data = [list dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]; - [data saveToFile:fileName inDirectory:NSCachesDirectory]; - DSLog(@"•-• File %@ saved", fileName); -} - -- (NSString *)description { - return [[super description] stringByAppendingString:[NSString stringWithFormat:@" {%u}", self.height]]; -} - -- (NSString *)debugDescription { -// [self saveToJsonFile]; - return [[super debugDescription] stringByAppendingString:[NSString stringWithFormat:@" {%u}", self.height]]; -} - -- (NSDictionary *)compareWithPrevious:(DSMasternodeList *)other { - return [self compareWithPrevious:other - blockHeightLookup:^uint32_t(UInt256 blockHash) { - return [self.chain heightForBlockHash:blockHash]; - }]; -} - -- (NSDictionary *)compareWithPrevious:(DSMasternodeList *)other blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - return [self compare:other usingOurString:@"current" usingTheirString:@"previous" blockHeightLookup:blockHeightLookup]; -} - -- (NSDictionary *)compare:(DSMasternodeList *)other { - return [self compare:other - blockHeightLookup:^uint32_t(UInt256 blockHash) { - return [self.chain heightForBlockHash:blockHash]; - }]; -} - -- (NSDictionary *)compare:(DSMasternodeList *)other blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - return [self compare:other usingOurString:@"ours" usingTheirString:@"theirs" blockHeightLookup:blockHeightLookup]; -} - -- (NSDictionary *)listOfChangedNodesComparedTo:(DSMasternodeList *)previous { - NSMutableArray *added = [NSMutableArray array]; - NSMutableArray *removed = [NSMutableArray array]; - NSMutableArray *addedValidity = [NSMutableArray array]; - NSMutableArray *removedValidity = [NSMutableArray array]; - for (NSData *data in self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *currentEntry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - DSSimplifiedMasternodeEntry *previousEntry = previous.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - if (currentEntry && !previousEntry) { - [added addObject:currentEntry]; - } else if ([currentEntry isValidAtBlockHeight:self.height] && ![previousEntry isValidAtBlockHeight:previous.height]) { - [addedValidity addObject:currentEntry]; - } else if (![currentEntry isValidAtBlockHeight:self.height] && [previousEntry isValidAtBlockHeight:previous.height]) { - [removedValidity addObject:currentEntry]; - } - } - - for (NSData *data in previous.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *currentEntry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - DSSimplifiedMasternodeEntry *previousEntry = previous.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - if (!currentEntry && previousEntry) { - [removed addObject:previousEntry]; - } - } - - return @{MASTERNODE_LIST_ADDED_NODES: added, MASTERNODE_LIST_REMOVED_NODES: removed, MASTERNODE_LIST_ADDED_VALIDITY: addedValidity, MASTERNODE_LIST_REMOVED_VALIDITY: removedValidity}; -} - -- (NSDictionary *)compare:(DSMasternodeList *)other usingOurString:(NSString *)ours usingTheirString:(NSString *)theirs blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - for (NSData *data in self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *ourEntry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - DSSimplifiedMasternodeEntry *theirEntry = other.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - if (ourEntry && theirEntry) { - NSDictionary *entryComparison = [ourEntry compare:theirEntry ourBlockHash:self.blockHash theirBlockHash:other.blockHash usingOurString:ours usingTheirString:theirs blockHeightLookup:blockHeightLookup]; - if (entryComparison.count) { - dictionary[data] = entryComparison; - } - } else if (ourEntry) { - dictionary[data] = @{@"absent": uint256_hex(ourEntry.providerRegistrationTransactionHash)}; - } - } - return dictionary; -} - -- (NSDictionary *)toDictionaryUsingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - for (NSData *data in self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *ourEntry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[data]; - if (ourEntry) { - NSDictionary *entryDictionary = [ourEntry toDictionaryAtBlockHash:self.blockHash usingBlockHeightLookup:blockHeightLookup]; - dictionary[[data base64String]] = entryDictionary; - } - } - return dictionary; -} - -- (DSQuorumEntry *)quorumEntryForLockRequestID:(UInt256)requestID ofQuorumType:(LLMQType)quorumType { - NSArray *quorumsForLock = [self.quorums[@(quorumType)] allValues]; - UInt256 lowestValue = UINT256_MAX; - DSQuorumEntry *firstQuorum = nil; - for (DSQuorumEntry *quorumEntry in quorumsForLock) { - UInt256 orderingHash = uint256_reverse([quorumEntry orderingHashForRequestID:requestID forQuorumType:quorumType]); - if (uint256_sup(lowestValue, orderingHash)) { - lowestValue = orderingHash; - firstQuorum = quorumEntry; - } - } - return firstQuorum; -} - - -- (DSQuorumEntry *_Nullable)quorumEntryForPlatformWithQuorumHash:(UInt256)quorumHash ofQuorumType:(LLMQType)quorumType { - NSArray *quorumsForPlatform = [self.quorums[@(quorumType)] allValues]; - for (DSQuorumEntry *quorumEntry in quorumsForPlatform) { - if (uint256_eq(quorumEntry.quorumHash, quorumHash)) { - return quorumEntry; - } - NSAssert(!uint256_eq(quorumEntry.quorumHash, uint256_reverse(quorumHash)), @"these should not be inversed"); - } - return nil; -} - -- (DSQuorumEntry *)quorumEntryForPlatformWithQuorumHash:(UInt256)quorumHash { - return [self quorumEntryForPlatformWithQuorumHash:quorumHash ofQuorumType:quorum_type_for_platform(self.chain.chainType)]; -} - -- (NSArray *)quorumEntriesRankedForInstantSendRequestID:(UInt256)requestID { - LLMQType quorumType = quorum_type_for_chain_locks(self.chain.chainType); - NSArray *quorumsForIS = [self.quorums[@(quorumType)] allValues]; - NSMutableDictionary *orderedQuorumDictionary = [NSMutableDictionary dictionary]; - for (DSQuorumEntry *quorumEntry in quorumsForIS) { - UInt256 orderingHash = uint256_reverse([quorumEntry orderingHashForRequestID:requestID forQuorumType:quorumType]); - orderedQuorumDictionary[quorumEntry] = uint256_data(orderingHash); - } - NSArray *orderedQuorums = [orderedQuorumDictionary keysSortedByValueUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - return uint256_sup([obj1 UInt256], [obj2 UInt256]) ? NSOrderedDescending : NSOrderedAscending; - }]; - return orderedQuorums; -} - -- (NSArray *)peers:(uint32_t)peerCount withConnectivityNonce:(uint64_t)connectivityNonce { - NSArray *registrationTransactionHashes = [self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash allKeys]; - NSArray *sortedHashes = [registrationTransactionHashes sortedArrayUsingComparator:^NSComparisonResult(NSData *_Nonnull obj1, NSData *_Nonnull obj2) { - UInt256 hash1 = [[[obj1 mutableCopy] appendUInt64:connectivityNonce] blake3]; - UInt256 hash2 = [[[obj2 mutableCopy] appendUInt64:connectivityNonce] blake3]; - return uint256_sup(hash1, hash2) ? NSOrderedDescending : NSOrderedAscending; - }]; - NSMutableArray *mArray = [NSMutableArray array]; - for (uint32_t i = 0; i < MIN(peerCount, self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash.count); i++) { - DSSimplifiedMasternodeEntry *masternodeEntry = self.mSimplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[sortedHashes[i]]; - if (masternodeEntry.isValid) { - DSPeer *peer = [DSPeer peerWithSimplifiedMasternodeEntry:masternodeEntry]; - [mArray addObject:peer]; - } - } - return mArray; -} - -- (DSSimplifiedMasternodeEntry *)masternodeForRegistrationHash:(UInt256)registrationHash { - return self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[uint256_data(registrationHash)]; -} - -- (BOOL)hasUnverifiedNonRotatedQuorums { - for (NSNumber *quorumType in self.quorums) { - LLMQType llmqType = (LLMQType) quorumType.unsignedIntValue; - if (llmqType == quorum_type_for_isd_locks(self.chain.chainType) || !quorum_should_process_type_for_chain(llmqType, self.chain.chainType)) { - continue; - } - NSDictionary *quorumsOfType = self.quorums[quorumType]; - for (NSData *quorumHash in quorumsOfType) { - DSQuorumEntry *entry = quorumsOfType[quorumHash]; - if (!entry.verified) { - return YES; - } - } - } - return NO; -} - -- (BOOL)hasUnverifiedRotatedQuorums { - NSArray *quorumsForISDLock = [self.quorums[@(quorum_type_for_isd_locks(self.chain.chainType))] allValues]; - for (DSQuorumEntry *entry in quorumsForISDLock) { - if (!entry.verified) { - return YES; - } - } - return NO; -} - -- (DSQuorumEntry *_Nullable)quorumEntryOfType:(LLMQType)llmqType withQuorumHash:(UInt256)quorumHash { - NSDictionary *quorums = [self quorumsOfType:llmqType]; - for (NSData *hash in quorums) { - DSQuorumEntry *entry = quorums[hash]; - if (uint256_eq(entry.quorumHash, quorumHash)) { - return entry; - } - } - return NULL; -} - -- (DSMasternodeList *)mergedWithMasternodeList:(DSMasternodeList *)masternodeList { - for (NSData *proTxHash in self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash) { - DSSimplifiedMasternodeEntry *entry = self.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[proTxHash]; - DSSimplifiedMasternodeEntry *newEntry = masternodeList.simplifiedMasternodeListDictionaryByReversedRegistrationTransactionHash[proTxHash]; - [entry mergedWithSimplifiedMasternodeEntry:newEntry atBlockHeight:masternodeList.height]; - } - for (NSNumber *quorumType in self.mQuorums) { - NSDictionary *quorumsOfType = self.quorums[quorumType]; - for (NSData *quorumHash in quorumsOfType) { - DSQuorumEntry *entry = quorumsOfType[quorumHash]; - if (!entry.verified) { - DSQuorumEntry *quorumEntry = [masternodeList quorumEntryOfType:(LLMQType)quorumType.unsignedIntegerValue withQuorumHash:entry.quorumHash]; - if (quorumEntry.verified) { - [entry mergedWithQuorumEntry:quorumEntry]; - } - } - } - } - return self; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.h b/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.h index f21b2c9c7..0b0167d5b 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.h +++ b/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.h @@ -22,7 +22,16 @@ NS_ASSUME_NONNULL_BEGIN @interface DSMasternodeListDiffService : DSMasternodeListService -- (void)sendReversedHashes:(NSString *)baseBlockHash blockHash:(NSString *)blockHash; +@property (nonatomic, readonly) NSOrderedSet *retrievalQueue; +@property (nonatomic, readonly) NSUInteger retrievalQueueCount; +//@property (nonatomic, readonly) NSUInteger retrievalQueueMaxAmount; + +- (NSUInteger)addToRetrievalQueue:(NSData *)masternodeBlockHashData; +- (NSUInteger)removeFromRetrievalQueue:(NSData *)masternodeBlockHashData; +- (NSUInteger)addToRetrievalQueueArray:(NSArray *_Nonnull)masternodeBlockHashDataArray; +//- (void)cleanListsRetrievalQueue; + +- (void)fetchMasternodeListsToRetrieve:(void (^)(NSOrderedSet *listsToRetrieve))completion; @end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.m b/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.m index 8bb336468..7221514e9 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.m +++ b/DashSync/shared/Models/Masternode/DSMasternodeListDiffService.m @@ -15,66 +15,203 @@ // limitations under the License. // +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChainManager+Protected.h" #import "DSGetMNListDiffRequest.h" #import "DSMasternodeListDiffService.h" #import "DSMasternodeListService+Protected.h" -#import "DSMasternodeListStore+Protected.h" +#import "DSMasternodeManager+Protected.h" +#import "DSTransactionManager+Protected.h" #import "NSString+Dash.h" +@interface DSMasternodeListDiffService () + +@property (nonatomic, assign) NSMutableOrderedSet *retrievalQueue; + +@end + @implementation DSMasternodeListDiffService +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [MLDiffService] ", self.chain.name]; +} + - (void)composeMasternodeListRequest:(NSOrderedSet *)list { for (NSData *blockHashData in list) { // we should check the associated block still exists - if ([self.store hasBlockForBlockHash:blockHashData]) { + if ([self.chain.masternodeManager hasBlockForBlockHash:blockHashData]) { //there is the rare possibility we have the masternode list as a checkpoint, so lets first try that NSUInteger pos = [list indexOfObject:blockHashData]; UInt256 blockHash = blockHashData.UInt256; - DSMasternodeList *masternodeList = [self.delegate masternodeListSerivceDidRequestFileFromBlockHash:self blockHash:blockHash]; - if (masternodeList) { + BOOL success = [self.chain.masternodeManager processRequestFromFileForBlockHash:blockHash]; + if (success) { [self removeFromRetrievalQueue:blockHashData]; - [self checkWaitingForQuorums]; + if (![self retrievalQueueCount]) { + [self.chain.chainManager.transactionManager checkWaitingForQuorums]; + } } else { // we need to go get it - UInt256 prevKnownBlockHash = [self.store closestKnownBlockHashForBlockHash:blockHash]; + uint32_t blockHeight = [self.chain heightForBlockHash:blockHash]; + UInt256 prevKnownBlockHash = [self closestKnownBlockHashForBlockHeight:blockHeight]; UInt256 prevInQueueBlockHash = (pos ? [list objectAtIndex:pos - 1].UInt256 : UINT256_ZERO); - UInt256 previousBlockHash = pos - ? ([self.store heightForBlockHash:prevKnownBlockHash] > [self.store heightForBlockHash:prevInQueueBlockHash] - ? prevKnownBlockHash - : prevInQueueBlockHash) - : prevKnownBlockHash; + u256 *prev_known_block_hash = u256_ctor_u(prevKnownBlockHash); + u256 *prev_in_queue_block_hash = u256_ctor_u(prevInQueueBlockHash); + uint32_t prevKnownHeight = [self.chain heightForBlockHash:u256_cast(prev_known_block_hash)]; + uint32_t prevInQueueBlockHeight = [self.chain heightForBlockHash:u256_cast(prev_in_queue_block_hash)]; +// uint32_t prevKnownHeight = DHeightForBlockHash(self.chain.sharedProcessorObj, prev_known_block_hash); +// uint32_t prevInQueueBlockHeight = DHeightForBlockHash(self.chain.sharedProcessorObj, prev_in_queue_block_hash); + UInt256 previousBlockHash = pos ? (prevKnownHeight > prevInQueueBlockHeight ? prevKnownBlockHash : prevInQueueBlockHash) : prevKnownBlockHash; // request at: every new block - NSAssert(([self.store heightForBlockHash:previousBlockHash] != UINT32_MAX) || uint256_is_zero(previousBlockHash), @"This block height should be known"); +// NSAssert(([self.store heightForBlockHash:previousBlockHash] != UINT32_MAX) || uint256_is_zero(previousBlockHash), @"This block height should be known"); [self requestMasternodeListDiff:previousBlockHash forBlockHash:blockHash]; +// [self requestMasternodeListDiff:@"00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6".hexToData.reverse.UInt256 forBlockHash:@"c21ff900433ace7e6b7841bdfec8c449ca06414b237167e30b00000000000000".hexToData.UInt256]; } } else { - DSLog(@"[%@] Missing block (%@)", self.chain.name, blockHashData.hexString); + DSLog(@"%@ Missing block (%@)", self.logPrefix, blockHashData.hexString); [self removeFromRetrievalQueue:blockHashData]; } } } +- (void)fetchMasternodeListsToRetrieve:(void (^)(NSOrderedSet *listsToRetrieve))completion { + if (![self retrievalQueueCount]) { + DSLog(@"%@ No masternode lists in retrieval", self.logPrefix); + [self.chain.masternodeManager masternodeListServiceEmptiedRetrievalQueue:self]; + return; + } + if ([self.requestsInRetrieval count]) { + DSLog(@"%@ Already in retrieval", self.logPrefix); + return; + } + if ([self peerIsDisconnected]) { + if (self.chain.chainManager.syncPhase != DSChainSyncPhase_Offline) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), self.chain.networkingQueue, ^{ + [self fetchMasternodeListsToRetrieve:completion]; + }); + } + return; + } + completion([self retrievalQueue]); +} + +- (void)dequeueMasternodeListRequest { + [self fetchMasternodeListsToRetrieve:^(NSOrderedSet *list) { + [self composeMasternodeListRequest:list]; + [self startTimeOutObserver]; + }]; +} + +- (NSOrderedSet *)retrievalQueue { + @synchronized (_retrievalQueue) { + return [_retrievalQueue copy]; + } +} + +- (NSUInteger)retrievalQueueCount { + @synchronized (_retrievalQueue) { + return [_retrievalQueue count]; + } +} + +- (NSUInteger)addToRetrievalQueue:(NSData *)masternodeBlockHashData { + NSUInteger newCount = 0, maxAmount = 0; + @synchronized (_retrievalQueue) { + [_retrievalQueue addObject:uint256_data(masternodeBlockHashData.UInt256)]; + newCount = [_retrievalQueue count]; + maxAmount = MAX(self.retrievalQueueMaxAmount, newCount); + self.retrievalQueueMaxAmount = maxAmount; + [_retrievalQueue sortUsingComparator:^NSComparisonResult(NSData *obj1, NSData *obj2) { + return ([self.chain heightForBlockHash:obj1.UInt256] < [self.chain heightForBlockHash:obj2.UInt256]) ? NSOrderedAscending : NSOrderedDescending; + }]; + } + [self notifyQueueChange:newCount maxAmount:maxAmount]; + return newCount; + +} + +- (NSUInteger)addToRetrievalQueueArray:(NSArray *_Nonnull)masternodeBlockHashDataArray { + NSMutableArray *nonEmptyBlockHashes = [NSMutableArray array]; + NSUInteger newCount = 0, maxAmount = 0; + @synchronized (_retrievalQueue) { + for (NSData *blockHashData in masternodeBlockHashDataArray) { + NSAssert(uint256_is_not_zero(blockHashData.UInt256), @"We should not be adding an empty block hash"); + if (uint256_is_not_zero(blockHashData.UInt256)) { + [nonEmptyBlockHashes addObject:blockHashData]; + } + } + [_retrievalQueue addObjectsFromArray:nonEmptyBlockHashes]; + newCount = [_retrievalQueue count]; + maxAmount = MAX(self.retrievalQueueMaxAmount, newCount); + self.retrievalQueueMaxAmount = maxAmount; + [_retrievalQueue sortUsingComparator:^NSComparisonResult(NSData *obj1, NSData *obj2) { + return ([self.chain heightForBlockHash:obj1.UInt256] < [self.chain heightForBlockHash:obj2.UInt256]) ? NSOrderedAscending : NSOrderedDescending; + }]; + } + [self notifyQueueChange:newCount maxAmount:maxAmount]; + return newCount; +} + +- (NSUInteger)removeFromRetrievalQueue:(NSData *)masternodeBlockHashData { + NSUInteger newCount = 0, maxAmount = 0; + @synchronized (_retrievalQueue) { + [_retrievalQueue removeObject:masternodeBlockHashData]; + newCount = [_retrievalQueue count]; + maxAmount = MAX(self.retrievalQueueMaxAmount, newCount); + + } + [self notifyQueueChange:newCount maxAmount:maxAmount]; + return newCount; +} + +- (void)cleanListsRetrievalQueue { + @synchronized (_retrievalQueue) { + [_retrievalQueue removeAllObjects]; + } +} + + - (void)requestMasternodeListDiff:(UInt256)previousBlockHash forBlockHash:(UInt256)blockHash { DSGetMNListDiffRequest *request = [DSGetMNListDiffRequest requestWithBaseBlockHash:previousBlockHash blockHash:blockHash]; DSMasternodeListRequest *matchedRequest = [self requestInRetrievalFor:previousBlockHash blockHash:blockHash]; if (matchedRequest) { - DSLog(@"[%@] •••• mnlistdiff request with such a range already in retrieval: %u..%u %@ .. %@", self.chain.name, [self.store heightForBlockHash:previousBlockHash], [self.store heightForBlockHash:blockHash], uint256_hex(previousBlockHash), uint256_hex(blockHash)); +// DSLog(@"[%@] •••• mnlistdiff request with such a range already in retrieval: %u..%u %@ .. %@", self.chain.name, [self.store heightForBlockHash:previousBlockHash], [self.store heightForBlockHash:blockHash], uint256_hex(previousBlockHash), uint256_hex(blockHash)); return; } - DSLog(@"[%@] •••• requestMasternodeListDiff: %u..%u %@ .. %@", self.chain.name, [self.store heightForBlockHash:previousBlockHash], [self.store heightForBlockHash:blockHash], uint256_hex(previousBlockHash), uint256_hex(blockHash)); + uint32_t prev_h = [self.chain heightForBlockHash:previousBlockHash]; + uint32_t h = [self.chain heightForBlockHash:blockHash]; + +// uint32_t prev_h = DHeightForBlockHash(self.chain.sharedProcessorObj, u256_ctor_u(previousBlockHash)); +// uint32_t h = DHeightForBlockHash(self.chain.sharedProcessorObj, u256_ctor_u(blockHash)); + DSLog(@"%@ Request: %u..%u %@ .. %@", self.logPrefix, prev_h, h, uint256_hex(previousBlockHash), uint256_hex(blockHash)); + if (prev_h == 0) { + DSLog(@"%@ Zero height", self.logPrefix); + } + if (prev_h == 530000) { + DSLog(@"start from checkpoint"); + } [self sendMasternodeListRequest:request]; } +- (void)notifyQueueChange:(NSUInteger)newCount maxAmount:(NSUInteger)maxAmount { + @synchronized (self.chain.chainManager.syncState) { + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueCount = (uint32_t) newCount; + self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueMaxAmount = (uint32_t) maxAmount; + [self.chain.chainManager notifySyncStateChanged]; + } + +} + /// test-only /// Used for fast obtaining list diff chain for specific block hashes like this: /// //DSMasternodeListDiffService *service = self.masternodeListDiffService; // [service sendReversedHashes:@"00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c" blockHash:@"000000e6b51b9aba9754e6b4ef996ef1d142d6cfcc032c1fd7fc78ca6663ee0a"]; // [service sendReversedHashes:@"000000e6b51b9aba9754e6b4ef996ef1d142d6cfcc032c1fd7fc78ca6663ee0a" blockHash:@"00000009d7c0bcb59acf741f25239f45820eea178b74597d463ca80e104f753b"]; --(void)sendReversedHashes:(NSString *)baseBlockHash blockHash:(NSString *)blockHash { - DSGetMNListDiffRequest *request = [DSGetMNListDiffRequest requestWithBaseBlockHash:baseBlockHash.hexToData.reverse.UInt256 - blockHash:blockHash.hexToData.reverse.UInt256]; - [self sendMasternodeListRequest:request]; -} +//-(void)sendReversedHashes:(NSString *)baseBlockHash blockHash:(NSString *)blockHash { +// DSGetMNListDiffRequest *request = [DSGetMNListDiffRequest requestWithBaseBlockHash:baseBlockHash.hexToData.reverse.UInt256 +// blockHash:blockHash.hexToData.reverse.UInt256]; +// [self sendMasternodeListRequest:request]; +//} @end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListService+Protected.h b/DashSync/shared/Models/Masternode/DSMasternodeListService+Protected.h index 6d7e838af..fa4f81aaa 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListService+Protected.h +++ b/DashSync/shared/Models/Masternode/DSMasternodeListService+Protected.h @@ -15,20 +15,26 @@ // limitations under the License. // -#import "DSMasternodeListStore.h" -#import "DSMnDiffProcessingResult.h" +#import "DSKeyManager.h" +#import "DSMasternodeListService.h" #import "DSPeer.h" NS_ASSUME_NONNULL_BEGIN @interface DSMasternodeListService (Protected) -@property (nonatomic, readonly) DSMasternodeListStore *store; +//@property (nonatomic, assign) NSMutableOrderedSet *retrievalQueue; -- (void)checkWaitingForQuorums; -- (void)updateAfterProcessingMasternodeListWithBlockHash:(NSData *)blockHashData fromPeer:(DSPeer *)peer; -- (BOOL)shouldProcessDiffResult:(DSMnDiffProcessingResult *)diffResult skipPresenceInRetrieval:(BOOL)skipPresenceInRetrieval; -- (DSMasternodeListRequest*__nullable)requestInRetrievalFor:(UInt256)baseBlockHash blockHash:(UInt256)blockHash; +- (NSString *)logPrefix; + +//- (void)updateAfterProcessingMasternodeListWithBlockHash:(NSData *)blockHashData fromPeer:(DSPeer *)peer; +- (BOOL)shouldProcessDiffResult:(u256 *)block_hash + isValid:(BOOL)isValid + skipPresenceInRetrieval:(BOOL)skipPresenceInRetrieval; +//- (BOOL)shouldProcessDiffResult:(DSMnDiffProcessingResult *)diffResult skipPresenceInRetrieval:(BOOL)skipPresenceInRetrieval; +//- (DSMasternodeListRequest*__nullable)requestInRetrievalFor:(UInt256)baseBlockHash blockHash:(UInt256)blockHash; +- (UInt256)closestKnownBlockHashForBlockHeight:(uint32_t)blockHeight; +- (void)startTimeOutObserver; @end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListService.h b/DashSync/shared/Models/Masternode/DSMasternodeListService.h index ae1c225f8..b82346eea 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListService.h +++ b/DashSync/shared/Models/Masternode/DSMasternodeListService.h @@ -18,8 +18,6 @@ #import "DSChain.h" #import "DSInsightManager.h" #import "DSMasternodeListRequest.h" -#import "DSMasternodeListStore.h" -#import "DSPeer.h" #import NS_ASSUME_NONNULL_BEGIN @@ -30,60 +28,45 @@ FOUNDATION_EXPORT NSString *const DSMasternodeListDiffValidationErrorNotificatio #define CHAIN_FAULTY_DML_MASTERNODE_PEERS [NSString stringWithFormat:@"%@_%@", peer.chain.uniqueID, FAULTY_DML_MASTERNODE_PEERS] #define MAX_FAULTY_DML_PEERS 1 -typedef NS_ENUM(NSUInteger, DSMasternodeListRequestMode) { - DSMasternodeListRequestMode_MNLISTDIFF = 1, - DSMasternodeListRequestMode_QRINFO = 2, - DSMasternodeListRequestMode_MIXED = DSMasternodeListRequestMode_MNLISTDIFF | DSMasternodeListRequestMode_QRINFO -}; -@class DSMasternodeListService; - -@protocol DSMasternodeListServiceDelegate - -- (DSMasternodeList *__nullable)masternodeListSerivceDidRequestFileFromBlockHash:(DSMasternodeListService *)service blockHash:(UInt256)blockHash; -- (void)masternodeListSerivceExceededMaxFailuresForMasternodeList:(DSMasternodeListService *)service blockHash:(UInt256)blockHash; -- (void)masternodeListSerivceEmptiedRetrievalQueue:(DSMasternodeListService *)service; - -@end +@class DSPeer; @interface DSMasternodeListService : NSObject @property (nonatomic, readonly, nonnull) DSChain *chain; -@property (nonatomic, nullable) DSMasternodeList *currentMasternodeList; @property (nonatomic, readonly) NSMutableSet *requestsInRetrieval; -@property (nonatomic, readonly) NSMutableOrderedSet *retrievalQueue; -@property (nonatomic, readonly) NSMutableOrderedSet *neededQueue; // TODO: Make storing hashes for tip list separately, to avoid -@property (nonatomic, readonly) NSUInteger retrievalQueueCount; -@property (nonatomic, readonly) NSUInteger retrievalQueueMaxAmount; -@property (nullable, nonatomic, weak) id delegate; +//@property (nonatomic, readonly) NSOrderedSet *retrievalQueue; +//@property (nonatomic, readonly) NSUInteger retrievalQueueCount; +@property (nonatomic, readwrite) NSUInteger retrievalQueueMaxAmount; @property (nonatomic, assign) uint16_t timedOutAttempt; @property (nonatomic, assign) uint16_t timeOutObserverTry; -- (instancetype)initWithChain:(DSChain *)chain store:(DSMasternodeListStore *)store delegate:(id)delegate; +- (instancetype)initWithChain:(DSChain *)chain; -- (void)populateRetrievalQueueWithBlockHashes:(NSOrderedSet *)blockHashes; -- (void)getRecentMasternodeList; - (void)dequeueMasternodeListRequest; - (void)stop; -- (void)addToRetrievalQueue:(NSData *)masternodeBlockHashData; -- (void)addToRetrievalQueueArray:(NSArray *)masternodeBlockHashDataArray; +- (BOOL)peerIsDisconnected; + - (void)cleanAllLists; -- (void)cleanListsRetrievalQueue; - (void)cleanRequestsInRetrieval; -- (void)composeMasternodeListRequest:(NSOrderedSet *)list; +//- (void)composeMasternodeListRequest:(NSOrderedSet *)list; -- (void)fetchMasternodeListsToRetrieve:(void (^)(NSOrderedSet *listsToRetrieve))completion; -- (void)removeFromRetrievalQueue:(NSData *)masternodeBlockHashData; -- (BOOL)removeRequestInRetrievalForBaseBlockHash:(UInt256)baseBlockHash blockHash:(UInt256)blockHash; +//- (void)fetchMasternodeListsToRetrieve:(void (^)(NSOrderedSet *listsToRetrieve))completion; -- (BOOL)hasLatestBlockInRetrievalQueueWithHash:(UInt256)blockHash; +//- (NSUInteger)addToRetrievalQueue:(NSData *)masternodeBlockHashData; +//- (void)removeFromRetrievalQueue:(NSData *)masternodeBlockHashData; +- (void)cleanListsRetrievalQueue; +- (BOOL)removeRequestInRetrievalForBaseBlockHash:(UInt256)baseBlockHash blockHash:(UInt256)blockHash; - (void)disconnectFromDownloadPeer; -- (void)issueWithMasternodeListFromPeer:(DSPeer *)peer; +//- (void)issueWithMasternodeListFromPeer:(DSPeer *)peer; - (void)sendMasternodeListRequest:(DSMasternodeListRequest *)request; +//- (void)checkWaitingForQuorums; +- (DSMasternodeListRequest*__nullable)requestInRetrievalFor:(UInt256)baseBlockHash blockHash:(UInt256)blockHash; + @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListService.m b/DashSync/shared/Models/Masternode/DSMasternodeListService.m index 17c7dd746..7e9f93746 100644 --- a/DashSync/shared/Models/Masternode/DSMasternodeListService.m +++ b/DashSync/shared/Models/Masternode/DSMasternodeListService.m @@ -15,29 +15,19 @@ // limitations under the License. // -#import "DSDAPIClient.h" -#import "DSMasternodeListService.h" #import "DSMasternodeListService+Protected.h" -#import "DSMasternodeListStore+Protected.h" +#import "DSChain+Params.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" -#import "DSTransactionManager+Protected.h" #import "NSData+Dash.h" @interface DSMasternodeListService () -// List Hashes of blocks for which masternode lists are need to be requested -@property (nonatomic) DSMasternodeListStore *store; -@property (nonatomic, strong) NSMutableOrderedSet *retrievalQueue; -@property (nonatomic, strong) NSMutableOrderedSet *neededQueue; -@property (nonatomic, assign) NSUInteger retrievalQueueMaxAmount; -// List: list of block ranges baseBlockHash + blockHash + @property (nonatomic, strong) NSMutableSet *requestsInRetrieval; @property (nonatomic, strong) dispatch_source_t timeoutTimer; @@ -45,24 +35,28 @@ @interface DSMasternodeListService () @implementation DSMasternodeListService -- (instancetype)initWithChain:(DSChain *)chain store:(DSMasternodeListStore *)store delegate:(id)delegate { +- (instancetype)initWithChain:(DSChain *)chain { NSParameterAssert(chain); if (!(self = [super init])) return nil; _chain = chain; - _store = store; - _delegate = delegate; - _retrievalQueue = [NSMutableOrderedSet orderedSet]; _requestsInRetrieval = [NSMutableSet set]; _timedOutAttempt = 0; _timeOutObserverTry = 0; return self; } +- (UInt256)closestKnownBlockHashForBlockHeight:(uint32_t)blockHeight { + u256 *closest_block_hash = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_closest_known_masternode_list_block_hash(self.chain.sharedProcessorObj, blockHeight); + UInt256 known = u256_cast(closest_block_hash); + u256_dtor(closest_block_hash); + return known; +} + - (void)startTimeOutObserver { [self cancelTimeOutObserver]; @synchronized (self) { NSSet *requestsInRetrieval = [self.requestsInRetrieval copy]; - NSUInteger masternodeListCount = [self.store knownMasternodeListsCount]; +// uintptr_t masternodeListCount = DKnownMasternodeListsCount(self.chain.sharedCacheObj); self.timeOutObserverTry++; uint16_t timeOutObserverTry = self.timeOutObserverTry; dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * (self.timedOutAttempt + 1) * NSEC_PER_SEC)); @@ -77,8 +71,10 @@ - (void)startTimeOutObserver { NSSet *requestsInRetrieval2 = [self.requestsInRetrieval copy]; NSMutableSet *leftToGet = [requestsInRetrieval mutableCopy]; [leftToGet intersectSet:requestsInRetrieval2]; - if ((masternodeListCount == [self.store knownMasternodeListsCount]) && [requestsInRetrieval isEqualToSet:leftToGet]) { - DSLog(@"[%@] %@ TimedOut", self.chain.name, self); +// uintptr_t count = DKnownMasternodeListsCount(self.chain.sharedCacheObj); + + if (/*(masternodeListCount == count) &&*/ [requestsInRetrieval isEqualToSet:leftToGet]) { + DSLog(@"%@ TimedOut -> dequeueMasternodeListRequest", self.logPrefix); self.timedOutAttempt++; [self disconnectFromDownloadPeer]; [self cleanRequestsInRetrieval]; @@ -102,30 +98,21 @@ - (void)cancelTimeOutObserver { } } -- (NSString *)logListSet:(NSOrderedSet *)list { - NSString *str = @"\n"; - for (NSData *blockHashData in list) { - str = [str stringByAppendingString:[NSString stringWithFormat:@"•••• -> %d: %@,\n", - [self.store heightForBlockHash:blockHashData.UInt256], blockHashData.hexString]]; - } - return str; -} - -- (void)checkWaitingForQuorums { - if (![self retrievalQueueCount]) { - [self.chain.chainManager.transactionManager checkWaitingForQuorums]; - } -} - -- (void)composeMasternodeListRequest:(NSOrderedSet *)list { - /* Should be overriden */ -} +//- (void)checkWaitingForQuorums { +// if (![self retrievalQueueCount]) { +// [self.chain.chainManager.transactionManager checkWaitingForQuorums]; +// } +//} +//- (void)composeMasternodeListRequest:(NSOrderedSet *)list { +// /* Should be overriden */ +//} +// - (void)dequeueMasternodeListRequest { - [self fetchMasternodeListsToRetrieve:^(NSOrderedSet *list) { - [self composeMasternodeListRequest:list]; - [self startTimeOutObserver]; - }]; +// [self fetchMasternodeListsToRetrieve:^(NSOrderedSet *list) { +// [self composeMasternodeListRequest:list]; +// [self startTimeOutObserver]; +// }]; } - (void)stop { @@ -133,207 +120,95 @@ - (void)stop { [self cleanAllLists]; } -- (void)getRecentMasternodeList { - @synchronized(self.retrievalQueue) { - DSMerkleBlock *merkleBlock = [self.chain blockFromChainTip:0]; - if (!merkleBlock) { - // sometimes it happens while rescan - DSLog(@"[%@] getRecentMasternodeList: (no block exist) for tip", self.chain.name); - return; - } - UInt256 merkleBlockHash = merkleBlock.blockHash; - if ([self hasLatestBlockInRetrievalQueueWithHash:merkleBlockHash]) { - //we are asking for the same as the last one - return; - } - if ([self.store addBlockToValidationQueue:merkleBlock]) { - DSLog(@"[%@] MasternodeListService.Getting masternode list %u", self.chain.name, merkleBlock.height); - NSData *merkleBlockHashData = uint256_data(merkleBlockHash); - BOOL emptyRequestQueue = ![self retrievalQueueCount]; - [self addToRetrievalQueue:merkleBlockHashData]; - if (emptyRequestQueue) { - [self dequeueMasternodeListRequest]; - } - } - } -} - -- (void)setCurrentMasternodeList:(DSMasternodeList *_Nullable)currentMasternodeList { - if (self.chain.isEvolutionEnabled) { - if (!_currentMasternodeList) { - for (DSSimplifiedMasternodeEntry *masternodeEntry in currentMasternodeList.simplifiedMasternodeEntries) { - if (masternodeEntry.isValid) { - [self.chain.chainManager.DAPIClient addDAPINodeByAddress:masternodeEntry.ipAddressString]; - } - } - } else { - NSDictionary *updates = [currentMasternodeList listOfChangedNodesComparedTo:_currentMasternodeList]; - NSArray *added = updates[MASTERNODE_LIST_ADDED_NODES]; - NSArray *removed = updates[MASTERNODE_LIST_REMOVED_NODES]; - NSArray *addedValidity = updates[MASTERNODE_LIST_ADDED_VALIDITY]; - NSArray *removedValidity = updates[MASTERNODE_LIST_REMOVED_VALIDITY]; - for (DSSimplifiedMasternodeEntry *masternodeEntry in added) { - if (masternodeEntry.isValid) { - [self.chain.chainManager.DAPIClient addDAPINodeByAddress:masternodeEntry.ipAddressString]; - } - } - for (DSSimplifiedMasternodeEntry *masternodeEntry in addedValidity) { - [self.chain.chainManager.DAPIClient addDAPINodeByAddress:masternodeEntry.ipAddressString]; - } - for (DSSimplifiedMasternodeEntry *masternodeEntry in removed) { - [self.chain.chainManager.DAPIClient removeDAPINodeByAddress:masternodeEntry.ipAddressString]; - } - for (DSSimplifiedMasternodeEntry *masternodeEntry in removedValidity) { - [self.chain.chainManager.DAPIClient removeDAPINodeByAddress:masternodeEntry.ipAddressString]; - } - } - } - bool changed = _currentMasternodeList != currentMasternodeList; - _currentMasternodeList = currentMasternodeList; - if (changed) { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSCurrentMasternodeListDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain, DSMasternodeManagerNotificationMasternodeListKey: self.currentMasternodeList ? self.currentMasternodeList : [NSNull null]}]; - }); - } -} - -- (void)populateRetrievalQueueWithBlockHashes:(NSOrderedSet *)blockHashes { - @synchronized(self.retrievalQueue) { - NSArray *orderedBlockHashes = [blockHashes sortedArrayUsingComparator:^NSComparisonResult(NSData *_Nonnull obj1, NSData *_Nonnull obj2) { - uint32_t height1 = [self.store heightForBlockHash:obj1.UInt256]; - uint32_t height2 = [self.store heightForBlockHash:obj2.UInt256]; - return (height1 > height2) ? NSOrderedDescending : NSOrderedAscending; - }]; - [self addToRetrievalQueueArray:orderedBlockHashes]; - } - [self dequeueMasternodeListRequest]; -} - -- (BOOL)shouldProcessDiffResult:(DSMnDiffProcessingResult *)diffResult skipPresenceInRetrieval:(BOOL)skipPresenceInRetrieval { - DSMasternodeList *masternodeList = diffResult.masternodeList; - UInt256 masternodeListBlockHash = masternodeList.blockHash; - NSData *masternodeListBlockHashData = uint256_data(masternodeListBlockHash); - BOOL hasInRetrieval = [self.retrievalQueue containsObject:masternodeListBlockHashData]; -// uint32_t masternodeListBlockHeight = [self.store heightForBlockHash:masternodeListBlockHash]; - BOOL shouldNot = !hasInRetrieval && !skipPresenceInRetrieval; - //DSLog(@"•••• shouldProcessDiffResult: %d: %@ %d", masternodeListBlockHeight, uint256_reverse_hex(masternodeListBlockHash), !shouldNot); - if (shouldNot) { - //We most likely wiped data in the meantime - [self cleanRequestsInRetrieval]; - [self dequeueMasternodeListRequest]; - return NO; - } - BOOL isValid = [diffResult isTotallyValid]; - if (!isValid) { - DSLog(@"[%@] Invalid diff result: %@", self.chain.name, diffResult.debugDescription); - } - return isValid; - -} - -- (void)updateAfterProcessingMasternodeListWithBlockHash:(NSData *)blockHashData fromPeer:(DSPeer *)peer { - [self removeFromRetrievalQueue:blockHashData]; - [self dequeueMasternodeListRequest]; - [self checkWaitingForQuorums]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; -} - -- (void)addToRetrievalQueue:(NSData *)masternodeBlockHashData { - NSAssert(uint256_is_not_zero(masternodeBlockHashData.UInt256), @"the hash data must not be empty"); - [self.retrievalQueue addObject:masternodeBlockHashData]; - [self updateMasternodeRetrievalQueue]; -} - -- (void)addToRetrievalQueueArray:(NSArray *)masternodeBlockHashDataArray { - NSMutableArray *nonEmptyBlockHashes = [NSMutableArray array]; - for (NSData *blockHashData in masternodeBlockHashDataArray) { - NSAssert(uint256_is_not_zero(blockHashData.UInt256), @"We should not be adding an empty block hash"); - if (uint256_is_not_zero(blockHashData.UInt256)) { - [nonEmptyBlockHashes addObject:blockHashData]; - } - } - [self.retrievalQueue addObjectsFromArray:nonEmptyBlockHashes]; - [self updateMasternodeRetrievalQueue]; -} - -- (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 = (uint32_t) self.retrievalQueueMaxAmount; - DSLog(@"[%@] Masternode list queue updated: %f/%lu", self.chain.name, count, self.retrievalQueueMaxAmount); - [self.chain.chainManager notifySyncStateChanged]; - } -} +//- (void)updateAfterProcessingMasternodeListWithBlockHash:(NSData *)blockHashData fromPeer:(DSPeer *)peer { +// +// [self removeFromRetrievalQueue:blockHashData]; +// DSLog(@"%@ updateAfterProcessingMasternodeListWithBlockHash %@ -> dequeueMasternodeListRequest (mn)", self.logPrefix, blockHashData.hexString); +// [self dequeueMasternodeListRequest]; +// [self checkWaitingForQuorums]; +// [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; +//} + +//- (NSUInteger)addToRetrievalQueue:(NSData *)masternodeBlockHashData { +// @synchronized (_retrievalQueue) { +// [_retrievalQueue addObject:uint256_data(masternodeBlockHashData.UInt256)]; +// NSUInteger newCount = [_retrievalQueue count]; +// _retrievalQueueMaxAmount = MAX(self.retrievalQueueMaxAmount, newCount); +// [_retrievalQueue sortUsingComparator:^NSComparisonResult(NSData *obj1, NSData *obj2) { +// if ([self.chain heightForBlockHash:obj1.UInt256] < [self.chain heightForBlockHash:obj2.UInt256]) { +// return NSOrderedAscending; +// } else { +// return NSOrderedDescending; +// } +// }]; +// return newCount; +// } +//} +// +//- (void)removeFromRetrievalQueue:(NSData *)masternodeBlockHashData { +// [_retrievalQueue removeObject:masternodeBlockHashData]; +//// [self.retrievalQueue ] +//// DMnDiffQueueRemove(self.chain.sharedProcessorObj, u256_ctor(masternodeBlockHashData)); +//} - (void)cleanRequestsInRetrieval { [self.requestsInRetrieval removeAllObjects]; } -- (void)cleanListsRetrievalQueue { - [self.retrievalQueue removeAllObjects]; - @synchronized (self.chain.chainManager.syncState) { - self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueCount = 0; - self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueMaxAmount = (uint32_t) self.retrievalQueueMaxAmount; - DSLog(@"[%@] Masternode list queue cleaned up: 0/%lu", self.chain.name, self.retrievalQueueMaxAmount); - [self.chain.chainManager notifySyncStateChanged]; - } -} +- (void)cleanListsRetrievalQueue {} - (void)cleanAllLists { - self.currentMasternodeList = nil; [self cleanListsRetrievalQueue]; [self cleanRequestsInRetrieval]; + // dispatch_async(dispatch_get_main_queue(), ^{ + // [[NSNotificationCenter defaultCenter] postNotificationName:DSCurrentMasternodeListDidChangeNotification + // object:nil + // userInfo:@{ + // DSChainManagerNotificationChainKey: self.chain, + // DSMasternodeManagerNotificationMasternodeListKey: [NSNull null] + // }]; + // }); + } - (DSPeerManager *)peerManager { return self.chain.chainManager.peerManager; } -- (NSUInteger)retrievalQueueCount { - return self.retrievalQueue.count; -} - -- (void)updateMasternodeRetrievalQueue { - NSUInteger currentCount = self.retrievalQueue.count; - 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 = (uint32_t) currentCount; - self.chain.chainManager.syncState.masternodeListSyncInfo.retrievalQueueMaxAmount = (uint32_t) 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 { - if (![self retrievalQueueCount]) { - DSLog(@"[%@] No masternode lists in retrieval: %@", self.chain.name, self); - [self.delegate masternodeListSerivceEmptiedRetrievalQueue:self]; - return; - } - if ([self.requestsInRetrieval count]) { - DSLog(@"[%@] A masternode list is already in retrieval: %@", self.chain.name, self); - return; - } +//- (NSOrderedSet *)retrievalQueue { +// @synchronized (_retrievalQueue) { +// return [_retrievalQueue copy]; +// } +// +//// indexmap_IndexSet_u8_32 *queue = dash_spv_masternode_processor_processing_processor_cache_MasternodeProcessorCache_mn_list_retrieval_queue(self.chain.sharedCacheObj); +//// NSMutableOrderedSet *set = [NSMutableOrderedSet orderedSetWithCapacity:queue->count]; +//// for (int i = 0; i < queue->count; i++) { +//// [set addObject:NSDataFromPtr(queue->values[i])]; +//// } +//// indexmap_IndexSet_u8_32_destroy(queue); +//// return [_retrievalQueue copy]; +//} +// +//- (NSUInteger)retrievalQueueCount { +// @synchronized (_retrievalQueue) { +// return [_retrievalQueue count]; +// } +//// return DMnDiffQueueCount(self.chain.sharedCacheObj); +//} +//- (NSUInteger)retrievalQueueMaxAmount { +// return DMnDiffQueueMaxAmount(self.chain.sharedCacheObj); +//} + +- (BOOL)peerIsDisconnected { BOOL peerIsDisconnected; @synchronized (self.peerManager.downloadPeer) { peerIsDisconnected = !self.peerManager.downloadPeer || self.peerManager.downloadPeer.status != DSPeerStatus_Connected; } - if (peerIsDisconnected) { - if (self.chain.chainManager.syncPhase != DSChainSyncPhase_Offline) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), self.chain.networkingQueue, ^{ - [self fetchMasternodeListsToRetrieve:completion]; - }); - } - return; - } - completion([self.retrievalQueue copy]); + return peerIsDisconnected; } + + - (DSMasternodeListRequest*__nullable)requestInRetrievalFor:(UInt256)baseBlockHash blockHash:(UInt256)blockHash { DSMasternodeListRequest *matchedRequest = nil; for (DSMasternodeListRequest *request in [self.requestsInRetrieval copy]) { @@ -347,61 +222,43 @@ - (DSMasternodeListRequest*__nullable)requestInRetrievalFor:(UInt256)baseBlockHa - (BOOL)removeRequestInRetrievalForBaseBlockHash:(UInt256)baseBlockHash blockHash:(UInt256)blockHash { DSMasternodeListRequest *matchedRequest = [self requestInRetrievalFor:baseBlockHash blockHash:blockHash]; - if (!matchedRequest) { - #if DEBUG - NSSet *requestsInRetrieval; - @synchronized (self.requestsInRetrieval) { - requestsInRetrieval = [self.requestsInRetrieval copy]; - } - NSMutableArray *requestsInRetrievalStrings = [NSMutableArray array]; - for (DSMasternodeListRequest *requestInRetrieval in requestsInRetrieval) { - [requestsInRetrievalStrings addObject:[requestInRetrieval logWithBlockHeightLookup:^uint32_t(UInt256 blockHash) { - return [self.store heightForBlockHash:blockHash]; - }]]; - } - DSLog(@"[%@] A masternode list (%@ .. %@) was received that is not set to be retrieved (%@)", self.chain.name, uint256_hex(baseBlockHash), uint256_hex(blockHash), [requestsInRetrievalStrings componentsJoinedByString:@", "]); - #endif /* DEBUG */ - return NO; - } + if (!matchedRequest) return NO; @synchronized (self.requestsInRetrieval) { [self.requestsInRetrieval removeObject:matchedRequest]; } return YES; } -- (BOOL)hasLatestBlockInRetrievalQueueWithHash:(UInt256)blockHash { - return [self.retrievalQueue lastObject] && uint256_eq(blockHash, [self.retrievalQueue lastObject].UInt256); -} - - (void)disconnectFromDownloadPeer { [self.peerManager.downloadPeer disconnect]; } -- (void)issueWithMasternodeListFromPeer:(DSPeer *)peer { - [self.peerManager peerMisbehaving:peer errorMessage:@"Issue with Deterministic Masternode list"]; - NSArray *faultyPeers = [[NSUserDefaults standardUserDefaults] arrayForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; - if (faultyPeers.count >= MAX_FAULTY_DML_PEERS) { - DSLog(@"[%@] Exceeded max failures for masternode list, starting from scratch", self.chain.name); - //no need to remove local masternodes - [self cleanListsRetrievalQueue]; - [self.store deleteAllOnChain]; - [self.delegate masternodeListSerivceExceededMaxFailuresForMasternodeList:self blockHash:self.currentMasternodeList.blockHash]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; - [self getRecentMasternodeList]; - } else { - if (!faultyPeers) { - faultyPeers = @[peer.location]; - } else if (![faultyPeers containsObject:peer.location]) { - faultyPeers = [faultyPeers arrayByAddingObject:peer.location]; - } - [[NSUserDefaults standardUserDefaults] setObject:faultyPeers forKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; - [self dequeueMasternodeListRequest]; - } - [self.chain.chainManager notify:DSMasternodeListDiffValidationErrorNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; -} +//- (void)issueWithMasternodeListFromPeer:(DSPeer *)peer { +// [self.peerManager peerMisbehaving:peer errorMessage:@"Issue with Deterministic Masternode list"]; +// NSArray *faultyPeers = [[NSUserDefaults standardUserDefaults] arrayForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; +// if (faultyPeers.count >= MAX_FAULTY_DML_PEERS) { +// DSLog(@"%@ Exceeded max failures for masternode list, starting from scratch", self.logPrefix); +// //no need to remove local masternodes +// [self cleanListsRetrievalQueue]; +//// [self.store deleteAllOnChain]; +//// [self.store removeOldMasternodeLists]; +// [[NSUserDefaults standardUserDefaults] removeObjectForKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; +// [self.chain.masternodeManager getRecentMasternodeList]; +// } else { +// if (!faultyPeers) { +// faultyPeers = @[peer.location]; +// } else if (![faultyPeers containsObject:peer.location]) { +// faultyPeers = [faultyPeers arrayByAddingObject:peer.location]; +// } +// [[NSUserDefaults standardUserDefaults] setObject:faultyPeers forKey:CHAIN_FAULTY_DML_MASTERNODE_PEERS]; +// DSLog(@"%@ Failure %lu for masternode list from peer: %@", self.logPrefix, (unsigned long)faultyPeers.count, peer); +// [self dequeueMasternodeListRequest]; +// } +// [self.chain.chainManager notify:DSMasternodeListDiffValidationErrorNotification userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; +//} - (void)sendMasternodeListRequest:(DSMasternodeListRequest *)request { -// DSLog(@"•••• sendMasternodeListRequest: %@", [request toData].hexString); + // DSLog(@"•••• sendMasternodeListRequest: %@", [request toData].hexString); [self.peerManager sendRequest:request]; @synchronized (self.requestsInRetrieval) { [self.requestsInRetrieval addObject:request]; diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListStore+Protected.h b/DashSync/shared/Models/Masternode/DSMasternodeListStore+Protected.h deleted file mode 100644 index a9954344d..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeListStore+Protected.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "BigIntTypes.h" -#import "DSMasternodeList.h" -#import "DSMasternodeListStore.h" -#import "DSMerkleBlock.h" -#import "DSQuorumEntry.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSMasternodeListStore (Protected) - -@property (nonatomic, readwrite, nullable) DSMasternodeList *masternodeListAwaitingQuorumValidation; -@property (nonatomic, readonly) NSMutableSet *masternodeListQueriesNeedingQuorumsValidated; -@property (nonatomic, readwrite, assign) UInt256 lastQueriedBlockHash; //last by height, not by time queried - -- (void)savePlatformPingInfoForEntries:(NSArray *)entries - inContext:(NSManagedObjectContext *)context; -//- (void)checkPingTimesForMasternodesInContext:(NSManagedObjectContext *)context withCompletion:(void (^)(NSMutableDictionary *pingTimes, NSMutableDictionary *errors))completion; -- (UInt256)closestKnownBlockHashForBlockHash:(UInt256)blockHash; -- (DSQuorumEntry *)quorumEntryForChainLockRequestID:(UInt256)requestID forMerkleBlock:(DSMerkleBlock *)merkleBlock; -- (DSQuorumEntry *)quorumEntryForInstantSendRequestID:(UInt256)requestID forMerkleBlock:(DSMerkleBlock *)merkleBlock; - -- (BOOL)addBlockToValidationQueue:(DSMerkleBlock *)merkleBlock; -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListStore.h b/DashSync/shared/Models/Masternode/DSMasternodeListStore.h deleted file mode 100644 index 89085e3aa..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeListStore.h +++ /dev/null @@ -1,76 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "BigIntTypes.h" -#import "DSChain.h" -#import "DSMasternodeList.h" -#import "DSQuorumSnapshot.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -FOUNDATION_EXPORT NSString *const DSCurrentMasternodeListDidChangeNotification; -FOUNDATION_EXPORT NSString *const DSMasternodeListDidChangeNotification; -FOUNDATION_EXPORT NSString *const DSMasternodeManagerNotificationMasternodeListKey; -FOUNDATION_EXPORT NSString *const DSQuorumListDidChangeNotification; - -#define CHAINLOCK_ACTIVATION_HEIGHT 1088640 - -@interface DSMasternodeListStore : NSObject - -@property (nonatomic, readonly) NSUInteger knownMasternodeListsCount; -@property (nonatomic, readonly) NSArray *recentMasternodeLists; -@property (nonatomic, readonly) uint32_t earliestMasternodeListBlockHeight; -@property (nonatomic, readonly) uint32_t lastMasternodeListBlockHeight; -@property (nonatomic, readonly) NSMutableDictionary *masternodeListsByBlockHash; -@property (nonatomic, readonly) NSMutableSet *masternodeListsBlockHashStubs; -@property (nonatomic, readonly) NSMutableOrderedSet *activeQuorums; - -@property (nonatomic, readonly) NSMutableDictionary *cachedQuorumSnapshots; -@property (nonatomic, readonly) NSMutableDictionary *cachedCLSignatures; - -- (instancetype)initWithChain:(DSChain *)chain; -- (void)setUp:(void (^)(DSMasternodeList *masternodeList))completion; -- (void)deleteAllOnChain; -- (void)deleteEmptyMasternodeLists; -- (BOOL)hasBlockForBlockHash:(NSData *)blockHashData; -- (BOOL)hasMasternodeListAt:(NSData *)blockHashData; -- (BOOL)hasMasternodeListCurrentlyBeingSaved; -- (uint32_t)heightForBlockHash:(UInt256)blockhash; -- (void)loadLocalMasternodes; -- (DSMasternodeList *)loadMasternodeListAtBlockHash:(NSData *)blockHash withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (DSMasternodeList *_Nullable)loadMasternodeListsWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (DSMasternodeList *_Nullable)masternodeListBeforeBlockHash:(UInt256)blockHash; -- (DSMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (void)removeAllMasternodeLists; -- (void)removeOldMasternodeLists:(uint32_t)lastBlockHeight; -- (void)removeOldSimplifiedMasternodeEntries; - -- (void)saveMasternodeList:(DSMasternodeList *)masternodeList - addedMasternodes:(NSDictionary *)addedMasternodes - modifiedMasternodes:(NSDictionary *)modifiedMasternodes - completion:(void (^)(NSError *error))completion; -- (void)saveQuorumSnapshot:(DSQuorumSnapshot *)quorumSnapshot - completion:(void (^)(NSError *error))completion; - -+ (void)saveMasternodeList:(DSMasternodeList *)masternodeList toChain:(DSChain *)chain havingModifiedMasternodes:(NSDictionary *)modifiedMasternodes createUnknownBlocks:(BOOL)createUnknownBlocks inContext:(NSManagedObjectContext *)context completion:(void (^)(NSError *error))completion; - -- (DSQuorumEntry *_Nullable)quorumEntryForPlatformHavingQuorumHash:(UInt256)quorumHash forBlockHeight:(uint32_t)blockHeight; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSMasternodeListStore.m b/DashSync/shared/Models/Masternode/DSMasternodeListStore.m deleted file mode 100644 index 3211e1729..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeListStore.m +++ /dev/null @@ -1,791 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSMasternodeListStore.h" -#import "DSAddressEntity+CoreDataClass.h" -#import "DSBlock.h" -#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" -#import "DSMasternodeListEntity+CoreDataClass.h" -#import "DSMerkleBlock.h" -#import "DSMerkleBlockEntity+CoreDataClass.h" -#import "DSMnDiffProcessingResult.h" -#import "DSOptionsManager.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "DSQuorumSnapshotEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataClass.h" -#import "NSData+Dash.h" -#import "NSError+Dash.h" -#import "NSManagedObject+Sugar.h" - -@interface DSMasternodeListStore () - -@property (nonatomic, strong) DSChain *chain; -@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; -@property (nonatomic, strong) DSMasternodeList *masternodeListAwaitingQuorumValidation; -@property (nonatomic, strong) NSMutableDictionary *masternodeListsByBlockHash; -@property (nonatomic, strong) NSMutableSet *masternodeListsBlockHashStubs; -@property (nonatomic, strong) NSMutableSet *masternodeListQueriesNeedingQuorumsValidated; -@property (nonatomic, strong) NSMutableDictionary *cachedBlockHashHeights; -@property (nonatomic, strong) dispatch_queue_t masternodeSavingQueue; -@property (nonatomic, assign) UInt256 lastQueriedBlockHash; //last by height, not by time queried -@property (atomic, assign) uint32_t masternodeListCurrentlyBeingSavedCount; -@property (nonatomic, strong) NSMutableOrderedSet *activeQuorums; -@property (nonatomic, strong) dispatch_group_t savingGroup; -@end - -@implementation DSMasternodeListStore - -- (instancetype)initWithChain:(DSChain *)chain { - NSParameterAssert(chain); - if (!(self = [super init])) return nil; - _chain = chain; - _masternodeListsByBlockHash = [NSMutableDictionary dictionary]; - _masternodeListsBlockHashStubs = [NSMutableSet set]; - _masternodeListQueriesNeedingQuorumsValidated = [NSMutableSet set]; - _cachedBlockHashHeights = [NSMutableDictionary dictionary]; - _cachedQuorumSnapshots = [NSMutableDictionary dictionary]; - _masternodeListCurrentlyBeingSavedCount = 0; - _masternodeSavingQueue = dispatch_queue_create([[NSString stringWithFormat:@"org.dashcore.dashsync.masternodesaving.%@", chain.uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL); - _savingGroup = dispatch_group_create(); - self.lastQueriedBlockHash = UINT256_ZERO; - self.managedObjectContext = chain.chainManagedObjectContext; - return self; -} - -- (void)setUp:(void (^)(DSMasternodeList *masternodeList))completion { - [self deleteEmptyMasternodeLists]; //this is just for sanity purposes - [self loadMasternodeListsWithBlockHeightLookup:nil]; - [self removeOldSimplifiedMasternodeEntries]; - [self loadLocalMasternodes]; -} - -- (void)savePlatformPingInfoForEntries:(NSArray *)entries - inContext:(NSManagedObjectContext *)context { - [context performBlockAndWait:^{ - for (DSSimplifiedMasternodeEntry *entry in entries) { - [entry savePlatformPingInfoInContext:context]; - } - NSError *savingError = nil; - [context save:&savingError]; - }]; -} - -- (NSArray *)recentMasternodeLists { - return [[self.masternodeListsByBlockHash allValues] sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES]]]; -} - -- (NSUInteger)knownMasternodeListsCount { - @synchronized (self.masternodeListsByBlockHash) { - @synchronized (self.masternodeListsBlockHashStubs) { - NSMutableSet *masternodeListHashes = [NSMutableSet setWithArray:self.masternodeListsByBlockHash.allKeys]; - [masternodeListHashes addObjectsFromArray:[self.masternodeListsBlockHashStubs allObjects]]; - return [masternodeListHashes count]; - } - } -} - -- (uint32_t)earliestMasternodeListBlockHeight { - uint32_t earliest = UINT32_MAX; - @synchronized (self.masternodeListsBlockHashStubs) { - for (NSData *blockHash in self.masternodeListsBlockHashStubs) { - earliest = MIN(earliest, [self heightForBlockHash:blockHash.UInt256]); - } - } - @synchronized (self.masternodeListsByBlockHash) { - for (NSData *blockHash in self.masternodeListsByBlockHash) { - earliest = MIN(earliest, [self heightForBlockHash:blockHash.UInt256]); - } - } - return earliest; -} - -- (uint32_t)lastMasternodeListBlockHeight { - uint32_t last = 0; - @synchronized (self.masternodeListsBlockHashStubs) { - for (NSData *blockHash in self.masternodeListsBlockHashStubs) { - last = MAX(last, [self heightForBlockHash:blockHash.UInt256]); - } - } - @synchronized (self.masternodeListsByBlockHash) { - for (NSData *blockHash in self.masternodeListsByBlockHash) { - last = MAX(last, [self heightForBlockHash:blockHash.UInt256]); - } - } - return last ? last : UINT32_MAX; -} - -- (uint32_t)heightForBlockHash:(UInt256)blockhash { - if (uint256_is_zero(blockhash)) return 0; - @synchronized (self.cachedBlockHashHeights) { - NSNumber *cachedHeightNumber = [self.cachedBlockHashHeights objectForKey:uint256_data(blockhash)]; - if (cachedHeightNumber) return [cachedHeightNumber intValue]; - uint32_t chainHeight = [self.chain heightForBlockHash:blockhash]; - if (chainHeight != UINT32_MAX) [self.cachedBlockHashHeights setObject:@(chainHeight) forKey:uint256_data(blockhash)]; - return chainHeight; - } -} - -- (UInt256)closestKnownBlockHashForBlockHash:(UInt256)blockHash { - DSMasternodeList *masternodeList = [self masternodeListBeforeBlockHash:blockHash]; - if (masternodeList) - return masternodeList.blockHash; - else - return self.chain.genesisHash; -} - -- (void)deleteAllOnChain { - DSLog(@"[%@] DSMasternodeListStore.deleteAllOnChain", self.chain.name); - [self.managedObjectContext performBlockAndWait:^{ - DSChainEntity *chainEntity = [self.chain chainEntityInContext:self.managedObjectContext]; - [DSSimplifiedMasternodeEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSMasternodeListEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumSnapshotEntity deleteAllOnChainEntity:chainEntity]; - [self.managedObjectContext ds_save]; - }]; -} - -- (void)deleteEmptyMasternodeLists { - [self.managedObjectContext performBlockAndWait:^{ - NSFetchRequest *fetchRequest = [[DSMasternodeListEntity fetchRequest] copy]; - [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"block.chain == %@ && masternodes.@count == 0", [self.chain chainEntityInContext:self.managedObjectContext]]]; - NSArray *masternodeListEntities = [DSMasternodeListEntity fetchObjects:fetchRequest inContext:self.managedObjectContext]; - for (DSMasternodeListEntity *entity in [masternodeListEntities copy]) { - DSLog(@"[%@] DSMasternodeListStore.deleteEmptyMasternodeLists: %@", self.chain.name, entity); - [self.managedObjectContext deleteObject:entity]; - } - [self.managedObjectContext ds_save]; - }]; -} - -- (BOOL)hasBlocksWithHash:(UInt256)blockHash { - __block BOOL hasBlock = NO; - [self.managedObjectContext performBlockAndWait:^{ - hasBlock = !![DSMerkleBlockEntity countObjectsInContext:self.managedObjectContext matching:@"blockHash == %@", uint256_data(blockHash)]; - }]; - return hasBlock; -} - -- (BOOL)hasBlockForBlockHash:(NSData *)blockHashData { - UInt256 blockHash = blockHashData.UInt256; - BOOL hasBlock = ([self.chain blockForBlockHash:blockHash] != nil); - if (!hasBlock) { - hasBlock = [self hasBlocksWithHash:blockHash]; - - } - if (!hasBlock && self.chain.isTestnet) { - //We can trust insight if on testnet - [self.chain blockUntilGetInsightForBlockHash:blockHash]; - hasBlock = !![[self.chain insightVerifiedBlocksByHashDictionary] objectForKey:blockHashData]; - } - return hasBlock; -} - - -- (BOOL)hasMasternodeListAt:(NSData *)blockHashData { - BOOL hasList; - @synchronized (self.masternodeListsByBlockHash) { - hasList = [self.masternodeListsByBlockHash objectForKey:blockHashData]; - } - BOOL hasStub; - @synchronized (self.masternodeListsBlockHashStubs) { - hasStub = [self.masternodeListsBlockHashStubs containsObject:blockHashData]; - } - return hasList || hasStub; -} - -- (BOOL)hasMasternodeListCurrentlyBeingSaved { - return !!self.masternodeListCurrentlyBeingSavedCount; -} - -- (void)loadLocalMasternodes { - NSFetchRequest *fetchRequest = [[DSLocalMasternodeEntity fetchRequest] copy]; - [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"providerRegistrationTransaction.transactionHash.chain == %@", [self.chain chainEntityInContext:self.managedObjectContext]]]; - NSArray *localMasternodeEntities = [DSLocalMasternodeEntity fetchObjects:fetchRequest inContext:self.managedObjectContext]; - for (DSLocalMasternodeEntity *localMasternodeEntity in localMasternodeEntities) { - [localMasternodeEntity loadLocalMasternode]; // lazy loaded into the list - } -} - -- (DSMasternodeList *)loadMasternodeListAtBlockHash:(NSData *)blockHash withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - __block DSMasternodeList *masternodeList = nil; - dispatch_group_enter(self.savingGroup); - [self.managedObjectContext performBlockAndWait:^{ - DSMasternodeListEntity *masternodeListEntity = [DSMasternodeListEntity anyObjectInContext:self.managedObjectContext matching:@"block.chain == %@ && block.blockHash == %@", [self.chain chainEntityInContext:self.managedObjectContext], blockHash]; - NSMutableDictionary *simplifiedMasternodeEntryPool = [NSMutableDictionary dictionary]; - NSMutableDictionary *quorumEntryPool = [NSMutableDictionary dictionary]; - 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.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); - } - }]; - dispatch_group_leave(self.savingGroup); - return masternodeList; -} -- (DSMasternodeList *)loadMasternodeListsWithBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - __block DSMasternodeList *currentList = nil; - dispatch_group_enter(self.savingGroup); - [self.managedObjectContext performBlockAndWait:^{ - NSFetchRequest *fetchRequest = [[DSMasternodeListEntity fetchRequest] copy]; - [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"block.chain == %@", [self.chain chainEntityInContext:self.managedObjectContext]]]; - [fetchRequest setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"block.height" ascending:YES]]]; - NSArray *masternodeListEntities = [DSMasternodeListEntity fetchObjects:fetchRequest inContext:self.managedObjectContext]; - NSMutableDictionary *simplifiedMasternodeEntryPool = [NSMutableDictionary dictionary]; - NSMutableDictionary *quorumEntryPool = [NSMutableDictionary dictionary]; - uint32_t neededMasternodeListHeight = self.chain.lastTerminalBlockHeight - 23; //2*8+7 - for (uint32_t i = (uint32_t)masternodeListEntities.count - 1; i != UINT32_MAX; i--) { - DSMasternodeListEntity *masternodeListEntity = [masternodeListEntities objectAtIndex:i]; - if ((i == masternodeListEntities.count - 1) || ((self.masternodeListsByBlockHash.count < 3) && (neededMasternodeListHeight >= masternodeListEntity.block.height))) { //either last one or there are less than 3 (we aim for 3) - //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]; - 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); - if (i == masternodeListEntities.count - 1) { - currentList = masternodeList; - } - neededMasternodeListHeight = masternodeListEntity.block.height - 8; - } else { - //just keep a stub around - [self.cachedBlockHashHeights setObject:@(masternodeListEntity.block.height) forKey:masternodeListEntity.block.blockHash]; - [self.masternodeListsBlockHashStubs addObject:masternodeListEntity.block.blockHash]; - } - } - }]; - dispatch_group_leave(self.savingGroup); - return currentList; -} - -- (DSMasternodeList *_Nullable)masternodeListBeforeBlockHash:(UInt256)blockHash { - uint32_t minDistance = UINT32_MAX; - uint32_t blockHeight = [self heightForBlockHash:blockHash]; - DSMasternodeList *closestMasternodeList = nil; - NSDictionary *lists; - @synchronized (self.masternodeListsByBlockHash) { - lists = [self.masternodeListsByBlockHash copy]; - } - - for (NSData *blockHashData in lists) { - uint32_t masternodeListBlockHeight = [self heightForBlockHash:blockHashData.UInt256]; - if (blockHeight <= masternodeListBlockHeight) continue; - uint32_t distance = blockHeight - masternodeListBlockHeight; - if (distance < minDistance) { - minDistance = distance; - closestMasternodeList = lists[blockHashData]; - } - } - if (self.chain.isMainnet && - closestMasternodeList.height < CHAINLOCK_ACTIVATION_HEIGHT && - blockHeight >= CHAINLOCK_ACTIVATION_HEIGHT) - return nil; //special mainnet case - return closestMasternodeList; -} - -- (DSMasternodeList *)masternodeListForBlockHash:(UInt256)blockHash withBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSData *blockHashData = uint256_data(blockHash); - DSMasternodeList *masternodeList = [self.masternodeListsByBlockHash objectForKey:blockHashData]; - if (!masternodeList && [self.masternodeListsBlockHashStubs containsObject:blockHashData]) { - masternodeList = [self loadMasternodeListAtBlockHash:blockHashData withBlockHeightLookup:blockHeightLookup]; - } - return masternodeList; -} - -- (void)removeAllMasternodeLists { - DSLog(@"[%@] ••• removeAllMasternodeLists -> ", self.chain.name); - @synchronized (self.masternodeListsByBlockHash) { - [self.masternodeListsByBlockHash removeAllObjects]; - } - @synchronized (self.masternodeListsBlockHashStubs) { - [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 { - dispatch_group_enter(self.savingGroup); - [self.managedObjectContext performBlockAndWait:^{ - @autoreleasepool { - NSMutableArray *masternodeListBlockHashes = [[self.masternodeListsByBlockHash allKeys] mutableCopy]; - [masternodeListBlockHashes addObjectsFromArray:[self.masternodeListsBlockHashStubs allObjects]]; - NSArray *masternodeListEntities = [DSMasternodeListEntity objectsInContext:self.managedObjectContext matching:@"block.height < %@ && block.blockHash IN %@ && (block.usedByQuorums.@count == 0)", @(lastBlockHeight - 50), masternodeListBlockHashes]; - BOOL removedItems = !!masternodeListEntities.count; - for (DSMasternodeListEntity *masternodeListEntity in [masternodeListEntities copy]) { - DSLog(@"[%@] Removing masternodeList at height %u", self.chain.name, masternodeListEntity.block.height); - DSLog(@"[%@] quorums are %@", self.chain.name, masternodeListEntity.block.usedByQuorums); - //A quorum is on a block that can only have one masternode list. - //A block can have one quorum of each type. - //A quorum references the masternode list by it's block - //we need to check if this masternode list is being referenced by a quorum using the inverse of quorum.block.masternodeList - [self.managedObjectContext deleteObject:masternodeListEntity]; - @synchronized (self.masternodeListsByBlockHash) { - [self.masternodeListsByBlockHash removeObjectForKey:masternodeListEntity.block.blockHash]; - } - } - 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 - NSArray *recentMasternodeLists = [DSMasternodeListEntity objectsSortedBy:@"block.height" ascending:NO offset:0 limit:10 inContext:self.managedObjectContext]; - uint32_t oldTime = lastBlockHeight - 24; - uint32_t oldestBlockHeight = recentMasternodeLists.count ? MIN([recentMasternodeLists lastObject].block.height, oldTime) : oldTime; - NSArray *oldQuorums = [DSQuorumEntryEntity objectsInContext:self.managedObjectContext matching:@"chain == %@ && SUBQUERY(referencedByMasternodeLists, $masternodeList, $masternodeList.block.height > %@).@count == 0", [self.chain chainEntityInContext:self.managedObjectContext], @(oldestBlockHeight)]; - for (DSQuorumEntryEntity *unusedQuorumEntryEntity in [oldQuorums copy]) { - [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]; - } - } - } - }]; - dispatch_group_leave(self.savingGroup); -} - -- (void)removeOldQuorumSnapshots { - // TODO: implement mechanics of deletion outdated quorum snapshots from rust cache -} - -- (void)removeOldSimplifiedMasternodeEntries { - //this serves both for cleanup, but also for initial migration - dispatch_group_enter(self.savingGroup); - [self.managedObjectContext performBlockAndWait:^{ - NSArray *simplifiedMasternodeEntryEntities = [DSSimplifiedMasternodeEntryEntity objectsInContext:self.managedObjectContext matching:@"masternodeLists.@count == 0"]; - BOOL deletedSomething = FALSE; - NSUInteger deletionCount = 0; - for (DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity in [simplifiedMasternodeEntryEntities copy]) { - DSLog(@"[%@] removeOldSimplifiedMasternodeEntries: %@", self.chain.name, simplifiedMasternodeEntryEntity.providerRegistrationTransactionHash.hexString); - [self.managedObjectContext deleteObject:simplifiedMasternodeEntryEntity]; - deletedSomething = TRUE; - deletionCount++; - if ((deletionCount % 3000) == 0) { - [self.managedObjectContext ds_save]; - } - } - if (deletedSomething) { - [self.managedObjectContext ds_save]; - } - }]; - dispatch_group_leave(self.savingGroup); -} - -- (void)notifyMasternodeListUpdate { - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DSMasternodeListDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - [[NSNotificationCenter defaultCenter] postNotificationName:DSQuorumListDidChangeNotification object:nil userInfo:@{DSChainManagerNotificationChainKey: self.chain}]; - }); -} - -- (void)saveMasternodeList:(DSMasternodeList *)masternodeList addedMasternodes:(NSDictionary *)addedMasternodes modifiedMasternodes:(NSDictionary *)modifiedMasternodes completion:(void (^)(NSError *error))completion { - UInt256 blockHash = masternodeList.blockHash; - NSData *blockHashData = uint256_data(blockHash); - if ([self hasMasternodeListAt:blockHashData]) { - // in rare race conditions this might already exist - // but also as we can get it from different sources - // with different quorums verification status - DSMasternodeList *storedList = [self.masternodeListsByBlockHash objectForKey:blockHashData]; - if (storedList) { - masternodeList = [storedList mergedWithMasternodeList:masternodeList]; - } else { - completion(NULL); - return; - } - } - 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); - //We will want to create unknown blocks if they came from insight - BOOL createUnknownBlocks = masternodeList.chain.allowInsightBlocksForVerification; - self.masternodeListCurrentlyBeingSavedCount++; - //This will create a queue for masternodes to be saved without blocking the networking queue - [DSMasternodeListStore saveMasternodeList:masternodeList - toChain:self.chain - havingModifiedMasternodes:modifiedMasternodes - createUnknownBlocks:createUnknownBlocks - inContext:self.managedObjectContext - completion:^(NSError *error) { - self.masternodeListCurrentlyBeingSavedCount--; - dispatch_group_leave(self.savingGroup); - if (error) { - DSLog(@"[%@] Finished saving MNL at height %u with error: %@", self.chain.name, [self heightForBlockHash:masternodeList.blockHash], error.description); - } - completion(error); - }]; -} - -- (void)saveQuorumSnapshot:(DSQuorumSnapshot *)quorumSnapshot - completion:(void (^)(NSError *error))completion { - if (!quorumSnapshot) { - return; - } - UInt256 blockHash = quorumSnapshot.blockHash; - NSData *blockHashData = uint256_data(blockHash); - uint32_t blockHeight = [self heightForBlockHash:blockHash]; - if ([self.cachedQuorumSnapshots objectForKey:blockHashData]) { - return; - } - DSLog(@"[%@] Queued saving Quorum Snapshot for: %u: %@", self.chain.name, blockHeight, uint256_hex(blockHash)); - [self.cachedQuorumSnapshots setObject:quorumSnapshot forKey:blockHashData]; - dispatch_group_enter(self.savingGroup); - NSManagedObjectContext *context = self.managedObjectContext; - [context performBlockAndWait:^{ - @autoreleasepool { - BOOL createUnknownBlocks = self.chain.allowInsightBlocksForVerification; - DSChainEntity *chainEntity = [self.chain chainEntityInContext:context]; - DSMerkleBlockEntity *merkleBlockEntity = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:blockHash inContext:context]; - if (!merkleBlockEntity) { - merkleBlockEntity = [DSMerkleBlockEntity merkleBlockEntityForBlockHashFromCheckpoint:blockHash chain:self.chain inContext:context]; - } - //NSAssert(!merkleBlockEntity || !merkleBlockEntity.quorumSnapshot, @"Merkle block should not have a quorum snapshot already"); - NSError *error = nil; - if (!merkleBlockEntity) { - if (createUnknownBlocks) { - merkleBlockEntity = [DSMerkleBlockEntity createMerkleBlockEntityForBlockHash:blockHash blockHeight:blockHeight chainEntity:chainEntity inContext:context]; - } else { - DSLog(@"[%@] Merkle block should exist for block hash %@", self.chain.name, blockHashData.hexString); - error = [NSError errorWithCode:600 localizedDescriptionKey:@"Merkle block should exist"]; - } - } else if (merkleBlockEntity.quorumSnapshot) { - DSLog(@"[%@] Merkle block already have quorum snapshot for %@", self.chain.name, blockHashData.hexString); - error = [NSError errorWithCode:600 localizedDescriptionKey:@"Merkle block should not have a quorum snapshot already"]; // DGaF - // skip we're just processing saved snapshot - //[merkleBlockEntity.quorumSnapshot updateAttributesFromPotentialQuorumSnapshot:quorumSnapshot onBlock:merkleBlockEntity] - } - if (error) { - [DSQuorumSnapshotEntity deleteAllOnChainEntity:chainEntity]; - } else { - DSQuorumSnapshotEntity *quorumSnapshotEntity = [DSQuorumSnapshotEntity managedObjectInBlockedContext:context]; - [quorumSnapshotEntity updateAttributesFromPotentialQuorumSnapshot:quorumSnapshot onBlock:merkleBlockEntity]; - DSLog(@"[%@] Finished saving Quorum Snapshot at height %u: %@", self.chain.name, blockHeight, uint256_hex(blockHash)); - } - error = [context ds_save]; - if (completion) { - completion(error); - } - } - }]; - dispatch_group_leave(self.savingGroup); -} - -+ (void)saveMasternodeList:(DSMasternodeList *)masternodeList - toChain:(DSChain *)chain - havingModifiedMasternodes:(NSDictionary *)modifiedMasternodes - createUnknownBlocks:(BOOL)createUnknownBlocks - inContext:(NSManagedObjectContext *)context - completion:(void (^)(NSError *error))completion { - DSLog(@"[%@] Queued saving MNL at height %u: %@", chain.name, masternodeList.height, uint256_hex(masternodeList.blockHash)); - [context performBlockAndWait:^{ - //masternodes - @autoreleasepool { - DSChainEntity *chainEntity = [chain chainEntityInContext:context]; - UInt256 mnlBlockHash = masternodeList.blockHash; - uint32_t mnlHeight = masternodeList.height; - NSData *mnlBlockHashData = uint256_data(mnlBlockHash); - DSMerkleBlockEntity *merkleBlockEntity = [DSMerkleBlockEntity merkleBlockEntityForBlockHash:mnlBlockHash inContext:context]; - if (!merkleBlockEntity) { - merkleBlockEntity = [DSMerkleBlockEntity merkleBlockEntityForBlockHashFromCheckpoint:mnlBlockHash chain:chain inContext:context]; - } -// NSAssert(!merkleBlockEntity || !merkleBlockEntity.masternodeList, @"Merkle block should not have a masternode list already"); - NSError *error = nil; - BOOL shouldMerge = false; - if (!merkleBlockEntity) { - if (createUnknownBlocks) { - merkleBlockEntity = [DSMerkleBlockEntity createMerkleBlockEntityForBlockHash:mnlBlockHash blockHeight:mnlHeight chainEntity:chainEntity inContext:context]; - } else { - DSLog(@"[%@] Merkle block should exist for block hash %@", chain.name, mnlBlockHashData); - error = [NSError errorWithCode:600 localizedDescriptionKey:@"Merkle block should exist"]; - } - } else if (merkleBlockEntity.masternodeList) { - // NEW: merge masternode list - // We merge quorums as they can have different verification status depending on source - shouldMerge = true; - //error = [NSError errorWithCode:600 localizedDescriptionKey:@"Merkle block should not have a masternode list already"]; - } - if (shouldMerge) { - DSMasternodeListEntity *masternodeListEntity = merkleBlockEntity.masternodeList; - - NSArray *knownSimplifiedMasternodeEntryEntities = [DSSimplifiedMasternodeEntryEntity objectsInContext:context matching:@"chain == %@", chainEntity]; - NSMutableDictionary *indexedKnownSimplifiedMasternodeEntryEntities = [NSMutableDictionary dictionary]; - for (DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity in knownSimplifiedMasternodeEntryEntities) { - NSData *proRegTxHash = simplifiedMasternodeEntryEntity.providerRegistrationTransactionHash; - [indexedKnownSimplifiedMasternodeEntryEntities setObject:simplifiedMasternodeEntryEntity forKey:proRegTxHash]; - } - NSDictionary *indexedMasternodes = [indexedKnownSimplifiedMasternodeEntryEntities copy]; - NSMutableSet *votingAddressStrings = [NSMutableSet set]; - NSMutableSet *operatorAddressStrings = [NSMutableSet set]; - NSMutableSet *providerRegistrationTransactionHashes = [NSMutableSet set]; - NSArray *masternodes = masternodeList.simplifiedMasternodeEntries; - // TODO: check do we have to do the same for platform node addresses - for (DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry in masternodes) { - [votingAddressStrings addObject:simplifiedMasternodeEntry.votingAddress]; - [operatorAddressStrings addObject:simplifiedMasternodeEntry.operatorAddress]; - [providerRegistrationTransactionHashes addObject:uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash)]; - } - //this is the initial list sync so lets speed things up a little bit with some optimizations - NSDictionary *votingAddresses = [DSAddressEntity findAddressesAndIndexIn:votingAddressStrings onChain:(DSChain *)chain inContext:context]; - NSDictionary *operatorAddresses = [DSAddressEntity findAddressesAndIndexIn:votingAddressStrings onChain:(DSChain *)chain inContext:context]; - NSDictionary *localMasternodes = [DSLocalMasternodeEntity findLocalMasternodesAndIndexForProviderRegistrationHashes:providerRegistrationTransactionHashes inContext:context]; - NSAssert(masternodes, @"A masternode must have entries to be saved"); - for (DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry in masternodes) { - NSData *proRegTxHash = uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash); - DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity = [indexedMasternodes objectForKey:proRegTxHash]; - if (!simplifiedMasternodeEntryEntity) { - simplifiedMasternodeEntryEntity = [DSSimplifiedMasternodeEntryEntity managedObjectInBlockedContext:context]; - [simplifiedMasternodeEntryEntity setAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:mnlHeight knownOperatorAddresses:operatorAddresses knownVotingAddresses:votingAddresses localMasternodes:localMasternodes onChainEntity:chainEntity]; - } else if (simplifiedMasternodeEntry.updateHeight >= mnlHeight) { - // it was updated in this masternode list - [simplifiedMasternodeEntryEntity updateAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:mnlHeight knownOperatorAddresses:operatorAddresses knownVotingAddresses:votingAddresses localMasternodes:localMasternodes]; - } - [masternodeListEntity addMasternodesObject:simplifiedMasternodeEntryEntity]; - } - for (NSData *simplifiedMasternodeEntryHash in modifiedMasternodes) { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = modifiedMasternodes[simplifiedMasternodeEntryHash]; - NSData *proRegTxHash = uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash); - DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity = [indexedMasternodes objectForKey:proRegTxHash]; - NSAssert(simplifiedMasternodeEntryEntity, @"this masternode must be present (%@)", proRegTxHash.hexString); - [simplifiedMasternodeEntryEntity updateAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:mnlHeight knownOperatorAddresses:operatorAddresses knownVotingAddresses:votingAddresses localMasternodes:localMasternodes]; - } - NSDictionary *> *quorums = masternodeList.quorums; - for (NSNumber *llmqType in quorums) { - NSDictionary *quorumsForMasternodeType = quorums[llmqType]; - for (NSData *quorumHash in quorumsForMasternodeType) { - DSQuorumEntry *potentialQuorumEntry = quorumsForMasternodeType[quorumHash]; - DSQuorumEntryEntity *entity = [DSQuorumEntryEntity quorumEntryEntityFromPotentialQuorumEntryForMerging:potentialQuorumEntry inContext:context]; - if (entity) { - [masternodeListEntity addQuorumsObject:entity]; - } - } - } - chainEntity.baseBlockHash = mnlBlockHashData; - DSLog(@"[%@] Finished merging MNL at height %u: %@", chain.name, mnlHeight, mnlBlockHashData.hexString); - - } else if (!error) { - DSMasternodeListEntity *masternodeListEntity = [DSMasternodeListEntity managedObjectInBlockedContext:context]; - masternodeListEntity.block = merkleBlockEntity; - masternodeListEntity.masternodeListMerkleRoot = uint256_data(masternodeList.masternodeMerkleRoot); - masternodeListEntity.quorumListMerkleRoot = uint256_data(masternodeList.quorumMerkleRoot); - NSArray *knownSimplifiedMasternodeEntryEntities = [DSSimplifiedMasternodeEntryEntity objectsInContext:context matching:@"chain == %@", chainEntity]; - NSMutableDictionary *indexedKnownSimplifiedMasternodeEntryEntities = [NSMutableDictionary dictionary]; - for (DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity in knownSimplifiedMasternodeEntryEntities) { - NSData *proRegTxHash = simplifiedMasternodeEntryEntity.providerRegistrationTransactionHash; - [indexedKnownSimplifiedMasternodeEntryEntities setObject:simplifiedMasternodeEntryEntity forKey:proRegTxHash]; - } - NSDictionary *indexedMasternodes = [indexedKnownSimplifiedMasternodeEntryEntities copy]; - NSMutableSet *votingAddressStrings = [NSMutableSet set]; - NSMutableSet *operatorAddressStrings = [NSMutableSet set]; - NSMutableSet *providerRegistrationTransactionHashes = [NSMutableSet set]; - NSArray *masternodes = masternodeList.simplifiedMasternodeEntries; - for (DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry in masternodes) { - [votingAddressStrings addObject:simplifiedMasternodeEntry.votingAddress]; - [operatorAddressStrings addObject:simplifiedMasternodeEntry.operatorAddress]; - [providerRegistrationTransactionHashes addObject:uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash)]; - } - //this is the initial list sync so lets speed things up a little bit with some optimizations - NSDictionary *votingAddresses = [DSAddressEntity findAddressesAndIndexIn:votingAddressStrings onChain:(DSChain *)chain inContext:context]; - NSDictionary *operatorAddresses = [DSAddressEntity findAddressesAndIndexIn:votingAddressStrings onChain:(DSChain *)chain inContext:context]; - NSDictionary *localMasternodes = [DSLocalMasternodeEntity findLocalMasternodesAndIndexForProviderRegistrationHashes:providerRegistrationTransactionHashes inContext:context]; - NSAssert(masternodes, @"A masternode must have entries to be saved"); - for (DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry in masternodes) { - NSData *proRegTxHash = uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash); - DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity = [indexedMasternodes objectForKey:proRegTxHash]; - if (!simplifiedMasternodeEntryEntity) { - simplifiedMasternodeEntryEntity = [DSSimplifiedMasternodeEntryEntity managedObjectInBlockedContext:context]; - [simplifiedMasternodeEntryEntity setAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:mnlHeight knownOperatorAddresses:operatorAddresses knownVotingAddresses:votingAddresses localMasternodes:localMasternodes onChainEntity:chainEntity]; - } else if (simplifiedMasternodeEntry.updateHeight >= mnlHeight) { - // it was updated in this masternode list - [simplifiedMasternodeEntryEntity updateAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:mnlHeight knownOperatorAddresses:operatorAddresses knownVotingAddresses:votingAddresses localMasternodes:localMasternodes]; - } - [masternodeListEntity addMasternodesObject:simplifiedMasternodeEntryEntity]; - } - for (NSData *simplifiedMasternodeEntryHash in modifiedMasternodes) { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = modifiedMasternodes[simplifiedMasternodeEntryHash]; - NSData *proRegTxHash = uint256_data(simplifiedMasternodeEntry.providerRegistrationTransactionHash); - DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity = [indexedMasternodes objectForKey:proRegTxHash]; - NSAssert(simplifiedMasternodeEntryEntity, @"this masternode must be present (%@)", proRegTxHash.hexString); - [simplifiedMasternodeEntryEntity updateAttributesFromSimplifiedMasternodeEntry:simplifiedMasternodeEntry atBlockHeight:mnlHeight knownOperatorAddresses:operatorAddresses knownVotingAddresses:votingAddresses localMasternodes:localMasternodes]; - } - NSDictionary *> *quorums = masternodeList.quorums; - for (NSNumber *llmqType in quorums) { - NSDictionary *quorumsForMasternodeType = quorums[llmqType]; - for (NSData *quorumHash in quorumsForMasternodeType) { - DSQuorumEntry *potentialQuorumEntry = quorumsForMasternodeType[quorumHash]; - DSQuorumEntryEntity *entity = [DSQuorumEntryEntity quorumEntryEntityFromPotentialQuorumEntry:potentialQuorumEntry inContext:context]; - if (entity) { - [masternodeListEntity addQuorumsObject:entity]; - } - } - } - chainEntity.baseBlockHash = mnlBlockHashData; - DSLog(@"[%@] Finished saving MNL at height %u", chain.name, mnlHeight); - } else { - chainEntity.baseBlockHash = uint256_data(chain.genesisHash); - [DSLocalMasternodeEntity deleteAllOnChainEntity:chainEntity]; - [DSSimplifiedMasternodeEntryEntity deleteAllOnChainEntity:chainEntity]; - [DSQuorumEntryEntity deleteAllOnChainEntity:chainEntity]; - } - [context ds_save]; - if (completion) { - completion(error); - } - - } - }]; -} - -- (DSQuorumEntry *_Nullable)quorumEntryForPlatformHavingQuorumHash:(UInt256)quorumHash forBlockHeight:(uint32_t)blockHeight { - DSBlock *block = [self.chain blockAtHeightOrLastTerminal:blockHeight]; - return block ? [self quorumEntryForPlatformHavingQuorumHash:quorumHash forBlock:block] : nil; -} - -- (DSQuorumEntry *_Nullable)activeQuorumForTypeQuorumHash:(UInt256)quorumHash ofQuorumType:(LLMQType)quorumType { - for (DSQuorumEntry *quorumEntry in self.activeQuorums) { - if (uint256_eq(quorumEntry.quorumHash, quorumHash) && quorumEntry.llmqType == quorumType) { - return quorumEntry; - } - } - return nil; -} - - -- (DSQuorumEntry *)quorumEntryForPlatformHavingQuorumHash:(UInt256)quorumHash forBlock:(DSBlock *)block { - DSMasternodeList *masternodeList = [self masternodeListForBlockHash:block.blockHash withBlockHeightLookup:nil]; - if (!masternodeList) { - masternodeList = [self masternodeListBeforeBlockHash:block.blockHash]; - } - if (!masternodeList) { - DSLog(@"[%@] No masternode list found yet", self.chain.name); - return nil; - } - if (block.height - masternodeList.height > 32) { - DSLog(@"[%@] Masternode list is too old", self.chain.name); - return nil; - } - DSQuorumEntry *quorumEntry = [masternodeList quorumEntryForPlatformWithQuorumHash:quorumHash]; - if (quorumEntry == nil) { - quorumEntry = [self activeQuorumForTypeQuorumHash:quorumHash ofQuorumType:quorum_type_for_platform(self.chain.chainType)]; - } - if (quorumEntry == nil) { - quorumEntry = [self quorumEntryForPlatformHavingQuorumHash:quorumHash forBlockHeight:block.height - 1]; - } - return quorumEntry; -} - -- (DSQuorumEntry *)quorumEntryForLockRequestID:(UInt256)requestID - ofQuorumType:(LLMQType)quorumType - forMerkleBlock:(DSMerkleBlock *)merkleBlock - withExpirationOffset:(uint32_t)offset { - UInt256 blockHash = merkleBlock.blockHash; - DSQuorumEntry *activeQuorum = [self activeQuorumForTypeQuorumHash:blockHash ofQuorumType:quorumType]; - if (activeQuorum) { - return activeQuorum; - } - DSMasternodeList *masternodeList = [self masternodeListBeforeBlockHash:blockHash]; - if (!masternodeList) { - DSLog(@"[%@] No masternode list found yet", self.chain.name); - return nil; - } - if (merkleBlock.height - masternodeList.height > offset) { - DSLog(@"[%@] Masternode list for is too old (age: %d masternodeList height %d merkle block height %d)", self.chain.name, - merkleBlock.height - masternodeList.height, masternodeList.height, merkleBlock.height); - return nil; - } - - return [masternodeList quorumEntryForLockRequestID:requestID ofQuorumType:quorumType]; -} - -- (DSQuorumEntry *)quorumEntryForChainLockRequestID:(UInt256)requestID forMerkleBlock:(DSMerkleBlock *)merkleBlock { - return [self quorumEntryForLockRequestID:requestID - ofQuorumType:quorum_type_for_chain_locks(self.chain.chainType) - forMerkleBlock:merkleBlock - withExpirationOffset:24]; -} - -- (DSQuorumEntry *)quorumEntryForInstantSendRequestID:(UInt256)requestID forMerkleBlock:(DSMerkleBlock *)merkleBlock { - return [self quorumEntryForLockRequestID:requestID - ofQuorumType:quorum_type_for_is_locks(self.chain.chainType) - forMerkleBlock:merkleBlock - withExpirationOffset:32]; -} - -- (BOOL)addBlockToValidationQueue:(DSMerkleBlock *)merkleBlock { - UInt256 merkleBlockHash = merkleBlock.blockHash; - //DSLog(@"addBlockToValidationQueue: %u:%@", merkleBlock.height, uint256_hex(merkleBlockHash)); - NSData *merkleBlockHashData = uint256_data(merkleBlockHash); - if ([self hasMasternodeListAt:merkleBlockHashData]) { - DSLog(@"[%@] Already have that masternode list (or in stub) %u", self.chain.name, merkleBlock.height); - return NO; - } - self.lastQueriedBlockHash = merkleBlockHash; - @synchronized (self.masternodeListQueriesNeedingQuorumsValidated) { - [self.masternodeListQueriesNeedingQuorumsValidated addObject:merkleBlockHashData]; - } - return YES; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSMasternodeProcessorContext.h b/DashSync/shared/Models/Masternode/DSMasternodeProcessorContext.h deleted file mode 100644 index 22d5c5743..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeProcessorContext.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "BigIntTypes.h" -#import "DSChain.h" -#import "DSMasternodeList.h" -#import "DSQuorumSnapshot.h" -#import "DSPeer.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef DSMasternodeList *_Nullable(^_Nullable MasternodeListFinder)(UInt256 blockHash); -typedef UInt256(^_Nullable MerkleRootFinder)(UInt256 blockHash); -typedef DSMerkleBlock *_Nullable(^_Nullable MerkleBlockFinder)(UInt256 blockHash); - -@interface DSMasternodeProcessorContext : NSObject - -@property (nonatomic) DSChain *chain; -@property (nonatomic, nullable) DSPeer *peer; -@property (nonatomic) BOOL useInsightAsBackup; -@property (nonatomic) BOOL isFromSnapshot; -@property (nonatomic) BOOL isDIP0024; -@property (nonatomic, copy) MasternodeListFinder masternodeListLookup; -@property (nonatomic, copy) BlockHeightFinder blockHeightLookup; -@property (nonatomic, copy) MerkleRootFinder merkleRootLookup; - - -- (uint32_t)blockHeightForBlockHash:(UInt256)blockHash; -- (UInt256)merkleRootForBlockHash:(UInt256)blockHash; -- (DSBlock *_Nullable)blockForBlockHeight:(uint32_t)blockHeight; -- (NSData *_Nullable)CLSignatureForBlockHash:(UInt256)blockHash; -- (DSQuorumSnapshot *_Nullable)quorumSnapshotForBlockHash:(UInt256)blockHash; -- (DSMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash; - -- (BOOL)saveCLSignature:(UInt256)blockHash signature:(UInt768)signature; -- (BOOL)saveQuorumSnapshot:(DSQuorumSnapshot *)snapshot; -- (BOOL)saveMasternodeList:(DSMasternodeList *)masternodeList forBlockHash:(UInt256)blockHash; - - - -- (void)blockUntilGetInsightForBlockHash:(UInt256)blockHash; -- (ProcessingError)shouldProcessDiffWithRange:(UInt256)baseBlockHash blockHash:(UInt256)blockHash; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSMasternodeProcessorContext.m b/DashSync/shared/Models/Masternode/DSMasternodeProcessorContext.m deleted file mode 100644 index 1e53060b1..000000000 --- a/DashSync/shared/Models/Masternode/DSMasternodeProcessorContext.m +++ /dev/null @@ -1,106 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "DSMasternodeProcessorContext.h" -#import "NSData+Dash.h" -#import "DSChain+Protected.h" -#import "DSChainManager.h" -#import "DSMasternodeManager.h" - -@implementation DSMasternodeProcessorContext - -- (uint32_t)blockHeightForBlockHash:(UInt256)blockHash { - return self.blockHeightLookup(blockHash); -} - -- (DSMerkleBlock *_Nullable)blockForBlockHeight:(uint32_t)blockHeight { - return [self.chain blockAtHeight:blockHeight]; - -} -- (UInt256)merkleRootForBlockHash:(UInt256)blockHash { - return self.merkleRootLookup(blockHash); -} - -- (NSData *_Nullable)CLSignatureForBlockHash:(UInt256)blockHash { - return [self.chain.chainManager.masternodeManager CLSignatureForBlockHash:blockHash]; -} - -- (DSQuorumSnapshot *_Nullable)quorumSnapshotForBlockHash:(UInt256)blockHash { - return [self.chain.chainManager.masternodeManager quorumSnapshotForBlockHash:blockHash]; -} - -- (DSMasternodeList *_Nullable)masternodeListForBlockHash:(UInt256)blockHash { - return self.masternodeListLookup(blockHash); -} - -- (BOOL)saveCLSignature:(UInt256)blockHash signature:(UInt768)signature { - return [self.chain.chainManager.masternodeManager saveCLSignature:uint256_data(blockHash) signatureData:uint768_data(signature)]; -} - -- (BOOL)saveQuorumSnapshot:(DSQuorumSnapshot *)snapshot { - return [self.chain.chainManager.masternodeManager saveQuorumSnapshot:snapshot]; -} - -- (BOOL)saveMasternodeList:(DSMasternodeList *)masternodeList forBlockHash:(UInt256)blockHash { - return [self.chain.chainManager.masternodeManager saveMasternodeList:masternodeList forBlockHash:blockHash]; -} - -- (void)blockUntilGetInsightForBlockHash:(UInt256)blockHash { - [self.chain blockUntilGetInsightForBlockHash:blockHash]; -} - -- (NSString *)description { - return [[super description] stringByAppendingString:[NSString stringWithFormat:@" {%@}: [%@: %@ (%u)] genesis: %@ protocol: %u, insight: %i, from_snapshot: %i, dip-24: %i}", self.chain.name, self.peer.location, self.peer.useragent, self.peer.version, uint256_hex(self.chain.genesisHash), self.chain.protocolVersion, self.useInsightAsBackup, self.isFromSnapshot, self.isDIP0024]]; -} - -- (ProcessingError)shouldProcessDiffWithRange:(UInt256)baseBlockHash blockHash:(UInt256)blockHash { - uint32_t baseBlockHeight = [self blockHeightForBlockHash:baseBlockHash]; - uint32_t blockHeight = [self blockHeightForBlockHash:blockHash]; - if (blockHeight == UINT32_MAX) { - DSLog(@"•••• shouldProcessDiffWithRange: unknown blockHash: %u..%u %@ .. %@", baseBlockHeight, blockHeight, uint256_reverse_hex(baseBlockHash), uint256_reverse_hex(blockHash)); - return ProcessingError_UnknownBlockHash; - } - DSChain *chain = self.chain; - DSMasternodeManager *manager = chain.chainManager.masternodeManager; - DSMasternodeListService *service = self.isDIP0024 ? manager.quorumRotationService : manager.masternodeListDiffService; - BOOL hasRemovedFromRetrieval = [service removeRequestInRetrievalForBaseBlockHash:baseBlockHash blockHash:blockHash]; - if (!hasRemovedFromRetrieval) { - DSLog(@"•••• shouldProcessDiffWithRange: persist in retrieval: %u..%u %@ .. %@", baseBlockHeight, blockHeight, uint256_reverse_hex(baseBlockHash), uint256_reverse_hex(blockHash)); - return ProcessingError_PersistInRetrieval; - } - NSData *blockHashData = uint256_data(blockHash); - DSMasternodeList *list = self.masternodeListLookup(blockHash); - BOOL needToVerifyRotatedQuorums = self.isDIP0024 && (!manager.quorumRotationService.masternodeListAtH || [manager.quorumRotationService.masternodeListAtH hasUnverifiedRotatedQuorums]); - BOOL needToVerifyNonRotatedQuorums = !self.isDIP0024 && [list hasUnverifiedNonRotatedQuorums]; - BOOL noNeedToVerifyQuorums = !(needToVerifyRotatedQuorums || needToVerifyNonRotatedQuorums); - BOOL hasLocallyStored = [manager.store hasMasternodeListAt:blockHashData]; - if (hasLocallyStored && noNeedToVerifyQuorums) { - DSLog(@"•••• shouldProcessDiffWithRange: already persist: %u: %@ needToVerifyRotatedQuorums: %d needToVerifyNonRotatedQuorums: %d", blockHeight, uint256_reverse_hex(blockHash), needToVerifyRotatedQuorums, needToVerifyNonRotatedQuorums); - [service removeFromRetrievalQueue:blockHashData]; - return ProcessingError_LocallyStored; - } - DSMasternodeList *baseMasternodeList = self.masternodeListLookup(baseBlockHash); - if (!baseMasternodeList && !uint256_eq(chain.genesisHash, baseBlockHash) && uint256_is_not_zero(baseBlockHash)) { - // this could have been deleted in the meantime, if so rerequest - [service issueWithMasternodeListFromPeer:self.peer]; - DSLog(@"•••• No base masternode list at: %d: %@", baseBlockHeight, uint256_reverse_hex(baseBlockHash)); - return ProcessingError_HasNoBaseBlockHash; - } - return ProcessingError_None; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSMnDiffProcessingResult.h b/DashSync/shared/Models/Masternode/DSMnDiffProcessingResult.h deleted file mode 100644 index 43b097dea..000000000 --- a/DashSync/shared/Models/Masternode/DSMnDiffProcessingResult.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "DSMasternodeList.h" -#import "dash_shared_core.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface DSMnDiffProcessingResult : NSObject - -@property (nonatomic) uint8_t errorStatus; -@property (nonatomic) UInt256 baseBlockHash; -@property (nonatomic) UInt256 blockHash; -@property (nonatomic) BOOL foundCoinbase; -@property (nonatomic) BOOL validCoinbase; -@property (nonatomic) BOOL rootMNListValid; -@property (nonatomic) BOOL rootQuorumListValid; -@property (nonatomic) BOOL validQuorums; -@property (nonatomic) DSMasternodeList *masternodeList; -@property (nonatomic) NSDictionary *addedMasternodes; -@property (nonatomic) NSDictionary *modifiedMasternodes; -@property (nonatomic) NSArray *addedQuorums; -@property (nonatomic) NSOrderedSet *neededMissingMasternodeLists; -/// -@property (nonatomic) NSDictionary *clSignatures; - -+ (instancetype)processingResultWith:(MNListDiffResult *)result onChain:(DSChain *)chain; - -- (BOOL)isValid; -- (BOOL)isTotallyValid; -- (BOOL)hasRotatedQuorumsForChain:(DSChain *)chain; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSMnDiffProcessingResult.m b/DashSync/shared/Models/Masternode/DSMnDiffProcessingResult.m deleted file mode 100644 index 60b2d443e..000000000 --- a/DashSync/shared/Models/Masternode/DSMnDiffProcessingResult.m +++ /dev/null @@ -1,98 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "NSData+Dash.h" -#import "DSMnDiffProcessingResult.h" -#import "DSMasternodeList+Mndiff.h" -#import "DSQuorumEntry+Mndiff.h" -#import "DSSimplifiedMasternodeEntry+Mndiff.h" - -@implementation DSMnDiffProcessingResult - -+ (instancetype)processingResultWith:(MNListDiffResult *)result onChain:(DSChain *)chain { - DSMnDiffProcessingResult *processingResult = [[DSMnDiffProcessingResult alloc] init]; - uint8_t errorStatus = result->error_status; - processingResult.errorStatus = errorStatus; - if (errorStatus > 0) { - return processingResult; - } - if (result->masternode_list == NULL) { - DSLog(@"[%@] DSQRInfoProcessingResult.error.unknown", chain.name); - processingResult.errorStatus = ProcessingError_ParseError; - return processingResult; - } - - [processingResult setErrorStatus:errorStatus]; - [processingResult setBaseBlockHash:*(UInt256 *)result->base_block_hash]; - [processingResult setBlockHash:*(UInt256 *)result->block_hash]; - [processingResult setFoundCoinbase:result->has_found_coinbase]; - [processingResult setValidCoinbase:result->has_valid_coinbase]; - [processingResult setRootMNListValid:result->has_valid_mn_list_root]; - [processingResult setRootQuorumListValid:result->has_valid_llmq_list_root]; - [processingResult setValidQuorums:result->has_valid_quorums]; - MasternodeList *result_masternode_list = result->masternode_list; - [processingResult setMasternodeList:[DSMasternodeList masternodeListWith:result_masternode_list onChain:chain]]; - NSDictionary *addedMasternodes = [DSSimplifiedMasternodeEntry simplifiedEntriesWith:result->added_masternodes count:result->added_masternodes_count onChain:chain]; - [processingResult setAddedMasternodes:addedMasternodes]; - NSDictionary *modifiedMasternodes = [DSSimplifiedMasternodeEntry simplifiedEntriesWith:result->modified_masternodes count:result->modified_masternodes_count onChain:chain]; - [processingResult setModifiedMasternodes:modifiedMasternodes]; - NSArray *addedQuorums = [DSQuorumEntry entriesWith:result->added_quorums count:result->added_quorums_count onChain:chain]; - [processingResult setAddedQuorums:addedQuorums]; - uint8_t(**needed_masternode_lists)[32] = result->needed_masternode_lists; - uintptr_t needed_masternode_lists_count = result->needed_masternode_lists_count; - NSMutableOrderedSet *neededMissingMasternodeLists = [NSMutableOrderedSet orderedSetWithCapacity:needed_masternode_lists_count]; - for (NSUInteger i = 0; i < needed_masternode_lists_count; i++) { - NSData *hash = [NSData dataWithBytes:needed_masternode_lists[i] length:32]; - [neededMissingMasternodeLists addObject:hash]; - } - [processingResult setNeededMissingMasternodeLists:[neededMissingMasternodeLists copy]]; - uint8_t (**quorums_cl_signatures_hashes)[32] = result->quorums_cl_signatures_hashes; - uint8_t (**quorums_cl_signatures)[96] = result->quorums_cl_signatures; - uintptr_t quorums_cl_sigs_count = result->quorums_cl_sigs_count; - NSMutableDictionary *clSignatures = [NSMutableDictionary dictionaryWithCapacity:quorums_cl_sigs_count]; - for (NSUInteger i = 0; i < quorums_cl_sigs_count; i++) { - [clSignatures setObject:uint768_data(*(UInt768 *)quorums_cl_signatures[i]) - forKey:uint256_data(*(UInt256 *)quorums_cl_signatures_hashes[i])]; - } - [processingResult setClSignatures:clSignatures]; - return processingResult; -} - -- (BOOL)isValid { -// return self.foundCoinbase && self.validQuorums && self.rootMNListValid && self.rootQuorumListValid; - return self.foundCoinbase && self.rootQuorumListValid; -} -- (BOOL)isTotallyValid { - return [self isValid] && self.validCoinbase; -} - -- (BOOL)hasRotatedQuorumsForChain:(DSChain*)chain { - return [[self.addedQuorums indexesOfObjectsPassingTest:^BOOL(DSQuorumEntry * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - // TODO: make it more reliable as quorum type values may change - return obj.llmqType == quorum_type_for_isd_locks(chain.chainType) && (*stop = TRUE); - }] count] > 0; -} - -- (NSString *)debugDescription { - return [NSString stringWithFormat:@"•-• %@: {\nvalidity: %d%d%d%d%d, \nmasternodeList: %@, \nmasternodes: [added:%lu, modified:%lu], \nquorums: [added: %lu],\nneeded: [%@]}", - [super debugDescription], self.foundCoinbase, self.validCoinbase, self.rootMNListValid, self.rootQuorumListValid, self.validQuorums, - self.masternodeList, self.addedMasternodes.count, self.modifiedMasternodes.count, self.addedQuorums.count, self.neededMissingMasternodeLists - - ]; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSQRInfoProcessingResult.h b/DashSync/shared/Models/Masternode/DSQRInfoProcessingResult.h deleted file mode 100644 index 8d67dfe0e..000000000 --- a/DashSync/shared/Models/Masternode/DSQRInfoProcessingResult.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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.h" -#import "DSMnDiffProcessingResult.h" -#import "DSQuorumSnapshot+Mndiff.h" -#import "dash_shared_core.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQRInfoProcessingResult : NSObject - -@property (nonatomic) uint8_t errorStatus; -@property (nonatomic) DSQuorumSnapshot *snapshotAtHC; -@property (nonatomic) DSQuorumSnapshot *snapshotAtH2C; -@property (nonatomic) DSQuorumSnapshot *snapshotAtH3C; -@property (nonatomic) DSQuorumSnapshot *_Nullable snapshotAtH4C; - -@property (nonatomic) DSMnDiffProcessingResult *mnListDiffResultAtTip; -@property (nonatomic) DSMnDiffProcessingResult *mnListDiffResultAtH; -@property (nonatomic) DSMnDiffProcessingResult *mnListDiffResultAtHC; -@property (nonatomic) DSMnDiffProcessingResult *mnListDiffResultAtH2C; -@property (nonatomic) DSMnDiffProcessingResult *mnListDiffResultAtH3C; -@property (nonatomic) DSMnDiffProcessingResult *_Nullable mnListDiffResultAtH4C; - -@property (nonatomic) BOOL extraShare; - -@property (nonatomic) NSOrderedSet *lastQuorumPerIndex; -@property (nonatomic) NSOrderedSet *snapshotList; -@property (nonatomic) NSOrderedSet *mnListDiffList; - -+ (instancetype)processingResultWith:(QRInfoResult *)result onChain:(DSChain *)chain; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSQRInfoProcessingResult.m b/DashSync/shared/Models/Masternode/DSQRInfoProcessingResult.m deleted file mode 100644 index b96d5d120..000000000 --- a/DashSync/shared/Models/Masternode/DSQRInfoProcessingResult.m +++ /dev/null @@ -1,95 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSQRInfoProcessingResult.h" - -@implementation DSQRInfoProcessingResult - -+ (instancetype)processingResultWith:(QRInfoResult *)result onChain:(DSChain *)chain { - DSQRInfoProcessingResult *processingResult = [[DSQRInfoProcessingResult alloc] init]; - uint8_t errorStatus = result->error_status; - processingResult.errorStatus = errorStatus; - if (errorStatus > 0) { - DSLog(@"[%@] DSQRInfoProcessingResult.error %ul", chain.name, errorStatus); - return processingResult; - } - if (result->result_at_tip == NULL) { - DSLog(@"[%@] DSQRInfoProcessingResult.error.unknown", chain.name); - processingResult.errorStatus = ProcessingError_ParseError; - return processingResult; - } - MNListDiffResult *diffResultAtHC = result->result_at_h_c; - MNListDiffResult *diffResultAtH2C = result->result_at_h_2c; - MNListDiffResult *diffResultAtH3C = result->result_at_h_3c; - MNListDiffResult *diffResultAtH4C = result->result_at_h_4c; - processingResult.snapshotAtHC = [DSQuorumSnapshot quorumSnapshotWith:result->snapshot_at_h_c forBlockHash:*((UInt256 *)diffResultAtHC->block_hash)]; - processingResult.snapshotAtH2C = [DSQuorumSnapshot quorumSnapshotWith:result->snapshot_at_h_2c forBlockHash:*((UInt256 *)diffResultAtH2C->block_hash)]; - processingResult.snapshotAtH3C = [DSQuorumSnapshot quorumSnapshotWith:result->snapshot_at_h_3c forBlockHash:*((UInt256 *)diffResultAtH3C->block_hash)]; - BOOL extraShare = result->extra_share; - processingResult.extraShare = extraShare; - - processingResult.mnListDiffResultAtTip = [DSMnDiffProcessingResult processingResultWith:result->result_at_tip onChain:chain]; - processingResult.mnListDiffResultAtH = [DSMnDiffProcessingResult processingResultWith:result->result_at_h onChain:chain]; - processingResult.mnListDiffResultAtHC = [DSMnDiffProcessingResult processingResultWith:diffResultAtHC onChain:chain]; - processingResult.mnListDiffResultAtH2C = [DSMnDiffProcessingResult processingResultWith:diffResultAtH2C onChain:chain]; - processingResult.mnListDiffResultAtH3C = [DSMnDiffProcessingResult processingResultWith:diffResultAtH3C onChain:chain]; - if (extraShare) { - processingResult.snapshotAtH4C = [DSQuorumSnapshot quorumSnapshotWith:result->snapshot_at_h_4c forBlockHash:*((UInt256 *)diffResultAtH4C->block_hash)]; - processingResult.mnListDiffResultAtH4C = [DSMnDiffProcessingResult processingResultWith:diffResultAtH4C onChain:chain]; - } - NSMutableOrderedSet *lastQuorumPerIndex = [NSMutableOrderedSet orderedSet]; - for (NSUInteger i = 0; i < result->last_quorum_per_index_count; i++) { - DSQuorumEntry *entry = [[DSQuorumEntry alloc] initWithEntry:result->last_quorum_per_index[i] onChain:chain]; - [lastQuorumPerIndex addObject:entry]; - } - processingResult.lastQuorumPerIndex = [lastQuorumPerIndex copy]; - NSAssert(result->quorum_snapshot_list_count == result->mn_list_diff_list_count, @"Num of snapshots & diffs should be equal"); - NSMutableOrderedSet *snapshotList = [NSMutableOrderedSet orderedSet]; - NSMutableOrderedSet *mnListDiffList = [NSMutableOrderedSet orderedSet]; - for (NSUInteger i = 0; i < result->quorum_snapshot_list_count; i++) { - MNListDiffResult *diff = result->mn_list_diff_list[i] ; - DSQuorumSnapshot *snapshot = [DSQuorumSnapshot quorumSnapshotWith:result->quorum_snapshot_list[i] forBlockHash:*((UInt256 *)diff->block_hash)]; - DSMnDiffProcessingResult *mnListDiff = [DSMnDiffProcessingResult processingResultWith:diff onChain:chain]; - [snapshotList addObject:snapshot]; - [mnListDiffList addObject:mnListDiff]; - } - processingResult.snapshotList = snapshotList; - processingResult.mnListDiffList = mnListDiffList; - return processingResult; -} - - -- (NSString *)debugDescription { - return [NSString stringWithFormat:@"%@: {\n diffs: [\ntip: %@,\nh: %@,\nh-c: %@,\nh-2c: %@,\nh-3c: %@,\nh-4c: %@\n],\n snapshots: [\nh-c: %@,\nh-2c: %@,\nh-3c: %@,\nh-4c: %@], \n lastQuorums: %@, \n diffs: %@, \n, snapshots: %@ \n]}", - [super debugDescription], - self.mnListDiffResultAtTip, - self.mnListDiffResultAtH, - self.mnListDiffResultAtHC, - self.mnListDiffResultAtH2C, - self.mnListDiffResultAtH3C, - self.mnListDiffResultAtH4C, - self.snapshotAtHC, - self.snapshotAtH2C, - self.snapshotAtH3C, - self.snapshotAtH4C, - self.lastQuorumPerIndex, - self.snapshotList, - self.mnListDiffList - ]; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSQuorumEntry+Mndiff.h b/DashSync/shared/Models/Masternode/DSQuorumEntry+Mndiff.h deleted file mode 100644 index 5427ea640..000000000 --- a/DashSync/shared/Models/Masternode/DSQuorumEntry+Mndiff.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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.h" -#import "DSQuorumEntry.h" -#import "dash_shared_core.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQuorumEntry (Mndiff) - -+ (NSDictionary *> *)entriesWithMap:(LLMQMap *_Nullable *_Nonnull)entries count:(uintptr_t)count onChain:(DSChain *)chain; -+ (NSArray *)entriesWith:(LLMQEntry *_Nullable *_Nonnull)entries count:(uintptr_t)count onChain:(DSChain *)chain; - -- (LLMQEntry *)ffi_malloc; -+ (void)ffi_free:(LLMQEntry *)entry; - -@end - - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSQuorumEntry+Mndiff.m b/DashSync/shared/Models/Masternode/DSQuorumEntry+Mndiff.m deleted file mode 100644 index da662b673..000000000 --- a/DashSync/shared/Models/Masternode/DSQuorumEntry+Mndiff.m +++ /dev/null @@ -1,86 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "DSQuorumEntry+Mndiff.h" -#import "NSData+Dash.h" - -@implementation DSQuorumEntry (Mndiff) - -+ (NSDictionary *> *)entriesWithMap:(LLMQMap *_Nullable *_Nonnull)entries count:(uintptr_t)count onChain:(DSChain *)chain { - NSMutableDictionary *> *quorums = [NSMutableDictionary dictionaryWithCapacity:count]; - for (NSUInteger i = 0; i < count; i++) { - LLMQMap *llmq_map = entries[i]; - LLMQType llmqType = (LLMQType)llmq_map->llmq_type; - NSMutableDictionary *quorumsOfType = [[NSMutableDictionary alloc] initWithCapacity:llmq_map->count]; - for (NSUInteger j = 0; j < llmq_map->count; j++) { - LLMQEntry *quorum_entry = llmq_map->values[j]; - NSData *hash = [NSData dataWithBytes:quorum_entry->llmq_hash length:32]; - DSQuorumEntry *entry = [[DSQuorumEntry alloc] initWithEntry:quorum_entry onChain:chain]; - [quorumsOfType setObject:entry forKey:hash]; - } - [quorums setObject:quorumsOfType forKey:@(llmqType)]; - } - return quorums; -} - -+ (NSArray *)entriesWith:(LLMQEntry *_Nullable *_Nonnull)entries count:(uintptr_t)count onChain:(DSChain *)chain { - NSMutableArray *result = [NSMutableArray arrayWithCapacity:count]; - for (NSUInteger i = 0; i < count; i++) { - [result addObject:[[DSQuorumEntry alloc] initWithEntry:entries[i] onChain:chain]]; - } - return result; -} - -- (LLMQEntry *)ffi_malloc { - LLMQEntry *quorum_entry = malloc(sizeof(LLMQEntry)); - quorum_entry->all_commitment_aggregated_signature = uint768_malloc([self allCommitmentAggregatedSignature]); - quorum_entry->commitment_hash = uint256_malloc([self commitmentHash]); - quorum_entry->llmq_type = (int8_t) [self llmqType]; - quorum_entry->entry_hash = uint256_malloc([self quorumEntryHash]); - quorum_entry->llmq_hash = uint256_malloc([self quorumHash]); - quorum_entry->public_key = uint384_malloc([self quorumPublicKey]); - quorum_entry->threshold_signature = uint768_malloc([self quorumThresholdSignature]); - quorum_entry->verification_vector_hash = uint256_malloc([self quorumVerificationVectorHash]); - quorum_entry->saved = [self saved]; - NSData *signersBitset = [self signersBitset]; - quorum_entry->signers_bitset = data_malloc(signersBitset); - quorum_entry->signers_bitset_length = signersBitset.length; - quorum_entry->signers_count = [self signersCount]; - NSData *validMembersBitset = [self validMembersBitset]; - quorum_entry->valid_members_bitset = data_malloc(validMembersBitset); - quorum_entry->valid_members_bitset_length = validMembersBitset.length; - quorum_entry->valid_members_count = [self validMembersCount]; - quorum_entry->verified = [self verified]; - quorum_entry->version = [self version]; - return quorum_entry; -} - -+ (void)ffi_free:(LLMQEntry *)entry { - free(entry->all_commitment_aggregated_signature); - if (entry->commitment_hash) - free(entry->commitment_hash); - free(entry->entry_hash); - free(entry->llmq_hash); - free(entry->public_key); - free(entry->threshold_signature); - free(entry->verification_vector_hash); - free(entry->signers_bitset); - free(entry->valid_members_bitset); - free(entry); -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSQuorumEntry.h b/DashSync/shared/Models/Masternode/DSQuorumEntry.h deleted file mode 100644 index c211c2633..000000000 --- a/DashSync/shared/Models/Masternode/DSQuorumEntry.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// DSQuorumEntry.h -// DashSync -// -// Created by Sam Westrich on 4/25/19. -// - -#import "BigIntTypes.h" -#import "DSChain.h" -#import "dash_shared_core.h" -#import - -@class DSChain, DSMasternodeList, DSQuorumEntryEntity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSQuorumEntry : NSObject - -@property (nonatomic, readonly) uint16_t version; -@property (nonatomic, readonly) uint32_t quorumIndex; -@property (nonatomic, readonly) UInt256 quorumHash; -@property (nonatomic, readonly) UInt384 quorumPublicKey; -@property (nonatomic, readonly) UInt768 quorumThresholdSignature; -@property (nonatomic, readonly) UInt256 quorumVerificationVectorHash; -@property (nonatomic, readonly) UInt768 allCommitmentAggregatedSignature; -@property (nonatomic, readonly) int32_t signersCount; -@property (nonatomic, readonly) LLMQType llmqType; -@property (nonatomic, readonly) int32_t validMembersCount; -@property (nonatomic, readonly) NSData *signersBitset; -@property (nonatomic, readonly) NSData *validMembersBitset; -@property (nonatomic, readonly) uint32_t length; -@property (nonatomic, readonly, getter=toData) NSData *data; -@property (nonatomic, readonly) UInt256 quorumEntryHash; -@property (nonatomic, readonly) UInt256 commitmentHash; -@property (nonatomic, readonly) DSChain *chain; -@property (nonatomic, readonly) BOOL verified; -@property (nonatomic, assign) BOOL saved; - -- (instancetype)initWithVersion:(uint16_t)version type:(LLMQType)type quorumHash:(UInt256)quorumHash quorumIndex:(uint32_t)quorumIndex quorumPublicKey:(UInt384)quorumPublicKey quorumEntryHash:(UInt256)commitmentHash verified:(BOOL)verified onChain:(DSChain *)chain; -- (instancetype)initWithVersion:(uint16_t)version - type:(LLMQType)type - quorumHash:(UInt256)quorumHash - quorumIndex:(uint32_t)quorumIndex - signersCount:(int32_t)signersCount - signersBitset:(NSData *)signersBitset - validMembersCount:(int32_t)validMembersCount - validMembersBitset:(NSData *)validMembersBitset - quorumPublicKey:(UInt384)quorumPublicKey - quorumVerificationVectorHash:(UInt256)quorumVerificationVectorHash - quorumThresholdSignature:(UInt768)quorumThresholdSignature -allCommitmentAggregatedSignature:(UInt768)allCommitmentAggregatedSignature - quorumEntryHash:(UInt256)quorumEntryHash - onChain:(DSChain *)chain; - -- (instancetype)initWithEntry:(LLMQEntry *)entry onChain:(DSChain *)chain; - -- (BOOL)validateWithMasternodeList:(DSMasternodeList *)masternodeList; -- (BOOL)validateWithMasternodeList:(DSMasternodeList *)masternodeList blockHeightLookup:(BlockHeightFinder)blockHeightLookup; - -- (DSQuorumEntryEntity *)matchingQuorumEntryEntityInContext:(NSManagedObjectContext *)context; - -- (UInt256)orderingHashForRequestID:(UInt256)requestID forQuorumType:(LLMQType)quorumType; - -+ (uint32_t)quorumSizeForType:(LLMQType)type; - -- (void)mergedWithQuorumEntry:(DSQuorumEntry *)quorumEntry; - -- (BOOL)useLegacyBLSScheme; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Masternode/DSQuorumEntry.m b/DashSync/shared/Models/Masternode/DSQuorumEntry.m deleted file mode 100644 index 903df11e7..000000000 --- a/DashSync/shared/Models/Masternode/DSQuorumEntry.m +++ /dev/null @@ -1,277 +0,0 @@ -// -// DSQuorumEntry.m -// DashSync -// -// Created by Sam Westrich on 4/25/19. -// - -#import "DSQuorumEntry.h" -#import "DSBlock.h" -#import "DSChainManager.h" -#import "DSMasternodeList.h" -#import "DSMasternodeList+Mndiff.h" -#import "DSMasternodeManager.h" -#import "DSMerkleBlock.h" -#import "DSQuorumEntry+Mndiff.h" -#import "DSQuorumEntryEntity+CoreDataClass.h" -#import "DSSimplifiedMasternodeEntry.h" -#import "NSData+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" - -@interface DSQuorumEntry () - -@property (nonatomic, assign) uint16_t version; -@property (nonatomic, assign) uint32_t quorumIndex; -@property (nonatomic, assign) UInt256 quorumHash; -@property (nonatomic, assign) UInt384 quorumPublicKey; -@property (nonatomic, assign) UInt768 quorumThresholdSignature; -@property (nonatomic, assign) UInt256 quorumVerificationVectorHash; -@property (nonatomic, assign) UInt768 allCommitmentAggregatedSignature; -@property (nonatomic, assign) int32_t signersCount; -@property (nonatomic, assign) LLMQType llmqType; -@property (nonatomic, assign) int32_t validMembersCount; -@property (nonatomic, strong) NSData *signersBitset; -@property (nonatomic, strong) NSData *validMembersBitset; -@property (nonatomic, assign) UInt256 quorumEntryHash; -@property (nonatomic, assign) UInt256 commitmentHash; -@property (nonatomic, assign) uint32_t length; -@property (nonatomic, strong) DSChain *chain; -@property (nonatomic, assign) BOOL verified; - -@end - -@implementation DSQuorumEntry - -- (id)copyWithZone:(NSZone *)zone { - DSQuorumEntry *copy = [[[self class] alloc] init]; - if (!copy) return nil; - // Copy NSObject subclasses - [copy setSignersBitset:self.signersBitset]; - [copy setValidMembersBitset:self.validMembersBitset]; - - // Set primitives - [copy setVersion:self.version]; - [copy setQuorumHash:self.quorumHash]; - [copy setQuorumPublicKey:self.quorumPublicKey]; - [copy setQuorumThresholdSignature:self.quorumThresholdSignature]; - [copy setQuorumVerificationVectorHash:self.quorumVerificationVectorHash]; - [copy setAllCommitmentAggregatedSignature:self.allCommitmentAggregatedSignature]; - [copy setSignersCount:self.signersCount]; - [copy setLlmqType:self.llmqType]; - [copy setValidMembersCount:self.validMembersCount]; - [copy setQuorumEntryHash:self.quorumEntryHash]; - [copy setCommitmentHash:self.commitmentHash]; -// [copy setLength:self.length]; - [copy setQuorumIndex:self.quorumIndex]; - [copy setChain:self.chain]; - - return copy; -} - -- (instancetype)initWithVersion:(uint16_t)version - type:(LLMQType)type - quorumHash:(UInt256)quorumHash - quorumIndex:(uint32_t)quorumIndex - signersCount:(int32_t)signersCount - signersBitset:(NSData *)signersBitset - validMembersCount:(int32_t)validMembersCount - validMembersBitset:(NSData *)validMembersBitset - quorumPublicKey:(UInt384)quorumPublicKey - quorumVerificationVectorHash:(UInt256)quorumVerificationVectorHash - quorumThresholdSignature:(UInt768)quorumThresholdSignature -allCommitmentAggregatedSignature:(UInt768)allCommitmentAggregatedSignature - quorumEntryHash:(UInt256)quorumEntryHash - onChain:(DSChain *)chain { - if (!(self = [super init])) return nil; - - self.llmqType = type; - self.version = version; - self.quorumHash = quorumHash; - self.quorumIndex = quorumIndex; - self.signersCount = signersCount; - self.signersBitset = signersBitset; - self.validMembersCount = validMembersCount; - self.validMembersBitset = validMembersBitset; - self.quorumPublicKey = quorumPublicKey; - self.quorumVerificationVectorHash = quorumVerificationVectorHash; - self.quorumVerificationVectorHash = quorumVerificationVectorHash; - self.quorumThresholdSignature = quorumThresholdSignature; - self.allCommitmentAggregatedSignature = allCommitmentAggregatedSignature; - self.quorumEntryHash = quorumEntryHash; - self.chain = chain; - - return self; -} - -- (instancetype)initWithVersion:(uint16_t)version type:(LLMQType)type quorumHash:(UInt256)quorumHash quorumIndex:(uint32_t)quorumIndex quorumPublicKey:(UInt384)quorumPublicKey quorumEntryHash:(UInt256)quorumEntryHash verified:(BOOL)verified onChain:(DSChain *)chain { - if (!(self = [super init])) return nil; - - self.llmqType = type; - self.version = version; - self.quorumHash = quorumHash; - self.quorumPublicKey = quorumPublicKey; - self.quorumEntryHash = quorumEntryHash; - self.quorumIndex = quorumIndex; - self.verified = verified; - self.chain = chain; - self.saved = TRUE; - - return self; -} - -- (instancetype)initWithEntry:(LLMQEntry *)entry onChain:(DSChain *)chain { - if (!(self = [super init])) return nil; - self.allCommitmentAggregatedSignature = *((UInt768 *)entry->all_commitment_aggregated_signature); - if (entry->commitment_hash) { - self.commitmentHash = *((UInt256 *)entry->commitment_hash); - } - self.llmqType = entry->llmq_type; - self.quorumEntryHash = *((UInt256 *)entry->entry_hash); - self.quorumHash = *((UInt256 *)entry->llmq_hash); - self.quorumPublicKey = *((UInt384 *)entry->public_key); - self.quorumThresholdSignature = *((UInt768 *)entry->threshold_signature); - self.quorumVerificationVectorHash = *((UInt256 *)entry->verification_vector_hash); - self.quorumIndex = entry->index; - self.saved = entry->saved; - self.signersBitset = [NSData dataWithBytes:entry->signers_bitset length:entry->signers_bitset_length]; - self.signersCount = (uint32_t)entry->signers_count; - self.validMembersBitset = [NSData dataWithBytes:entry->valid_members_bitset length:entry->valid_members_bitset_length]; - self.validMembersCount = (uint32_t)entry->valid_members_count; - self.verified = entry->verified; - self.version = entry->version; - self.chain = chain; - return self; -} - -- (NSData *)toData { - NSMutableData *data = [NSMutableData data]; - [data appendUInt16:self.version]; - [data appendUInt8:self.llmqType]; - [data appendUInt256:self.quorumHash]; - if (self.version == LLMQVersion_Indexed || self.version == LLMQVersion_BLSBasicIndexed) - [data appendUInt32:self.quorumIndex]; - [data appendVarInt:self.signersCount]; - [data appendData:self.signersBitset]; - [data appendVarInt:self.validMembersCount]; - [data appendData:self.validMembersBitset]; - [data appendUInt384:self.quorumPublicKey]; - [data appendUInt256:self.quorumVerificationVectorHash]; - [data appendUInt768:self.quorumThresholdSignature]; - [data appendUInt768:self.allCommitmentAggregatedSignature]; - return data; -} - -- (UInt256)commitmentHash { - if (uint256_is_zero(_commitmentHash)) { - NSData *data = [self commitmentData]; - _commitmentHash = [data SHA256_2]; - } - return _commitmentHash; -} - -- (NSData *)commitmentData { - NSMutableData *data = [NSMutableData data]; - [data appendVarInt:self.llmqType]; - [data appendUInt256:self.quorumHash]; - if (self.version == LLMQVersion_Indexed || self.version == LLMQVersion_BLSBasicIndexed) - [data appendUInt32:self.quorumIndex]; - [data appendVarInt:self.validMembersCount]; - [data appendData:self.validMembersBitset]; - [data appendUInt384:self.quorumPublicKey]; - [data appendUInt256:self.quorumVerificationVectorHash]; - return data; -} - -- (uint32_t)quorumThreshold { - return quorum_threshold_for_type(self.llmqType); -} - -- (BOOL)validateWithMasternodeList:(DSMasternodeList *)masternodeList { - return [self validateWithMasternodeList:masternodeList - blockHeightLookup:^uint32_t(UInt256 blockHash) { - DSMerkleBlock *block = [self.chain blockForBlockHash:blockHash]; - if (!block) { - DSLog(@"[%@] Unknown block %@", self.chain.name, uint256_reverse_hex(blockHash)); - NSAssert(block, @"block should be known"); - } - return block.height; - }]; -} - -- (BOOL)validateWithMasternodeList:(DSMasternodeList *)masternodeList blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - if (!masternodeList) { - DSLog(@"[%@] Trying to validate a quorum without a masternode list", self.chain.name); - return NO; - } - MasternodeList *list = [masternodeList ffi_malloc]; - LLMQEntry *quorum = [self ffi_malloc]; - BOOL is_valid = validate_masternode_list(list, quorum, blockHeightLookup(masternodeList.blockHash), self.chain.chainType, NULL); - [DSMasternodeList ffi_free:list]; - [DSQuorumEntry ffi_free:quorum]; - self.verified = is_valid; - return is_valid; -} - -- (DSQuorumEntryEntity *)matchingQuorumEntryEntityInContext:(NSManagedObjectContext *)context { - return [DSQuorumEntryEntity anyObjectInContext:context matching:@"quorumPublicKeyData == %@", uint384_data(self.quorumPublicKey)]; -} - -- (UInt256)orderingHashForRequestID:(UInt256)requestID forQuorumType:(LLMQType)quorumType { - NSMutableData *data = [NSMutableData data]; - [data appendVarInt:quorumType]; - [data appendUInt256:self.quorumHash]; - [data appendUInt256:requestID]; - return [data SHA256_2]; -} - -+ (uint32_t)quorumSizeForType:(LLMQType)type { - return quorum_size_for_type(type); -} - - -- (NSString *)description { - uint32_t height = [self.chain heightForBlockHash:self.quorumHash]; - return [[super description] stringByAppendingString:[NSString stringWithFormat:@" - %u", height]]; -} - -- (NSString *)debugDescription { - uint32_t height = [self.chain heightForBlockHash:self.quorumHash]; - return [[super debugDescription] stringByAppendingString:[NSString stringWithFormat:@" - %u -%u", height, self.version]]; -} - -- (BOOL)isEqual:(id)object { - if (self == object) return YES; - if (![object isKindOfClass:[DSQuorumEntry class]]) return NO; - return uint256_eq(self.quorumEntryHash, ((DSQuorumEntry *)object).quorumEntryHash); -} - -- (NSUInteger)hash { - return [uint256_data(self.quorumEntryHash) hash]; -} - -- (void)mergedWithQuorumEntry:(DSQuorumEntry *)quorumEntry { - self.allCommitmentAggregatedSignature = quorumEntry.allCommitmentAggregatedSignature; - self.commitmentHash = quorumEntry.commitmentHash; - self.llmqType = quorumEntry.llmqType; - self.quorumEntryHash = quorumEntry.quorumEntryHash; - self.quorumHash = quorumEntry.quorumHash; - self.quorumPublicKey = quorumEntry.quorumPublicKey; - self.quorumThresholdSignature = quorumEntry.quorumThresholdSignature; - self.quorumVerificationVectorHash = quorumEntry.quorumVerificationVectorHash; - self.quorumIndex = quorumEntry.quorumIndex; - self.saved = quorumEntry.saved; - self.signersBitset = quorumEntry.signersBitset; - self.signersCount = quorumEntry.signersCount; - self.validMembersBitset = quorumEntry.validMembersBitset; - self.validMembersCount = quorumEntry.validMembersCount; - self.verified = quorumEntry.verified; - self.version = quorumEntry.version; - self.chain = quorumEntry.chain; -} - -- (BOOL)useLegacyBLSScheme { - return self.version <= 2; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSQuorumRotationService.h b/DashSync/shared/Models/Masternode/DSQuorumRotationService.h index 4139b8b6f..93055e0a9 100644 --- a/DashSync/shared/Models/Masternode/DSQuorumRotationService.h +++ b/DashSync/shared/Models/Masternode/DSQuorumRotationService.h @@ -17,17 +17,21 @@ #import #import "DSMasternodeListService.h" +#import "DSMerkleBlock.h" NS_ASSUME_NONNULL_BEGIN @interface DSQuorumRotationService : DSMasternodeListService -@property (nonatomic) DSMasternodeList *masternodeListAtTip; -@property (nonatomic) DSMasternodeList *masternodeListAtH; -@property (nonatomic) DSMasternodeList *masternodeListAtHC; -@property (nonatomic) DSMasternodeList *masternodeListAtH2C; -@property (nonatomic) DSMasternodeList *masternodeListAtH3C; -@property (nonatomic) DSMasternodeList *masternodeListAtH4C; +@property (nonatomic, assign) NSTimeInterval lastSyncedTimestamp; +//@property (nonatomic, assign, nullable) DMasternodeList *masternodeListAtTip; +//@property (nonatomic, assign, nullable) DMasternodeList *masternodeListAtH; +//@property (nonatomic, assign, nullable) DMasternodeList *masternodeListAtHC; +//@property (nonatomic, assign, nullable) DMasternodeList *masternodeListAtH2C; +//@property (nonatomic, assign, nullable) DMasternodeList *masternodeListAtH3C; +//@property (nonatomic, assign, nullable) DMasternodeList *masternodeListAtH4C; +- (void)getRecent:(UInt256)blockHash; +- (void)fetchMasternodeListToRetrieve:(void (^)(NSData *listsToRetrieve))completion; @end diff --git a/DashSync/shared/Models/Masternode/DSQuorumRotationService.m b/DashSync/shared/Models/Masternode/DSQuorumRotationService.m index dcfb6a254..08fd48097 100644 --- a/DashSync/shared/Models/Masternode/DSQuorumRotationService.m +++ b/DashSync/shared/Models/Masternode/DSQuorumRotationService.m @@ -15,87 +15,86 @@ // limitations under the License. // +#import "DSChain+Params.h" +#import "DSChain+Protected.h" +#import "DSChainManager+Protected.h" #import "DSGetQRInfoRequest.h" #import "DSQuorumRotationService.h" #import "DSMasternodeListService+Protected.h" -#import "DSMasternodeListStore+Protected.h" +#import "DSMasternodeManager.h" + +@interface DSQuorumRotationService () + +@property (nonatomic, strong) NSData *retrievalBlockHash; + +@end @implementation DSQuorumRotationService -- (DSMasternodeList *)currentMasternodeList { - return self.masternodeListAtTip; +- (NSString *)logPrefix { + return [NSString stringWithFormat:@"[%@] [QRInfoService] ", self.chain.name]; +} + +- (BOOL)hasRecentQrInfoSync { + return ([[NSDate date] timeIntervalSince1970] - self.lastSyncedTimestamp < 30); } -- (void)composeMasternodeListRequest:(NSOrderedSet *)list { - NSData *blockHashData = [list lastObject]; + +- (void)composeMasternodeListRequest:(NSData *)blockHashData { if (!blockHashData) { return; } - if ([self.store hasBlockForBlockHash:blockHashData]) { + if ([self.chain.masternodeManager hasBlockForBlockHash:blockHashData]) { UInt256 blockHash = blockHashData.UInt256; - UInt256 previousBlockHash = [self.store closestKnownBlockHashForBlockHash:blockHash]; - NSAssert(([self.store heightForBlockHash:previousBlockHash] != UINT32_MAX) || uint256_is_zero(previousBlockHash), @"This block height should be known"); + uint32_t blockHeight = [self.chain heightForBlockHash:blockHash]; + UInt256 previousBlockHash = [self closestKnownBlockHashForBlockHeight:blockHeight]; +// NSAssert(([self.store heightForBlockHash:previousBlockHash] != UINT32_MAX) || uint256_is_zero(previousBlockHash), @"This block height should be known"); [self requestQuorumRotationInfo:previousBlockHash forBlockHash:blockHash]; } else { - DSLog(@"[%@] Missing block (%@)", self.chain.name, blockHashData.hexString); - [self removeFromRetrievalQueue:blockHashData]; + DSLog(@"%@ Missing block: %@ (%@)", self.logPrefix, blockHashData.hexString, blockHashData.reverse.hexString); + self.retrievalBlockHash = nil; } - /* - NSMutableDictionary *hashes = [NSMutableDictionary dictionary]; - for (NSData *blockHashData in list) { - // we should check the associated block still exists - if ([self.store hasBlockForBlockHash:blockHashData]) { - //there is the rare possibility we have the masternode list as a checkpoint, so lets first try that - NSUInteger pos = [list indexOfObject:blockHashData]; - UInt256 blockHash = blockHashData.UInt256; - // No checkpoints for qrinfo at this moment - UInt256 prevKnownBlockHash = [self.store closestKnownBlockHashForBlockHash:blockHash]; - UInt256 prevInQueueBlockHash = (pos ? [list objectAtIndex:pos - 1].UInt256 : UINT256_ZERO); - UInt256 previousBlockHash = pos - ? ([self.store heightForBlockHash:prevKnownBlockHash] > [self.store heightForBlockHash:prevInQueueBlockHash] - ? prevKnownBlockHash - : prevInQueueBlockHash) - : prevKnownBlockHash; - [hashes setObject:uint256_data(blockHash) forKey:uint256_data(previousBlockHash)]; - NSAssert(([self.store heightForBlockHash:previousBlockHash] != UINT32_MAX) || uint256_is_zero(previousBlockHash), @"This block height should be known"); - [self requestQuorumRotationInfo:previousBlockHash forBlockHash:blockHash]; - } else { - DSLog(@"Missing block (%@)", blockHashData.hexString); - [self removeFromRetrievalQueue:blockHashData]; - } - }*/ } +- (void)dequeueMasternodeListRequest { + [self fetchMasternodeListToRetrieve:^(NSData *blockHashData) { + [self composeMasternodeListRequest:blockHashData]; + [self startTimeOutObserver]; + }]; +} -- (void)getRecentMasternodeList { - @synchronized(self.retrievalQueue) { - DSMerkleBlock *merkleBlock = [self.chain blockFromChainTip:0]; - if (!merkleBlock) { - // sometimes it happens while rescan - DSLog(@"[%@] getRecentMasternodeList: (no block exist) for tip", self.chain.name); - return; - } - UInt256 merkleBlockHash = merkleBlock.blockHash; - if ([self hasLatestBlockInRetrievalQueueWithHash:merkleBlockHash]) { - //we are asking for the same as the last one - return; - } - uint32_t lastHeight = merkleBlock.height; - DKGParams dkgParams = self.chain.isDevnetAny ? DKG_DEVNET_DIP_0024 : DKG_60_75; - uint32_t rotationOffset = dkgParams.mining_window_end; - uint32_t updateInterval = dkgParams.interval; - BOOL needUpdate = !self.masternodeListAtH || [self.masternodeListAtH hasUnverifiedRotatedQuorums] || - (lastHeight % updateInterval == rotationOffset && lastHeight >= [self.store heightForBlockHash:self.masternodeListAtH.blockHash] + rotationOffset); - if (needUpdate && [self.store addBlockToValidationQueue:merkleBlock]) { - DSLog(@"[%@] QuorumRotationService.Getting masternode list %u", self.chain.name, merkleBlock.height); - NSData *merkleBlockHashData = uint256_data(merkleBlockHash); - BOOL emptyRequestQueue = ![self retrievalQueueCount]; - [self addToRetrievalQueue:merkleBlockHashData]; - if (emptyRequestQueue) { - [self dequeueMasternodeListRequest]; - } +- (NSUInteger)retrievalQueueMaxAmount { return 1; } + +- (void)fetchMasternodeListToRetrieve:(void (^)(NSData *listsToRetrieve))completion { + if ([self.requestsInRetrieval count]) { + DSLog(@"%@ A masternode list is already in retrieval", self.logPrefix); + return; + } + if ([self peerIsDisconnected]) { + if (self.chain.chainManager.syncPhase != DSChainSyncPhase_Offline) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), self.chain.networkingQueue, ^{ + [self fetchMasternodeListToRetrieve:completion]; + }); } + return; } + completion([self.retrievalBlockHash copy]); +} + +- (void)getRecent:(UInt256)blockHash { +// if (self.retrievalBlockHash) +// BOOL hasLatestBlockWithHash = uint256_eq(self.retrievalBlockHash.UInt256, blockHash); +// if (hasLatestBlockWithHash || [self hasRecentQrInfoSync]) +// return; + self.retrievalBlockHash = uint256_data(blockHash); + [self dequeueMasternodeListRequest]; +// NSUInteger newCount = [self addToRetrievalQueue:uint256_data(blockHash)]; +// if (newCount == 1) { +// } +} + +- (void)cleanListsRetrievalQueue { + self.retrievalBlockHash = nil; } - (void)requestQuorumRotationInfo:(UInt256)previousBlockHash forBlockHash:(UInt256)blockHash { @@ -103,12 +102,17 @@ - (void)requestQuorumRotationInfo:(UInt256)previousBlockHash forBlockHash:(UInt2 // blockHeight % dkgInterval == activeSigningQuorumsCount + 11 + 8 DSMasternodeListRequest *matchedRequest = [self requestInRetrievalFor:previousBlockHash blockHash:blockHash]; if (matchedRequest) { - DSLog(@"[%@] •••• qrinfo request with such a range already in retrieval: %@ .. %@", self.chain.name, uint256_hex(previousBlockHash), uint256_hex(blockHash)); + DSLog(@"%@ Request: already in retrieval: %@ .. %@", self.logPrefix, uint256_hex(previousBlockHash), uint256_hex(blockHash)); return; } NSArray *baseBlockHashes = @[[NSData dataWithUInt256:previousBlockHash]]; DSGetQRInfoRequest *request = [DSGetQRInfoRequest requestWithBaseBlockHashes:baseBlockHashes blockHash:blockHash extraShare:YES]; - DSLog(@"[%@] •••• requestQuorumRotationInfo: %@ .. %@", self.chain.name, uint256_hex(previousBlockHash), uint256_hex(blockHash)); + uint32_t prev_h = [self.chain heightForBlockHash:previousBlockHash]; + uint32_t h = [self.chain heightForBlockHash:blockHash]; + +// uint32_t prev_h = DHeightForBlockHash(self.chain.sharedProcessorObj, u256_ctor_u(previousBlockHash)); +// uint32_t h = DHeightForBlockHash(self.chain.sharedProcessorObj, u256_ctor_u(blockHash)); + DSLog(@"%@ Request: %u..%u %@ .. %@", self.logPrefix, prev_h, h, uint256_hex(previousBlockHash), uint256_hex(blockHash)); [self sendMasternodeListRequest:request]; } diff --git a/DashSync/shared/Models/Masternode/DSQuorumSnapshot+Mndiff.m b/DashSync/shared/Models/Masternode/DSQuorumSnapshot+Mndiff.m deleted file mode 100644 index 7ca914c69..000000000 --- a/DashSync/shared/Models/Masternode/DSQuorumSnapshot+Mndiff.m +++ /dev/null @@ -1,62 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSQuorumSnapshot+Mndiff.h" - -@implementation DSQuorumSnapshot (Mndiff) - -+ (instancetype)quorumSnapshotWith:(LLMQSnapshot *)quorumSnapshot forBlockHash:(UInt256)blockHash { - DSQuorumSnapshot *snapshot = [[DSQuorumSnapshot alloc] init]; - NSUInteger memberListLength = quorumSnapshot->member_list_length; - NSData *memberList = [NSData dataWithBytes:quorumSnapshot->member_list length:memberListLength]; - NSUInteger skipListLength = quorumSnapshot->skip_list_length; - NSMutableArray *skipList = [NSMutableArray arrayWithCapacity:skipListLength]; - const int32_t *skip_list_bytes = quorumSnapshot->skip_list; - for (NSUInteger i = 0; i < skipListLength; i++) { - [skipList addObject:@(skip_list_bytes[i])]; - } - [snapshot setMemberList:[memberList copy]]; - [snapshot setSkipList:[skipList copy]]; - [snapshot setSkipListMode:quorumSnapshot->skip_list_mode]; - [snapshot setBlockHash:blockHash]; - return snapshot; -} - -- (LLMQSnapshot *)ffi_malloc { - LLMQSnapshot *entry = malloc(sizeof(LLMQSnapshot)); - NSUInteger skipListCount = [self.skipList count]; - int32_t *skipList = malloc(skipListCount * sizeof(int32_t)); - NSUInteger i = 0; - for (NSNumber *skipMember in self.skipList) { - skipList[i] = skipMember.intValue; - i++; - } - entry->member_list = data_malloc(self.memberList); - entry->member_list_length = self.memberList.length; - entry->skip_list = skipList; - entry->skip_list_length = skipListCount; - entry->skip_list_mode = self.skipListMode; - return entry; -} - -+ (void)ffi_free:(LLMQSnapshot *)entry { - free(entry->member_list); - free(entry->skip_list); - free(entry); -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSQuorumSnapshot.m b/DashSync/shared/Models/Masternode/DSQuorumSnapshot.m deleted file mode 100644 index b83da7190..000000000 --- a/DashSync/shared/Models/Masternode/DSQuorumSnapshot.m +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSQuorumSnapshot.h" - -@implementation DSQuorumSnapshot - -- (NSString *)debugDescription { - return [NSString stringWithFormat:@"%@: mode: %d, members: %@, skipped: %@", - [super debugDescription], self.skipListMode, self.memberList, self.skipList - ]; -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry+Mndiff.m b/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry+Mndiff.m deleted file mode 100644 index 4c09a71a3..000000000 --- a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry+Mndiff.m +++ /dev/null @@ -1,204 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2021 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 "DSBlock.h" -#import "DSSimplifiedMasternodeEntry+Mndiff.h" -#import "NSData+Dash.h" - -@implementation DSSimplifiedMasternodeEntry (Mndiff) - -+ (instancetype)simplifiedEntryWith:(MasternodeEntry *)entry onChain:(DSChain *)chain { - UInt256 confirmedHash = *((UInt256 *)entry->confirmed_hash); - // TODO: Refactor to avoid unnecessary SHAing - /*uint8_t (*confirmed_hash_hashed_with_provider_registration_transaction_hash)[32] = entry->confirmed_hash_hashed_with_provider_registration_transaction_hash; - NSData *confirmedHashHashedWithProviderRegistrationTransactionHashData = confirmed_hash_hashed_with_provider_registration_transaction_hash - ? [NSData dataWithBytes:confirmed_hash_hashed_with_provider_registration_transaction_hash length:32] - : nil; - UInt256 confirmedHashHashedWithProviderRegistrationTransactionHash = [confirmedHashHashedWithProviderRegistrationTransactionHashData UInt256];*/ - BOOL isValid = entry->is_valid; - UInt160 keyIDVoting = *((UInt160 *)entry->key_id_voting); - uint32_t knownConfirmedAtHeight = entry->known_confirmed_at_height; - UInt256 simplifiedMasternodeEntryHash = *((UInt256 *)entry->entry_hash); - OperatorPublicKey *operator_public_key = entry->operator_public_key; - UInt384 operatorPublicKey = *((UInt384 *)operator_public_key->data); - uint16_t operatorPublicKeyVersion = operator_public_key->version; - uintptr_t previous_operator_public_keys_count = entry->previous_operator_public_keys_count; - BlockOperatorPublicKey *previous_operator_public_keys = entry->previous_operator_public_keys; - NSMutableDictionary *operatorPublicKeys = [NSMutableDictionary dictionaryWithCapacity:previous_operator_public_keys_count]; - for (NSUInteger i = 0; i < previous_operator_public_keys_count; i++) { - BlockOperatorPublicKey prev_operator_public_key = previous_operator_public_keys[i]; - UInt256 blockHash = *((UInt256 *)prev_operator_public_key.block_hash); - uint32_t blockHeight = prev_operator_public_key.block_height; - NSData *block = [NSData dataWithBlockHash:blockHash height:blockHeight]; - NSMutableData *data = [NSMutableData dataWithUInt384:*((UInt384 *)prev_operator_public_key.key)]; - [data appendData:[NSData dataWithUInt16:prev_operator_public_key.version]]; - [operatorPublicKeys setObject:data forKey:block]; - } - uintptr_t previous_entry_hashes_count = entry->previous_entry_hashes_count; - MasternodeEntryHash *previous_entry_hashes = entry->previous_entry_hashes; - NSMutableDictionary *masternodeEntryHashes = [NSMutableDictionary dictionaryWithCapacity:previous_entry_hashes_count]; - for (NSUInteger i = 0; i < previous_entry_hashes_count; i++) { - MasternodeEntryHash entry_hash = previous_entry_hashes[i]; - UInt256 blockHash = *((UInt256 *)entry_hash.block_hash); - uint32_t blockHeight = entry_hash.block_height; - NSData *block = [NSData dataWithBlockHash:blockHash height:blockHeight]; - NSData *hash = [NSData dataWithBytes:entry_hash.hash length:32]; - [masternodeEntryHashes setObject:hash forKey:block]; - } - uintptr_t previous_validity_count = entry->previous_validity_count; - Validity *previous_validity = entry->previous_validity; - NSMutableDictionary *validities = [NSMutableDictionary dictionaryWithCapacity:previous_validity_count]; - for (NSUInteger i = 0; i < previous_validity_count; i++) { - Validity validity = previous_validity[i]; - UInt256 blockHash = *((UInt256 *)validity.block_hash); - uint32_t blockHeight = validity.block_height; - NSData *block = [NSData dataWithBlockHash:blockHash height:blockHeight]; - NSNumber *isValid = [NSNumber numberWithBool:validity.is_valid]; - [validities setObject:isValid forKey:block]; - } - UInt256 providerRegistrationTransactionHash = *((UInt256 *)entry->provider_registration_transaction_hash); - UInt128 address = *((UInt128 *)entry->ip_address); - uint16_t port = entry->port; - uint32_t updateHeight = entry->update_height; - uint16_t type = entry->mn_type; - uint16_t platformHTTPPort = entry->platform_http_port; - UInt160 platformNodeID = *((UInt160 *)entry->platform_node_id); - return [self simplifiedMasternodeEntryWithProviderRegistrationTransactionHash:providerRegistrationTransactionHash - confirmedHash:confirmedHash - address:address - port:port - operatorBLSPublicKey:operatorPublicKey - operatorPublicKeyVersion:operatorPublicKeyVersion - previousOperatorBLSPublicKeys:operatorPublicKeys - keyIDVoting:keyIDVoting - isValid:isValid - type:type - platformHTTPPort:platformHTTPPort - platformNodeID:platformNodeID - previousValidity:validities - knownConfirmedAtHeight:knownConfirmedAtHeight - updateHeight:updateHeight - simplifiedMasternodeEntryHash:simplifiedMasternodeEntryHash - previousSimplifiedMasternodeEntryHashes:masternodeEntryHashes - onChain:chain]; -} -+ (NSDictionary *)simplifiedEntriesWith:(MasternodeEntry *_Nullable *_Nonnull)entries count:(uintptr_t)count onChain:(DSChain *)chain { - NSMutableDictionary *masternodes = [NSMutableDictionary dictionaryWithCapacity:count]; - for (NSUInteger i = 0; i < count; i++) { - MasternodeEntry *c_entry = entries[i]; - DSSimplifiedMasternodeEntry *entry = [DSSimplifiedMasternodeEntry simplifiedEntryWith:c_entry onChain:chain]; - UInt256 hash = uint256_reverse(entry.providerRegistrationTransactionHash); - [masternodes setObject:entry forKey:uint256_data(hash)]; - } - return masternodes; -} - -- (MasternodeEntry *)ffi_malloc { - uint32_t known_confirmed_at_height = [self knownConfirmedAtHeight]; - NSDictionary *previousOperatorPublicKeys = [self previousOperatorPublicKeys]; - NSDictionary *previousSimplifiedMasternodeEntryHashes = [self previousSimplifiedMasternodeEntryHashes]; - NSDictionary *previousValidity = [self previousValidity]; - MasternodeEntry *masternode_entry = malloc(sizeof(MasternodeEntry)); - masternode_entry->confirmed_hash = uint256_malloc([self confirmedHash]); - masternode_entry->confirmed_hash_hashed_with_provider_registration_transaction_hash = uint256_malloc([self confirmedHashHashedWithProviderRegistrationTransactionHash]); - masternode_entry->is_valid = [self isValid]; - masternode_entry->key_id_voting = uint160_malloc([self keyIDVoting]); - masternode_entry->known_confirmed_at_height = known_confirmed_at_height; - masternode_entry->entry_hash = uint256_malloc([self simplifiedMasternodeEntryHash]); - OperatorPublicKey *operator_public_key = malloc(sizeof(OperatorPublicKey)); - memcpy(operator_public_key->data, [self operatorPublicKey].u8, sizeof(UInt384)); - operator_public_key->version = self.operatorPublicKeyVersion; - masternode_entry->operator_public_key = operator_public_key; - NSUInteger previousOperatorPublicKeysCount = [previousOperatorPublicKeys count]; - BlockOperatorPublicKey *previous_operator_public_keys = malloc(previousOperatorPublicKeysCount * sizeof(BlockOperatorPublicKey)); - NSUInteger i = 0; - for (NSData *block in previousOperatorPublicKeys) { - NSData *keyVersionData = previousOperatorPublicKeys[block]; - UInt256 blockHash = *(UInt256 *)(block.bytes); - uint32_t blockHeight = *(uint32_t *)(block.bytes + sizeof(UInt256)); - BlockOperatorPublicKey obj = {.block_height = blockHeight}; - memcpy(obj.block_hash, blockHash.u8, sizeof(UInt256)); - if (keyVersionData.length == 48) { - obj.version = 0; - memcpy(obj.key, keyVersionData.bytes, sizeof(UInt384)); - } else { - UInt384 keyData = [keyVersionData UInt384AtOffset:0]; - obj.version = [keyVersionData UInt16AtOffset:48]; - memcpy(obj.key, keyData.u8, sizeof(UInt384)); - } - previous_operator_public_keys[i++] = obj; - } - masternode_entry->previous_operator_public_keys = previous_operator_public_keys; - masternode_entry->previous_operator_public_keys_count = previousOperatorPublicKeysCount; - NSUInteger previousSimplifiedMasternodeEntryHashesCount = [previousSimplifiedMasternodeEntryHashes count]; - MasternodeEntryHash *previous_masternode_entry_hashes = malloc(previousSimplifiedMasternodeEntryHashesCount * sizeof(MasternodeEntryHash)); - i = 0; - for (NSData *block in previousSimplifiedMasternodeEntryHashes) { - NSData *hashData = previousSimplifiedMasternodeEntryHashes[block]; - UInt256 blockHash = *(UInt256 *)(block.bytes); - uint32_t blockHeight = *(uint32_t *)(block.bytes + sizeof(UInt256)); - MasternodeEntryHash obj = {.block_height = blockHeight}; - memcpy(obj.hash, hashData.bytes, sizeof(UInt256)); - memcpy(obj.block_hash, blockHash.u8, sizeof(UInt256)); - previous_masternode_entry_hashes[i++] = obj; - } - masternode_entry->previous_entry_hashes = previous_masternode_entry_hashes; - masternode_entry->previous_entry_hashes_count = previousSimplifiedMasternodeEntryHashesCount; - NSUInteger previousValidityCount = [previousValidity count]; - Validity *previous_validity = malloc(previousValidityCount * sizeof(Validity)); - i = 0; - for (NSData *block in previousValidity) { - NSNumber *flag = previousValidity[block]; - UInt256 blockHash = *(UInt256 *)(block.bytes); - uint32_t blockHeight = *(uint32_t *)(block.bytes + sizeof(UInt256)); - Validity obj = {.block_height = blockHeight, .is_valid = [flag boolValue]}; - memcpy(obj.block_hash, blockHash.u8, sizeof(UInt256)); - previous_validity[i++] = obj; - } - masternode_entry->previous_validity = previous_validity; - masternode_entry->previous_validity_count = previousValidityCount; - masternode_entry->provider_registration_transaction_hash = uint256_malloc([self providerRegistrationTransactionHash]); - masternode_entry->ip_address = uint128_malloc([self address]); - masternode_entry->port = [self port]; - masternode_entry->update_height = [self updateHeight]; - masternode_entry->mn_type = [self type]; - masternode_entry->platform_http_port = [self platformHTTPPort]; - masternode_entry->platform_node_id = uint160_malloc([self platformNodeID]); - return masternode_entry; - -} - -+ (void)ffi_free:(MasternodeEntry *)entry { - free(entry->confirmed_hash); - if (entry->confirmed_hash_hashed_with_provider_registration_transaction_hash) - free(entry->confirmed_hash_hashed_with_provider_registration_transaction_hash); - free(entry->operator_public_key); - free(entry->entry_hash); - free(entry->ip_address); - free(entry->key_id_voting); - free(entry->platform_node_id); - free(entry->provider_registration_transaction_hash); - if (entry->previous_entry_hashes) - free(entry->previous_entry_hashes); - if (entry->previous_operator_public_keys) - free(entry->previous_operator_public_keys); - if (entry->previous_validity) - free(entry->previous_validity); - free(entry); -} - -@end diff --git a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry.h b/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry.h deleted file mode 100644 index 21df4bb6c..000000000 --- a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// DSSimplifiedMasternodeEntry.h -// DashSync -// -// Created by Sam Westrich on 7/12/18. -// - -#import "BigIntTypes.h" -#import "dash_shared_core.h" -#import - -@class DSChain, DSSimplifiedMasternodeEntryEntity, DSWallet, DSBlock; - -@interface DSSimplifiedMasternodeEntry : NSObject - -@property (nonatomic, readonly) UInt256 providerRegistrationTransactionHash; -@property (nonatomic, readonly) UInt256 confirmedHash; -@property (nonatomic, readonly) UInt256 confirmedHashHashedWithProviderRegistrationTransactionHash; -@property (nonatomic, readonly) UInt128 address; -@property (nonatomic, readonly) NSString *host; -@property (nonatomic, readonly) NSString *ipAddressString; -@property (nonatomic, readonly) uint16_t port; -@property (nonatomic, readonly) NSString *portString; -@property (nonatomic, readonly) NSString *validString; -@property (nonatomic, readonly) UInt384 operatorPublicKey; -@property (nonatomic, readonly) uint16_t operatorPublicKeyVersion; -@property (nonatomic, readonly) NSDictionary *previousOperatorPublicKeys; -@property (nonatomic, readonly) NSDictionary *previousSimplifiedMasternodeEntryHashes; -@property (nonatomic, readonly) NSDictionary *previousValidity; -@property (nonatomic, readonly) uint32_t knownConfirmedAtHeight; -@property (nonatomic, readonly) uint32_t updateHeight; -@property (nonatomic, readonly) UInt160 keyIDVoting; -@property (nonatomic, readonly) NSString *votingAddress; -@property (nonatomic, readonly) NSString *operatorAddress; -@property (nonatomic, readonly) NSString *platformNodeAddress; -@property (nonatomic, readonly) BOOL isValid; -@property (nonatomic, readonly) uint16_t type; -@property (nonatomic, readonly) uint16_t platformHTTPPort; -@property (nonatomic, readonly) UInt160 platformNodeID; -@property (nonatomic, readonly) UInt256 simplifiedMasternodeEntryHash; -@property (nonatomic, readonly) DSChain *chain; -@property (nonatomic, readonly) NSData *payloadData; -@property (nonatomic, readonly) NSString *uniqueID; -@property (nonatomic, readonly, class) uint32_t payloadLength; -@property (nonatomic, readonly) uint64_t platformPing; -@property (nonatomic, readonly) NSDate *platformPingDate; - -+ (instancetype)simplifiedMasternodeEntryWithProviderRegistrationTransactionHash:(UInt256)providerRegistrationTransactionHash confirmedHash:(UInt256)confirmedHash address:(UInt128)address port:(uint16_t)port operatorBLSPublicKey:(UInt384)operatorBLSPublicKey operatorPublicKeyVersion:(uint16_t)operatorPublicKeyVersion previousOperatorBLSPublicKeys:(NSDictionary *)previousOperatorBLSPublicKeys keyIDVoting:(UInt160)keyIDVoting isValid:(BOOL)isValid type:(uint16_t)type platformHTTPPort:(uint16_t)platformHTTPPort platformNodeID:(UInt160)platformNodeID previousValidity:(NSDictionary *)previousValidity knownConfirmedAtHeight:(uint32_t)knownConfirmedAtHeight updateHeight:(uint32_t)updateHeight simplifiedMasternodeEntryHash:(UInt256)simplifiedMasternodeEntryHash previousSimplifiedMasternodeEntryHashes:(NSDictionary *)previousSimplifiedMasternodeEntryHashes onChain:(DSChain *)chain; -- (DSSimplifiedMasternodeEntryEntity *)simplifiedMasternodeEntryEntityInContext:(NSManagedObjectContext *)context; -- (UInt256)simplifiedMasternodeEntryHashAtBlock:(DSBlock *)merkleBlock; -- (UInt256)simplifiedMasternodeEntryHashAtBlockHash:(UInt256)blockHash; -- (UInt256)simplifiedMasternodeEntryHashAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (UInt256)simplifiedMasternodeEntryHashAtBlockHeight:(uint32_t)blockHeight; -- (UInt384)operatorPublicKeyAtBlock:(DSBlock *)merkleBlock; -- (UInt384)operatorPublicKeyAtBlockHash:(UInt256)blockHash; -- (UInt384)operatorPublicKeyAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (UInt384)operatorPublicKeyAtBlockHeight:(uint32_t)blockHeight; -- (BOOL)isValidAtBlock:(DSBlock *)merkleBlock; -- (BOOL)isValidAtBlockHash:(UInt256)blockHash; -- (BOOL)isValidAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (BOOL)isValidAtBlockHeight:(uint32_t)blockHeight; -- (UInt256)confirmedHashAtBlock:(DSBlock *)merkleBlock; -- (UInt256)confirmedHashAtBlockHash:(UInt256)blockHash; -- (UInt256)confirmedHashAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (UInt256)confirmedHashAtBlockHeight:(uint32_t)blockHeight; -- (UInt256)confirmedHashHashedWithProviderRegistrationTransactionHashAtBlockHeight:(uint32_t)blockHeight; -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other ourBlockHash:(UInt256)ourBlockHash theirBlockHash:(UInt256)theirBlockHash usingOurString:(NSString *)ours usingTheirString:(NSString *)theirs; -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other ourBlockHash:(UInt256)ourBlockHash theirBlockHash:(UInt256)theirBlockHash usingOurString:(NSString *)ours usingTheirString:(NSString *)theirs blockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other atBlockHash:(UInt256)blockHash; -- (NSDictionary *)toDictionaryAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup; -- (void)setPlatformPing:(uint64_t)platformPing at:(NSDate *)time; -- (void)savePlatformPingInfoInContext:(NSManagedObjectContext *)context; -- (void)mergedWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)masternodeEntry atBlockHeight:(uint32_t)blockHeight; - -@end diff --git a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry.m b/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry.m deleted file mode 100644 index 201bc2bde..000000000 --- a/DashSync/shared/Models/Masternode/DSSimplifiedMasternodeEntry.m +++ /dev/null @@ -1,552 +0,0 @@ -// -// DSSimplifiedMasternodeEntry.m -// DashSync -// -// Created by Sam Westrich on 7/12/18. -// - -#import "DSSimplifiedMasternodeEntry.h" -#import "DSBlock.h" -#import "DSKeyManager.h" -#import "DSMerkleBlock.h" -#import "DSMutableOrderedDataKeyDictionary.h" -#import "DSSimplifiedMasternodeEntryEntity+CoreDataProperties.h" -#import "DSWallet.h" -#import "NSData+Dash.h" -#import "NSDictionary+Dash.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" -#import - -#define LOG_SMNE_CHANGES 1 - -#if LOG_SMNE_CHANGES -#define DSDSMNELog(s, ...) DSLog(s, ##__VA_ARGS__) -#else -#define DSDSMNELog(s, ...) -#endif - -@interface DSSimplifiedMasternodeEntry () - -@property (nonatomic, assign) UInt256 providerRegistrationTransactionHash; -@property (nonatomic, assign) UInt256 confirmedHash; -@property (nonatomic, assign) UInt256 confirmedHashHashedWithProviderRegistrationTransactionHash; -@property (nonatomic, assign) UInt256 simplifiedMasternodeEntryHash; -@property (nonatomic, assign) UInt128 address; -@property (nonatomic, assign) uint16_t port; -@property (nonatomic, assign) UInt384 operatorPublicKey; //this is using BLS -@property (nonatomic, assign) uint16_t operatorPublicKeyVersion; // 1: legacy | 2: basic -@property (nonatomic, assign) UInt160 keyIDVoting; -@property (nonatomic, assign) BOOL isValid; -@property (nonatomic, assign) uint16_t type; -@property (nonatomic, assign) uint16_t platformHTTPPort; -@property (nonatomic, assign) UInt160 platformNodeID; -@property (nonatomic, assign) uint32_t knownConfirmedAtHeight; -@property (nonatomic, assign) uint32_t updateHeight; -@property (nonatomic, strong) DSChain *chain; -@property (null_resettable, nonatomic, copy) NSString *host; -@property (null_resettable, nonatomic, copy) NSString *ipAddressString; -@property (null_resettable, nonatomic, copy) NSString *portString; -@property (nonatomic, strong) NSDictionary *previousOperatorPublicKeys; -@property (nonatomic, strong) NSDictionary *previousValidity; -@property (nonatomic, strong) NSDictionary *previousSimplifiedMasternodeEntryHashes; -@property (nonatomic, assign) uint64_t platformPing; -@property (nonatomic, strong) NSDate *platformPingDate; - -@end - - -@implementation DSSimplifiedMasternodeEntry - -- (NSData *)payloadData { - NSMutableData *hashImportantData = [NSMutableData data]; - [hashImportantData appendUInt256:self.providerRegistrationTransactionHash]; - [hashImportantData appendUInt256:self.confirmedHash]; - [hashImportantData appendUInt128:self.address]; - [hashImportantData appendUInt16:CFSwapInt16HostToBig(self.port)]; - [hashImportantData appendUInt384:self.operatorPublicKey]; - [hashImportantData appendUInt160:self.keyIDVoting]; - [hashImportantData appendUInt8:self.isValid]; - return [hashImportantData copy]; -} - -- (UInt256)calculateSimplifiedMasternodeEntryHash { - return [self payloadData].SHA256_2; -} - -+ (instancetype)simplifiedMasternodeEntryWithProviderRegistrationTransactionHash:(UInt256)providerRegistrationTransactionHash - confirmedHash:(UInt256)confirmedHash - address:(UInt128)address - port:(uint16_t)port - operatorBLSPublicKey:(UInt384)operatorBLSPublicKey - operatorPublicKeyVersion:(uint16_t)operatorPublicKeyVersion - previousOperatorBLSPublicKeys:(NSDictionary *)previousOperatorBLSPublicKeys - keyIDVoting:(UInt160)keyIDVoting - isValid:(BOOL)isValid - type:(uint16_t)type - platformHTTPPort:(uint16_t)platformHTTPPort - platformNodeID:(UInt160)platformNodeID - previousValidity:(NSDictionary *)previousValidity - knownConfirmedAtHeight:(uint32_t)knownConfirmedAtHeight - updateHeight:(uint32_t)updateHeight - simplifiedMasternodeEntryHash:(UInt256)simplifiedMasternodeEntryHash - previousSimplifiedMasternodeEntryHashes:(NSDictionary *)previousSimplifiedMasternodeEntryHashes - onChain:(DSChain *)chain { - DSSimplifiedMasternodeEntry *simplifiedMasternodeEntry = [[DSSimplifiedMasternodeEntry alloc] init]; - simplifiedMasternodeEntry.providerRegistrationTransactionHash = providerRegistrationTransactionHash; - simplifiedMasternodeEntry.confirmedHash = confirmedHash; - simplifiedMasternodeEntry.address = address; - simplifiedMasternodeEntry.port = port; - simplifiedMasternodeEntry.keyIDVoting = keyIDVoting; - simplifiedMasternodeEntry.operatorPublicKey = operatorBLSPublicKey; - simplifiedMasternodeEntry.operatorPublicKeyVersion = operatorPublicKeyVersion; - simplifiedMasternodeEntry.isValid = isValid; - simplifiedMasternodeEntry.type = type; - simplifiedMasternodeEntry.platformHTTPPort = platformHTTPPort; - simplifiedMasternodeEntry.platformNodeID = platformNodeID; - simplifiedMasternodeEntry.knownConfirmedAtHeight = knownConfirmedAtHeight; - simplifiedMasternodeEntry.updateHeight = updateHeight; - simplifiedMasternodeEntry.simplifiedMasternodeEntryHash = uint256_is_not_zero(simplifiedMasternodeEntryHash) ? simplifiedMasternodeEntryHash : [simplifiedMasternodeEntry calculateSimplifiedMasternodeEntryHash]; - simplifiedMasternodeEntry.chain = chain; - simplifiedMasternodeEntry.previousOperatorPublicKeys = previousOperatorBLSPublicKeys ? previousOperatorBLSPublicKeys : [NSDictionary dictionary]; - simplifiedMasternodeEntry.previousSimplifiedMasternodeEntryHashes = previousSimplifiedMasternodeEntryHashes ? previousSimplifiedMasternodeEntryHashes : [NSDictionary dictionary]; - simplifiedMasternodeEntry.previousValidity = previousValidity ? previousValidity : [NSDictionary dictionary]; - return simplifiedMasternodeEntry; -} - -- (BOOL)isValidAtBlock:(DSBlock *)block { - if (!block || block.height == UINT32_MAX) { - NSAssert(NO, @"Block should be set"); - return self.isValid; - } - if (![self.previousValidity count]) return self.isValid; - return [self isValidAtBlockHeight:block.height]; -} - -- (BOOL)isValidAtBlockHash:(UInt256)blockHash { - if (![self.previousValidity count]) return self.isValid; - uint32_t blockHeight = [self.chain heightForBlockHash:blockHash]; - return [self isValidAtBlockHeight:blockHeight]; -} - -- (BOOL)isValidAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - if (![self.previousValidity count]) return self.isValid; - uint32_t blockHeight = blockHeightLookup(blockHash); - return [self isValidAtBlockHeight:blockHeight]; -} - -- (BOOL)isValidAtBlockHeight:(uint32_t)blockHeight { - if (![self.previousValidity count]) return self.isValid; - NSAssert(blockHeight != UINT32_MAX, @"block height should be set"); - if (blockHeight == UINT32_MAX) { - return self.isValid; - } - NSDictionary *previousValidity = self.previousValidity; - uint32_t minDistance = UINT32_MAX; - BOOL isValid = self.isValid; - for (NSData *previousBlock in previousValidity) { - DSBlockInfo blockInfo = *(DSBlockInfo *)(previousBlock.bytes); - uint32_t prevHeight = blockInfo.u32[8]; - if (prevHeight <= blockHeight) continue; - uint32_t distance = prevHeight - blockHeight; - if (distance < minDistance) { - minDistance = distance; - DSDSMNELog(@"Validity for proTxHash %@ : Using %@ instead of %@ for list at block height %u (previousBlock.height %u)", uint256_hex(self.providerRegistrationTransactionHash), previousValidity[previousBlock].boolValue ? @"YES" : @"NO", isValid ? @"YES" : @"NO", blockHeight, prevHeight); - isValid = [previousValidity[previousBlock] boolValue]; - } - } - return isValid; -} - -- (UInt256)simplifiedMasternodeEntryHashAtBlock:(DSBlock *)block { - if (!block || block.height == UINT32_MAX) { - NSAssert(NO, @"Block should be set"); - return self.simplifiedMasternodeEntryHash; - } - if (![self.previousSimplifiedMasternodeEntryHashes count]) return self.simplifiedMasternodeEntryHash; - return [self simplifiedMasternodeEntryHashAtBlockHeight:block.height]; -} - -- (UInt256)simplifiedMasternodeEntryHashAtBlockHash:(UInt256)blockHash { - if (![self.previousSimplifiedMasternodeEntryHashes count]) return self.simplifiedMasternodeEntryHash; - uint32_t blockHeight = [self.chain heightForBlockHash:blockHash]; - return [self simplifiedMasternodeEntryHashAtBlockHeight:blockHeight]; -} - -- (UInt256)simplifiedMasternodeEntryHashAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - if (![self.previousSimplifiedMasternodeEntryHashes count]) return self.simplifiedMasternodeEntryHash; - uint32_t blockHeight = blockHeightLookup(blockHash); - return [self simplifiedMasternodeEntryHashAtBlockHeight:blockHeight]; -} - -- (UInt256)simplifiedMasternodeEntryHashAtBlockHeight:(uint32_t)blockHeight { - if (![self.previousSimplifiedMasternodeEntryHashes count]) return self.simplifiedMasternodeEntryHash; - NSAssert(blockHeight != UINT32_MAX, @"block height should be set"); - if (blockHeight == UINT32_MAX) { - return self.simplifiedMasternodeEntryHash; - } - NSDictionary *previousSimplifiedMasternodeEntryHashes = self.previousSimplifiedMasternodeEntryHashes; - uint32_t minDistance = UINT32_MAX; - UInt256 usedSimplifiedMasternodeEntryHash = self.simplifiedMasternodeEntryHash; - for (NSData *previousBlock in previousSimplifiedMasternodeEntryHashes) { - DSBlockInfo blockInfo = *(DSBlockInfo *)(previousBlock.bytes); - uint32_t prevHeight = blockInfo.u32[8]; - if (prevHeight <= blockHeight) continue; - uint32_t distance = prevHeight - blockHeight; - if (distance < minDistance) { - minDistance = distance; - DSLog(@"[%@] SME Hash for proTxHash %@ : Using %@ instead of %@ for list at block height %u", self.chain.name, uint256_hex(self.providerRegistrationTransactionHash), uint256_hex(*(UInt256 *)(blockInfo.u8)), uint256_hex(usedSimplifiedMasternodeEntryHash), blockHeight); - usedSimplifiedMasternodeEntryHash = previousSimplifiedMasternodeEntryHashes[previousBlock].UInt256; - } - } - return usedSimplifiedMasternodeEntryHash; -} - -- (UInt384)operatorPublicKeyAtBlock:(DSBlock *)block { - if (!block || block.height == UINT32_MAX) { - NSAssert(NO, @"Block should be set"); - return self.operatorPublicKey; - } - if (![self.previousOperatorPublicKeys count]) return self.operatorPublicKey; - return [self operatorPublicKeyAtBlockHeight:block.height]; -} - -- (UInt384)operatorPublicKeyAtBlockHash:(UInt256)blockHash { - if (![self.previousOperatorPublicKeys count]) return self.operatorPublicKey; - uint32_t blockHeight = [self.chain heightForBlockHash:blockHash]; - return [self operatorPublicKeyAtBlockHeight:blockHeight]; -} - -- (UInt384)operatorPublicKeyAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - if (![self.previousOperatorPublicKeys count]) return self.operatorPublicKey; - uint32_t blockHeight = blockHeightLookup(blockHash); - return [self operatorPublicKeyAtBlockHeight:blockHeight]; -} - -- (UInt384)operatorPublicKeyAtBlockHeight:(uint32_t)blockHeight { - if (![self.previousOperatorPublicKeys count]) return self.operatorPublicKey; - NSDictionary *previousOperatorPublicKeyAtBlockHashes = self.previousOperatorPublicKeys; - uint32_t minDistance = UINT32_MAX; - UInt384 usedPreviousOperatorPublicKeyAtBlockHash = self.operatorPublicKey; - for (NSData *previousBlock in previousOperatorPublicKeyAtBlockHashes) { - DSBlockInfo blockInfo = *(DSBlockInfo *)(previousBlock.bytes); - uint32_t prevHeight = blockInfo.u32[8]; - if (prevHeight <= blockHeight) continue; - uint32_t distance = prevHeight - blockHeight; - if (distance < minDistance) { - minDistance = distance; - DSDSMNELog(@"OperatorKey : Using %@ instead of %@ for list at block height %u", uint384_hex(previousOperatorPublicKeyAtBlockHashes[previousBlock].UInt384), uint384_hex(usedPreviousOperatorPublicKeyAtBlockHash), blockHeight); - usedPreviousOperatorPublicKeyAtBlockHash = previousOperatorPublicKeyAtBlockHashes[previousBlock].UInt384; - } - } - return usedPreviousOperatorPublicKeyAtBlockHash; -} - -- (UInt256)confirmedHashAtBlock:(DSBlock *)block { - if (!block || block.height == UINT32_MAX) { - NSAssert(NO, @"Block should be set"); - return self.confirmedHash; - } - if (!self.knownConfirmedAtHeight) return self.confirmedHash; - return [self confirmedHashAtBlockHeight:block.height]; -} - -- (UInt256)confirmedHashAtBlockHash:(UInt256)blockHash { - if (!self.knownConfirmedAtHeight) return self.confirmedHash; - uint32_t blockHeight = [self.chain heightForBlockHash:blockHash]; - return [self confirmedHashAtBlockHeight:blockHeight]; -} - -- (UInt256)confirmedHashAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - if (!self.knownConfirmedAtHeight) return self.confirmedHash; - uint32_t blockHeight = blockHeightLookup(blockHash); - return [self confirmedHashAtBlockHeight:blockHeight]; -} - -- (UInt256)confirmedHashAtBlockHeight:(uint32_t)blockHeight { - if (!self.knownConfirmedAtHeight) return self.confirmedHash; - if (self.knownConfirmedAtHeight > blockHeight) { - return UINT256_ZERO; - } else { - return self.confirmedHash; - } -} - -- (UInt256)confirmedHashHashedWithProviderRegistrationTransactionHashAtBlockHeight:(uint32_t)blockHeight { - if (!self.knownConfirmedAtHeight) return self.confirmedHashHashedWithProviderRegistrationTransactionHash; - if (self.knownConfirmedAtHeight > blockHeight) { - return [DSSimplifiedMasternodeEntry hashConfirmedHash:UINT256_ZERO withProviderRegistrationTransactionHash:self.providerRegistrationTransactionHash]; - } else { - return self.confirmedHashHashedWithProviderRegistrationTransactionHash; - } -} - -- (void)setConfirmedHash:(UInt256)confirmedHash { - _confirmedHash = confirmedHash; - if (uint256_is_not_zero(self.providerRegistrationTransactionHash)) { - [self updateConfirmedHashHashedWithProviderRegistrationTransactionHash]; - } -} - -- (void)setProviderRegistrationTransactionHash:(UInt256)providerRegistrationTransactionHash { - _providerRegistrationTransactionHash = providerRegistrationTransactionHash; - if (uint256_is_not_zero(self.confirmedHash)) { - [self updateConfirmedHashHashedWithProviderRegistrationTransactionHash]; - } -} - -+ (UInt256)hashConfirmedHash:(UInt256)confirmedHash withProviderRegistrationTransactionHash:(UInt256)providerRegistrationTransactionHash { - ByteArray byte_array = masternode_hash_confirmed_hash(confirmedHash.u8, providerRegistrationTransactionHash.u8); - NSData *data = [DSKeyManager NSDataFrom:byte_array]; - return data.UInt256; -} - -- (void)updateConfirmedHashHashedWithProviderRegistrationTransactionHash { - self.confirmedHashHashedWithProviderRegistrationTransactionHash = [DSSimplifiedMasternodeEntry hashConfirmedHash:self.confirmedHash withProviderRegistrationTransactionHash:self.providerRegistrationTransactionHash]; -} - -+ (uint32_t)payloadLength { - return 151; -} - -- (NSString *)host { - if (_host) return _host; - _host = [NSString stringWithFormat:@"%@:%d", [self ipAddressString], self.port]; - return _host; -} - -- (NSString *)ipAddressString { - if (_ipAddressString) return _ipAddressString; - char s[INET6_ADDRSTRLEN]; - - if (_address.u64[0] == 0 && _address.u32[2] == CFSwapInt32HostToBig(0xffff)) { - _ipAddressString = @(inet_ntop(AF_INET, &_address.u32[3], s, sizeof(s))); - } else { - _ipAddressString = @(inet_ntop(AF_INET6, &_address, s, sizeof(s))); - } - return _ipAddressString; -} - -- (NSString *)portString { - if (_portString) return _portString; - _portString = [NSString stringWithFormat:@"%d", self.port]; - return _portString; -} - -- (NSString *)validString { - return self.isValid ? DSLocalizedString(@"Up", @"The server is up and running") : DSLocalizedString(@"Down", @"The server is not working"); -} - -- (NSString *)uniqueID { - return [NSData dataWithUInt256:self.providerRegistrationTransactionHash].shortHexString; -} - -- (DSSimplifiedMasternodeEntryEntity *)simplifiedMasternodeEntryEntityInContext:(NSManagedObjectContext *)context { - DSSimplifiedMasternodeEntryEntity *simplifiedMasternodeEntryEntity = [DSSimplifiedMasternodeEntryEntity anyObjectInContext:context matching:@"providerRegistrationTransactionHash = %@", [NSData dataWithUInt256:self.providerRegistrationTransactionHash]]; - return simplifiedMasternodeEntryEntity; -} - -- (NSString *)votingAddress { - return [DSKeyManager addressFromHash160:self.keyIDVoting forChain:self.chain]; -} - -- (NSString *)platformNodeAddress { - return [DSKeyManager addressFromHash160:self.platformNodeID forChain:self.chain]; -} - -- (NSString *)operatorAddress { - return [DSKeyManager addressWithPublicKeyData:uint384_data(self.operatorPublicKey) forChain:self.chain]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"", self.host, @(self.isValid)]; -} - -- (BOOL)isEqual:(id)other { - DSSimplifiedMasternodeEntry *entry = (DSSimplifiedMasternodeEntry *)other; - if (![other isKindOfClass:[DSSimplifiedMasternodeEntry class]]) return NO; - return other == self || uint256_eq(self.providerRegistrationTransactionHash, entry.providerRegistrationTransactionHash); -} - -- (NSUInteger)hash { - return self.providerRegistrationTransactionHash.u64[0]; -} - -- (NSDictionary *)toDictionaryAtBlockHash:(UInt256)blockHash usingBlockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - dictionary[@"address"] = [uint128_data(_address) base64String]; - dictionary[@"port"] = @(_port); - UInt384 ourOperatorPublicKeyAtBlockHash = [self operatorPublicKeyAtBlockHash:blockHash usingBlockHeightLookup:blockHeightLookup]; - dictionary[@"operatorPublicKey"] = [uint384_data(ourOperatorPublicKeyAtBlockHash) base64String]; - dictionary[@"keyIDVoting"] = [uint160_data(_keyIDVoting) base64String]; - dictionary[@"isValid"] = [self isValidAtBlockHash:blockHash usingBlockHeightLookup:blockHeightLookup] ? @"YES" : @"NO"; - UInt256 ourSimplifiedMasternodeEntryHash = [self simplifiedMasternodeEntryHashAtBlockHash:blockHash usingBlockHeightLookup:blockHeightLookup]; - dictionary[@"simplifiedMasternodeEntryHashAtBlockHash"] = @{@"SimplifiedMasternodeEntryHash": uint256_base64(ourSimplifiedMasternodeEntryHash), @"blockHeight": @(blockHeightLookup(blockHash))}; - dictionary[@"previousSimplifiedMasternodeEntryHashes"] = @{@"PreviousSimplifiedMasternodeEntryHash": self.previousSimplifiedMasternodeEntryHashes}; - dictionary[@"previousValidity"] = self.previousValidity; - dictionary[@"previousOperatorPublicKeys"] = self.previousOperatorPublicKeys; - dictionary[@"confirmedHash"] = uint256_base64(_confirmedHash); - - return dictionary; -} - -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other ourBlockHash:(UInt256)ourBlockHash theirBlockHash:(UInt256)theirBlockHash { - return [self compare:other ourBlockHash:ourBlockHash theirBlockHash:theirBlockHash usingOurString:@"ours" usingTheirString:@"theirs"]; -} - -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other ourBlockHash:(UInt256)ourBlockHash theirBlockHash:(UInt256)theirBlockHash usingOurString:(NSString *)ours usingTheirString:(NSString *)theirs { - return [self compare:other - ourBlockHash:ourBlockHash - theirBlockHash:theirBlockHash - usingOurString:ours - usingTheirString:theirs - blockHeightLookup:^uint32_t(UInt256 blockHash) { - return [self.chain heightForBlockHash:blockHash]; - }]; -} - -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other ourBlockHash:(UInt256)ourBlockHash theirBlockHash:(UInt256)theirBlockHash usingOurString:(NSString *)ours usingTheirString:(NSString *)theirs blockHeightLookup:(BlockHeightFinder)blockHeightLookup { - NSMutableDictionary *differences = [NSMutableDictionary dictionary]; - - if (!ours) ours = @"ours"; - if (!theirs) theirs = @"theirs"; - - if (!uint128_eq(_address, other.address)) { - differences[@"address"] = @{ours: uint128_data(_address), theirs: uint128_data(other.address)}; - } - - if (_port != other.port) { - differences[@"port"] = @{ours: @(_port), theirs: @(other.port)}; - } - - UInt384 ourOperatorPublicKeyAtBlockHash = [self operatorPublicKeyAtBlockHash:ourBlockHash usingBlockHeightLookup:blockHeightLookup]; - UInt384 theirOperatorPublicKeyAtBlockHash = [other operatorPublicKeyAtBlockHash:theirBlockHash usingBlockHeightLookup:blockHeightLookup]; - - if (!uint384_eq(ourOperatorPublicKeyAtBlockHash, theirOperatorPublicKeyAtBlockHash)) { - differences[@"operatorPublicKey"] = @{ours: uint384_data(ourOperatorPublicKeyAtBlockHash), theirs: uint384_data(theirOperatorPublicKeyAtBlockHash)}; - } - - if (!uint160_eq(_keyIDVoting, other.keyIDVoting)) { - differences[@"keyIDVoting"] = @{ours: uint160_data(_keyIDVoting), theirs: uint160_data(other.keyIDVoting)}; - } - - BOOL ourIsValid = [self isValidAtBlockHash:ourBlockHash usingBlockHeightLookup:blockHeightLookup]; - BOOL theirIsValid = [other isValidAtBlockHash:theirBlockHash usingBlockHeightLookup:blockHeightLookup]; - - if (ourIsValid != theirIsValid) { - differences[@"isValid"] = @{ours: ourIsValid ? @"YES" : @"NO", theirs: theirIsValid ? @"YES" : @"NO"}; - differences[@"previousValidity"] = @{ours: self.previousValidity, theirs: other.previousValidity}; - } - - UInt256 ourSimplifiedMasternodeEntryHash = [self simplifiedMasternodeEntryHashAtBlockHash:ourBlockHash usingBlockHeightLookup:blockHeightLookup]; - UInt256 theirSimplifiedMasternodeEntryHash = [other simplifiedMasternodeEntryHashAtBlockHash:theirBlockHash usingBlockHeightLookup:blockHeightLookup]; - - if (!uint256_eq(ourSimplifiedMasternodeEntryHash, theirSimplifiedMasternodeEntryHash)) { - differences[@"simplifiedMasternodeEntryHashAtBlockHash"] = @{ours: uint256_hex(ourSimplifiedMasternodeEntryHash), theirs: uint256_hex(theirSimplifiedMasternodeEntryHash), @"ourBlockHeight": @(blockHeightLookup(ourBlockHash)), @"theirBlockHeight": @(blockHeightLookup(theirBlockHash))}; - } - - if (![self.previousSimplifiedMasternodeEntryHashes isEqualToDictionary:other.previousSimplifiedMasternodeEntryHashes]) { - differences[@"previousSimplifiedMasternodeEntryHashes"] = @{ours: self.previousSimplifiedMasternodeEntryHashes, theirs: other.previousSimplifiedMasternodeEntryHashes}; - } - - if (![self.previousValidity isEqualToDictionary:other.previousValidity] && ourIsValid == theirIsValid) { - differences[@"previousValidity"] = @{ours: self.previousValidity, theirs: other.previousValidity}; - } - - if (![self.previousOperatorPublicKeys isEqualToDictionary:other.previousOperatorPublicKeys]) { - differences[@"previousOperatorPublicKeys"] = @{ours: self.previousOperatorPublicKeys, theirs: other.previousOperatorPublicKeys}; - } - - if (!uint256_eq(_confirmedHash, other.confirmedHash)) { - differences[@"confirmedHash"] = @{ours: uint256_data(_confirmedHash), theirs: uint256_data(other.confirmedHash)}; - } - - return differences; -} - -- (NSDictionary *)compare:(DSSimplifiedMasternodeEntry *)other atBlockHash:(UInt256)blockHash { - return [self compare:other ourBlockHash:blockHash theirBlockHash:blockHash]; -} - -- (void)setPlatformPing:(uint64_t)platformPing at:(NSDate *)time { - self.platformPing = platformPing; - self.platformPingDate = time; -} - -- (void)savePlatformPingInfoInContext:(NSManagedObjectContext *)context { - DSSimplifiedMasternodeEntryEntity *masternodeEntity = [self simplifiedMasternodeEntryEntityInContext:context]; - masternodeEntity.platformPing = self.platformPing; - masternodeEntity.platformPingDate = self.platformPingDate; -} - -- (void)mergedWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)masternodeEntry atBlockHeight:(uint32_t)blockHeight { - if (self.updateHeight < blockHeight) { - self.updateHeight = blockHeight; - if (!uint128_eq(self.address, masternodeEntry.address)) { - self.address = masternodeEntry.address; - } - if (!uint256_eq(self.confirmedHash, masternodeEntry.confirmedHash)) { - self.confirmedHash = masternodeEntry.confirmedHash; - self.knownConfirmedAtHeight = masternodeEntry.knownConfirmedAtHeight; - } - if (self.port != masternodeEntry.port) { - self.port = masternodeEntry.port; - } - if (!uint160_eq(self.keyIDVoting, masternodeEntry.keyIDVoting)) { - self.keyIDVoting = masternodeEntry.keyIDVoting; - } - if (!uint384_eq(self.operatorPublicKey, masternodeEntry.operatorPublicKey)) { - self.operatorPublicKey = masternodeEntry.operatorPublicKey; - self.operatorPublicKeyVersion = masternodeEntry.operatorPublicKeyVersion; - } - if (self.isValid != masternodeEntry.isValid) { - self.isValid = masternodeEntry.isValid; - } - self.simplifiedMasternodeEntryHash = masternodeEntry.simplifiedMasternodeEntryHash; - [self mergePreviousFieldsUsingSimplifiedMasternodeEntrysPreviousFields:masternodeEntry atBlockHeight:blockHeight]; - } - else if (blockHeight < self.updateHeight) { - [self mergePreviousFieldsUsingSimplifiedMasternodeEntrysPreviousFields:masternodeEntry atBlockHeight:blockHeight]; - } -} - -- (NSDictionary *)blockHashDictionaryFromBlockDictionary:(NSDictionary *)blockHashDictionary { - NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; - for (NSData *block in blockHashDictionary) { - DSBlockInfo blockInfo = *(DSBlockInfo *)(block.bytes); - UInt256 blockHash = *(UInt256 *)(blockInfo.u8); - NSData *blockHashData = uint256_data(blockHash); - if (blockHashData) { - rDictionary[blockHashData] = blockHashDictionary[block]; - } - } - return rDictionary; -} - -- (void)mergePreviousFieldsUsingSimplifiedMasternodeEntrysPreviousFields:(DSSimplifiedMasternodeEntry *)entry atBlockHeight:(uint32_t)blockHeight { - //SimplifiedMasternodeEntryHashes - NSDictionary *oldPreviousSimplifiedMasternodeEntryHashesDictionary = entry.previousSimplifiedMasternodeEntryHashes; - if (oldPreviousSimplifiedMasternodeEntryHashesDictionary && oldPreviousSimplifiedMasternodeEntryHashesDictionary.count) { - self.previousSimplifiedMasternodeEntryHashes = [NSDictionary mergeDictionary:self.previousSimplifiedMasternodeEntryHashes withDictionary:oldPreviousSimplifiedMasternodeEntryHashesDictionary]; - } - - //OperatorBLSPublicKeys - NSDictionary *oldPreviousOperatorBLSPublicKeysDictionary = entry.previousOperatorPublicKeys; - if (oldPreviousOperatorBLSPublicKeysDictionary && oldPreviousOperatorBLSPublicKeysDictionary.count) { - self.previousOperatorPublicKeys = [NSDictionary mergeDictionary:self.previousOperatorPublicKeys withDictionary:oldPreviousOperatorBLSPublicKeysDictionary]; - } - - //MasternodeValidity - NSDictionary *oldPreviousValidityDictionary = entry.previousValidity; - if (oldPreviousValidityDictionary && oldPreviousValidityDictionary.count) { - self.previousValidity = [NSDictionary mergeDictionary:self.previousValidity withDictionary:oldPreviousValidityDictionary]; - } - - if (uint256_is_not_zero(self.confirmedHash) && uint256_is_not_zero(entry.confirmedHash) && (self.knownConfirmedAtHeight > blockHeight)) { - //we now know it was confirmed earlier so update to earlier - self.knownConfirmedAtHeight = blockHeight; - } - -} - -@end diff --git a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction+Mndiff.h b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinAcceptMessage.h similarity index 65% rename from DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction+Mndiff.h rename to DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinAcceptMessage.h index 786eb8d78..61ae08762 100644 --- a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction+Mndiff.h +++ b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinAcceptMessage.h @@ -1,6 +1,6 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 Dash Core Group. All rights reserved. +// +// Created by Andrei Ashikhmin +// 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. @@ -15,16 +15,17 @@ // limitations under the License. // -#import "DSChain.h" -#import "DSCoinbaseTransaction.h" -#import "dash_shared_core.h" +#import "DSMessageRequest.h" #import NS_ASSUME_NONNULL_BEGIN -@interface DSCoinbaseTransaction (Mndiff) +@interface DSCoinJoinAcceptMessage : DSMessageRequest + +@property (nonatomic, readonly) NSData *data; -//+ (instancetype)coinbaseTransactionWith:(CoinbaseTransaction *)coinbaseTransaction onChain:(DSChain *)chain; ++ (instancetype)requestWithData:(NSData *)data; ++ (NSString *)type; @end diff --git a/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinAcceptMessage.m b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinAcceptMessage.m new file mode 100644 index 000000000..f58f2f30b --- /dev/null +++ b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinAcceptMessage.m @@ -0,0 +1,47 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCoinJoinAcceptMessage.h" +#import "DSPeer.h" + +@implementation DSCoinJoinAcceptMessage + ++ (instancetype)requestWithData:(NSData *)data { + return [[DSCoinJoinAcceptMessage alloc] initWithData:data]; +} + ++ (NSString *)type { + return MSG_COINJOIN_ACCEPT; +} + +- (instancetype)initWithData:(NSData *)data { + self = [super init]; + if (self) { + _data = data; + } + return self; +} + +- (NSString *)type { + return DSCoinJoinAcceptMessage.type; +} + +- (NSData *)toData { + return self.data; +} +@end diff --git a/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinEntryMessage.h b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinEntryMessage.h new file mode 100644 index 000000000..883ef01c1 --- /dev/null +++ b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinEntryMessage.h @@ -0,0 +1,32 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSMessageRequest.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DSCoinJoinEntryMessage : DSMessageRequest + +@property (nonatomic, readonly) NSData *data; + ++ (instancetype)requestWithData:(NSData *)data; ++ (NSString *)type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DSDirectionalKey.m b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinEntryMessage.m similarity index 53% rename from DashSync/shared/Models/Platform/Document/DSDirectionalKey.m rename to DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinEntryMessage.m index 1e52f674f..a5394b25d 100644 --- a/DashSync/shared/Models/Platform/Document/DSDirectionalKey.m +++ b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinEntryMessage.m @@ -1,6 +1,6 @@ -// -// Created by Sam Westrich -// Copyright © 2021 Dash Core Group. All rights reserved. +// +// Created by Andrei Ashikhmin +// 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. @@ -15,24 +15,32 @@ // limitations under the License. // -#import "DSDirectionalKey.h" - -@interface DSDirectionalKey () +#import "DSCoinJoinEntryMessage.h" +#import "DSPeer.h" -@property (nonatomic, strong) NSData *key; -@property (nonatomic, assign) bool ascending; +@implementation DSCoinJoinEntryMessage -@end ++ (instancetype)requestWithData:(NSData *)data { + return [[DSCoinJoinEntryMessage alloc] initWithData:data]; +} -@implementation DSDirectionalKey ++ (NSString *)type { + return MSG_COINJOIN_ENTRY; +} -- (instancetype)initWithKey:(NSData *)key ascending:(bool)ascending { +- (instancetype)initWithData:(NSData *)data { self = [super init]; if (self) { - self.key = key; - self.ascending = ascending; + _data = data; } return self; } +- (NSString *)type { + return DSCoinJoinEntryMessage.type; +} + +- (NSData *)toData { + return self.data; +} @end diff --git a/DashSync/shared/Models/Platform/Document/DSDirectionalKey.h b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinSignedInputs.h similarity index 68% rename from DashSync/shared/Models/Platform/Document/DSDirectionalKey.h rename to DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinSignedInputs.h index 682e72542..24b22b09b 100644 --- a/DashSync/shared/Models/Platform/Document/DSDirectionalKey.h +++ b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinSignedInputs.h @@ -1,6 +1,6 @@ // -// Created by Sam Westrich -// Copyright © 2021 Dash Core Group. All rights reserved. +// Created by Andrei Ashikhmin +// 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. @@ -15,16 +15,17 @@ // limitations under the License. // +#import "DSMessageRequest.h" #import NS_ASSUME_NONNULL_BEGIN -@interface DSDirectionalKey : NSObject +@interface DSCoinJoinSignedInputs : DSMessageRequest -@property (nonatomic, readonly) NSData *key; -@property (nonatomic, readonly) bool ascending; +@property (nonatomic, readonly) NSData *data; -- (instancetype)initWithKey:(NSData *)key ascending:(bool)ascending; ++ (instancetype)requestWithData:(NSData *)data; ++ (NSString *)type; @end diff --git a/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinSignedInputs.m b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinSignedInputs.m new file mode 100644 index 000000000..2a6e2bb83 --- /dev/null +++ b/DashSync/shared/Models/Messages/CoinJoin/DSCoinJoinSignedInputs.m @@ -0,0 +1,47 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSCoinJoinSignedInputs.h" +#import "DSPeer.h" + +@implementation DSCoinJoinSignedInputs + ++ (instancetype)requestWithData:(NSData *)data { + return [[DSCoinJoinSignedInputs alloc] initWithData:data]; +} + ++ (NSString *)type { + return MSG_COINJOIN_SIGNED_INPUTS; +} + +- (instancetype)initWithData:(NSData *)data { + self = [super init]; + if (self) { + _data = data; + } + return self; +} + +- (NSString *)type { + return DSCoinJoinSignedInputs.type; +} + +- (NSData *)toData { + return self.data; +} +@end diff --git a/DashSync/shared/Models/Messages/CoinJoin/DSSendCoinJoinQueue.h b/DashSync/shared/Models/Messages/CoinJoin/DSSendCoinJoinQueue.h new file mode 100644 index 000000000..af37f22e3 --- /dev/null +++ b/DashSync/shared/Models/Messages/CoinJoin/DSSendCoinJoinQueue.h @@ -0,0 +1,30 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSMessageRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSSendCoinJoinQueue : DSMessageRequest + +@property (nonatomic, readonly) BOOL send; + ++ (instancetype)requestWithShouldSend:(BOOL)shouldSend; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Messages/CoinJoin/DSSendCoinJoinQueue.m b/DashSync/shared/Models/Messages/CoinJoin/DSSendCoinJoinQueue.m new file mode 100644 index 000000000..02a09fe32 --- /dev/null +++ b/DashSync/shared/Models/Messages/CoinJoin/DSSendCoinJoinQueue.m @@ -0,0 +1,39 @@ +// +// Created by Andrei Ashikhmin +// 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 "DSSendCoinJoinQueue.h" +#import "DSPeer.h" +#import "NSData+Dash.h" + +@implementation DSSendCoinJoinQueue + ++ (instancetype)requestWithShouldSend:(BOOL)shouldSend { + return [[DSSendCoinJoinQueue alloc] initWithShouldSend:shouldSend]; +} + +- (instancetype)initWithShouldSend:(BOOL)shouldSend { + self = [super initWithType:MSG_SENDDSQ]; + if (self) { + _send = shouldSend; + } + return self; +} + +- (NSData *)toData { + return [NSData dataWithUInt8:(uint8_t)_send]; +} +@end diff --git a/DashSync/shared/Models/Network/DSPeer.h b/DashSync/shared/Models/Network/DSPeer.h index db8421e13..9b6fa7a68 100644 --- a/DashSync/shared/Models/Network/DSPeer.h +++ b/DashSync/shared/Models/Network/DSPeer.h @@ -28,8 +28,6 @@ #import "BigIntTypes.h" #import "DSChain.h" -//#import "DSGovernanceHashesRequest.h" -//#import "DSGovernanceSyncRequest.h" #import "DSMessageRequest.h" #import @@ -137,16 +135,16 @@ typedef NS_ENUM(uint32_t, DSInvType) #define MSG_GOVOBJVOTE @"govobjvote" #define MSG_GOVOBJSYNC @"govsync" -//Private send +// CoinJoin -#define MSG_DARKSENDANNOUNCE @"dsa" -#define MSG_DARKSENDCONTROL @"dsc" -#define MSG_DARKSENDFINISH @"dsf" -#define MSG_DARKSENDINITIATE @"dsi" -#define MSG_DARKSENDQUORUM @"dsq" -#define MSG_DARKSENDSESSION @"dss" -#define MSG_DARKSENDSESSIONUPDATE @"dssu" -#define MSG_DARKSENDTX @"dstx" +#define MSG_COINJOIN_ACCEPT @"dsa" +#define MSG_COINJOIN_ENTRY @"dsi" +#define MSG_COINJOIN_QUEUE @"dsq" +#define MSG_COINJOIN_BROADCAST_TX @"dstx" +#define MSG_COINJOIN_STATUS_UPDATE @"dssu" +#define MSG_COINJOIN_COMPLETE @"dsc" +#define MSG_COINJOIN_FINAL_TRANSACTION @"dsf" +#define MSG_COINJOIN_SIGNED_INPUTS @"dss" #define REJECT_INVALID 0x10 // transaction is invalid for some reason (invalid signature, output value > input, etc) #define REJECT_SPENT 0x12 // an input is already spent @@ -179,7 +177,7 @@ typedef NS_ENUM(uint32_t, DSSyncCountInfo); typedef void (^MempoolCompletionBlock)(BOOL success, BOOL needed, BOOL interruptedByDisconnect); -@class DSPeer, DSTransaction, DSMerkleBlock, DSBlock, DSChain, DSSpork, DSGovernanceObject, DSGovernanceVote, DSTransactionLockVote, DSInstantSendTransactionLock, DSChainLock; +@class DSPeer, DSTransaction, DSMerkleBlock, DSBlock, DSChain, DSSpork, DSGovernanceObject, DSGovernanceVote, DSTransactionLockVote, DSInstantSendTransactionLock, DSChainLock, DSGovernanceSyncRequest, DSGovernanceHashesRequest; @protocol DSPeerDelegate @required @@ -248,7 +246,7 @@ typedef void (^MempoolCompletionBlock)(BOOL success, BOOL needed, BOOL interrupt @end -typedef NS_ENUM(NSUInteger, DSPeerStatus) +typedef NS_ENUM(NSInteger, DSPeerStatus) { DSPeerStatus_Unknown = -1, DSPeerStatus_Disconnected = 0, @@ -257,14 +255,13 @@ typedef NS_ENUM(NSUInteger, DSPeerStatus) DSPeerStatus_Banned }; -typedef NS_ENUM(NSUInteger, DSPeerType) +typedef NS_ENUM(NSInteger, DSPeerType) { DSPeerType_Unknown = -1, DSPeerType_FullNode = 0, DSPeerType_MasterNode }; -@class DSGovernanceSyncRequest, DSGovernanceHashesRequest; @interface DSPeer : NSObject @@ -308,11 +305,11 @@ typedef NS_ENUM(NSUInteger, DSPeerType) @property (nonatomic, readonly) DSChain *chain; + (instancetype)peerWithAddress:(UInt128)address andPort:(uint16_t)port onChain:(DSChain *)chain; -+ (instancetype)peerWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry; +//+ (instancetype)peerWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry; + (instancetype)peerWithHost:(NSString *)host onChain:(DSChain *)chain; - (instancetype)initWithAddress:(UInt128)address andPort:(uint16_t)port onChain:(DSChain *)chain; -- (instancetype)initWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry; +//- (instancetype)initWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry; - (instancetype)initWithAddress:(UInt128)address port:(uint16_t)port onChain:(DSChain *)chain timestamp:(NSTimeInterval)timestamp services:(uint64_t)services; - (instancetype)initWithHost:(NSString *)host onChain:(DSChain *)chain; diff --git a/DashSync/shared/Models/Network/DSPeer.m b/DashSync/shared/Models/Network/DSPeer.m index 5a3cadb03..b376adca9 100644 --- a/DashSync/shared/Models/Network/DSPeer.m +++ b/DashSync/shared/Models/Network/DSPeer.m @@ -28,8 +28,8 @@ #import "DSPeer.h" #import "DSAddrRequest.h" -#import "DSBlockchainIdentityRegistrationTransition.h" #import "DSBloomFilter.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainLock.h" @@ -48,7 +48,6 @@ #import "DSInstantSendTransactionLock.h" #import "DSInvRequest.h" #import "DSKeyManager.h" -#import "DSMasternodeManager.h" #import "DSMerkleBlock.h" #import "DSNotFoundRequest.h" #import "DSOptionsManager.h" @@ -56,7 +55,6 @@ #import "DSPeerManager.h" #import "DSPingRequest.h" #import "DSReachabilityManager.h" -#import "DSSimplifiedMasternodeEntry.h" #import "DSSpork.h" #import "DSSporkManager.h" #import "DSTransaction.h" @@ -64,6 +62,7 @@ #import "DSTransactionHashEntity+CoreDataClass.h" #import "DSTransactionInvRequest.h" #import "DSVersionRequest.h" +#import "DSCoinJoinManager.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" #import "NSDate+Utils.h" @@ -88,7 +87,7 @@ #define HEADER_LENGTH 24 #define MAX_MSG_LENGTH 0x02000000 #define CONNECT_TIMEOUT 3.0 -#define MEMPOOL_TIMEOUT 3.0 +#define MEMPOOL_TIMEOUT 5.0 #define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); #define UNLOCK(lock) dispatch_semaphore_signal(lock); @@ -143,9 +142,9 @@ + (instancetype)peerWithHost:(NSString *)host onChain:(DSChain *)chain { return [[self alloc] initWithHost:host onChain:chain]; } -+ (instancetype)peerWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry { - return [[self alloc] initWithSimplifiedMasternodeEntry:simplifiedMasternodeEntry]; -} +//+ (instancetype)peerWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry { +// return [[self alloc] initWithSimplifiedMasternodeEntry:simplifiedMasternodeEntry]; +//} - (instancetype)initWithAddress:(UInt128)address andPort:(uint16_t)port onChain:(DSChain *)chain { if (!(self = [super init])) return nil; @@ -157,9 +156,9 @@ - (instancetype)initWithAddress:(UInt128)address andPort:(uint16_t)port onChain: return self; } -- (instancetype)initWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry { - return [self initWithAddress:simplifiedMasternodeEntry.address andPort:simplifiedMasternodeEntry.port onChain:simplifiedMasternodeEntry.chain]; -} +//- (instancetype)initWithSimplifiedMasternodeEntry:(DSSimplifiedMasternodeEntry *)simplifiedMasternodeEntry { +// return [self initWithAddress:simplifiedMasternodeEntry.address andPort:simplifiedMasternodeEntry.port onChain:simplifiedMasternodeEntry.chain]; +//} - (instancetype)initWithHost:(NSString *)host onChain:(DSChain *)chain { if (!chain) return nil; @@ -377,7 +376,7 @@ - (void)receivedOrphanBlock { - (void)sendRequest:(DSMessageRequest *)request { NSString *type = [request type]; NSData *payload = [request toData]; - //DSLog(@"%@:%u sendRequest: [%@]: %@", self.host, self.port, type, [payload hexString]); +// DSLog(@"%@:%u sendRequest: [%@]: %@", self.host, self.port, type, [payload hexString]); [self sendMessage:payload type:type]; } @@ -843,23 +842,17 @@ - (void)acceptMessage:(NSData *)message type:(NSString *)type { [self acceptGovObjectMessage:message]; //else if ([MSG_GOVOBJSYNC isEqual:type]) [self acceptGovObjectSyncMessage:message]; - //private send - else if ([MSG_DARKSENDANNOUNCE isEqual:type]) - [self acceptDarksendAnnounceMessage:message]; - else if ([MSG_DARKSENDCONTROL isEqual:type]) - [self acceptDarksendControlMessage:message]; - else if ([MSG_DARKSENDFINISH isEqual:type]) - [self acceptDarksendFinishMessage:message]; - else if ([MSG_DARKSENDINITIATE isEqual:type]) - [self acceptDarksendInitiateMessage:message]; - else if ([MSG_DARKSENDQUORUM isEqual:type]) - [self acceptDarksendQuorumMessage:message]; - else if ([MSG_DARKSENDSESSION isEqual:type]) - [self acceptDarksendSessionMessage:message]; - else if ([MSG_DARKSENDSESSIONUPDATE isEqual:type]) - [self acceptDarksendSessionUpdateMessage:message]; - else if ([MSG_DARKSENDTX isEqual:type]) - [self acceptDarksendTransactionMessage:message]; + // CoinJoin + else if ([MSG_COINJOIN_COMPLETE isEqual:type]) + [self acceptCoinJoinCompleteMessage:message]; + else if ([MSG_COINJOIN_FINAL_TRANSACTION isEqual:type]) + [self acceptCoinJoinFinalTransaction:message]; + else if ([MSG_COINJOIN_QUEUE isEqual:type]) + [self acceptCoinJoinQueueMessage:message]; + else if ([MSG_COINJOIN_STATUS_UPDATE isEqual:type]) + [self acceptCoinJoinStatusUpdateMessage:message]; + else if ([MSG_COINJOIN_BROADCAST_TX isEqual:type]) + [self acceptCoinJoinBroadcastTxMessage:message]; #if DROP_MESSAGE_LOGGING else { DSLogWithLocation(self, @"dropping %@, len:%u, not implemented", type, message.length); @@ -1045,7 +1038,7 @@ - (void)acceptInvMessage:(NSData *)message { if (count == 0) { DSLogWithLocation(self, @"Got empty Inv message"); } - if (count > 0 && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_MasternodePing) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_MasternodePaymentVote) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_MasternodeVerify) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_GovernanceObjectVote) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_DSTx)) { + if (count > 0 && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_MasternodePing) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_MasternodePaymentVote) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_MasternodeVerify) && ([message UInt32AtOffset:l.unsignedIntegerValue] != DSInvType_GovernanceObjectVote)) { DSLogWithLocation(self, @"got inv with %u item%@ (first item %@ with hash %@/%@)", (int)count, count == 1 ? @"" : @"s", [self nameOfInvMessage:[message UInt32AtOffset:l.unsignedIntegerValue]], [NSData dataWithUInt256:[message UInt256AtOffset:l.unsignedIntegerValue + sizeof(uint32_t)]].hexString, [NSData dataWithUInt256:[message UInt256AtOffset:l.unsignedIntegerValue + sizeof(uint32_t)]].reverse.hexString); } #endif @@ -1068,7 +1061,7 @@ - (void)acceptInvMessage:(NSData *)message { switch (type) { case DSInvType_Tx: [txHashes addObject:uint256_obj(hash)]; break; case DSInvType_TxLockRequest: [txHashes addObject:uint256_obj(hash)]; break; - case DSInvType_DSTx: break; + case DSInvType_DSTx: [txHashes addObject:uint256_obj(hash)]; break; case DSInvType_TxLockVote: break; case DSInvType_InstantSendDeterministicLock: [instantSendLockDHashes addObject:uint256_obj(hash)]; break; case DSInvType_InstantSendLock: [instantSendLockHashes addObject:uint256_obj(hash)]; break; @@ -1122,9 +1115,10 @@ - (void)acceptInvMessage:(NSData *)message { } #endif + // [Testnet: 80.209.232.96:19999] got inv with 1 item (first item Block with hash fae11efe0d3bb7a96104e765b93251f98f3546805aae5755340b1ce4b4000000/000000b4e41c0b345557ae5a8046358ff95132b965e70461a9b73b0dfe1ee1fa) if (blockHashes.count == 1 && [self.lastBlockHash isEqual:blockHashes[0]]) [blockHashes removeAllObjects]; if (blockHashes.count == 1) self.lastBlockHash = blockHashes[0]; - + if (blockHashes.count > 0) { // remember blockHashes in case we need to re-request them with an updated bloom filter [self dispatchAsyncInDelegateQueue:^{ [self.knownBlockHashes unionOrderedSet:blockHashes]; @@ -1151,42 +1145,32 @@ - (void)acceptInvMessage:(NSData *)message { if (instantSendLockHashes.count > 0) { for (NSValue *hash in instantSendLockHashes) { UInt256 h; - if (![self.knownInstantSendLockHashes containsObject:hash]) continue; [hash getValue:&h]; } - [instantSendLockHashes minusOrderedSet:self.knownInstantSendLockHashes]; [self dispatchAsyncInDelegateQueue:^{ if (self->_status == DSPeerStatus_Connected) [self.transactionDelegate peer:self hasInstantSendLockHashes:instantSendLockHashes]; }]; - [self.knownInstantSendLockHashes unionOrderedSet:instantSendLockHashes]; } if (instantSendLockDHashes.count > 0) { for (NSValue *hash in instantSendLockDHashes) { UInt256 h; - if (![self.knownInstantSendLockDHashes containsObject:hash]) continue; [hash getValue:&h]; } - [instantSendLockDHashes minusOrderedSet:self.knownInstantSendLockDHashes]; - [self dispatchAsyncInDelegateQueue:^{ if (self->_status == DSPeerStatus_Connected) [self.transactionDelegate peer:self hasInstantSendLockDHashes:instantSendLockDHashes]; }]; - [self.knownInstantSendLockDHashes unionOrderedSet:instantSendLockDHashes]; } - - if (chainLockHashes.count > 0) { for (NSValue *hash in chainLockHashes) { UInt256 h; - if (![self.knownChainLockHashes containsObject:hash]) continue; [hash getValue:&h]; } @@ -1195,7 +1179,6 @@ - (void)acceptInvMessage:(NSData *)message { [self dispatchAsyncInDelegateQueue:^{ if (self->_status == DSPeerStatus_Connected) [self.transactionDelegate peer:self hasChainLockHashes:chainLockHashes]; }]; - [self.knownChainLockHashes unionOrderedSet:chainLockHashes]; } @@ -1824,61 +1807,26 @@ - (void)acceptGovObjectSyncMessage:(NSData *)message { DSLogWithLocation(self, @"Gov Object Sync"); } -// MARK: - Accept Dark send - -- (void)acceptDarksendAnnounceMessage:(NSData *)message { -} - -- (void)acceptDarksendControlMessage:(NSData *)message { -} - -- (void)acceptDarksendFinishMessage:(NSData *)message { -} +// MARK: - Accept CoinJoin messages -- (void)acceptDarksendInitiateMessage:(NSData *)message { +- (void)acceptCoinJoinCompleteMessage:(NSData *)message { + [[DSCoinJoinManager sharedInstanceForChain:self.chain] processMessageFrom:self message:message type:MSG_COINJOIN_COMPLETE]; } -- (void)acceptDarksendQuorumMessage:(NSData *)message { +- (void)acceptCoinJoinFinalTransaction:(NSData *)message { + [[DSCoinJoinManager sharedInstanceForChain:self.chain] processMessageFrom:self message:message type:MSG_COINJOIN_FINAL_TRANSACTION]; } -- (void)acceptDarksendSessionMessage:(NSData *)message { +- (void)acceptCoinJoinQueueMessage:(NSData *)message { + [[DSCoinJoinManager sharedInstanceForChain:self.chain] processMessageFrom:self message:message type:MSG_COINJOIN_QUEUE]; } -- (void)acceptDarksendSessionUpdateMessage:(NSData *)message { +- (void)acceptCoinJoinStatusUpdateMessage:(NSData *)message { + [[DSCoinJoinManager sharedInstanceForChain:self.chain] processMessageFrom:self message:message type:MSG_COINJOIN_STATUS_UPDATE]; } -- (void)acceptDarksendTransactionMessage:(NSData *)message { - // DSTransaction *tx = [DSTransaction transactionWithMessage:message]; - // - // if (! tx) { - // [self error:@"malformed tx message: %@", message]; - // return; - // } - // else if (! self.sentFilter && ! self.sentTxAndBlockGetdata) { - // [self error:@"got tx message before loading a filter"]; - // return; - // } - // - // DSLogPrivate(@"%@:%u got tx %@", self.host, self.port, uint256_obj(tx.txHash)); - // - // dispatch_async(self.delegateQueue, ^{ - // [self.delegate peer:self relayedTransaction:tx]; - // }); - // - // if (self.currentBlock) { // we're collecting tx messages for a merkleblock - // [self.currentBlockTxHashes removeObject:uint256_obj(tx.txHash)]; - // - // if (self.currentBlockTxHashes.count == 0) { // we received the entire block including all matched tx - // BRMerkleBlock *block = self.currentBlock; - // - // self.currentBlock = nil; - // self.currentBlockTxHashes = nil; - // - // dispatch_sync(self.delegateQueue, ^{ // syncronous dispatch so we don't get too many queued up tx - // [self.delegate peer:self relayedBlock:block]; - // }); - // } - // } +- (void)acceptCoinJoinBroadcastTxMessage:(NSData *)message { + [[DSCoinJoinManager sharedInstanceForChain:self.chain] processMessageFrom:self message:message type:MSG_COINJOIN_BROADCAST_TX]; } // MARK: - hash diff --git a/DashSync/shared/Models/Notifications/DSSyncState.h b/DashSync/shared/Models/Notifications/DSSyncState.h index b3a775d28..89882c36a 100644 --- a/DashSync/shared/Models/Notifications/DSSyncState.h +++ b/DashSync/shared/Models/Notifications/DSSyncState.h @@ -24,6 +24,7 @@ typedef NS_ENUM(uint16_t, DSSyncStateKind) { DSSyncStateKind_Chain = 0, DSSyncStateKind_Headers = 1, DSSyncStateKind_Masternodes = 2, +// DSSyncStateKind_Platofrm = 3, }; @@ -32,10 +33,21 @@ typedef NS_ENUM(uint16_t, DSSyncStateKind) { @property (nonatomic, assign) uint32_t retrievalQueueCount; @property (nonatomic, assign) uint32_t retrievalQueueMaxAmount; @property (nonatomic, assign) double storedCount; +@property (nonatomic, assign) double stubCount; @property (nonatomic, assign) uint32_t lastBlockHeight; +- (void)updateWithSyncState:(DMNSyncState *)state; @end +//@interface DSPlatformSyncState : NSObject +// +//@property (nonatomic, assign) uint32_t retrievalQueueCount; +//@property (nonatomic, assign) uint32_t retrievalQueueMaxAmount; +//@property (nonatomic, assign) double storedCount; +//@property (nonatomic, assign) double stubCount; +// +//@end + @interface DSSyncState : NSObject diff --git a/DashSync/shared/Models/Notifications/DSSyncState.m b/DashSync/shared/Models/Notifications/DSSyncState.m index 09f03a4f7..a1aa09c86 100644 --- a/DashSync/shared/Models/Notifications/DSSyncState.m +++ b/DashSync/shared/Models/Notifications/DSSyncState.m @@ -19,21 +19,41 @@ #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; + copy.stubCount = self.stubCount; return copy; } - (NSString *)description { - return [NSString stringWithFormat:@"%u/%u/%f/%u", + return [NSString stringWithFormat:@"%u/%u/%f/%f/%u", self.retrievalQueueCount, self.retrievalQueueMaxAmount, self.storedCount, + self.stubCount, self.lastBlockHeight]; } +- (void)updateWithSyncState:(DMNSyncState *)state { + switch (state->tag) { + case DMNSyncStateQueueChanged: + self.retrievalQueueCount = (uint32_t) state->queue_changed.count; + self.retrievalQueueMaxAmount = (uint32_t) state->queue_changed.max_amount; + break; + case DMNSyncStateStoreChanged: + self.storedCount = (uint32_t) state->store_changed.count; + self.lastBlockHeight = state->store_changed.last_block_height; + break; + case DMNSyncStateStubCount: + self.stubCount = state->stub_count.count; + default: + break; + } +} + @end @implementation DSSyncState diff --git a/DashSync/shared/Models/Payment/DSPaymentProtocol.m b/DashSync/shared/Models/Payment/DSPaymentProtocol.m index b0aa68bf7..3bc4608a3 100644 --- a/DashSync/shared/Models/Payment/DSPaymentProtocol.m +++ b/DashSync/shared/Models/Payment/DSPaymentProtocol.m @@ -27,6 +27,7 @@ #import "DSPaymentProtocol.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSTransaction.h" #import "NSData+Dash.h" #import "NSDate+Utils.h" diff --git a/DashSync/shared/Models/Payment/DSPaymentRequest.h b/DashSync/shared/Models/Payment/DSPaymentRequest.h index 64d5efbc2..1f2d59fb0 100644 --- a/DashSync/shared/Models/Payment/DSPaymentRequest.h +++ b/DashSync/shared/Models/Payment/DSPaymentRequest.h @@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN -@class DSPaymentProtocolRequest, DSPaymentProtocolPayment, DSPaymentProtocolACK, DSChain, DSBlockchainIdentity, DSAccount; +@class DSPaymentProtocolRequest, DSPaymentProtocolPayment, DSPaymentProtocolACK, DSChain, DSIdentity, DSAccount; // BIP21 bitcoin payment request URI https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki @interface DSPaymentRequest : NSObject @@ -61,22 +61,36 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithData:(NSData *)data onChain:(DSChain *)chain; - (instancetype)initWithURL:(NSURL *)url onChain:(DSChain *)chain; -- (DSPaymentProtocolRequest *)protocolRequestForBlockchainIdentity:(DSBlockchainIdentity *_Nullable)blockchainIdentity onAccount:(DSAccount *)account inContext:(NSManagedObjectContext *)context; +- (DSPaymentProtocolRequest *)protocolRequestForIdentity:(DSIdentity *_Nullable)identity + onAccount:(DSAccount *)account + inContext:(NSManagedObjectContext *)context; -- (BOOL)isValidAsDashpayPaymentRequestForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity onAccount:(DSAccount *)account inContext:(NSManagedObjectContext *)context; +- (BOOL)isValidAsDashpayPaymentRequestForIdentity:(DSIdentity *)identity + onAccount:(DSAccount *)account + inContext:(NSManagedObjectContext *)context; -- (NSString *)paymentAddressForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity onAccount:(DSAccount *)account fallbackToPaymentAddressIfIssue:(BOOL)fallbackToPaymentAddressIfIssue inContext:(NSManagedObjectContext *)context; +- (NSString *)paymentAddressForIdentity:(DSIdentity *)identity + onAccount:(DSAccount *)account + fallbackToPaymentAddressIfIssue:(BOOL)fallbackToPaymentAddressIfIssue + inContext:(NSManagedObjectContext *)context; - (void)fetchBIP70WithTimeout:(NSTimeInterval)timeout completion:(void (^)(DSPaymentProtocolRequest *req, NSError *error))completion; // fetches a BIP70 request over HTTP and calls completion block // https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki -+ (void)fetch:(NSString *)url scheme:(NSString *)scheme callbackScheme:(NSString *)callbackScheme onChain:(DSChain *)chain timeout:(NSTimeInterval)timeout - completion:(void (^)(DSPaymentProtocolRequest *req, NSError *error))completion; ++ (void)fetch:(NSString *)url + scheme:(NSString *)scheme +callbackScheme:(NSString *)callbackScheme + onChain:(DSChain *)chain + timeout:(NSTimeInterval)timeout + completion:(void (^)(DSPaymentProtocolRequest *req, NSError *error))completion; // posts a BIP70 payment object to the specified URL -+ (void)postPayment:(DSPaymentProtocolPayment *)payment scheme:(NSString *)scheme to:(NSString *)paymentURL onChain:(DSChain *)chain ++ (void)postPayment:(DSPaymentProtocolPayment *)payment + scheme:(NSString *)scheme + to:(NSString *)paymentURL + onChain:(DSChain *)chain timeout:(NSTimeInterval)timeout completion:(void (^)(DSPaymentProtocolACK *ack, NSError *error))completion; diff --git a/DashSync/shared/Models/Payment/DSPaymentRequest.m b/DashSync/shared/Models/Payment/DSPaymentRequest.m index 54d62d751..d57166a5f 100644 --- a/DashSync/shared/Models/Payment/DSPaymentRequest.m +++ b/DashSync/shared/Models/Payment/DSPaymentRequest.m @@ -28,10 +28,11 @@ #import "DSPaymentRequest.h" #import "DSAccount.h" -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSCurrencyPriceObject.h" #import "DSDashpayUserEntity+CoreDataClass.h" #import "DSFriendRequestEntity+CoreDataClass.h" @@ -97,9 +98,11 @@ - (void)setString:(NSString *)string { NSURL *url = [NSURL URLWithString:s]; if (!url || !url.scheme) { - if ([DSKeyManager isValidDashAddress:s forChain:self.chain] || - [s isValidDashPrivateKeyOnChain:self.chain] || - [DSKeyManager isValidDashBIP38Key:s]) { + + if (dash_spv_crypto_bip_bip38_is_valid_payment_request_address(DChar(s), self.chain.chainType)) { +// if (DIsValidDashAddress(DChar(s), self.chain.chainType) || +// [s isValidDashPrivateKeyOnChain:self.chain] || +// [DSKeyManager isValidDashBIP38Key:s]) { url = [NSURL URLWithString:[NSString stringWithFormat:@"dash://%@", s]]; self.scheme = @"dash"; } @@ -237,7 +240,7 @@ - (NSURL *)url { - (BOOL)isValidAsNonDashpayPaymentRequest { if ([self.scheme isEqualToString:@"dash"]) { - BOOL valid = [DSKeyManager isValidDashAddress:self.paymentAddress forChain:self.chain] || (self.r && [NSURL URLWithString:self.r]); + BOOL valid = DIsValidDashAddress(DChar(self.paymentAddress), self.chain.chainType) || (self.r && [NSURL URLWithString:self.r]); if (!valid) { DSLog(@"Not a valid dash request"); } @@ -257,11 +260,13 @@ - (BOOL)isValidAsNonDashpayPaymentRequest { } } -- (BOOL)isValidAsDashpayPaymentRequestForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity onAccount:(DSAccount *)account inContext:(NSManagedObjectContext *)context { +- (BOOL)isValidAsDashpayPaymentRequestForIdentity:(DSIdentity *)identity + onAccount:(DSAccount *)account + inContext:(NSManagedObjectContext *)context { if ([self.scheme isEqualToString:@"dash"]) { __block DSIncomingFundsDerivationPath *friendshipDerivationPath = nil; [context performBlockAndWait:^{ - DSDashpayUserEntity *dashpayUserEntity = [blockchainIdentity matchingDashpayUserInContext:context]; + DSDashpayUserEntity *dashpayUserEntity = [identity matchingDashpayUserInContext:context]; for (DSFriendRequestEntity *friendRequest in dashpayUserEntity.incomingRequests) { if ([[friendRequest.sourceContact.associatedBlockchainIdentity.dashpayUsername stringValue] isEqualToString:self.dashpayUsername]) { @@ -269,7 +274,7 @@ - (BOOL)isValidAsDashpayPaymentRequestForBlockchainIdentity:(DSBlockchainIdentit } } }]; - BOOL valid = [DSKeyManager isValidDashAddress:self.paymentAddress forChain:self.chain] || (self.r && [NSURL URLWithString:self.r]) || friendshipDerivationPath; + BOOL valid = DIsValidDashAddress(DChar(self.paymentAddress), self.chain.chainType) || (self.r && [NSURL URLWithString:self.r]) || friendshipDerivationPath; if (!valid) { DSLog(@"Not a valid dash request"); } @@ -289,8 +294,11 @@ - (BOOL)isValidAsDashpayPaymentRequestForBlockchainIdentity:(DSBlockchainIdentit } } -- (NSString *)paymentAddressForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity onAccount:(DSAccount *)account fallbackToPaymentAddressIfIssue:(BOOL)fallbackToPaymentAddressIfIssue inContext:(NSManagedObjectContext *)context { - if (!blockchainIdentity || !self.dashpayUsername) { +- (NSString *)paymentAddressForIdentity:(DSIdentity *)identity + onAccount:(DSAccount *)account + fallbackToPaymentAddressIfIssue:(BOOL)fallbackToPaymentAddressIfIssue + inContext:(NSManagedObjectContext *)context { + if (!identity || !self.dashpayUsername) { if (fallbackToPaymentAddressIfIssue) { return [self paymentAddress]; } else { @@ -299,7 +307,7 @@ - (NSString *)paymentAddressForBlockchainIdentity:(DSBlockchainIdentity *)blockc } __block DSIncomingFundsDerivationPath *friendshipDerivationPath = nil; [context performBlockAndWait:^{ - DSDashpayUserEntity *dashpayUserEntity = [blockchainIdentity matchingDashpayUserInContext:context]; + DSDashpayUserEntity *dashpayUserEntity = [identity matchingDashpayUserInContext:context]; for (DSFriendRequestEntity *friendRequest in dashpayUserEntity.incomingRequests) { if ([[friendRequest.sourceContact.associatedBlockchainIdentity.dashpayUsername stringValue] isEqualToString:self.dashpayUsername]) { @@ -319,13 +327,15 @@ - (NSString *)paymentAddressForBlockchainIdentity:(DSBlockchainIdentity *)blockc return friendshipDerivationPath.receiveAddress; } -- (DSPaymentProtocolRequest *)protocolRequestForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity onAccount:(DSAccount *)account inContext:(NSManagedObjectContext *)context { - if (!blockchainIdentity || !self.dashpayUsername) { +- (DSPaymentProtocolRequest *)protocolRequestForIdentity:(DSIdentity *)identity + onAccount:(DSAccount *)account + inContext:(NSManagedObjectContext *)context { + if (!identity || !self.dashpayUsername) { return [self protocolRequest]; } __block DSIncomingFundsDerivationPath *friendshipDerivationPath = nil; [context performBlockAndWait:^{ - DSDashpayUserEntity *dashpayUserEntity = [blockchainIdentity matchingDashpayUserInContext:context]; + DSDashpayUserEntity *dashpayUserEntity = [identity matchingDashpayUserInContext:context]; for (DSFriendRequestEntity *friendRequest in dashpayUserEntity.incomingRequests) { if ([[friendRequest.sourceContact.associatedBlockchainIdentity.dashpayUsername stringValue] isEqualToString:self.dashpayUsername]) { @@ -379,7 +389,7 @@ - (DSPaymentProtocolRequest *)protocolRequestForBlockchainIdentity:(DSBlockchain - (DSPaymentProtocolRequest *)protocolRequest { NSData *name = [self.label dataUsingEncoding:NSUTF8StringEncoding]; NSMutableData *script = [NSMutableData data]; - if ([DSKeyManager isValidDashAddress:self.paymentAddress forChain:self.chain]) { + if (DIsValidDashAddress(DChar(self.paymentAddress), self.chain.chainType)) { [script appendData:[DSKeyManager scriptPubKeyForAddress:self.paymentAddress forChain:self.chain]]; } #if SHAPESHIFT_ENABLED @@ -469,13 +479,10 @@ + (void)fetch:(NSString *)url scheme:(NSString *)scheme callbackScheme:(NSString } if (!request) { - DSLog(@"unexpected response from %@:\n%@", req.URL.host, - [[NSString alloc] initWithData:data - encoding:NSUTF8StringEncoding]); - completion(nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), - req.URL.host]]); + DSLog(@"unexpected response from %@:\n%@", req.URL.host, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); } else if (![request.details.chain isEqual:chain]) { - completion(nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Requested network \"%@\" not currently in use", nil), request.details.chain.networkName]]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Requested network \"%@\" not currently in use", nil, request.details.chain.networkName)]); } else completion(request, nil); }] resume]; @@ -522,7 +529,7 @@ + (void)postPayment:(DSPaymentProtocolPayment *)payment scheme:(NSString *)schem [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); if (completion) { - completion(nil, [NSError errorWithCode:417 descriptionKey:[NSString stringWithFormat:DSLocalizedString(@"Unexpected response from %@", nil), req.URL.host]]); + completion(nil, [NSError errorWithCode:417 descriptionKey:DSLocalizedFormat(@"Unexpected response from %@", nil, req.URL.host)]); } } else if (completion) completion(ack, nil); diff --git a/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.h b/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.h index ac5cb6549..b9590be08 100644 --- a/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.h +++ b/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.h @@ -42,6 +42,7 @@ typedef NS_ENUM(NSInteger, DSCoreDataMigrationVersionValue) DSCoreDataMigrationVersionValue_19 = 19, DSCoreDataMigrationVersionValue_20 = 20, DSCoreDataMigrationVersionValue_21 = 21, + DSCoreDataMigrationVersionValue_22 = 22, }; @interface DSCoreDataMigrationVersion : NSObject diff --git a/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.m b/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.m index e6526f1f1..941126d1e 100644 --- a/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.m +++ b/DashSync/shared/Models/Persistence/Migration/Internal/DSCoreDataMigrationVersion.m @@ -20,7 +20,7 @@ @implementation DSCoreDataMigrationVersion + (DSCoreDataMigrationVersionValue)current { - return DSCoreDataMigrationVersionValue_21; + return DSCoreDataMigrationVersionValue_22; } + (NSString *)modelResourceForVersion:(DSCoreDataMigrationVersionValue)version { @@ -46,6 +46,7 @@ + (NSString *)modelResourceForVersion:(DSCoreDataMigrationVersionValue)version { case DSCoreDataMigrationVersionValue_19: return @"DashSync 19"; case DSCoreDataMigrationVersionValue_20: return @"DashSync 20"; case DSCoreDataMigrationVersionValue_21: return @"DashSync 21"; + case DSCoreDataMigrationVersionValue_22: return @"DashSync 22"; default: return [NSString stringWithFormat:@"DashSync %ld", (long)version]; } diff --git a/DashSync/shared/Models/Persistence/Migration/Policies/DSMerkleBlockEntity6To7MigrationPolicy.m b/DashSync/shared/Models/Persistence/Migration/Policies/DSMerkleBlockEntity6To7MigrationPolicy.m index 95b2bc2d1..ed27f87bc 100644 --- a/DashSync/shared/Models/Persistence/Migration/Policies/DSMerkleBlockEntity6To7MigrationPolicy.m +++ b/DashSync/shared/Models/Persistence/Migration/Policies/DSMerkleBlockEntity6To7MigrationPolicy.m @@ -130,7 +130,7 @@ - (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance NSNumber *height = [sInstance valueForKey:@"height"]; NSManagedObject *chainEntity = [sInstance valueForKey:@"chain"]; NSParameterAssert(chainEntity); - if (height != nil && [height intValue] != BLOCK_UNKNOWN_HEIGHT && [[chainEntity valueForKey:@"type"] intValue] == ChainType_MainNet && [height intValue] > self.lastKnownSourceBlockHeight) { + if (height != nil && [height intValue] != BLOCK_UNKNOWN_HEIGHT && [[chainEntity valueForKey:@"type"] intValue] == dash_spv_crypto_network_chain_type_ChainType_MainNet && [height intValue] > self.lastKnownSourceBlockHeight) { self.lastKnownSourceBlockHeight = [height unsignedIntValue]; } @@ -144,7 +144,7 @@ - (BOOL)endEntityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager self.lastKnownSourceBlockWithCheckpoint = [[DSMerkleBlock alloc] initWithCheckpoint:lastCheckpoint onChain:chain]; } if (self.lastKnownSourceBlockWithCheckpoint) { - DSChainEntity *chainEntity = [self chainEntityForType:ChainType_MainNet inContext:manager.destinationContext]; + DSChainEntity *chainEntity = [self chainEntityForType:dash_spv_crypto_network_chain_type_ChainType_MainNet inContext:manager.destinationContext]; if (chainEntity) { [chainEntity setValue:uint256_data(self.lastKnownSourceBlockWithCheckpoint.blockHash) forKey:@"syncBlockHash"]; [chainEntity setValue:@(self.lastKnownSourceBlockWithCheckpoint.height) forKey:@"syncBlockHeight"]; @@ -176,7 +176,7 @@ - (BOOL)endEntityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager } -- (DSChainEntity *)chainEntityForType:(ChainType_Tag)type inContext:(NSManagedObjectContext *)context { +- (DSChainEntity *)chainEntityForType:(uint16_t)type inContext:(NSManagedObjectContext *)context { NSFetchRequest *fetchRequest = [DSChainEntity fetchRequest]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"type = %d", type]; NSError *error = nil; diff --git a/DashSync/shared/Models/Platform/Base/DPBaseObject.h b/DashSync/shared/Models/Platform/Base/DPBaseObject.h index 235cfb82b..d3f7d9cb8 100644 --- a/DashSync/shared/Models/Platform/Base/DPBaseObject.h +++ b/DashSync/shared/Models/Platform/Base/DPBaseObject.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @interface DPBaseObject : NSObject -- (void)resetSerializedValues NS_REQUIRES_SUPER; +//- (void)resetSerializedValues NS_REQUIRES_SUPER; @end diff --git a/DashSync/shared/Models/Platform/Base/DPBaseObject.m b/DashSync/shared/Models/Platform/Base/DPBaseObject.m index 8d8d103be..16d7370b8 100644 --- a/DashSync/shared/Models/Platform/Base/DPBaseObject.m +++ b/DashSync/shared/Models/Platform/Base/DPBaseObject.m @@ -19,69 +19,70 @@ #import "BigIntTypes.h" +#import "DSChain+Params.h" #import "NSData+Dash.h" #import "NSMutableData+Dash.h" -#import +//#import @implementation DPBaseObject @synthesize chain = _chain; -- (void)resetSerializedValues { - _serialized = nil; - _serializedHash = nil; - _serializedBaseData = nil; - _serializedBaseDataHash = nil; -} +//- (void)resetSerializedValues { +// _serialized = nil; +// _serializedHash = nil; +// _serializedBaseData = nil; +// _serializedBaseDataHash = nil; +//} #pragma mark - DPPSerializableObject -@synthesize serialized = _serialized; -@synthesize serializedHash = _serializedHash; -@synthesize serializedBaseData = _serializedBaseData; -@synthesize serializedBaseDataHash = _serializedBaseDataHash; - -- (DSMutableStringValueDictionary *)keyValueDictionary { - NSAssert(NO, @"Should be overriden in subclass"); - return [DSMutableStringValueDictionary dictionary]; -} - -- (NSData *)serialized { - if (_serialized == nil) { - NSMutableData *data = [NSMutableData data]; - [data appendUInt32:self.chain.platformProtocolVersion]; - [data appendData:[self.keyValueDictionary ds_cborEncodedObject]]; - _serialized = [data copy]; - } - return _serialized; -} - -- (NSData *)serializedBaseData { - if (_serializedBaseData == nil) { - NSMutableData *data = [NSMutableData data]; - [data appendUInt32:self.chain.platformProtocolVersion]; - [data appendData:[self.baseKeyValueDictionary ds_cborEncodedObject]]; - _serializedBaseData = [data copy]; - } - return _serializedBaseData; -} - -- (NSData *)serializedHash { - if (_serializedHash == nil) { - _serializedHash = uint256_data([self.serialized SHA256_2]); - } - return _serializedHash; -} - -- (NSData *)serializedBaseDataHash { - if (_serializedBaseDataHash == nil) { - _serializedBaseDataHash = uint256_data([self.serializedBaseData SHA256_2]); - } - return _serializedBaseDataHash; -} +//@synthesize serialized = _serialized; +//@synthesize serializedHash = _serializedHash; +//@synthesize serializedBaseData = _serializedBaseData; +//@synthesize serializedBaseDataHash = _serializedBaseDataHash; + +//- (DSMutableStringValueDictionary *)keyValueDictionary { +// NSAssert(NO, @"Should be overriden in subclass"); +// return [DSMutableStringValueDictionary dictionary]; +//} + +//- (NSData *)serialized { +// if (_serialized == nil) { +// NSMutableData *data = [NSMutableData data]; +// [data appendUInt32:self.chain.platformProtocolVersion]; +// [data appendData:[self.keyValueDictionary ds_cborEncodedObject]]; +// _serialized = [data copy]; +// } +// return _serialized; +//} +// +//- (NSData *)serializedBaseData { +// if (_serializedBaseData == nil) { +// NSMutableData *data = [NSMutableData data]; +// [data appendUInt32:self.chain.platformProtocolVersion]; +// [data appendData:[self.baseKeyValueDictionary ds_cborEncodedObject]]; +// _serializedBaseData = [data copy]; +// } +// return _serializedBaseData; +//} +// +//- (NSData *)serializedHash { +// if (_serializedHash == nil) { +// _serializedHash = uint256_data([self.serialized SHA256_2]); +// } +// return _serializedHash; +//} +// +//- (NSData *)serializedBaseDataHash { +// if (_serializedBaseDataHash == nil) { +// _serializedBaseDataHash = uint256_data([self.serializedBaseData SHA256_2]); +// } +// return _serializedBaseDataHash; +//} -@synthesize baseKeyValueDictionary; +//@synthesize baseKeyValueDictionary; @end diff --git a/DashSync/shared/Models/Platform/Base/DPSerializableObject.h b/DashSync/shared/Models/Platform/Base/DPSerializableObject.h index 2ed6eeec5..58e39b751 100644 --- a/DashSync/shared/Models/Platform/Base/DPSerializableObject.h +++ b/DashSync/shared/Models/Platform/Base/DPSerializableObject.h @@ -26,12 +26,12 @@ NS_ASSUME_NONNULL_BEGIN @protocol DPSerializableObject @property (readonly, strong, nonnull, nonatomic) DSChain *chain; -@property (readonly, strong, nullable, nonatomic) DSMutableStringValueDictionary *keyValueDictionary; -@property (readonly, strong, nullable, nonatomic) DSMutableStringValueDictionary *baseKeyValueDictionary; -@property (readonly, strong, nullable, nonatomic) NSData *serialized; -@property (readonly, strong, nullable, nonatomic) NSData *serializedBaseData; -@property (readonly, strong, nullable, nonatomic) NSData *serializedHash; -@property (readonly, strong, nullable, nonatomic) NSData *serializedBaseDataHash; +//@property (readonly, strong, nullable, nonatomic) DSMutableStringValueDictionary *keyValueDictionary; +//@property (readonly, strong, nullable, nonatomic) DSMutableStringValueDictionary *baseKeyValueDictionary; +//@property (readonly, strong, nullable, nonatomic) NSData *serialized; +//@property (readonly, strong, nullable, nonatomic) NSData *serializedBaseData; +//@property (readonly, strong, nullable, nonatomic) NSData *serializedHash; +//@property (readonly, strong, nullable, nonatomic) NSData *serializedBaseDataHash; @end diff --git a/DashSync/shared/Models/Platform/Base/DPTypes.h b/DashSync/shared/Models/Platform/Base/DPTypes.h index a8320ead8..c5c626618 100644 --- a/DashSync/shared/Models/Platform/Base/DPTypes.h +++ b/DashSync/shared/Models/Platform/Base/DPTypes.h @@ -18,7 +18,7 @@ #ifndef DPTypes_h #define DPTypes_h -typedef NSDictionary DSStringValueDictionary; +//typedef NSDictionary DSStringValueDictionary; typedef NSMutableDictionary DSMutableStringValueDictionary; typedef NS_ENUM(NSUInteger, DWIdentityPublicKeyPurpose) { diff --git a/DashSync/shared/Models/Platform/Contract/DPContract+Protected.h b/DashSync/shared/Models/Platform/Contract/DPContract+Protected.h index 358d37401..0bbcd1714 100644 --- a/DashSync/shared/Models/Platform/Contract/DPContract+Protected.h +++ b/DashSync/shared/Models/Platform/Contract/DPContract+Protected.h @@ -17,15 +17,13 @@ #import "DPContract.h" -@class DSBlockchainIdentity; - NS_ASSUME_NONNULL_BEGIN @interface DPContract () -- (void)setContractState:(DPContractState)contractState inContext:(NSManagedObjectContext *)context; +@property (assign, nonatomic) DPContractState contractState; -- (DSContractTransition *)contractRegistrationTransitionForIdentity:(DSBlockchainIdentity *)blockchainIdentity; +- (void)saveAndWaitInContext:(NSManagedObjectContext *)context; @end diff --git a/DashSync/shared/Models/Platform/Contract/DPContract.h b/DashSync/shared/Models/Platform/Contract/DPContract.h index 284881153..0faae8fac 100644 --- a/DashSync/shared/Models/Platform/Contract/DPContract.h +++ b/DashSync/shared/Models/Platform/Contract/DPContract.h @@ -16,6 +16,7 @@ // #import "BigIntTypes.h" +#import "DSKeyManager.h" #import "DPBaseObject.h" NS_ASSUME_NONNULL_BEGIN @@ -23,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const DPContractDidUpdateNotification; FOUNDATION_EXPORT NSString *const DSContractUpdateNotificationKey; -@class DSChain, DSContractTransition, DSBlockchainIdentity; +@class DSChain, DSIdentity; typedef NS_ENUM(NSUInteger, DPContractState) { @@ -36,7 +37,7 @@ typedef NS_ENUM(NSUInteger, DPContractState) @interface DPContract : DPBaseObject @property (readonly, copy, nonatomic) NSString *localContractIdentifier; -@property (readonly, nonatomic) UInt256 registeredBlockchainIdentityUniqueID; +@property (readonly, nonatomic) UInt256 registeredIdentityUniqueID; @property (readonly, copy, nonatomic) NSString *name; @property (readonly, nonatomic) UInt256 contractId; @property (readonly, copy, nonatomic) NSString *base58ContractId; @@ -44,35 +45,22 @@ typedef NS_ENUM(NSUInteger, DPContractState) @property (readonly, copy, nonatomic) NSString *base58OwnerId; @property (readonly, copy, nonatomic) NSString *statusString; @property (readonly, nonatomic) DPContractState contractState; -@property (readonly, copy, nonatomic) NSString *jsonSchemaId; -@property (readonly, copy, nonatomic) DSStringValueDictionary *objectDictionary; - -@property (assign, nonatomic) NSInteger version; -@property (copy, nonatomic) NSString *jsonMetaSchema; -@property (copy, nonatomic) NSDictionary *documents; -@property (copy, nonatomic) NSDictionary *definitions; +@property (readonly, nonatomic) DDataContract *raw_contract; +@property (readonly, nonatomic) DDocumentTypes *documents; - (instancetype)initWithLocalContractIdentifier:(NSString *)contractID - documents:(NSDictionary *)documents + raw_contract:(DDataContract *)raw_contract onChain:(DSChain *)chain; - (instancetype)init NS_UNAVAILABLE; -- (BOOL)isDocumentDefinedForType:(NSString *)type; -- (void)setDocumentSchema:(DSStringValueDictionary *)schema forType:(NSString *)type; -- (nullable DSStringValueDictionary *)documentSchemaForType:(NSString *)type; - -- (nullable NSDictionary *)documentSchemaRefForType:(NSString *)type; - -- (void)registerCreator:(DSBlockchainIdentity *)blockchainIdentity inContext:(NSManagedObjectContext *)context; -- (void)unregisterCreatorInContext:(NSManagedObjectContext *)context; +- (void)registerCreator:(DSIdentity *)identity; +- (void)unregisterCreator; + (DPContract *)localDashpayContractForChain:(DSChain *)chain; + (DPContract *)localDPNSContractForChain:(DSChain *)chain; -+ (DPContract *)localDashThumbnailContractForChain:(DSChain *)chain; - +//+ (DPContract *)localDashThumbnailContractForChain:(DSChain *)chain; -- (UInt256)contractIdIfRegisteredByBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; @end diff --git a/DashSync/shared/Models/Platform/Contract/DPContract.m b/DashSync/shared/Models/Platform/Contract/DPContract.m index 772e1bee1..ae0e3f8d0 100644 --- a/DashSync/shared/Models/Platform/Contract/DPContract.m +++ b/DashSync/shared/Models/Platform/Contract/DPContract.m @@ -19,28 +19,24 @@ #import "DSAuthenticationKeysDerivationPath.h" #import "DSBlockchainIdentityEntity+CoreDataClass.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSContractEntity+CoreDataClass.h" -#import "DSContractTransition.h" #import "DSDashPlatform.h" #import "DSWallet.h" -#import "NSData+DSCborDecoding.h" #import "NSData+Dash.h" #import "NSManagedObject+Sugar.h" #import "NSMutableData+Dash.h" #import "NSString+Bitcoin.h" +#import "NSError+Dash.h" NS_ASSUME_NONNULL_BEGIN -static NSInteger const DEFAULT_VERSION = 1; -static NSString *const DEFAULT_SCHEMA = @"https://schema.dash.org/dpp-0-4-0/meta/data-contract"; -static NSString *const DPCONTRACT_SCHEMA_ID = @"contract"; - @interface DPContract () -@property (strong, nonatomic) NSMutableDictionary *mutableDocuments; +@property (assign, nonatomic) DDataContract *raw_contract; @property (copy, nonatomic, null_resettable) NSString *localContractIdentifier; @property (assign, nonatomic) UInt256 contractId; -@property (assign, nonatomic) UInt256 registeredBlockchainIdentityUniqueID; +@property (assign, nonatomic) UInt256 registeredIdentityUniqueID; @property (assign, nonatomic) UInt256 entropy; @end @@ -51,18 +47,26 @@ @implementation DPContract #pragma mark - Init +- (void)dealloc { + if (self.raw_contract) { + dpp_data_contract_DataContract_destroy(self.raw_contract); + } +} + - (instancetype)initWithLocalContractIdentifier:(NSString *)localContractIdentifier - documents:(NSDictionary *)documents + raw_contract:(DDataContract *)raw_contract +// documents:(NSDictionary *)documents onChain:(DSChain *)chain { NSParameterAssert(localContractIdentifier); - NSParameterAssert(documents); + NSParameterAssert(raw_contract); if (!(self = [super init])) return nil; - _version = DEFAULT_VERSION; +// _version = DEFAULT_VERSION; _localContractIdentifier = localContractIdentifier; - _jsonMetaSchema = DEFAULT_SCHEMA; - _mutableDocuments = [documents mutableCopy]; - _definitions = @{}; +// _jsonMetaSchema = DEFAULT_SCHEMA; + _raw_contract = raw_contract; +// _mutableDocuments = [documents mutableCopy]; +// _definitions = @{}; _chain = chain; // [self.chain.chainManagedObjectContext performBlockAndWait:^{ @@ -75,217 +79,40 @@ - (instancetype)initWithLocalContractIdentifier:(NSString *)localContractIdentif return self; } -#pragma mark - Initializer Helpers - -+ (DPContract *)contractWithName:(NSString *)name - withLocalIdentifier:(NSString *)localIdentifier - documents:(NSDictionary *)documents - onChain:(DSChain *)chain { - NSParameterAssert(name); - NSParameterAssert(documents); - - NSDictionary *rawContract = @{ - @"name": name, - @"documents": documents, - }; - DPContract *contract = [self contractFromDictionary:rawContract withLocalIdentifier:localIdentifier onChain:chain]; - - return contract; -} - -+ (nullable DPContract *)contractFromDictionary:(DSStringValueDictionary *)contractDictionary - withLocalIdentifier:(NSString *)localIdentifier - onChain:(DSChain *)chain - error:(NSError *_Nullable __autoreleasing *)error { - return [self contractFromDictionary:contractDictionary withLocalIdentifier:localIdentifier skipValidation:NO onChain:chain error:error]; -} - -+ (nullable DPContract *)contractFromDictionary:(DSStringValueDictionary *)contractDictionary - withLocalIdentifier:(NSString *)localIdentifier - skipValidation:(BOOL)skipValidation - onChain:(DSChain *)chain - error:(NSError *_Nullable __autoreleasing *)error { - NSParameterAssert(contractDictionary); - - // TODO: validate rawContract - - DPContract *contract = [self contractFromDictionary:contractDictionary withLocalIdentifier:localIdentifier onChain:chain]; - - return contract; -} - -+ (nullable DPContract *)contractFromSerialized:(NSData *)data - onChain:(DSChain *)chain - error:(NSError *_Nullable __autoreleasing *)error { - return [self contractFromSerialized:data withLocalIdentifier:[data base64String] skipValidation:NO onChain:chain error:error]; -} - -+ (nullable DPContract *)contractFromSerialized:(NSData *)data - withLocalIdentifier:(NSString *)identifier - skipValidation:(BOOL)skipValidation - onChain:(DSChain *)chain - error:(NSError *_Nullable __autoreleasing *)error { - NSParameterAssert(data); - - DSStringValueDictionary *contractDictionary = [data ds_decodeCborError:error]; - if (!contractDictionary) { - return nil; - } - - return [self contractFromDictionary:contractDictionary - withLocalIdentifier:identifier - skipValidation:skipValidation - onChain:chain - error:error]; -} - -+ (DPContract *)contractFromDictionary:(DSStringValueDictionary *)rawContract withLocalIdentifier:(NSString *)localContractIdentifier onChain:(DSChain *)chain { - NSDictionary *documents = rawContract[@"documents"]; - - DPContract *contract = [[DPContract alloc] initWithLocalContractIdentifier:localContractIdentifier - documents:documents - onChain:chain]; - - NSString *jsonMetaSchema = rawContract[@"$schema"]; - if (jsonMetaSchema) { - contract.jsonMetaSchema = jsonMetaSchema; - } - - NSNumber *version = rawContract[@"version"]; - if (version) { - contract.version = version.integerValue; - } - - NSDictionary *definitions = rawContract[@"definitions"]; - if (definitions) { - contract.definitions = definitions; - } - - - return contract; -} #pragma mark - Contract Info - (UInt256)contractId { if (uint256_is_zero(_contractId)) { - NSAssert(uint256_is_not_zero(self.registeredBlockchainIdentityUniqueID), @"Registered Identity needs to be set"); + NSAssert(uint256_is_not_zero(self.registeredIdentityUniqueID), @"Registered Identity needs to be set"); NSAssert(uint256_is_not_zero(self.entropy), @"Entropy needs to be set"); NSMutableData *mData = [NSMutableData data]; - [mData appendUInt256:self.registeredBlockchainIdentityUniqueID]; + [mData appendUInt256:self.registeredIdentityUniqueID]; [mData appendUInt256:self.entropy]; _contractId = [mData SHA256_2]; } return _contractId; } -- (UInt256)contractIdIfRegisteredByBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - NSMutableData *mData = [NSMutableData data]; - [mData appendUInt256:blockchainIdentity.uniqueID]; - DSWallet *wallet = blockchainIdentity.wallet; - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentitiesECDSAKeysDerivationPathForWallet:wallet]; - NSMutableData *entropyData = [self.serializedHash mutableCopy]; - [entropyData appendUInt256:blockchainIdentity.uniqueID]; - [entropyData appendData:[derivationPath publicKeyDataAtIndex:UINT32_MAX - 1]]; //use the last key in 32 bit space (it won't probably ever be used anyways) - [mData appendData:uint256_data([entropyData SHA256])]; - return [mData SHA256_2]; //this is the contract ID -} - - (NSString *)base58ContractId { return uint256_base58(self.contractId); } - (NSString *)base58OwnerId { - NSAssert(uint256_is_not_zero(self.registeredBlockchainIdentityUniqueID), @"Registered Identity can not be 0"); - return uint256_base58(self.registeredBlockchainIdentityUniqueID); + NSAssert(uint256_is_not_zero(self.registeredIdentityUniqueID), @"Registered Identity can not be 0"); + return uint256_base58(self.registeredIdentityUniqueID); } - (NSString *)localContractIdentifier { if (!_localContractIdentifier) { - NSData *serializedData = uint256_data([self.serialized SHA256_2]); + Result_ok_Vec_u8_err_dash_spv_platform_error_Error *result = dash_spv_platform_contract_manager_ContractsManager_contract_serialized_hash(self.chain.sharedContractsObj, self.raw_contract); + NSData *serializedData = NSDataFromPtr(result->ok); + Result_ok_Vec_u8_err_dash_spv_platform_error_Error_destroy(result); _localContractIdentifier = [NSString stringWithFormat:@"%@-%@", [serializedData base58String], self.chain.uniqueID]; } return _localContractIdentifier; } -- (NSString *)jsonSchemaId { - return DPCONTRACT_SCHEMA_ID; -} - -- (void)setVersion:(NSInteger)version { - _version = version; - [self resetSerializedValues]; -} - -- (void)setJsonMetaSchema:(NSString *)jsonMetaSchema { - _jsonMetaSchema = [jsonMetaSchema copy]; - [self resetSerializedValues]; -} - -- (NSDictionary *)documents { - return [self.mutableDocuments copy]; -} - -- (void)setDocuments:(NSDictionary *)documents { - _mutableDocuments = [documents mutableCopy]; - [self resetSerializedValues]; -} - -- (void)setDefinitions:(NSDictionary *)definitions { - _definitions = [definitions copy]; - [self resetSerializedValues]; -} - -- (BOOL)isDocumentDefinedForType:(NSString *)type { - NSParameterAssert(type); - if (!type) { - return NO; - } - - return (self.mutableDocuments[type] != nil); -} - -- (void)setDocumentSchema:(DSStringValueDictionary *)schema forType:(NSString *)type { - NSParameterAssert(schema); - NSParameterAssert(type); - if (!schema || !type) { - return; - } - - self.mutableDocuments[type] = schema; -} - -- (nullable DSStringValueDictionary *)documentSchemaForType:(NSString *)type { - NSParameterAssert(type); - if (!type) { - return nil; - } - - return self.mutableDocuments[type]; -} - -- (nullable NSDictionary *)documentSchemaRefForType:(NSString *)type { - NSParameterAssert(type); - if (!type) { - return nil; - } - - if (![self isDocumentDefinedForType:type]) { - return nil; - } - - NSString *refValue = [NSString stringWithFormat:@"%@#/documents/%@", - self.jsonSchemaId, type]; - NSDictionary *dpObjectSchemaRef = @{@"$ref": refValue}; - - return dpObjectSchemaRef; -} - -- (void)resetSerializedValues { - [super resetSerializedValues]; - _keyValueDictionary = nil; -} - - (NSString *)name { return [DSDashPlatform nameForContractWithIdentifier:self.localContractIdentifier]; } @@ -304,35 +131,25 @@ - (NSString *)statusString { return @"Other State"; } -- (void)unregisterCreatorInContext:(NSManagedObjectContext *)context { - self.registeredBlockchainIdentityUniqueID = UINT256_ZERO; +- (void)unregisterCreator { + self.registeredIdentityUniqueID = UINT256_ZERO; self.contractId = UINT256_ZERO; //will be lazy loaded self.entropy = UINT256_ZERO; - [self saveAndWaitInContext:context]; } -- (void)registerCreator:(DSBlockchainIdentity *)blockchainIdentity inContext:(NSManagedObjectContext *)context { - NSParameterAssert(blockchainIdentity); - self.registeredBlockchainIdentityUniqueID = blockchainIdentity ? blockchainIdentity.uniqueID : UINT256_ZERO; +- (void)registerCreator:(DSIdentity *)identity { + NSParameterAssert(identity); + self.registeredIdentityUniqueID = identity ? identity.uniqueID : UINT256_ZERO; self.contractId = UINT256_ZERO; //will be lazy loaded - DSWallet *wallet = blockchainIdentity.wallet; - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentitiesECDSAKeysDerivationPathForWallet:wallet]; - NSMutableData *entropyData = [self.serializedHash mutableCopy]; - [entropyData appendUInt256:blockchainIdentity.uniqueID]; - [entropyData appendData:[derivationPath publicKeyDataAtIndex:UINT32_MAX - 1]]; //use the last key in 32 bit space (it won't probably ever be used anyways) - self.entropy = [entropyData SHA256]; - [self saveAndWaitInContext:context]; -} - -- (void)setContractState:(DPContractState)contractState inContext:(NSManagedObjectContext *)context { - _contractState = contractState; - [self saveAndWaitInContext:context]; -} + DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath identitiesECDSAKeysDerivationPathForWallet:identity.wallet]; + Result_ok_Vec_u8_err_dash_spv_platform_error_Error *result = dash_spv_platform_contract_manager_ContractsManager_contract_serialized_hash(self.chain.sharedContractsObj, self.raw_contract); + NSData *serializedHash = NSDataFromPtr(result->ok); + Result_ok_Vec_u8_err_dash_spv_platform_error_Error_destroy(result); + NSMutableData *entropyData = [serializedHash mutableCopy]; + [entropyData appendUInt256:identity.uniqueID]; + [entropyData appendData:[derivationPath publicKeyDataAtIndexPath:[NSIndexPath indexPathWithIndex:UINT32_MAX - 1]]]; //use the last key in 32 bit space (it won't probably ever be used anyways) -#pragma mark - Transitions - -- (DSContractTransition *)contractRegistrationTransitionForIdentity:(DSBlockchainIdentity *)blockchainIdentity { - return [[DSContractTransition alloc] initWithContract:self withTransitionVersion:1 blockchainIdentityUniqueId:blockchainIdentity.uniqueID onChain:self.chain]; + self.entropy = [entropyData SHA256]; } @@ -341,7 +158,7 @@ - (DSContractTransition *)contractRegistrationTransitionForIdentity:(DSBlockchai - (DSContractEntity *)contractEntityInContext:(NSManagedObjectContext *)context { __block DSContractEntity *entity = nil; [context performBlockAndWait:^{ - entity = [DSContractEntity anyObjectInContext:context matching:@"localContractIdentifier == %@ && chain == %@", self.localContractIdentifier, [self.chain chainEntityInContext:context]]; + entity = [DSContractEntity entityWithLocalContractIdentifier:self.localContractIdentifier onChain:self.chain inContext:context]; }]; return entity; } @@ -354,18 +171,16 @@ - (void)saveAndWaitInContext:(NSManagedObjectContext *)context { entity = [DSContractEntity managedObjectInBlockedContext:context]; entity.chain = [self.chain chainEntityInContext:context]; entity.localContractIdentifier = self.localContractIdentifier; - if (uint256_is_not_zero(self.registeredBlockchainIdentityUniqueID)) { - entity.registeredBlockchainIdentityUniqueID = uint256_data(self.registeredBlockchainIdentityUniqueID); - } - if (uint256_is_not_zero(self.entropy)) { + if (uint256_is_not_zero(self.registeredIdentityUniqueID)) + entity.registeredBlockchainIdentityUniqueID = uint256_data(self.registeredIdentityUniqueID); + if (uint256_is_not_zero(self.entropy)) entity.entropy = uint256_data(self.entropy); - } hasChange = YES; } - if (uint256_is_not_zero(self.registeredBlockchainIdentityUniqueID) && (!entity.registeredBlockchainIdentityUniqueID || !uint256_eq(entity.registeredBlockchainIdentityUniqueID.UInt256, self.registeredBlockchainIdentityUniqueID))) { - entity.registeredBlockchainIdentityUniqueID = uint256_data(self.registeredBlockchainIdentityUniqueID); + if (uint256_is_not_zero(self.registeredIdentityUniqueID) && (!entity.registeredBlockchainIdentityUniqueID || !uint256_eq(entity.registeredBlockchainIdentityUniqueID.UInt256, self.registeredIdentityUniqueID))) { + entity.registeredBlockchainIdentityUniqueID = uint256_data(self.registeredIdentityUniqueID); hasChange = YES; - } else if (uint256_is_zero(self.registeredBlockchainIdentityUniqueID) && entity.registeredBlockchainIdentityUniqueID) { + } else if (uint256_is_zero(self.registeredIdentityUniqueID) && entity.registeredBlockchainIdentityUniqueID) { entity.registeredBlockchainIdentityUniqueID = nil; hasChange = YES; } @@ -386,7 +201,9 @@ - (void)saveAndWaitInContext:(NSManagedObjectContext *)context { if (hasChange) { [context ds_save]; dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:DPContractDidUpdateNotification object:nil userInfo:@{DSContractUpdateNotificationKey: self}]; + [[NSNotificationCenter defaultCenter] postNotificationName:DPContractDidUpdateNotification + object:nil + userInfo:@{DSContractUpdateNotificationKey: self}]; }); } }]; @@ -395,70 +212,32 @@ - (void)saveAndWaitInContext:(NSManagedObjectContext *)context { #pragma mark - Special Contracts -+ (DPContract *)contractAtPath:(NSString *)resource ofType:(NSString *)type identifier:(NSString *)identifier forChain:(DSChain *)chain { - // TODO: read async'ly - NSString *bundlePath = [[NSBundle bundleForClass:self.class] pathForResource:@"DashSync" ofType:@"bundle"]; - NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; - NSString *path = [bundle pathForResource:resource ofType:type]; - NSError *error = nil; - NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&error]; - NSAssert(error == nil, @"Failed reading contract json"); - DSStringValueDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; - NSAssert(error == nil, @"Failed parsing json"); - - NSString *localIdentifier = [NSString stringWithFormat:@"%@-%@", identifier, chain.uniqueID]; - - DPContract *contract = [self contractFromDictionary:jsonObject withLocalIdentifier:localIdentifier onChain:chain error:&error]; - NSAssert(error == nil, @"Failed building DPContract"); - return contract; -} - + (DPContract *)localDashpayContractForChain:(DSChain *)chain { - DPContract *contract = [self contractAtPath:@"dashpay-contract" ofType:@"json" identifier:DASHPAY_CONTRACT forChain:chain]; + DDataContract *raw_contract = dash_spv_platform_contract_manager_ContractsManager_load_dashpay_contract(chain.sharedContractsObj); + DPContract *contract = [[DPContract alloc] initWithLocalContractIdentifier:[NSString stringWithFormat:@"%@-%@", DASHPAY_CONTRACT, chain.uniqueID] + raw_contract:raw_contract + onChain:chain]; if (uint256_is_not_zero(chain.dashpayContractID) && contract.contractState == DPContractState_Unknown) { - [contract setContractState:DPContractState_Registered inContext:[NSManagedObjectContext platformContext]]; + contract.contractState = DPContractState_Registered; contract.contractId = chain.dashpayContractID; [contract saveAndWaitInContext:[NSManagedObjectContext platformContext]]; } - return contract; } + (DPContract *)localDPNSContractForChain:(DSChain *)chain { - DPContract *contract = [self contractAtPath:@"dpns-contract" ofType:@"json" identifier:DPNS_CONTRACT forChain:chain]; + DDataContract *raw_contract = dash_spv_platform_contract_manager_ContractsManager_load_dpns_contract(chain.sharedContractsObj); + DPContract *contract = [[DPContract alloc] initWithLocalContractIdentifier:[NSString stringWithFormat:@"%@-%@", DPNS_CONTRACT, chain.uniqueID] + raw_contract:raw_contract + onChain:chain]; if (uint256_is_not_zero(chain.dpnsContractID) && contract.contractState == DPContractState_Unknown) { - [contract setContractState:DPContractState_Registered inContext:[NSManagedObjectContext platformContext]]; + contract.contractState = DPContractState_Registered; contract.contractId = chain.dpnsContractID; [contract saveAndWaitInContext:[NSManagedObjectContext platformContext]]; } return contract; } -+ (DPContract *)localDashThumbnailContractForChain:(DSChain *)chain { - DPContract *contract = [self contractAtPath:@"dashthumbnail-contract" ofType:@"json" identifier:DASHTHUMBNAIL_CONTRACT forChain:chain]; - return contract; -} - -#pragma mark - DPPSerializableObject - -@synthesize keyValueDictionary = _keyValueDictionary; - -- (DSMutableStringValueDictionary *)objectDictionary { - if (_keyValueDictionary == nil) { - DSMutableStringValueDictionary *json = [[DSMutableStringValueDictionary alloc] init]; - json[@"$schema"] = self.jsonMetaSchema; - json[@"ownerId"] = uint256_data(self.registeredBlockchainIdentityUniqueID); - json[@"$id"] = uint256_data(self.contractId); - json[@"documents"] = self.documents; - json[@"protocolVersion"] = @(0); - if (self.definitions.count > 0) { - json[@"definitions"] = self.definitions; - } - _keyValueDictionary = json; - } - return _keyValueDictionary; -} - @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Contract/DSDocumentType.h b/DashSync/shared/Models/Platform/Contract/DSDocumentType.h deleted file mode 100644 index aca0b9453..000000000 --- a/DashSync/shared/Models/Platform/Contract/DSDocumentType.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 - -NS_ASSUME_NONNULL_BEGIN - -@class DPContract; - -@interface DSDocumentType : NSObject - -@property (readonly, nonatomic) NSString *name; -@property (readonly, nonatomic) uint8_t contractIndex; -@property (readonly, nonatomic) NSArray *path; -@property (readonly, nonatomic) NSData *serializedPath; -@property (readonly, nonatomic, weak) DPContract *contract; -@property (readonly, nonatomic) NSArray *mainIndexPath; -@property (readonly, nonatomic) NSArray *> *secondaryIndexPaths; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Contract/DSDocumentType.m b/DashSync/shared/Models/Platform/Contract/DSDocumentType.m deleted file mode 100644 index 107506a12..000000000 --- a/DashSync/shared/Models/Platform/Contract/DSDocumentType.m +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSDocumentType.h" -#import "DPContract.h" -#import "NSData+Dash.h" - -@implementation DSDocumentType - -- (NSArray *)path { - return @[uint256_data(self.contract.contractId), [NSData dataWithUInt8:self.contractIndex]]; -} - -- (NSData *)serializedPath { - NSMutableData *contactenatedData = [NSMutableData data]; - for (NSData *pathData in self.path) { - [contactenatedData appendData:[NSData dataWithUInt8:pathData.length]]; - [contactenatedData appendData:pathData]; - } - return uint256_data([contactenatedData SHA256_2]); -} - -@end diff --git a/DashSync/shared/Models/Platform/DSDashPlatform.h b/DashSync/shared/Models/Platform/DSDashPlatform.h index 38a107b7e..4629de22e 100644 --- a/DashSync/shared/Models/Platform/DSDashPlatform.h +++ b/DashSync/shared/Models/Platform/DSDashPlatform.h @@ -15,7 +15,6 @@ // limitations under the License. // -#import "DPDocumentFactory.h" #import #define DPNS_CONTRACT @"DPNS_CONTRACT" @@ -30,15 +29,15 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, strong, nonatomic) DPContract *dashPayContract; @property (readonly, strong, nonatomic) DPContract *dpnsContract; -@property (readonly, strong, nonatomic) DPContract *dashThumbnailContract; +//@property (readonly, strong, nonatomic) DPContract *dashThumbnailContract; @property (readonly, strong, nonatomic) NSMutableDictionary *knownContracts; +- (DDataContract *)dashPayRawContract; +- (DDataContract *)dpnsRawContract; @property (readonly, strong, nonatomic) DSChain *chain; - (instancetype)init NS_UNAVAILABLE; -- (DPDocumentFactory *)documentFactoryForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity forContract:(DPContract *)contract; - + (NSString *)nameForContractWithIdentifier:(NSString *)identifier; + (instancetype)sharedInstanceForChain:(DSChain *)chain; diff --git a/DashSync/shared/Models/Platform/DSDashPlatform.m b/DashSync/shared/Models/Platform/DSDashPlatform.m index 3c44c0aab..2e8da29cf 100644 --- a/DashSync/shared/Models/Platform/DSDashPlatform.m +++ b/DashSync/shared/Models/Platform/DSDashPlatform.m @@ -15,10 +15,10 @@ // limitations under the License. // +#import "DSChain+Params.h" #import "DSDashPlatform.h" #import "DPContract.h" #import "DSChain.h" -#import "DSDAPIPlatformNetworkService.h" @interface DSDashPlatform () @@ -26,7 +26,7 @@ @interface DSDashPlatform () @property (strong, nonatomic, null_resettable) NSMutableDictionary *knownContracts; @property (strong, nonatomic) DPContract *dashPayContract; @property (strong, nonatomic) DPContract *dpnsContract; -@property (strong, nonatomic) DPContract *dashThumbnailContract; +//@property (strong, nonatomic) DPContract *dashThumbnailContract; @end @@ -61,11 +61,6 @@ + (instancetype)sharedInstanceForChain:(DSChain *)chain { return platformForChain; } -- (DPDocumentFactory *)documentFactoryForBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity forContract:(DPContract *)contract { - DPDocumentFactory *documentFactory = [[DPDocumentFactory alloc] initWithBlockchainIdentity:blockchainIdentity contract:contract onChain:self.chain]; - return documentFactory; -} - + (NSString *)nameForContractWithIdentifier:(NSString *)identifier { if ([identifier hasPrefix:DASHPAY_CONTRACT]) { return @"DashPay"; @@ -79,7 +74,7 @@ + (NSString *)nameForContractWithIdentifier:(NSString *)identifier { - (NSMutableDictionary *)knownContracts { if (!_knownContracts) { - _knownContracts = [NSMutableDictionary dictionaryWithObjects:@[[self dashPayContract], [self dpnsContract], [self dashThumbnailContract]] forKeys:@[DASHPAY_CONTRACT, DPNS_CONTRACT, DASHTHUMBNAIL_CONTRACT]]; + _knownContracts = [NSMutableDictionary dictionaryWithObjects:@[[self dashPayContract], [self dpnsContract]/*, [self dashThumbnailContract]*/] forKeys:@[DASHPAY_CONTRACT, DPNS_CONTRACT/*, DASHTHUMBNAIL_CONTRACT*/]]; } return _knownContracts; } @@ -98,11 +93,19 @@ - (DPContract *)dpnsContract { return _dpnsContract; } -- (DPContract *)dashThumbnailContract { - if (!_dashThumbnailContract) { - _dashThumbnailContract = [DPContract localDashThumbnailContractForChain:self.chain]; - } - return _dashThumbnailContract; +- (DDataContract *)dashPayRawContract { + return [self dashPayContract].raw_contract; } +- (DDataContract *)dpnsRawContract { + return [self dpnsContract].raw_contract; +} + +//- (DPContract *)dashThumbnailContract { +// if (!_dashThumbnailContract) { +// _dashThumbnailContract = [DPContract localDashThumbnailContractForChain:self.chain]; +// } +// return _dashThumbnailContract; +//} + @end diff --git a/DashSync/shared/Models/Platform/Document/DPDocument.h b/DashSync/shared/Models/Platform/Document/DPDocument.h deleted file mode 100644 index 0c78f23e2..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocument.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "BigIntTypes.h" -#import "DPSerializableObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DPDocumentState, DSDocumentType; - -@interface DPDocument : NSObject - -@property (readonly, copy, nonatomic) DSDocumentType *documentType; -@property (readonly, copy, nonatomic) NSString *tableName; -@property (readonly, nonatomic) UInt256 ownerId; -@property (readonly, copy, nonatomic) NSString *base58OwnerIdString; -@property (readonly, nonatomic) UInt256 contractId; -@property (readonly, copy, nonatomic) NSString *base58ContractIdString; -@property (readonly, nonatomic) UInt256 documentId; -@property (readonly, copy, nonatomic) NSString *base58DocumentIdString; -@property (readonly, copy, nonatomic) NSData *entropy; -@property (readonly, nonatomic) DPDocumentState *currentRegisteredDocumentState; -@property (readonly, nonatomic) DPDocumentState *currentLocalDocumentState; -@property (readonly, copy, nonatomic) NSNumber *currentRegisteredRevision; -@property (readonly, copy, nonatomic) NSNumber *currentLocalRevision; -@property (readonly, copy, nonatomic) DSStringValueDictionary *objectDictionary; -@property (readonly, nonatomic) NSData *mainIndexKey; - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingEntropy:(NSData *)entropy; - -+ (nullable DPDocument *)documentWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingEntropy:(NSString *)entropy; - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingDocumentId:(UInt256)documentId; - -+ (nullable DPDocument *)documentWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingDocumentId:(UInt256)documentId; - -- (instancetype)init NS_UNAVAILABLE; - -- (void)addStateForChangingData:(DSStringValueDictionary *)dataDictionary; - - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DPDocument.m b/DashSync/shared/Models/Platform/Document/DPDocument.m deleted file mode 100644 index 0b2d690c2..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocument.m +++ /dev/null @@ -1,166 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DPDocument.h" -#import "DPDocumentState.h" - -#import "DPErrors.h" - -#import "BigIntTypes.h" -#import "NSData+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DPDocument () - -@property (copy, nonatomic) NSString *tableName; -@property (assign, nonatomic) UInt256 ownerId; -@property (copy, nonatomic) NSString *base58OwnerIdString; -@property (assign, nonatomic) UInt256 contractId; -@property (copy, nonatomic) NSString *base58ContractIdString; -@property (assign, nonatomic) UInt256 documentId; -@property (copy, nonatomic) NSString *base58DocumentIdString; -@property (copy, nonatomic) NSData *entropy; -@property (copy, nonatomic) NSNumber *currentRevision; -@property (strong, nonatomic) DPDocumentState *currentRegisteredDocumentState; -@property (strong, nonatomic) DPDocumentState *currentLocalDocumentState; -@property (strong, nonatomic) NSMutableArray *documentStates; - -@end - -@implementation DPDocument - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName { - NSParameterAssert(dataDictionary); - NSAssert(uint256_is_not_zero(ownerId), @"Owner Id must be set"); - NSAssert(uint256_is_not_zero(contractId), @"Contract Id must be set"); - NSParameterAssert(tableName); - - self = [super init]; - if (self) { - self.tableName = tableName; - self.ownerId = ownerId; - self.contractId = contractId; - - self.currentRevision = @1; - self.currentLocalDocumentState = [DPDocumentState documentStateWithDataDictionary:dataDictionary]; - self.documentStates = [NSMutableArray arrayWithObject:self.currentLocalDocumentState]; - } - - return self; -} - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingEntropy:(NSData *)entropy { - NSParameterAssert(entropy); - NSAssert([entropy isKindOfClass:[NSData class]], @"Entropy must be binary"); - - self = [self initWithDataDictionary:dataDictionary createdByUserWithId:ownerId onContractWithId:contractId onTableWithName:tableName]; - if (self) { - self.entropy = entropy; - } - - return self; -} - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingDocumentId:(UInt256)documentId { - NSAssert(uint256_is_not_zero(documentId), @"Document Id must be set"); - - self = [self initWithDataDictionary:dataDictionary createdByUserWithId:ownerId onContractWithId:contractId onTableWithName:tableName]; - if (self) { - self.documentId = documentId; - } - - return self; -} - -+ (nullable DPDocument *)documentWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingEntropy:(NSData *)entropy { - return [[DPDocument alloc] initWithDataDictionary:dataDictionary createdByUserWithId:ownerId onContractWithId:contractId onTableWithName:tableName usingEntropy:entropy]; -} - -+ (nullable DPDocument *)documentWithDataDictionary:(DSStringValueDictionary *)dataDictionary createdByUserWithId:(UInt256)ownerId onContractWithId:(UInt256)contractId onTableWithName:(NSString *)tableName usingDocumentId:(UInt256)documentId { - return [[DPDocument alloc] initWithDataDictionary:dataDictionary createdByUserWithId:ownerId onContractWithId:contractId onTableWithName:tableName usingDocumentId:documentId]; -} - -- (NSString *)base58OwnerIdString { - if (!_base58OwnerIdString && uint256_is_not_zero(_ownerId)) { - _base58OwnerIdString = uint256_base58(_ownerId); - } - return _base58OwnerIdString; -} - -- (NSString *)base58ContractIdString { - if (!_base58ContractIdString && uint256_is_not_zero(_contractId)) { - _base58ContractIdString = uint256_base58(_contractId); - } - return _base58ContractIdString; -} - -- (NSString *)base58DocumentIdString { - if (!_base58DocumentIdString) { - _base58DocumentIdString = uint256_base58(self.documentId); - } - return _base58DocumentIdString; -} - - -- (UInt256)documentId { - if (uint256_is_zero(_documentId)) { - NSAssert(uint256_is_not_zero(_ownerId), @"Owner needs to be set"); - NSAssert(uint256_is_not_zero(_contractId), @"Owner needs to be set"); - NSAssert(_tableName, @"Table name needs to be set"); - //NSAssert(!uint160_is_zero(self.entropy),@"Entropy needs to be set"); - NSMutableData *mData = [NSMutableData data]; - [mData appendUInt256:_contractId]; - [mData appendUInt256:_ownerId]; - [mData appendData:[_tableName dataUsingEncoding:NSUTF8StringEncoding]]; - [mData appendData:self.entropy]; - _documentId = [mData SHA256_2]; - } - return _documentId; -} - -- (void)addStateForChangingData:(DSStringValueDictionary *)dataDictionary { - DPDocumentState *lastState = [self.documentStates lastObject]; - - DSMutableStringValueDictionary *stateDataDictionary = [lastState.dataChangeDictionary mutableCopy]; - [stateDataDictionary addEntriesFromDictionary:dataDictionary]; - - self.currentLocalDocumentState = [DPDocumentState documentStateWithDataDictionary:stateDataDictionary ofType:DPDocumentStateType_Update]; - - [self.documentStates addObject:self.currentLocalDocumentState]; -} - -#pragma mark - DPPSerializableObject - -- (DSMutableStringValueDictionary *)objectDictionary { - DSMutableStringValueDictionary *json = [[DSMutableStringValueDictionary alloc] init]; - json[@"$type"] = self.tableName; - json[@"$dataContractId"] = uint256_data(self.contractId); - json[@"$id"] = uint256_data(self.documentId); - json[@"$action"] = @(self.currentLocalDocumentState.documentStateType >> 1); - if (!(self.currentLocalDocumentState.documentStateType >> 1)) { - json[@"$entropy"] = self.entropy; - } - [json addEntriesFromDictionary:self.currentLocalDocumentState.dataChangeDictionary]; - return json; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DPDocumentFactory.h b/DashSync/shared/Models/Platform/Document/DPDocumentFactory.h deleted file mode 100644 index ee2de5ade..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocumentFactory.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DPContract.h" -#import "DPDocumentProtocol.h" -#import "DSBlockchainIdentity.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DSChain; - -@interface DPDocumentFactory : NSObject - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)identity - contract:(DPContract *)contract - onChain:(DSChain *)chain; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DPDocumentFactory.m b/DashSync/shared/Models/Platform/Document/DPDocumentFactory.m deleted file mode 100644 index 812914987..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocumentFactory.m +++ /dev/null @@ -1,145 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DPDocumentFactory.h" -#import "DPDocument.h" -#import "DPErrors.h" - -#import "BigIntTypes.h" -#import "NSData+Dash.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -//static NSInteger const DEFAULT_REVISION = 1; - -@interface DPDocumentFactory () - -@property (assign, nonatomic) UInt256 userId; -@property (strong, nonatomic) DPContract *contract; -@property (strong, nonatomic) DSChain *chain; - -@end - -@implementation DPDocumentFactory - -- (instancetype)initWithBlockchainIdentity:(DSBlockchainIdentity *)identity - contract:(DPContract *)contract - onChain:(DSChain *)chain { - NSParameterAssert(identity); - NSParameterAssert(contract); - - self = [super init]; - if (self) { - _userId = identity.uniqueID; - _contract = contract; - _chain = chain; - } - return self; -} - -#pragma mark - DPDocumentFactory - -- (nullable DPDocument *)documentOnTable:(NSString *)tableName - withDataDictionary:(nullable DSStringValueDictionary *)dataDictionary - usingEntropy:(NSData *)entropy - error:(NSError *_Nullable __autoreleasing *)error { - NSParameterAssert(tableName); - - if (!dataDictionary) { - dataDictionary = @{}; - } - - if (uint256_is_zero(self.contract.contractId) && uint256_is_zero(self.contract.registeredBlockchainIdentityUniqueID)) { - if (error != NULL) { - *error = [NSError errorWithDomain:DPErrorDomain - code:DPErrorCode_InvalidDocumentType - userInfo:@{ - NSLocalizedDescriptionKey: - [NSString stringWithFormat:DSLocalizedString(@"Contract '%@' needs to first be locally registered or known", nil), - self.contract.name], - }]; - } - - return nil; - } - - if (![self.contract isDocumentDefinedForType:tableName]) { - if (error != NULL) { - *error = [NSError errorWithDomain:DPErrorDomain - code:DPErrorCode_UnknownContract - userInfo:@{ - NSLocalizedDescriptionKey: - [NSString stringWithFormat:DSLocalizedString(@"Contract '%@' doesn't contain a table named '%@'", nil), - self.contract.name, tableName], - }]; - } - - return nil; - } - - DPDocument *object = [[DPDocument alloc] initWithDataDictionary:dataDictionary createdByUserWithId:self.userId onContractWithId:self.contract.contractId onTableWithName:tableName usingEntropy:entropy]; - - return object; -} - -- (nullable DPDocument *)documentOnTable:(NSString *)tableName - withDataDictionary:(nullable DSStringValueDictionary *)dataDictionary - usingDocumentIdentifier:(NSData *)identifier - error:(NSError *_Nullable __autoreleasing *)error { - NSParameterAssert(tableName); - - if (!dataDictionary) { - dataDictionary = @{}; - } - - if (uint256_is_zero(self.contract.contractId) && uint256_is_zero(self.contract.registeredBlockchainIdentityUniqueID)) { - if (error != NULL) { - *error = [NSError errorWithDomain:DPErrorDomain - code:DPErrorCode_InvalidDocumentType - userInfo:@{ - NSLocalizedDescriptionKey: - [NSString stringWithFormat:DSLocalizedString(@"Contract '%@' needs to first be locally registered or known", nil), - self.contract.name], - }]; - } - - return nil; - } - - if (![self.contract isDocumentDefinedForType:tableName]) { - if (error != NULL) { - *error = [NSError errorWithDomain:DPErrorDomain - code:DPErrorCode_UnknownContract - userInfo:@{ - NSLocalizedDescriptionKey: - [NSString stringWithFormat:DSLocalizedString(@"Contract '%@' doesn't contain a table named '%@'", nil), - self.contract.name, tableName], - }]; - } - - return nil; - } - - DPDocument *object = [[DPDocument alloc] initWithDataDictionary:dataDictionary createdByUserWithId:self.userId onContractWithId:self.contract.contractId onTableWithName:tableName usingDocumentId:identifier.UInt256]; - - return object; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DPDocumentProtocol.h b/DashSync/shared/Models/Platform/Document/DPDocumentProtocol.h deleted file mode 100644 index 3be8d544d..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocumentProtocol.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Created by Andrew Podkovyrin -// Copyright © 2019 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 "DPTypes.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DPDocument; - -@protocol DPDocumentProtocol - -- (nullable DPDocument *)documentOnTable:(NSString *)tableName - withDataDictionary:(nullable DSStringValueDictionary *)dataDictionary - usingEntropy:(NSData *)entropy - error:(NSError *_Nullable __autoreleasing *)error; - -- (nullable DPDocument *)documentOnTable:(NSString *)tableName - withDataDictionary:(nullable DSStringValueDictionary *)dataDictionary - usingDocumentIdentifier:(NSData *)identifier - error:(NSError *_Nullable __autoreleasing *)error; - -//- (nullable DPDocument *)documentFromSerialized:(NSData *)data -// error:(NSError *_Nullable __autoreleasing *)error; -// -//- (nullable DPDocument *)documentFromSerialized:(NSData *)data -// skipValidation:(BOOL)skipValidation -// error:(NSError *_Nullable __autoreleasing *)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DPDocumentState.h b/DashSync/shared/Models/Platform/Document/DPDocumentState.h deleted file mode 100644 index 1dc3bcddd..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocumentState.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DPTypes.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@class DPDocument; - -typedef NS_ENUM(NSUInteger, DPDocumentStateType) -{ - DPDocumentStateType_Initial = 1, - DPDocumentStateType_Replace = 2, - DPDocumentStateType_Delete = 4, - DPDocumentStateType_Update = 8, -}; - -@interface DPDocumentState : NSObject - -@property (readonly, nonatomic) DPDocumentStateType documentStateType; -@property (readonly, nonatomic) DSStringValueDictionary *dataChangeDictionary; - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary; - -+ (DPDocumentState *)documentStateWithDataDictionary:(DSStringValueDictionary *)dataDictionary; - -+ (DPDocumentState *)documentStateWithDataDictionary:(DSStringValueDictionary *)dataDictionary ofType:(DPDocumentStateType)documentStateType; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DPDocumentState.m b/DashSync/shared/Models/Platform/Document/DPDocumentState.m deleted file mode 100644 index cc82729b2..000000000 --- a/DashSync/shared/Models/Platform/Document/DPDocumentState.m +++ /dev/null @@ -1,52 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DPDocumentState.h" - -@interface DPDocumentState () - -@property (assign, nonatomic) DPDocumentStateType documentStateType; -@property (strong, nonatomic) DSStringValueDictionary *dataChangeDictionary; - -@end - -@implementation DPDocumentState - -- (instancetype)initWithDataDictionary:(DSStringValueDictionary *)dataDictionary { - if (self = [self init]) { - self.dataChangeDictionary = dataDictionary; - if (dataDictionary[@"$updatedAt"] && !dataDictionary[@"$createdAt"]) { - self.documentStateType = DPDocumentStateType_Replace; - } else { - self.documentStateType = DPDocumentStateType_Initial; - } - } - return self; -} - -+ (DPDocumentState *)documentStateWithDataDictionary:(DSStringValueDictionary *)dataDictionary { - return [[DPDocumentState alloc] initWithDataDictionary:dataDictionary]; -} - -+ (DPDocumentState *)documentStateWithDataDictionary:(DSStringValueDictionary *)dataDictionary ofType:(DPDocumentStateType)documentStateType { - DPDocumentState *documentState = [[DPDocumentState alloc] initWithDataDictionary:dataDictionary]; - documentState.documentStateType = documentStateType; - return documentState; -} - - -@end diff --git a/DashSync/shared/Models/Platform/Document/DSDirectionalRange.h b/DashSync/shared/Models/Platform/Document/DSDirectionalRange.h deleted file mode 100644 index e947ef3a0..000000000 --- a/DashSync/shared/Models/Platform/Document/DSDirectionalRange.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 - -NS_ASSUME_NONNULL_BEGIN - -@class DSDirectionalKey; - -@interface DSDirectionalRange : NSObject - -@property (nonatomic, readonly) DSDirectionalKey *key; //also includes if values should be returned ascending or descending -@property (nonatomic, readonly, nullable) NSData *lowerBoundsValue; -@property (nonatomic, readonly, nullable) NSData *upperBoundsValue; -@property (nonatomic, readonly) bool lowerBoundsIncluded; -@property (nonatomic, readonly) bool upperBoundsIncluded; - -- (instancetype)initForKey:(NSData *)data withLowerBounds:(NSData *)lowerBoundsValue upperBounds:(NSData *)upperBoundsValue ascending:(bool)orderAscending includeLowerBounds:(bool)includeLowerBounds includeUpperBounds:(bool)includeUpperBounds; -- (instancetype)initForKey:(NSData *)data withLowerBounds:(NSData *)lowerBoundsValue ascending:(bool)orderAscending includeLowerBounds:(bool)includeLowerBounds; -- (instancetype)initForKey:(NSData *)data withUpperBounds:(NSData *)upperBoundsValue ascending:(bool)orderAscending includeUpperBounds:(bool)includeUpperBounds; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Document/DSDirectionalRange.m b/DashSync/shared/Models/Platform/Document/DSDirectionalRange.m deleted file mode 100644 index b7394cfd8..000000000 --- a/DashSync/shared/Models/Platform/Document/DSDirectionalRange.m +++ /dev/null @@ -1,68 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2021 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 "DSDirectionalRange.h" -#import "DSDirectionalKey.h" - -@interface DSDirectionalRange () - -@property (nonatomic, strong) DSDirectionalKey *key; -@property (nonatomic, strong) NSData *lowerBoundsValue; -@property (nonatomic, strong) NSData *upperBoundsValue; -@property (nonatomic, assign) bool lowerBoundsIncluded; -@property (nonatomic, assign) bool upperBoundsIncluded; - -@end - -@implementation DSDirectionalRange - -- (instancetype)initForKey:(NSData *)key withLowerBounds:(NSData *)lowerBoundsValue upperBounds:(NSData *)upperBoundsValue ascending:(bool)orderAscending includeLowerBounds:(bool)includeLowerBounds includeUpperBounds:(bool)includeUpperBounds { - self = [super init]; - if (self) { - self.key = [[DSDirectionalKey alloc] initWithKey:key ascending:orderAscending]; - self.lowerBoundsValue = lowerBoundsValue; - self.upperBoundsValue = upperBoundsValue; - self.lowerBoundsIncluded = includeLowerBounds; - self.upperBoundsIncluded = includeUpperBounds; - } - return self; -} - -- (instancetype)initForKey:(NSData *)key withLowerBounds:(NSData *)lowerBoundsValue ascending:(bool)orderAscending includeLowerBounds:(bool)includeLowerBounds { - self = [super init]; - if (self) { - self.key = [[DSDirectionalKey alloc] initWithKey:key ascending:orderAscending]; - self.lowerBoundsValue = lowerBoundsValue; - self.upperBoundsValue = nil; - self.lowerBoundsIncluded = includeLowerBounds; - self.upperBoundsIncluded = NO; - } - return self; -} -- (instancetype)initForKey:(NSData *)key withUpperBounds:(NSData *)upperBoundsValue ascending:(bool)orderAscending includeUpperBounds:(bool)includeUpperBounds { - self = [super init]; - if (self) { - self.key = [[DSDirectionalKey alloc] initWithKey:key ascending:orderAscending]; - self.lowerBoundsValue = nil; - self.upperBoundsValue = upperBoundsValue; - self.lowerBoundsIncluded = NO; - self.upperBoundsIncluded = includeUpperBounds; - } - return self; -} - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityCloseTransition.h b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityCloseTransition.h deleted file mode 100644 index 848a7dc4b..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityCloseTransition.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// DSBlockchainIdentityCloseTransition.h -// DashSync -// -// Created by Sam Westrich on 8/13/18. -// - -#import "DSTransition.h" - -@interface DSBlockchainIdentityCloseTransition : DSTransition - -//@property (nonatomic,assign) uint16_t blockchainIdentityCloseTransactionVersion; -//@property (nonatomic,assign) UInt256 registrationTransactionHash; -//@property (nonatomic,assign) UInt256 previousBlockchainIdentityTransactionHash; -//@property (nonatomic,assign) uint64_t creditFee; -//@property (nonatomic,strong) NSData * payloadSignature; -// -//- (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts inputSequences:(NSArray*)inputSequences outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts blockchainIdentityCloseTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain; -// -//-(instancetype)initWithBlockchainIdentityCloseTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain; - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityCloseTransition.m b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityCloseTransition.m deleted file mode 100644 index 63612fedb..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityCloseTransition.m +++ /dev/null @@ -1,114 +0,0 @@ -// -// DSBlockchainIdentityCloseTransition.m -// DashSync -// -// Created by Sam Westrich on 8/13/18. -// - -#import "DSBlockchainIdentityCloseTransition.h" -#import "DSTransactionFactory.h" -#import "NSData+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" - -@implementation DSBlockchainIdentityCloseTransition - -//- (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain -//{ -// if (! (self = [super initWithMessage:message onChain:chain])) return nil; -// self.type = DSTransactionType_SubscriptionCloseAccount; -// NSUInteger length = message.length; -// uint32_t off = self.payloadOffset; -// -// if (length - off < 1) return nil; -// NSNumber * payloadLengthSize = nil; -// uint64_t payloadLength = [message varIntAtOffset:off length:&payloadLengthSize]; -// off += payloadLengthSize.unsignedLongValue; -// -// if (length - off < 2) return nil; -// self.blockchainIdentityCloseTransactionVersion = [message UInt16AtOffset:off]; -// off += 2; -// -// if (length - off < 32) return nil; -// self.registrationTransactionHash = [message UInt256AtOffset:off]; -// off += 32; -// -// if (length - off < 32) return nil; -// self.previousBlockchainIdentityTransactionHash = [message UInt256AtOffset:off]; -// off += 32; -// -// if (length - off < 8) return nil; -// self.creditFee = [message UInt64AtOffset:off]; -// off += 8; -// -// if (length - off < 1) return nil; -// NSNumber * payloadSignatureLength = nil; -// self.payloadSignature = [message dataAtOffset:off length:&payloadSignatureLength]; -// off += payloadSignatureLength.unsignedLongValue; -// -// -// self.payloadOffset = off; -// if ([self payloadData].length != payloadLength) return nil; -// self.txHash = self.data.SHA256_2; -// -// return self; -//} -// -//- (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts inputSequences:(NSArray*)inputSequences outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts blockchainIdentityCloseTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain { -// if (!(self = [super initWithInputHashes:hashes inputIndexes:indexes inputScripts:scripts inputSequences:inputSequences outputAddresses:addresses outputAmounts:amounts onChain:chain])) return nil; -// self.type = DSTransactionType_SubscriptionCloseAccount; -// self.version = SPECIAL_TX_VERSION; -// self.blockchainIdentityCloseTransactionVersion = version; -// self.registrationTransactionHash = registrationTransactionHash; -// self.previousBlockchainIdentityTransactionHash = previousBlockchainIdentityTransactionHash; -// self.creditFee = creditFee; -// return self; -//} -// -//-(instancetype)initWithBlockchainIdentityCloseTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain { -// if (!(self = [super initOnChain:chain])) return nil; -// self.type = DSTransactionType_SubscriptionCloseAccount; -// self.version = SPECIAL_TX_VERSION; -// self.blockchainIdentityCloseTransactionVersion = version; -// self.registrationTransactionHash = registrationTransactionHash; -// self.previousBlockchainIdentityTransactionHash = previousBlockchainIdentityTransactionHash; -// self.creditFee = creditFee; -// return self; -//} -// -//-(NSData*)payloadData { -// NSMutableData * data = [NSMutableData data]; -// [data appendUInt16:self.blockchainIdentityCloseTransactionVersion]; -// [data appendUInt256:self.registrationTransactionHash]; -// [data appendUInt256:self.previousBlockchainIdentityTransactionHash]; -// [data appendUInt64:self.creditFee]; -// [data appendVarInt:self.payloadSignature.length]; -// [data appendData:self.payloadSignature]; -// return data; -//} -// -//- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex -//{ -// NSMutableData * data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; -// NSData * payloadData = [self payloadData]; -// [data appendVarInt:payloadData.length]; -// [data appendData:payloadData]; -// if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; -// return data; -//} -// -//- (size_t)size -//{ -// return [super size] + [self payloadData].length; -//} -// -//-(Class)entityClass { -// return [DSBlockchainIdentityCloseTransitionEntity class]; -//} -// -//-(BOOL)transactionTypeRequiresInputs { -// return NO; -//} - - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityRegistrationTransition.h b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityRegistrationTransition.h deleted file mode 100644 index c59e1b2f3..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityRegistrationTransition.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// DSBlockchainIdentityRegistrationTransition.h -// DashSync -// -// Created by Sam Westrich on 7/12/18. -// - -#import "BigIntTypes.h" -#import "DSBlockchainIdentity.h" -#import "DSTransition.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSBlockchainIdentityRegistrationTransition : DSTransition - -@property (nonatomic, readonly) NSDictionary *publicKeys; -@property (nonatomic, readonly) DSUTXO lockedOutpoint; - -- (instancetype)initWithVersion:(uint16_t)version registeringPublicKeys:(NSDictionary *)publicKeys usingCreditFundingTransaction:(DSCreditFundingTransaction *)creditFundingTransaction onChain:(DSChain *)chain; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityRegistrationTransition.m b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityRegistrationTransition.m deleted file mode 100644 index fd6a9ea15..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityRegistrationTransition.m +++ /dev/null @@ -1,119 +0,0 @@ -// -// DSBlockchainIdentityRegistrationTransition.m -// DashSync -// -// Created by Sam Westrich on 7/12/18. -// - -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "BigIntTypes.h" -#import "DSCreditFundingTransaction.h" -#import "DSInstantSendTransactionLock.h" -#import "DSKeyManager.h" -#import "DSTransaction+Protected.h" -#import "DSTransactionFactory.h" -#import "DSTransition+Protected.h" -#import "NSData+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" - -@interface DSBlockchainIdentityRegistrationTransition () - -@property (nonatomic, strong) NSDictionary *publicKeys; -@property (nonatomic, strong) DSCreditFundingTransaction *creditFundingTransaction; - -@end - -@implementation DSBlockchainIdentityRegistrationTransition - -- (instancetype)initOnChain:(DSChain *)chain { - if (!(self = [super initOnChain:chain])) return nil; - self.type = DSTransitionType_IdentityRegistration; - return self; -} - -- (instancetype)initWithVersion:(uint16_t)version registeringPublicKeys:(NSDictionary *)publicKeys usingCreditFundingTransaction:(DSCreditFundingTransaction *)creditFundingTransaction onChain:(DSChain *)chain { - NSParameterAssert(chain); - NSParameterAssert(publicKeys); - NSAssert(publicKeys.count, @"There must be at least one key when registering a user"); - - if (!(self = [self initOnChain:chain])) return nil; - self.version = version; - self.creditFundingTransaction = creditFundingTransaction; - self.blockchainIdentityUniqueId = [dsutxo_data(creditFundingTransaction.lockedOutpoint) SHA256_2]; - self.publicKeys = publicKeys; - return self; -} - -- (NSMutableArray *)platformKeyDictionaries { - NSMutableArray *platformKeys = [NSMutableArray array]; - for (NSNumber *indexIdentifier in self.publicKeys) { - OpaqueKey *key = self.publicKeys[indexIdentifier].pointerValue; - DSMutableStringValueDictionary *platformKeyDictionary = [[DSMutableStringValueDictionary alloc] init]; - platformKeyDictionary[@"id"] = @([indexIdentifier unsignedIntValue]); - platformKeyDictionary[@"purpose"] = @(DWIdentityPublicKeyPurposeAuthentication); - platformKeyDictionary[@"securityLevel"] = @(DWIdentityPublicKeySecurityLevelMaster); - platformKeyDictionary[@"readOnly"] = @NO; - platformKeyDictionary[@"type"] = @(key->tag); - platformKeyDictionary[@"data"] = [DSKeyManager publicKeyData:key]; - [platformKeys addObject:platformKeyDictionary]; - } - return platformKeys; -} - -- (DSMutableStringValueDictionary *)assetLockProofDictionary { - DSMutableStringValueDictionary *assetLockDictionary = [DSMutableStringValueDictionary dictionary]; - if (self.creditFundingTransaction.instantSendLockAwaitingProcessing) { - assetLockDictionary[@"type"] = @(0); - assetLockDictionary[@"instantLock"] = self.creditFundingTransaction.instantSendLockAwaitingProcessing.toData; - assetLockDictionary[@"outputIndex"] = @(self.creditFundingTransaction.lockedOutpoint.n); - assetLockDictionary[@"transaction"] = [self.creditFundingTransaction toData]; - } else { - assetLockDictionary[@"type"] = @(1); - assetLockDictionary[@"coreChainLockedHeight"] = @(self.creditFundingTransaction.blockHeight); - assetLockDictionary[@"outPoint"] = dsutxo_data(self.creditFundingTransaction.lockedOutpoint); - } - - return assetLockDictionary; -} - -- (DSMutableStringValueDictionary *)baseKeyValueDictionary { - DSMutableStringValueDictionary *json = [super baseKeyValueDictionary]; - json[@"assetLockProof"] = [self assetLockProofDictionary]; - json[@"publicKeys"] = [self platformKeyDictionaries]; - return json; -} - -- (void)applyKeyValueDictionary:(DSMutableStringValueDictionary *)keyValueDictionary { - [super applyKeyValueDictionary:keyValueDictionary]; - NSDictionary *assetLockDictionary = keyValueDictionary[@"assetLock"]; - self.creditFundingTransaction = [DSCreditFundingTransaction transactionWithMessage:assetLockDictionary[@"transaction"] onChain:self.chain]; - NSDictionary *proofDictionary = keyValueDictionary[@"proof"]; - NSNumber *proofType = proofDictionary[@"type"]; - if ([proofType integerValue] == 0) { - //this is an instant send proof - NSData *instantSendLockData = proofDictionary[@"instantLock"]; - //todo: v18 make this deterministic - self.creditFundingTransaction.instantSendLockAwaitingProcessing = [DSInstantSendTransactionLock instantSendTransactionLockWithNonDeterministicMessage:instantSendLockData onChain:self.chain]; - } - - self.blockchainIdentityUniqueId = [dsutxo_data(self.lockedOutpoint) SHA256_2]; - NSArray *publicKeysDictionariesArray = keyValueDictionary[@"publicKeys"]; - NSMutableDictionary *platformKeys = [NSMutableDictionary dictionary]; - for (DSMutableStringValueDictionary *platformKeyDictionary in publicKeysDictionariesArray) { - KeyKind keyType = [platformKeyDictionary[@"type"] unsignedIntValue]; - NSUInteger identifier = [platformKeyDictionary[@"id"] unsignedIntValue]; - NSData *keyData = platformKeyDictionary[@"data"]; - OpaqueKey *key = [DSKeyManager keyWithPublicKeyData:keyData ofType:keyType]; - platformKeys[@(identifier)] = [NSValue valueWithPointer:key]; - } - self.publicKeys = [platformKeys copy]; -} - -//- (NSString *)description -//{ -// NSString *txid = [NSString hexWithData:[NSData dataWithBytes:self.txHash.u8 length:sizeof(UInt256)].reverse]; -// return [NSString stringWithFormat:@"%@<%p>(id=%@,username=%@,confirmedInBlock=%d)", [self class],self, txid,self.username,self.blockHeight]; -//} - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityTopupTransition.h b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityTopupTransition.h deleted file mode 100644 index 0483fd895..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityTopupTransition.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DSBlockchainIdentityTopupTransition.h -// DashSync -// -// Created by Sam Westrich on 7/30/18. -// - -#import "BigIntTypes.h" -#import "DSTransition.h" - -@class DSChain; - -@interface DSBlockchainIdentityTopupTransition : DSTransition - -@property (nonatomic, assign) uint16_t blockchainIdentityTopupTransactionVersion; -@property (nonatomic, readonly) uint64_t topupAmount; - - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityTopupTransition.m b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityTopupTransition.m deleted file mode 100644 index cd647ca71..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityTopupTransition.m +++ /dev/null @@ -1,21 +0,0 @@ -// -// DSBlockchainIdentityTopupTransition.m -// DashSync -// -// Created by Sam Westrich on 7/30/18. -// - -#import "DSBlockchainIdentityTopupTransition.h" -#import "DSTransactionFactory.h" -#import "NSData+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" - -@interface DSBlockchainIdentityTopupTransition () - -@end - -@implementation DSBlockchainIdentityTopupTransition - - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityUpdateTransition.h b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityUpdateTransition.h deleted file mode 100644 index 665501d7c..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityUpdateTransition.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// DSBlockchainIdentityResetUserKeyTransaction.h -// DashSync -// -// Created by Sam Westrich on 8/13/18. -// - -#import "BigIntTypes.h" -#import "DSTransition.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSBlockchainIdentityUpdateTransition : DSTransition - -//@property (nonatomic,assign) uint16_t blockchainIdentityResetTransactionVersion; -//@property (nonatomic,assign) UInt256 registrationTransactionHash; -//@property (nonatomic,assign) UInt256 previousBlockchainIdentityTransactionHash; -//@property (nonatomic,assign) uint64_t creditFee; -//@property (nonatomic,assign) UInt160 replacementPublicKeyHash; //we will get rid of this and do next line later -//@property (nullable, nonatomic,readonly) NSString * replacementAddress; // TODO: replacementAddress is not initialized -////@property (nonatomic,strong) NSData * replacementPublicKey; -//@property (nonatomic,strong) NSData * oldPublicKeyPayloadSignature; -// -//- (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts inputSequences:(NSArray*)inputSequences outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts blockchainIdentityResetTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash replacementPublicKeyHash:(UInt160)replacementPublicKeyHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain; -// -////this is what we will eventually go to (right below) -// -////- (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts inputSequences:(NSArray*)inputSequences outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts blockchainIdentityResetTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash replacementPublicKey:(NSData*)replacementPublicKey creditFee:(uint64_t)creditFee onChain:(DSChain *)chain; -// -//-(instancetype)initWithBlockchainIdentityResetTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash replacementPublicKeyHash:(UInt160)replacementPublicKeyHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain; -// -//-(void)signPayloadWithKey:(DSECDSAKey*)privateKey; -// -//-(BOOL)checkTransitionSignatureIsSignedByPublicKeyWithHash:(UInt160)oldPublicKeyHash; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityUpdateTransition.m b/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityUpdateTransition.m deleted file mode 100644 index 4701614c4..000000000 --- a/DashSync/shared/Models/Platform/Transitions/BlockchainIdentity/DSBlockchainIdentityUpdateTransition.m +++ /dev/null @@ -1,187 +0,0 @@ -// -// DSBlockchainIdentityResetTransition.m -// DashSync -// -// Created by Sam Westrich on 8/13/18. -// - -#import "DSBlockchainIdentityUpdateTransition.h" -#import "DSTransactionFactory.h" -#import "NSData+Dash.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" - -@implementation DSBlockchainIdentityUpdateTransition - -//- (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain -//{ -// NSParameterAssert(message); -// NSParameterAssert(chain); -// -// if (! (self = [super initWithMessage:message onChain:chain])) return nil; -// self.type = DSTransactionType_SubscriptionResetKey; -// NSUInteger length = message.length; -// uint32_t off = self.payloadOffset; -// -// if (length - off < 1) return nil; -// NSNumber * payloadLengthSize = nil; -// uint64_t payloadLength = [message varIntAtOffset:off length:&payloadLengthSize]; -// off += payloadLengthSize.unsignedLongValue; -// -// if (length - off < 2) return nil; -// self.blockchainIdentityResetTransactionVersion = [message UInt16AtOffset:off]; -// off += 2; -// -// if (length - off < 32) return nil; -// self.registrationTransactionHash = [message UInt256AtOffset:off]; -// off += 32; -// -// if (length - off < 32) return nil; -// self.previousBlockchainIdentityTransactionHash = [message UInt256AtOffset:off]; -// off += 32; -// -// if (length - off < 8) return nil; -// self.creditFee = [message UInt64AtOffset:off]; -// off += 8; -// -// if (length - off < 20) return nil; -// self.replacementPublicKeyHash = [message UInt160AtOffset:off]; -// off += 20; -// -//// if (length - off < 1) return nil; -//// NSNumber * replacementPubKeyLength = nil; -//// self.replacementPublicKey = [message dataAtOffset:off length:&replacementPubKeyLength]; -//// off += replacementPubKeyLength.unsignedLongValue; -// -// if (length - off < 1) return nil; -// NSNumber * oldPublicKeyPayloadSignatureLength = nil; -// self.oldPublicKeyPayloadSignature = [message dataAtOffset:off length:&oldPublicKeyPayloadSignatureLength]; -// off += oldPublicKeyPayloadSignatureLength.unsignedLongValue; -// -// -// self.payloadOffset = off; -// if ([self payloadData].length != payloadLength) return nil; -// self.txHash = self.data.SHA256_2; -// -// return self; -//} -// -//- (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts inputSequences:(NSArray*)inputSequences outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts blockchainIdentityResetTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash replacementPublicKeyHash:(UInt160)replacementPublicKeyHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain { -// NSParameterAssert(indexes); -// NSParameterAssert(scripts); -// NSParameterAssert(inputSequences); -// NSParameterAssert(addresses); -// NSParameterAssert(amounts); -// NSParameterAssert(chain); -// -// if (!(self = [super initWithInputHashes:hashes inputIndexes:indexes inputScripts:scripts inputSequences:inputSequences outputAddresses:addresses outputAmounts:amounts onChain:chain])) return nil; -// self.type = DSTransactionType_SubscriptionResetKey; -// self.version = SPECIAL_TX_VERSION; -// self.blockchainIdentityResetTransactionVersion = version; -// self.registrationTransactionHash = registrationTransactionHash; -// self.previousBlockchainIdentityTransactionHash = previousBlockchainIdentityTransactionHash; -// self.creditFee = creditFee; -// self.replacementPublicKeyHash = replacementPublicKeyHash; -// return self; -//} -// -//-(instancetype)initWithBlockchainIdentityResetTransactionVersion:(uint16_t)version registrationTransactionHash:(UInt256)registrationTransactionHash previousBlockchainIdentityTransactionHash:(UInt256)previousBlockchainIdentityTransactionHash replacementPublicKeyHash:(UInt160)replacementPublicKeyHash creditFee:(uint64_t)creditFee onChain:(DSChain *)chain { -// NSParameterAssert(chain); -// -// if (!(self = [super initOnChain:chain])) return nil; -// self.type = DSTransactionType_SubscriptionResetKey; -// self.version = SPECIAL_TX_VERSION; -// self.blockchainIdentityResetTransactionVersion = version; -// self.registrationTransactionHash = registrationTransactionHash; -// self.previousBlockchainIdentityTransactionHash = previousBlockchainIdentityTransactionHash; -// self.creditFee = creditFee; -// self.replacementPublicKeyHash = replacementPublicKeyHash; -// return self; -//} -// -////-(NSData*)payloadData { -//// NSMutableData * data = [NSMutableData data]; -//// [data appendUInt16:self.blockchainIdentityResetTransactionVersion]; -//// [data appendUInt256:self.registrationTransitionHash]; -//// [data appendUInt256:self.previousBlockchainIdentityTransactionHash]; -//// [data appendUInt64:self.creditFee]; -//// [data appendVarInt:self.replacementPublicKey.length]; -//// [data appendData:self.replacementPublicKey]; -//// [data appendVarInt:self.oldPublicKeyPayloadSignature.length]; -//// [data appendData:self.oldPublicKeyPayloadSignature]; -//// return data; -////} -//// -////-(NSData*)payloadDataForHash { -//// NSMutableData * data = [NSMutableData data]; -//// [data appendUInt16:self.blockchainIdentityResetTransactionVersion]; -//// [data appendUInt256:self.registrationTransitionHash]; -//// [data appendUInt256:self.previousBlockchainIdentityTransactionHash]; -//// [data appendUInt64:self.creditFee]; -//// [data appendVarInt:self.replacementPublicKey.length]; -//// [data appendData:self.replacementPublicKey]; -//// [data appendUInt8:0]; -//// return data; -////} -// -//-(NSData*)payloadData { -// NSMutableData * data = [NSMutableData data]; -// [data appendUInt16:self.blockchainIdentityResetTransactionVersion]; -// [data appendUInt256:self.registrationTransactionHash]; -// [data appendUInt256:self.previousBlockchainIdentityTransactionHash]; -// [data appendUInt64:self.creditFee]; -// [data appendUInt160:self.replacementPublicKeyHash]; -// [data appendVarInt:self.oldPublicKeyPayloadSignature.length]; -// [data appendData:self.oldPublicKeyPayloadSignature]; -// return data; -//} -// -//-(NSData*)payloadDataForHash { -// NSMutableData * data = [NSMutableData data]; -// [data appendUInt16:self.blockchainIdentityResetTransactionVersion]; -// [data appendUInt256:self.registrationTransactionHash]; -// [data appendUInt256:self.previousBlockchainIdentityTransactionHash]; -// [data appendUInt64:self.creditFee]; -// [data appendUInt160:self.replacementPublicKeyHash]; -// [data appendUInt8:0]; -// return data; -//} -// -//-(UInt256)payloadHash { -// return [self payloadDataForHash].SHA256_2; -//} -// -//-(BOOL)checkTransitionSignatureIsSignedByPublicKeyWithHash:(UInt160)oldPublicKeyHash { -// DSECDSAKey * blockchainIdentityPublicKey = [DSECDSAKey keyRecoveredFromCompactSig:self.oldPublicKeyPayloadSignature andMessageDigest:[self payloadHash]]; -// return uint160_eq([blockchainIdentityPublicKey hash160], oldPublicKeyHash); -//} -// -//-(void)signPayloadWithKey:(DSECDSAKey*)privateKey { -// NSParameterAssert(privateKey); -// -// DSLogPrivate(@"Private Key is %@",[privateKey privateKeyStringForChain:self.chain]); -// self.oldPublicKeyPayloadSignature = [privateKey compactSign:[self payloadHash]]; -// self.txHash = self.data.SHA256_2; //once the payload is signed the transaction hash is ready to go. -//} -// -// -//- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex -//{ -// NSMutableData * data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; -// NSData * payloadData = [self payloadData]; -// [data appendVarInt:payloadData.length]; -// [data appendData:payloadData]; -// if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; -// return data; -//} -// -//-(NSString*)pubkeyAddress { -// return [[NSData dataWithUInt160:self.replacementPublicKeyHash] addressFromHash160DataForChain:self.chain]; -//} -// -//- (size_t)size -//{ -// return [super size] + [self payloadData].length; -//} - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/DSContractTransition.h b/DashSync/shared/Models/Platform/Transitions/DSContractTransition.h deleted file mode 100644 index ebe8d19bf..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSContractTransition.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSTransition.h" - -NS_ASSUME_NONNULL_BEGIN - -@class DPContract; - -@interface DSContractTransition : DSTransition - -@property (nonatomic, readonly) DPContract *contract; - -- (instancetype)initWithContract:(DPContract *)contract withTransitionVersion:(uint16_t)version blockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId onChain:(DSChain *)chain; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Transitions/DSContractTransition.m b/DashSync/shared/Models/Platform/Transitions/DSContractTransition.m deleted file mode 100644 index e7265e0b8..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSContractTransition.m +++ /dev/null @@ -1,48 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2020 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 "DSContractTransition.h" -#import "DPContract+Protected.h" -#import "DPDocument.h" -#import "DSTransition+Protected.h" -#import "NSString+Dash.h" - -@interface DSContractTransition () - -@property (nonatomic, strong) DPContract *contract; - -@end - -@implementation DSContractTransition - -- (DSMutableStringValueDictionary *)baseKeyValueDictionary { - DSMutableStringValueDictionary *json = [super baseKeyValueDictionary]; - json[@"dataContract"] = self.contract.objectDictionary; - json[@"entropy"] = uint256_data(self.contract.entropy); - return json; -} - -- (instancetype)initWithContract:(DPContract *)contract withTransitionVersion:(uint16_t)version blockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId onChain:(DSChain *)chain { - if (self = [super initWithTransitionVersion:version blockchainIdentityUniqueId:blockchainIdentityUniqueId onChain:chain]) { - self.contract = contract; - } - self.type = DSTransitionType_DataContract; - - return self; -} - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/DSDocumentTransition.h b/DashSync/shared/Models/Platform/Transitions/DSDocumentTransition.h deleted file mode 100644 index c772c2083..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSDocumentTransition.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2019 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 "DSTransition.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, DSDocumentTransitionType) -{ - DSDocumentTransitionType_Create = 1, - DSDocumentTransitionType_Update = 2, - DSDocumentTransitionType_Delete = 3, -}; - -@class DSPlatformQuery; - -@interface DSDocumentTransition : DSTransition - -@property (nonatomic, readonly) NSArray *documents; -@property (nonatomic, readonly) DSPlatformQuery *expectedResponseQuery; - -- (instancetype)initForDocuments:(NSArray *)documents withTransitionVersion:(uint16_t)version blockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId onChain:(DSChain *)chain; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Transitions/DSDocumentTransition.m b/DashSync/shared/Models/Platform/Transitions/DSDocumentTransition.m deleted file mode 100644 index 242e3a334..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSDocumentTransition.m +++ /dev/null @@ -1,63 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2019 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 "DSDocumentTransition.h" -#import "DPDocument.h" -#import "DPDocumentState.h" -#import "DSPlatformQuery.h" -#import "DSPlatformTreeQuery.h" -#import "DSTransition+Protected.h" - -@interface DSDocumentTransition () - -@property (nonatomic, strong) NSArray *documents; -@property (nonatomic, strong) NSArray *actions; - -@end - -@implementation DSDocumentTransition - -- (NSArray *)documentsAsArrayOfDictionaries { - NSMutableArray *mArray = [NSMutableArray array]; - for (DPDocument *document in self.documents) { - [mArray addObject:document.objectDictionary]; - } - return mArray; -} - -- (DSMutableStringValueDictionary *)baseKeyValueDictionary { - DSMutableStringValueDictionary *json = [super baseKeyValueDictionary]; - json[@"transitions"] = [self documentsAsArrayOfDictionaries]; - json[@"ownerId"] = uint256_data(self.blockchainIdentityUniqueId); - return json; -} - -- (instancetype)initForDocuments:(NSArray *)documents withTransitionVersion:(uint16_t)version blockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId onChain:(DSChain *)chain { - if (!(self = [super initWithTransitionVersion:version blockchainIdentityUniqueId:blockchainIdentityUniqueId onChain:chain])) return nil; - - self.documents = documents; - self.type = DSTransitionType_Documents; - self.blockchainIdentityUniqueId = blockchainIdentityUniqueId; - - return self; -} - -- (DSPlatformQuery *)expectedResponseQuery { - return [DSPlatformQuery platformQueryForDocuments:self.documents]; -} - -@end diff --git a/DashSync/shared/Models/Platform/Transitions/DSTransition+Protected.h b/DashSync/shared/Models/Platform/Transitions/DSTransition+Protected.h deleted file mode 100644 index d24b64c44..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSTransition+Protected.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2019 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 "DSTransition.h" - -@class DSTransitionEntity; - -NS_ASSUME_NONNULL_BEGIN - -@interface DSTransition () - -@property (nonatomic, assign) BOOL saved; //don't trust this - -@property (nonatomic, assign) uint16_t version; -@property (nonatomic, assign) DSTransitionType type; -@property (nonatomic, assign) uint64_t creditFee; -@property (nonatomic, assign) UInt256 transitionHash; - -@property (nonatomic, assign) UInt256 blockchainIdentityUniqueId; - -@property (nonatomic, assign) NSTimeInterval createdTimestamp; -@property (nonatomic, assign) NSTimeInterval registeredTimestamp; - -@property (nonatomic, copy) NSData *signatureData; -@property (nonatomic, assign) KeyKind signatureType; -@property (nonatomic, assign) uint32_t signaturePublicKeyId; - -@property (nonatomic, readonly) DSMutableStringValueDictionary *keyValueDictionary; - -- (instancetype)initOnChain:(DSChain *)chain; -- (void)applyKeyValueDictionary:(DSMutableStringValueDictionary *)keyValueDictionary; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Transitions/DSTransition.h b/DashSync/shared/Models/Platform/Transitions/DSTransition.h deleted file mode 100644 index a99c0ce29..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSTransition.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// DSTransition.h -// DashSync -// -// Created by Sam Westrich on 7/12/18. -// - -#import "BigIntTypes.h" -#import "dash_shared_core.h" -#import "DPBaseObject.h" -#import "DSBlockchainIdentity.h" - -NS_ASSUME_NONNULL_BEGIN - -//Special Transaction -//https://github.com/dashpay/dips/blob/master/dip-0002-special-transactions.md -typedef NS_ENUM(NSUInteger, DSTransitionType) -{ - DSTransitionType_DataContract = 0, - DSTransitionType_Documents = 1, - DSTransitionType_IdentityRegistration = 2, - DSTransitionType_IdentityTopUp = 3, - DSTransitionType_IdentityUpdateKey = 4, - DSTransitionType_IdentityCloseAccount = 5, -}; - - -#define TS_VERSION 0x00000001u - -@class DSBlockchainIdentity; - -@interface DSTransition : DPBaseObject - -@property (nonatomic, readonly) uint16_t version; -@property (nonatomic, readonly) DSTransitionType type; -@property (nonatomic, readonly) UInt256 blockchainIdentityUniqueId; -@property (nonatomic, readonly) uint64_t creditFee; -@property (nonatomic, readonly) UInt256 transitionHash; - -@property (nonatomic, readonly, getter=toData) NSData *data; - -@property (nonatomic, readonly) NSTimeInterval createdTimestamp; -@property (nonatomic, readonly) NSTimeInterval registeredTimestamp; - -@property (nonatomic, readonly) KeyKind signatureType; -@property (nonatomic, readonly) NSData *signatureData; -@property (nonatomic, readonly) uint32_t signaturePublicKeyId; - -- (instancetype)initWithTransitionVersion:(uint16_t)version blockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId onChain:(DSChain *_Nonnull)chain; //local creation - -- (instancetype)initWithData:(NSData *)data onChain:(DSChain *)chain; - -- (void)signWithKey:(OpaqueKey *)privateKey atIndex:(uint32_t)index fromIdentity:(DSBlockchainIdentity *_Nullable)blockchainIdentity; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Platform/Transitions/DSTransition.m b/DashSync/shared/Models/Platform/Transitions/DSTransition.m deleted file mode 100644 index ade916806..000000000 --- a/DashSync/shared/Models/Platform/Transitions/DSTransition.m +++ /dev/null @@ -1,124 +0,0 @@ -// -// DSTransition.m -// DashSync -// -// Created by Sam Westrich on 7/12/18. -// - -#import "DSTransition.h" -#import "BigIntTypes.h" -#import "DSBlockchainIdentity.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSChainEntity+CoreDataClass.h" -#import "DSKeyManager.h" -#import "DSTransition+Protected.h" -#import "NSData+Dash.h" -#import "NSDate+Utils.h" -#import "NSManagedObject+Sugar.h" -#import "NSMutableData+Dash.h" -#import "NSString+Bitcoin.h" -#import - -@interface DSTransition () - -@property (nonatomic, strong) DSBlockchainIdentityRegistrationTransition *blockchainIdentityRegistrationTransaction; - -@end - -@implementation DSTransition - -@synthesize chain = _chain; - -- (instancetype)initOnChain:(DSChain *)chain { - if (!(self = [super init])) return nil; - - _version = TS_VERSION; - _chain = chain; - self.saved = FALSE; - self.createdTimestamp = [NSDate timeIntervalSince1970]; - return self; -} - -- (instancetype)initWithData:(NSData *)data onChain:(DSChain *)chain { - if (!(self = [self initOnChain:chain])) return nil; - NSError *error = nil; - _keyValueDictionary = [data ds_decodeCborError:&error]; - if (error || !_keyValueDictionary) { - return nil; - } - [self applyKeyValueDictionary:_keyValueDictionary]; - return self; -} - -- (instancetype)initWithTransitionVersion:(uint16_t)version blockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId onChain:(DSChain *_Nonnull)chain { - NSParameterAssert(chain); - - if (!(self = [self initOnChain:chain])) return nil; - self.type = DSTransitionType_Documents; - self.version = version; - self.blockchainIdentityUniqueId = blockchainIdentityUniqueId; - return self; -} - -- (BOOL)checkTransitionSignature:(OpaqueKey *)key { - return [DSKeyManager verifyMessageDigest:key digest:[self serializedBaseDataHash].UInt256 signature:self.signatureData]; -} - -- (BOOL)checkTransitionSignedByBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - return [blockchainIdentity verifySignature:self.signatureData ofType:KeyKind_ECDSA forMessageDigest:[self serializedBaseDataHash].UInt256]; -} - -- (void)signWithKey:(OpaqueKey *)privateKey atIndex:(uint32_t)index fromIdentity:(DSBlockchainIdentity *)blockchainIdentity { - NSParameterAssert(privateKey); - if ([self isKindOfClass:[DSBlockchainIdentityRegistrationTransition class]]) { - NSAssert(index == UINT32_MAX, @"index must not exist"); - } else { - NSAssert(index != UINT32_MAX, @"index must exist"); - } - //ATTENTION If this ever changes from ECDSA, change the max signature size defined above -// DSLogPrivate(@"Private Key is %@", [privateKey serializedPrivateKeyForChain:self.chain]); -// DSLogPrivate(@"Signing %@ with key %@", [self serializedBaseDataHash].hexString, privateKey.publicKeyData.hexString); - self.signatureType = (KeyKind) privateKey->tag; - self.signatureData = [DSKeyManager signMesasageDigest:privateKey digest:[self serializedBaseDataHash].UInt256]; - self.signaturePublicKeyId = index; - self.transitionHash = self.data.SHA256; -} - -// size in bytes if signed, or estimated size assuming compact pubkey sigs -- (size_t)size { - if (uint256_is_not_zero(_transitionHash)) return self.data.length; - return [self serialized].length; //todo figure this out (probably wrong) -} - -- (NSData *)toData { - return [self serialized]; -} - -@synthesize keyValueDictionary = _keyValueDictionary; - -- (DSMutableStringValueDictionary *)baseKeyValueDictionary { - DSMutableStringValueDictionary *json = [[DSMutableStringValueDictionary alloc] init]; - //json[@"protocolVersion"] = @(self.chain.platformProtocolVersion); - json[@"type"] = @(self.type); - return json; -} - -- (DSMutableStringValueDictionary *)keyValueDictionary { - if (_keyValueDictionary == nil) { - DSMutableStringValueDictionary *json = [self baseKeyValueDictionary]; - json[@"signature"] = self.signatureData; - if (self.signaturePublicKeyId != UINT32_MAX) { - json[@"signaturePublicKeyId"] = @(self.signaturePublicKeyId); - } - _keyValueDictionary = json; - } - return _keyValueDictionary; -} - -- (void)applyKeyValueDictionary:(DSMutableStringValueDictionary *)keyValueDictionary { - _keyValueDictionary = keyValueDictionary; - self.signatureData = keyValueDictionary[@"signature"]; - self.signaturePublicKeyId = [keyValueDictionary[@"signaturePublicKeyId"] unsignedIntValue]; -} - -@end diff --git a/DashSync/shared/Models/Spork/DSSpork.m b/DashSync/shared/Models/Spork/DSSpork.m index 29dc17b60..0f30cb32e 100644 --- a/DashSync/shared/Models/Spork/DSSpork.m +++ b/DashSync/shared/Models/Spork/DSSpork.m @@ -8,6 +8,7 @@ #import "DSSpork.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSChainManager.h" #import "DSKeyManager.h" #import "DSPeerManager.h" @@ -78,12 +79,38 @@ - (BOOL)checkSignature70208Method:(NSData *)signature { [stringMessageData appendString:DASH_MESSAGE_MAGIC]; [stringMessageData appendString:stringMessage]; UInt256 messageDigest = stringMessageData.SHA256_2; - - OpaqueKey *messagePublicKey = key_ecdsa_recovered_from_compact_sig(signature.bytes, signature.length, messageDigest.u8); - OpaqueKey *sporkPublicKey = [DSKeyManager keyWithPublicKeyData:[NSData dataFromHexString:[self sporkKey]] ofType:KeyKind_ECDSA]; - BOOL isEqual = [DSKeyManager keysPublicKeyDataIsEqual:sporkPublicKey key2:messagePublicKey]; - processor_destroy_opaque_key(messagePublicKey); - processor_destroy_opaque_key(sporkPublicKey); + Slice_u8 *compact_sig = slice_ctor(signature); + u256 *message_digest = Arr_u8_32_ctor(32, messageDigest.u8); + + DMaybeECDSAKey *msg_public_key = DECDSAKeyWithCompactSig(compact_sig, message_digest); +// slice_dtor(compact_sig); +// u256_dtor(message_digest); + if (!msg_public_key) { + return NO; + } + if (!msg_public_key->ok) { + DMaybeECDSAKeyDtor(msg_public_key); + return NO; + } + NSData *publicKeyData = [NSData dataFromHexString:[self sporkKey]]; + Slice_u8 *public_key_data = slice_ctor(publicKeyData); + DMaybeECDSAKey *spork_public_key = DECDSAKeyWithPublicKeyData(public_key_data); + if (!spork_public_key->ok) { + DMaybeECDSAKeyDtor(msg_public_key); + DMaybeECDSAKeyDtor(spork_public_key); + return NO; + } + Vec_u8 *spork_public_key_data = DECDSAKeyPublicKeyData(spork_public_key->ok); + BOOL isEqual = DECDSAKeyWithPublicKeyDataEqualTo(msg_public_key->ok, spork_public_key_data); + bytes_dtor(spork_public_key_data); + DMaybeECDSAKeyDtor(msg_public_key); + DMaybeECDSAKeyDtor(spork_public_key); + +// DOpaqueKey *messagePublicKey = key_ecdsa_recovered_from_compact_sig(signature.bytes, signature.length, messageDigest.u8); +// DOpaqueKey *sporkPublicKey = [DSKeyManager keyWithPublicKeyData:[NSData dataFromHexString:[self sporkKey]] ofType:KeyKind_ECDSA]; +// BOOL isEqual = [DSKeyManager keysPublicKeyDataIsEqual:sporkPublicKey key2:messagePublicKey]; +// processor_destroy_opaque_key(messagePublicKey); +// processor_destroy_opaque_key(sporkPublicKey); return isEqual; } @@ -91,9 +118,21 @@ - (BOOL)checkSignature:(NSData *)signature { if (self.chain.protocolVersion < 70209) { return [self checkSignature70208Method:signature]; } else { - OpaqueKey *messagePublicKey = key_ecdsa_recovered_from_compact_sig(signature.bytes, signature.length, self.sporkHash.u8); - NSString *sporkAddress = [DSKeyManager addressForKey:messagePublicKey forChainType:self.chain.chainType]; - processor_destroy_opaque_key(messagePublicKey); + Slice_u8 *compact_sig = slice_ctor(signature); + u256 *message_digest = Arr_u8_32_ctor(32, self.sporkHash.u8); + DMaybeECDSAKey *result = DECDSAKeyWithCompactSig(compact_sig, message_digest); + if (!result) return NO; + if (!result->ok) { + DMaybeECDSAKeyDtor(result); + return NO; + } + char *addr = DECDSAKeyPubAddress(result->ok, self.chain.chainType); + NSString *sporkAddress = [DSKeyManager NSStringFrom:addr]; + DMaybeECDSAKeyDtor(result); + +// DOpaqueKey *messagePublicKey = key_ecdsa_recovered_from_compact_sig(signature.bytes, signature.length, self.sporkHash.u8); +// NSString *sporkAddress = [DSKeyManager addressForKey:messagePublicKey forChainType:self.chain.chainType]; +// processor_destroy_opaque_key(messagePublicKey); DSSporkManager *sporkManager = self.chain.chainManager.sporkManager; return [[self sporkAddress] isEqualToString:sporkAddress] || (![sporkManager sporksUpdatedSignatures] && [self checkSignature70208Method:signature]); } @@ -101,10 +140,17 @@ - (BOOL)checkSignature:(NSData *)signature { - (NSString *)sporkKey { if (self.chain.sporkPublicKeyHexString) return self.chain.sporkPublicKeyHexString; + NSString *key = NULL; if (self.chain.sporkPrivateKeyBase58String) { - return [DSKeyManager NSDataFrom:key_ecdsa_public_key_data_for_private_key([self.chain.sporkPrivateKeyBase58String UTF8String], self.chain.chainType)].hexString; + DMaybeKeyData *result = DECDSAKeyPublicKeyDataForPrivateKey(DChar(self.chain.sporkPrivateKeyBase58String), self.chain.chainType); + if (result) { + NSData *data = NSDataFromPtr(result->ok); + if (data) + key = data.hexString; + DMaybeKeyDataDtor(result); + } } - return nil; + return key; } //starting in 12.3 sporks use addresses instead of public keys diff --git a/DashSync/shared/Models/System/DSEnvironment.h b/DashSync/shared/Models/System/DSEnvironment.h index d790d789c..33fdaafc5 100644 --- a/DashSync/shared/Models/System/DSEnvironment.h +++ b/DashSync/shared/Models/System/DSEnvironment.h @@ -12,6 +12,12 @@ #define DSLocalizedString(key, comment) \ [[DSEnvironment sharedInstance].resourceBundle localizedStringForKey:(key) value:@"" table:nil] +#define DSLocalizedFormat(key, comment, ...) \ + [NSString stringWithFormat:DSLocalizedString(key, comment), ##__VA_ARGS__] + +#define DSLocalizedChar(key, comment, ...) \ + (char *) [[NSString stringWithFormat:DSLocalizedString(key, comment), ##__VA_ARGS__] UTF8String] + @interface DSEnvironment : NSObject @property (nonatomic, readonly) BOOL watchOnly; // true if this is a "watch only" wallet with no signing ability diff --git a/DashSync/shared/Models/System/DSEnvironment.m b/DashSync/shared/Models/System/DSEnvironment.m index f4d64dfba..0b50051e1 100644 --- a/DashSync/shared/Models/System/DSEnvironment.m +++ b/DashSync/shared/Models/System/DSEnvironment.m @@ -8,6 +8,7 @@ #import "DSEnvironment.h" #import "DSAccount.h" #import "DSChainsManager.h" +#import "DSChain+Wallet.h" #import "DSWallet.h" @implementation DSEnvironment diff --git a/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.h b/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.h index 241518775..d1e9db213 100644 --- a/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.h +++ b/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.h @@ -25,7 +25,21 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) uint8_t specialTransactionVersion; @property (nonatomic, strong) NSMutableArray *creditOutputs; +@property (nonatomic, readonly) UInt160 creditBurnPublicKeyHash; +@property (nonatomic, readonly) DSUTXO lockedOutpoint; +- (instancetype)initOnChain:(DSChain *)chain withCreditOutputs:(NSArray *)creditOutputs; + +- (BOOL)checkInvitationDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index; +- (BOOL)checkDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index; + +- (void)markInvitationAddressAsUsedInWallet:(DSWallet *)wallet; +- (void)markAddressAsUsedInWallet:(DSWallet *)wallet; + +@end + +@interface DSAssetLockTransaction (FFI) ++ (instancetype)ffi_from:(dashcore_blockdata_transaction_Transaction *)transaction onChain:(DSChain *)chain; @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.m b/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.m index 1ce845e2a..e99ceb3ee 100644 --- a/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.m +++ b/DashSync/shared/Models/Transactions/Base/DSAssetLockTransaction.m @@ -16,12 +16,28 @@ // #import "DSAssetLockTransaction.h" -#import "DSChain.h" +#import "DSChain+Params.h" +#import "DSAssetLockDerivationPath.h" +#import "DSAssetLockTransactionEntity+CoreDataClass.h" +#import "DSDerivationPathFactory.h" +#import "DSGapLimit.h" #import "DSTransactionFactory.h" #import "NSData+Dash.h" +#import "NSMutableData+Dash.h" @implementation DSAssetLockTransaction +- (instancetype)initOnChain:(DSChain *)chain withCreditOutputs:(NSArray *)creditOutputs { + self = [super initOnChain:chain]; + if (self) { + self.type = DSTransactionType_AssetLock; + self.version = SPECIAL_TX_VERSION; + self.creditOutputs = [creditOutputs mutableCopy]; + self.specialTransactionVersion = 1; + } + return self; +} + - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { if (!(self = [super initWithMessage:message onChain:chain])) return nil; @@ -58,4 +74,117 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { return self; } + +- (NSData *)payloadData { + return [self basePayloadData]; +} + +- (NSData *)basePayloadData { + NSMutableData *data = [NSMutableData data]; + [data appendUInt8:self.specialTransactionVersion]; + NSUInteger creditOutputsCount = self.creditOutputs.count; + [data appendVarInt:creditOutputsCount]; + for (NSUInteger i = 0; i < creditOutputsCount; i++) { + DSTransactionOutput *output = self.creditOutputs[i]; + [data appendUInt64:output.amount]; + [data appendCountedData:output.outScript]; + } + return data; +} + + +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex anyoneCanPay:(BOOL)anyoneCanPay { + @synchronized(self) { + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; + [data appendCountedData:[self payloadData]]; + if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; + return data; + } +} +- (size_t)size { + @synchronized(self) { + if (uint256_is_not_zero(self.txHash)) return self.data.length; + return [super size] + [NSMutableData sizeOfVarInt:self.payloadData.length] + ([self basePayloadData].length); + } +} + +- (UInt256)creditBurnIdentityIdentifier { + DSUTXO outpoint = [self lockedOutpoint]; + if (dsutxo_is_zero(outpoint)) return UINT256_ZERO; + return [dsutxo_data(outpoint) SHA256_2]; +} + +- (DSUTXO)lockedOutpoint { + if (![self.creditOutputs count]) return DSUTXO_ZERO; + DSUTXO outpoint = {.hash = uint256_reverse(self.txHash), .n = 0}; //!OCLINT + return outpoint; +} + +- (UInt160)creditBurnPublicKeyHash { + DSTransactionOutput *output = self.creditOutputs.firstObject; + Vec_u8 *maybe_pub_key_hash = dash_spv_crypto_util_address_address_public_key_hash_from_script(bytes_ctor(output.outScript)); + if (maybe_pub_key_hash) { + NSData *result = NSDataFromPtr(maybe_pub_key_hash); + bytes_dtor(maybe_pub_key_hash); + return result.UInt160; + } + return UINT160_ZERO; +} +- (BOOL)checkInvitationDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index { + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:wallet]; + NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; + return [[path addressAtIndexPath:[NSIndexPath indexPathWithIndex:index]] isEqualToString:address]; +} + +- (BOOL)checkDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index { + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:wallet]; + NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; + return [[path addressAtIndexPath:[NSIndexPath indexPathWithIndex:index]] isEqualToString:address]; +} + + +- (void)markInvitationAddressAsUsedInWallet:(DSWallet *)wallet { + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityInvitationFundingDerivationPathForWallet:wallet]; + NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; + [path registerTransactionAddress:address]; + [path registerAddressesWithSettings:[DSGapLimit withLimit:10]]; +} + +- (void)markAddressAsUsedInWallet:(DSWallet *)wallet { + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityRegistrationFundingDerivationPathForWallet:wallet]; + NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; + [path registerTransactionAddress:address]; + [path registerAddressesWithSettings:[DSGapLimit withLimit:10]]; +} +- (void)markTopUpAddressAsUsedInWallet:(DSWallet *)wallet { + DSAssetLockDerivationPath *path = [[DSDerivationPathFactory sharedInstance] identityTopupFundingDerivationPathForWallet:wallet]; + NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; + [path registerTransactionAddress:address]; + [path registerAddressesWithSettings:[DSGapLimit withLimit:10]]; +} + +- (Class)entityClass { + return [DSAssetLockTransactionEntity class]; +} + + +@end + +@implementation DSAssetLockTransaction (FFI) ++ (instancetype)ffi_from:(dashcore_blockdata_transaction_Transaction *)transaction onChain:(DSChain *)chain { + if (!transaction->special_transaction_payload) { + return nil; + } + // TODO: it's used just for ui + switch (transaction->special_transaction_payload->tag) { + case dashcore_blockdata_transaction_special_transaction_TransactionPayload_AssetLockPayloadType: { + dashcore_blockdata_transaction_special_transaction_asset_lock_AssetLockPayload *payload = transaction->special_transaction_payload->asset_lock_payload_type; + // TODO: implement it + DSAssetLockTransaction *tx = [[DSAssetLockTransaction alloc] initOnChain:chain]; +// tx. + return tx; + } + default: return nil; + } +} @end diff --git a/DashSync/shared/Models/Transactions/Base/DSAssetUnlockTransaction.m b/DashSync/shared/Models/Transactions/Base/DSAssetUnlockTransaction.m index 01824db34..ad2d582ba 100644 --- a/DashSync/shared/Models/Transactions/Base/DSAssetUnlockTransaction.m +++ b/DashSync/shared/Models/Transactions/Base/DSAssetUnlockTransaction.m @@ -16,6 +16,7 @@ // #import "DSAssetUnlockTransaction.h" +#import "DSAssetUnlockTransactionEntity+CoreDataClass.h" #import "DSChain.h" #import "DSTransactionFactory.h" #import "NSData+Dash.h" @@ -23,6 +24,15 @@ @implementation DSAssetUnlockTransaction +- (instancetype)initOnChain:(DSChain *)chain { + self = [super initOnChain:chain]; + if (self) { + self.type = DSTransactionType_AssetUnlock; + self.version = SPECIAL_TX_VERSION; + } + return self; +} + - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { if (!(self = [super initWithMessage:message onChain:chain])) return nil; @@ -80,16 +90,22 @@ - (NSData *)basePayloadData { return data; } -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; [data appendCountedData:[self payloadData]]; if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; return data; } } - (size_t)size { - return [super size] + [self payloadData].length; + @synchronized(self) { + if (uint256_is_not_zero(self.txHash)) return self.data.length; + return [super size] + [NSMutableData sizeOfVarInt:self.payloadData.length] + ([self basePayloadData].length); + } +} +- (Class)entityClass { + return [DSAssetUnlockTransactionEntity class]; } @end diff --git a/DashSync/shared/Models/Transactions/Base/DSCreditFundingTransaction.h b/DashSync/shared/Models/Transactions/Base/DSCreditFundingTransaction.h deleted file mode 100644 index f5b8ca2d9..000000000 --- a/DashSync/shared/Models/Transactions/Base/DSCreditFundingTransaction.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2019 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 "BigIntTypes.h" -#import "DSTransaction.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DSCreditFundingTransaction : DSTransaction - -@property (nonatomic, readonly) uint64_t fundingAmount; -@property (nonatomic, readonly) UInt256 creditBurnIdentityIdentifier; -@property (nonatomic, readonly) DSUTXO lockedOutpoint; -@property (nonatomic, readonly) UInt160 creditBurnPublicKeyHash; -@property (nonatomic, readonly) uint32_t usedDerivationPathIndex; - -- (uint32_t)usedDerivationPathIndexForWallet:(DSWallet *)wallet; -- (void)markAddressAsUsedInWallet:(DSWallet *)wallet; -- (void)markInvitationAddressAsUsedInWallet:(DSWallet *)wallet; -- (BOOL)checkDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index; -- (BOOL)checkInvitationDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index; - -@end - -NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Transactions/Base/DSCreditFundingTransaction.m b/DashSync/shared/Models/Transactions/Base/DSCreditFundingTransaction.m deleted file mode 100644 index 91cadd9c3..000000000 --- a/DashSync/shared/Models/Transactions/Base/DSCreditFundingTransaction.m +++ /dev/null @@ -1,125 +0,0 @@ -// -// Created by Sam Westrich -// Copyright © 2019 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 "DSCreditFundingTransaction.h" -#import "DSAccount.h" -#import "DSCreditFundingDerivationPath.h" -#import "DSCreditFundingTransactionEntity+CoreDataClass.h" -#import "DSDerivationPathFactory.h" -#import "DSInstantSendTransactionLock.h" -#import "DSTransaction+Protected.h" -#import "DSTransactionOutput.h" -#import "DSWallet.h" -#import "NSData+Dash.h" - -@implementation DSCreditFundingTransaction - -- (UInt256)creditBurnIdentityIdentifier { - DSUTXO outpoint = [self lockedOutpoint]; - if (dsutxo_is_zero(outpoint)) return UINT256_ZERO; - return [dsutxo_data(outpoint) SHA256_2]; -} - -- (DSUTXO)lockedOutpoint { - for (int i = 0; i < self.outputs.count; i++) { - DSTransactionOutput *output = self.outputs[i]; - NSData *script = output.outScript; - if ([script UInt8AtOffset:0] == OP_RETURN && script.length == 22) { - DSUTXO outpoint = {.hash = uint256_reverse(self.txHash), .n = i}; //!OCLINT - return outpoint; - } - } - return DSUTXO_ZERO; -} - -- (UInt160)creditBurnPublicKeyHash { - for (DSTransactionOutput *output in self.outputs) { - NSData *script = output.outScript; - if ([script UInt8AtOffset:0] == OP_RETURN && script.length == 22) { - return [script subdataWithRange:NSMakeRange(2, 20)].UInt160; - } - } - return UINT160_ZERO; -} - -- (uint32_t)usedDerivationPathIndexForWallet:(DSWallet *)wallet { - DSCreditFundingDerivationPath *registrationFundingDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:wallet]; - NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; - return (uint32_t)[registrationFundingDerivationPath indexOfKnownAddress:address]; -} - -- (BOOL)checkDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index { - DSCreditFundingDerivationPath *registrationFundingDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:wallet]; - NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; - return [[registrationFundingDerivationPath addressAtIndex:index] isEqualToString:address]; -} - -- (BOOL)checkInvitationDerivationPathIndexForWallet:(DSWallet *)wallet isIndex:(uint32_t)index { - DSCreditFundingDerivationPath *registrationFundingDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:wallet]; - NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; - return [[registrationFundingDerivationPath addressAtIndex:index] isEqualToString:address]; -} - -- (void)markAddressAsUsedInWallet:(DSWallet *)wallet { - DSCreditFundingDerivationPath *registrationFundingDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityRegistrationFundingDerivationPathForWallet:wallet]; - NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; - [registrationFundingDerivationPath registerTransactionAddress:address]; - [registrationFundingDerivationPath registerAddressesWithGapLimit:10 error:nil]; -} - -- (void)markInvitationAddressAsUsedInWallet:(DSWallet *)wallet { - DSCreditFundingDerivationPath *registrationFundingDerivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityInvitationFundingDerivationPathForWallet:wallet]; - NSString *address = [DSKeyManager addressFromHash160:[self creditBurnPublicKeyHash] forChain:self.chain]; - [registrationFundingDerivationPath registerTransactionAddress:address]; - [registrationFundingDerivationPath registerAddressesWithGapLimit:10 error:nil]; -} - -- (uint32_t)usedDerivationPathIndex { - if (!self.accounts.count) return UINT32_MAX; - if (self.accounts.count == 1) { - return [self usedDerivationPathIndexForWallet:self.firstAccount.wallet]; - } else { - NSMutableArray *wallets = [NSMutableArray array]; - for (DSAccount *account in self.accounts) { - if (!account.wallet) continue; - if (![wallets containsObject:account.wallet]) { - [wallets addObject:account.wallet]; - } - } - for (DSWallet *wallet in wallets) { - uint32_t derivation = [self usedDerivationPathIndexForWallet:wallet]; - if (derivation != UINT32_MAX) return derivation; - } - return UINT32_MAX; - } -} - -- (void)setInstantSendReceivedWithInstantSendLock:(DSInstantSendTransactionLock *)instantSendLock { - self.instantSendReceived = instantSendLock.signatureVerified; - self.hasUnverifiedInstantSendLock = (instantSendLock && !instantSendLock.signatureVerified); - //we will always need to send this platform - self.instantSendLockAwaitingProcessing = instantSendLock; - if (!instantSendLock.saved) { - [instantSendLock saveInitial]; - } -} - -- (Class)entityClass { - return [DSCreditFundingTransactionEntity class]; -} - -@end diff --git a/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.h b/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.h index ed1b6e57c..3fc64cd78 100644 --- a/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.h +++ b/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.h @@ -7,24 +7,25 @@ #import "BigIntTypes.h" #import "DSChain.h" +#import "DSKeyManager.h" #import NS_ASSUME_NONNULL_BEGIN -@class DSChain, DSSimplifiedMasternodeEntry, DSQuorumEntry, DSMasternodeList; +@class DSChain; @interface DSInstantSendTransactionLock : NSObject @property (nonatomic, readonly) uint8_t version; @property (nonatomic, readonly) DSChain *chain; -@property (nonatomic, readonly) UInt256 transactionHash; -@property (nonatomic, readonly) UInt768 signature; -@property (nonatomic, readonly) NSArray *inputOutpoints; -@property (nonatomic, readonly) UInt256 cycleHash; +@property (nonatomic, readonly) DInstantLock *lock; + +@property (nonatomic, readonly) NSData *transactionHashData; +@property (nonatomic, readonly) NSData *signatureData; +@property (nonatomic, readonly) NSData *cycleHashData; + @property (nonatomic, readonly) BOOL signatureVerified; //verifies the signature and quorum together -@property (nonatomic, readonly) DSQuorumEntry *intendedQuorum; @property (nonatomic, readonly) BOOL saved; -@property (nonatomic, readonly) UInt256 requestID; @property (nonatomic, readonly, getter=isDeterministic) BOOL deterministic; @@ -39,9 +40,14 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)instantSendTransactionLockWithDeterministicMessage:(NSData *)message onChain:(DSChain *)chain; -- (instancetype)initWithTransactionHash:(UInt256)transactionHash withInputOutpoints:(NSArray *)inputOutpoints signature:(UInt768)signature signatureVerified:(BOOL)signatureVerified quorumVerified:(BOOL)quorumVerified onChain:(DSChain *)chain; - -- (DSQuorumEntry *_Nullable)findSigningQuorumReturnMasternodeList:(DSMasternodeList *_Nullable *_Nullable)returnMasternodeList; +- (instancetype)initWithTransactionHash:(NSData *)transactionHash + withInputOutpoints:(NSArray *)inputOutpoints + version:(uint8_t)version + signature:(NSData *)signature + cycleHash:(NSData *)cycleHash + signatureVerified:(BOOL)signatureVerified + quorumVerified:(BOOL)quorumVerified + onChain:(DSChain *)chain; @end diff --git a/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.m b/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.m index 5a2fb3cfd..cbc445c76 100644 --- a/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.m +++ b/DashSync/shared/Models/Transactions/Base/DSInstantSendTransactionLock.m @@ -7,13 +7,11 @@ #import "DSInstantSendTransactionLock.h" #import "DSChain.h" +#import "DSChain+Params.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainManager.h" #import "DSInstantSendLockEntity+CoreDataClass.h" -#import "DSMasternodeList.h" #import "DSMasternodeManager.h" -#import "DSQuorumEntry.h" -#import "DSSimplifiedMasternodeEntry.h" #import "DSSporkManager.h" #import "DSTransactionEntity+CoreDataClass.h" #import "DSTransactionHashEntity+CoreDataClass.h" @@ -24,27 +22,28 @@ @interface DSInstantSendTransactionLock () @property (nonatomic, strong) DSChain *chain; -@property (nonatomic, assign) uint8_t version; -@property (nonatomic, assign) UInt256 transactionHash; -@property (nonatomic, assign) UInt256 requestID; -@property (nonatomic, assign) UInt256 cycleHash; -@property (nonatomic, strong) NSArray *inputOutpoints; +@property (nonatomic, assign) DInstantLock *lock; @property (nonatomic, assign) BOOL signatureVerified; @property (nonatomic, assign) BOOL quorumVerified; -@property (nonatomic, strong) DSQuorumEntry *intendedQuorum; @property (nonatomic, assign) BOOL saved; -@property (nonatomic, assign) UInt768 signature; @end @implementation DSInstantSendTransactionLock +- (void)dealloc { + if (_lock) { + DInstantLockDtor(_lock); + _lock = NULL; + } +} + + (instancetype)instantSendTransactionLockWithNonDeterministicMessage:(NSData *)message onChain:(DSChain *)chain { - return [[self alloc] initWithNonDeterministicMessage:message onChain:chain]; + return [[self alloc] initWithMessage:message onChain:chain]; } + (instancetype)instantSendTransactionLockWithDeterministicMessage:(NSData *)message onChain:(DSChain *)chain { - return [[self alloc] initWithDeterministicMessage:message onChain:chain]; + return [[self alloc] initWithMessage:message onChain:chain]; } - (instancetype)init { @@ -61,181 +60,96 @@ - (instancetype)initOnChain:(DSChain *)chain { return self; } -//transaction hash (32) -//transaction outpoint (36) -//masternode outpoint (36) -//if spork 15 is active -// quorum hash 32 -// confirmed hash 32 -//masternode signature -// size - varint -// signature 65 or 96 depending on spork 15 -- (instancetype)initWithNonDeterministicMessage:(NSData *)message onChain:(DSChain *)chain { +- (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { if (!(self = [self initOnChain:chain])) return nil; if (![chain.chainManager.sporkManager deterministicMasternodeListEnabled] || ![chain.chainManager.sporkManager llmqInstantSendEnabled]) return nil; - uint32_t off = 0; - NSNumber *l = 0; - uint64_t count = 0; - self.signatureVerified = NO; - self.quorumVerified = NO; - @autoreleasepool { - self.chain = chain; - count = [message varIntAtOffset:off length:&l]; // input count - off += l.unsignedIntegerValue; - NSMutableArray *mutableInputOutpoints = [NSMutableArray array]; - for (NSUInteger i = 0; i < count; i++) { // inputs - DSUTXO outpoint = [message transactionOutpointAtOffset:off]; - off += 36; - [mutableInputOutpoints addObject:dsutxo_data(outpoint)]; - } - self.inputOutpoints = [mutableInputOutpoints copy]; - - self.transactionHash = [message UInt256AtOffset:off]; // tx - //DSLogPrivate(@"transactionHash is %@",uint256_reverse_hex(self.transactionHash)); - off += sizeof(UInt256); + self.lock = dash_spv_masternode_processor_processing_instant_lock_from_message(slice_ctor(message)); + return self; +} - self.signature = [message UInt768AtOffset:off]; - NSAssert(uint768_is_not_zero(self.signature), @"signature must be set"); - } +- (uint8_t)version { + return dashcore_ephemerealdata_instant_lock_InstantLock_get_version(self.lock); +} - return self; +- (NSData *)transactionHashData { + u256 *tx_hash = dash_spv_masternode_processor_processing_instant_lock_tx_hash(self.lock); + NSData *data = NSDataFromPtr(tx_hash); + u256_dtor(tx_hash); + return data; } -- (instancetype)initWithDeterministicMessage:(NSData *)message onChain:(DSChain *)chain { - if (!(self = [self initOnChain:chain])) return nil; - if (![chain.chainManager.sporkManager deterministicMasternodeListEnabled] || ![chain.chainManager.sporkManager llmqInstantSendEnabled]) return nil; - NSUInteger off = 0; - NSNumber *l = 0; - uint64_t count = 0; - self.version = [message readUInt8AtOffset:&off]; - self.signatureVerified = NO; - self.quorumVerified = NO; - @autoreleasepool { - self.chain = chain; - count = [message varIntAtOffset:off length:&l]; // input count - off += l.unsignedIntegerValue; - NSMutableArray *mutableInputOutpoints = [NSMutableArray array]; - for (NSUInteger i = 0; i < count; i++) { // inputs - DSUTXO outpoint = [message transactionOutpointAtOffset:off]; - off += 36; - [mutableInputOutpoints addObject:dsutxo_data(outpoint)]; - } - self.inputOutpoints = [mutableInputOutpoints copy]; +- (NSData *)signatureData { + u768 *sig = dash_spv_masternode_processor_processing_instant_lock_signature(self.lock); + NSData *data = NSDataFromPtr(sig); + u768_dtor(sig); + return data; +} - self.transactionHash = [message readUInt256AtOffset:&off]; // tx - //DSLogPrivate(@"transactionHash is %@",uint256_reverse_hex(self.transactionHash)); +- (NSData *)cycleHashData { + u256 *cycle_hash = dash_spv_masternode_processor_processing_instant_lock_cycle_hash(self.lock); + NSData *data = NSDataFromPtr(cycle_hash); + u256_dtor(cycle_hash); + return data; +} - self.cycleHash = [message readUInt256AtOffset:&off]; // tx - - self.signature = [message UInt768AtOffset:off]; - NSAssert(uint768_is_not_zero(self.signature), @"signature must be set"); - } +- (DOutPoints *)inputOutpoints { + return dash_spv_masternode_processor_processing_instant_lock_outpoints(self.lock); +} - return self; +- (DOutPoint *)inputOutpointAtIndex:(uintptr_t)index { + return dash_spv_masternode_processor_processing_instant_lock_outpoint_at_index(self.lock, index); } - (NSData *)toData { - NSMutableData *mData = [NSMutableData data]; - [mData appendVarInt:self.inputOutpoints.count]; - for (NSData *inputOutpoints in self.inputOutpoints) { - [mData appendUTXO:inputOutpoints.transactionOutpoint]; + if (self.lock) { + Vec_u8 *result = dash_spv_masternode_processor_processing_instant_lock_to_message(self.lock); + NSData *data = NSDataFromPtr(result); + Vec_u8_destroy(result); + return data; } - [mData appendUInt256:self.transactionHash]; - [mData appendUInt768:self.signature]; - return [mData copy]; + return nil; } -- (instancetype)initWithTransactionHash:(UInt256)transactionHash withInputOutpoints:(NSArray *)inputOutpoints signature:(UInt768)signature signatureVerified:(BOOL)signatureVerified quorumVerified:(BOOL)quorumVerified onChain:(DSChain *)chain { +- (instancetype)initWithTransactionHash:(NSData *)transactionHash + withInputOutpoints:(NSArray *)inputOutpoints + version:(uint8_t)version + signature:(NSData *)signature + cycleHash:(NSData *)cycleHash + signatureVerified:(BOOL)signatureVerified + quorumVerified:(BOOL)quorumVerified + onChain:(DSChain *)chain { if (!(self = [self initOnChain:chain])) return nil; - self.transactionHash = transactionHash; - self.inputOutpoints = inputOutpoints; + NSUInteger inputsCount = inputOutpoints.count; + DOutPoint **values = malloc(sizeof(DOutPoint *) * inputsCount); + for (int i = 0; i < inputsCount; i++) { + NSData *inputBytes = inputOutpoints[i]; + values[i] = DOutPointFromMessage(slice_ctor(inputBytes)); + } + DOutPoints *inputs = DOutPointsCtor(inputsCount, values); + + DTxid *txid = DTxidCtor(u256_ctor(transactionHash)); + DCycleHash *chash = dashcore_hash_types_CycleHash_ctor(u256_ctor(cycleHash)); + self.lock = DInstantLockCtor(version, inputs, txid, chash, DBLSSignatureCtor(u768_ctor(signature))); self.signatureVerified = signatureVerified; - self.signature = signature; self.quorumVerified = quorumVerified; self.saved = YES; //this is coming already from the persistant store and not from the network return self; } -- (UInt256)requestID { - if (uint256_is_not_zero(_requestID)) return _requestID; - NSMutableData *data = [NSMutableData data]; - [data appendString:@"islock"]; - [data appendVarInt:self.inputOutpoints.count]; - for (NSData *input in self.inputOutpoints) { - [data appendData:input]; - } - _requestID = [data SHA256_2]; - DSLogPrivate(@"[%@] the request ID is %@", self.chain.name, uint256_hex(_requestID)); - return _requestID; -} - -- (UInt256)signIDForQuorumEntry:(DSQuorumEntry *)quorumEntry { - NSMutableData *data = [NSMutableData data]; - [data appendVarInt:quorum_type_for_is_locks(self.chain.chainType)]; - [data appendUInt256:quorumEntry.quorumHash]; - [data appendUInt256:self.requestID]; - [data appendUInt256:self.transactionHash]; - return [data SHA256_2]; -} - -- (BOOL)verifySignatureAgainstQuorum:(DSQuorumEntry *)quorumEntry { - UInt256 signId = [self signIDForQuorumEntry:quorumEntry]; - return key_bls_verify(quorumEntry.quorumPublicKey.u8, quorumEntry.useLegacyBLSScheme, signId.u8, self.signature.u8); -} - -- (DSQuorumEntry *)findSigningQuorumReturnMasternodeList:(DSMasternodeList **)returnMasternodeList { - DSQuorumEntry *foundQuorum = nil; - LLMQType ISLockQuorumType = quorum_type_for_is_locks(self.chain.chainType); - for (DSMasternodeList *masternodeList in [self.chain.chainManager.masternodeManager.recentMasternodeLists copy]) { - for (DSQuorumEntry *quorumEntry in [[masternodeList quorumsOfType:ISLockQuorumType] allValues]) { - BOOL signatureVerified = [self verifySignatureAgainstQuorum:quorumEntry]; - if (signatureVerified) { - foundQuorum = quorumEntry; - if (returnMasternodeList) *returnMasternodeList = masternodeList; - break; - } - } - if (foundQuorum) break; - } - return foundQuorum; -} - -- (BOOL)verifySignatureWithQuorumOffset:(uint32_t)offset { - DSQuorumEntry *quorumEntry = [self.chain.chainManager.masternodeManager quorumEntryForInstantSendRequestID:[self requestID] withBlockHeightOffset:offset]; - if (quorumEntry && quorumEntry.verified) { - self.signatureVerified = [self verifySignatureAgainstQuorum:quorumEntry]; - if (!self.signatureVerified) { - DSLog(@"[%@] unable to verify IS signature with offset %d", self.chain.name, offset); - } else { - DSLog(@"[%@] IS signature verified with offset %d", self.chain.name, offset); - } - - } else if (quorumEntry) { - DSLog(@"[%@] quorum entry %@ found but is not yet verified", self.chain.name, uint256_hex(quorumEntry.quorumHash)); +- (BOOL)verifySignature { + if (self.lock) { +#if defined(DASHCORE_MESSAGE_VERIFICATION) + DMessageVerificationResult *result = dash_spv_masternode_processor_processing_processor_MasternodeProcessor_verify_is_lock(self.chain.sharedProcessorObj, self.lock); + BOOL verified = result->ok; + DMessageVerificationResultDtor(result); + return verified; +#else + return YES; +#endif } else { - DSLog(@"[%@] no quorum entry found", self.chain.name); - } - if (self.signatureVerified) { - self.intendedQuorum = quorumEntry; - } else if (quorumEntry.verified && offset == 8) { - //try again a few blocks more in the past - DSLog(@"[%@] trying with offset 0", self.chain.name); - return [self verifySignatureWithQuorumOffset:0]; - } else if (quorumEntry.verified && offset == 0) { - //try again a few blocks more in the future - DSLog(@"[%@] trying with offset 16", self.chain.name); - return [self verifySignatureWithQuorumOffset:16]; + return NO; } - DSLog(@"[%@] returning signature verified %d with offset %d", self.chain.name, self.signatureVerified, offset); - return self.signatureVerified; -} - -- (BOOL)verifySignature { - // TODO: Need to implement - return TRUE; - // - return [self verifySignatureWithQuorumOffset:8]; } - (void)saveInitial { @@ -244,7 +158,7 @@ - (void)saveInitial { NSManagedObjectContext *context = self.chain.chainManagedObjectContext; //saving here will only create, not update. [context performBlockAndWait:^{ // add the transaction to core data - if ([DSInstantSendLockEntity countObjectsInContext:context matching:@"transaction.transactionHash.txHash == %@", uint256_data(self.transactionHash)] == 0) { + if ([DSInstantSendLockEntity countObjectsInContext:context matching:@"transaction.transactionHash.txHash == %@", [self transactionHashData]] == 0) { [DSInstantSendLockEntity instantSendLockEntityFromInstantSendLock:self inContext:context]; [context ds_save]; } @@ -260,8 +174,7 @@ - (void)saveSignatureValid { //saving here will only create, not update. NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; [context performBlockAndWait:^{ // add the transaction to core data - NSArray *instantSendLocks = [DSInstantSendLockEntity objectsInContext:context matching:@"transaction.transactionHash.txHash == %@", uint256_data(self.transactionHash)]; - + NSArray *instantSendLocks = [DSInstantSendLockEntity objectsInContext:context matching:@"transaction.transactionHash.txHash == %@", [self transactionHashData]]; DSInstantSendLockEntity *instantSendLockEntity = [instantSendLocks firstObject]; if (instantSendLockEntity) { instantSendLockEntity.validSignature = TRUE; diff --git a/DashSync/shared/Models/Transactions/Base/DSTransaction.h b/DashSync/shared/Models/Transactions/Base/DSTransaction.h index 66fc56c01..04dbb7aea 100644 --- a/DashSync/shared/Models/Transactions/Base/DSTransaction.h +++ b/DashSync/shared/Models/Transactions/Base/DSTransaction.h @@ -29,10 +29,13 @@ #import "DSShapeshiftEntity+CoreDataClass.h" #import #import "DSChain.h" +#import "DSKeyManager.h" +#import "DSTransactionInput.h" +#import "DSTransactionOutput.h" NS_ASSUME_NONNULL_BEGIN -@class DSChain, DSAccount, DSWallet, DSTransactionLockVote, DSTransactionEntity, DSInstantSendTransactionLock, DSBlockchainIdentity, DSDerivationPath, DSTransactionInput, DSTransactionOutput; +@class DSChain, DSAccount, DSWallet, DSTransactionLockVote, DSTransactionEntity, DSInstantSendTransactionLock, DSIdentity, DSDerivationPath, DSTransactionInput, DSTransactionOutput; #define TX_FEE_PER_B 1ULL // standard tx fee per b of tx size #define TX_FEE_PER_INPUT 10000ULL // standard ix fee per input @@ -41,13 +44,14 @@ NS_ASSUME_NONNULL_BEGIN #define TX_MIN_OUTPUT_AMOUNT (TX_FEE_PER_B * 3 * (TX_OUTPUT_SIZE + TX_INPUT_SIZE)) //no txout can be below this amount #define TX_MAX_SIZE 100000 // no tx can be larger than this size in bytes //#define TX_UNCONFIRMED INT32_MAX // block height indicating transaction is unconfirmed -//#define TX_MAX_LOCK_HEIGHT 500000000 // a lockTime below this value is a block height, otherwise a timestamp +#define TX_MAX_LOCK_HEIGHT 500000000 // a lockTime below this value is a block height, otherwise a timestamp #define TX_VERSION 0x00000001u #define SPECIAL_TX_VERSION 0x00000003u #define TX_LOCKTIME 0x00000000u #define TXIN_SEQUENCE UINT32_MAX #define SIGHASH_ALL 0x00000001u +#define SIGHASH_ANYONECANPAY 0x80u #define MAX_ECDSA_SIGNATURE_SIZE 75 @@ -65,6 +69,14 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) DSTransactionSortType_BIP69, }; +typedef NS_ENUM(NSUInteger, DSTransactionDirection) +{ + DSTransactionDirection_Sent, + DSTransactionDirection_Received, + DSTransactionDirection_Moved, + DSTransactionDirection_NotAccountFunds, +}; + @interface DSTransaction : NSObject @property (nonatomic, readonly) NSArray *inputs; @@ -73,8 +85,8 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) @property (nonatomic, readonly) NSArray *inputAddresses; @property (nonatomic, readonly) NSArray *outputAddresses; -@property (nonatomic, readonly) NSSet *sourceBlockchainIdentities; -@property (nonatomic, readonly) NSSet *destinationBlockchainIdentities; +@property (nonatomic, readonly) NSSet *sourceIdentities; +@property (nonatomic, readonly) NSSet *destinationIdentities; @property (nonatomic, readonly) BOOL instantSendReceived; @property (nonatomic, readonly) BOOL confirmed; @@ -89,7 +101,8 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) @property (nonatomic, assign) uint32_t lockTime; @property (nonatomic, assign) uint64_t feeUsed; @property (nonatomic, assign) uint64_t roundedFeeCostPerByte; -@property (nonatomic, readonly) uint64_t amountSent; +@property (nonatomic, readonly) DSTransactionDirection direction; +@property (nonatomic, readonly) uint64_t dashAmount; @property (nonatomic, readonly) NSData *payloadData; @property (nonatomic, readonly) NSData *payloadDataForHash; @property (nonatomic, assign) uint32_t payloadOffset; @@ -104,7 +117,7 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) @property (nonatomic, readonly) NSString *longDescription; @property (nonatomic, readonly) BOOL isCoinbaseClassicTransaction; -@property (nonatomic, readonly) BOOL isCreditFundingTransaction; +@property (nonatomic, readonly) BOOL isImmatureCoinBase; @property (nonatomic, readonly) UInt256 creditBurnIdentityIdentifier; @property (nonatomic, strong) DSShapeshiftEntity *associatedShapeshift; @@ -116,7 +129,7 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) @property (nonatomic, readonly) BOOL transactionTypeRequiresInputs; + (instancetype)transactionWithMessage:(NSData *)message onChain:(DSChain *)chain; -+ (UInt256)devnetGenesisCoinbaseTxHash:(DevnetType)devnetType onProtocolVersion:(uint32_t)protocolVersion forChain:(DSChain *)chain; ++ (UInt256)devnetGenesisCoinbaseTxHash:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType onProtocolVersion:(uint32_t)protocolVersion forChain:(DSChain *)chain; - (instancetype)initOnChain:(DSChain *)chain; - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain; @@ -143,6 +156,8 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) - (void)hasSetInputsAndOutputs; - (BOOL)signWithSerializedPrivateKeys:(NSArray *)privateKeys; - (BOOL)signWithPrivateKeys:(NSArray *)keys; +// TMP method to handle specific c structures +- (BOOL)signWithMaybePrivateKeySets:(NSArray *)keysSets anyoneCanPay:(BOOL)anyoneCanPay; - (BOOL)signWithPreorderedPrivateKeys:(NSArray *)keys; - (NSString *_Nullable)shapeshiftOutboundAddress; @@ -152,7 +167,7 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) // priority = sum(input_amount_in_satoshis*input_age_in_blocks)/tx_size_in_bytes - (uint64_t)priorityForAmounts:(NSArray *)amounts withAges:(NSArray *)ages; -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex; +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex anyoneCanPay:(BOOL)anyoneCanPay; - (BOOL)hasNonDustOutputInWallet:(DSWallet *)wallet; @@ -168,12 +183,10 @@ typedef NS_ENUM(NSInteger, DSTransactionSortType) - (void)setInstantSendReceivedWithInstantSendLock:(DSInstantSendTransactionLock *)instantSendLock; -- (void)loadBlockchainIdentitiesFromDerivationPaths:(NSArray *)derivationPaths; +- (void)loadIdentitiesFromDerivationPaths:(NSArray *)derivationPaths; -@end +- (int32_t)getBlocksToMaturity; -@interface DSTransaction (Extensions) -- (DSTransactionDirection)direction; @end NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Transactions/Base/DSTransaction.m b/DashSync/shared/Models/Transactions/Base/DSTransaction.m index 428853fb9..f38577a1b 100644 --- a/DashSync/shared/Models/Transactions/Base/DSTransaction.m +++ b/DashSync/shared/Models/Transactions/Base/DSTransaction.m @@ -28,11 +28,15 @@ #import "DSAccount.h" #import "DSAddressEntity+CoreDataClass.h" +#import "DSAssetLockTransaction.h" #import "DSAssetUnlockTransaction.h" #import "DSChain.h" +#import "DSChain+Identity.h" +#import "DSChain+Params.h" +#import "DSChain+Transaction.h" +#import "DSChain+Wallet.h" #import "DSChainEntity+CoreDataClass.h" #import "DSChainManager.h" -#import "DSCreditFundingTransaction.h" #import "DSIdentitiesManager.h" #import "DSInstantSendTransactionLock.h" #import "DSMasternodeManager.h" @@ -40,8 +44,6 @@ #import "DSTransactionEntity+CoreDataClass.h" #import "DSTransactionFactory.h" #import "DSTransactionHashEntity+CoreDataClass.h" -#import "DSTransactionInput.h" -#import "DSTransactionOutput.h" #import "DSWallet.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" @@ -54,10 +56,12 @@ @interface DSTransaction () @property (nonatomic, strong) DSChain *chain; @property (nonatomic, assign) BOOL confirmed; -@property (nonatomic, strong) NSSet *sourceBlockchainIdentities; -@property (nonatomic, strong) NSSet *destinationBlockchainIdentities; +@property (nonatomic, strong) NSSet *sourceIdentities; +@property (nonatomic, strong) NSSet *destinationIdentities; @property (nonatomic, strong) NSMutableArray *mInputs; @property (nonatomic, strong) NSMutableArray *mOutputs; +@property (nonatomic, assign) DSTransactionDirection cachedDirection; +@property (nonatomic, assign) uint64_t cachedDashAmount; @end @@ -69,9 +73,11 @@ + (instancetype)transactionWithMessage:(NSData *)message onChain:(DSChain *)chai return [[self alloc] initWithMessage:message onChain:chain]; } -+ (UInt256)devnetGenesisCoinbaseTxHash:(DevnetType)devnetType onProtocolVersion:(uint32_t)protocolVersion forChain:(DSChain *)chain { ++ (UInt256)devnetGenesisCoinbaseTxHash:(dash_spv_crypto_network_chain_type_DevnetType *)devnetType + onProtocolVersion:(uint32_t)protocolVersion + forChain:(DSChain *)chain { DSTransaction *transaction = [[self alloc] initOnChain:chain]; - NSData *coinbaseData = [DSKeyManager NSDataFrom:devnet_genesis_coinbase_message(devnetType, protocolVersion)]; + NSData *coinbaseData = [DSKeyManager NSDataFrom:dash_spv_crypto_devnet_genesis_coinbase_message(devnetType, protocolVersion)]; [transaction addInputHash:UINT256_ZERO index:UINT32_MAX script:nil signature:coinbaseData sequence:UINT32_MAX]; [transaction addOutputScript:[NSData dataWithUInt8:OP_RETURN] amount:chain.baseReward]; return transaction.toData.SHA256_2; @@ -85,7 +91,7 @@ - (instancetype)init { - (instancetype)initOnChain:(DSChain *)chain { if (!(self = [super init])) return nil; - + _version = TX_VERSION; self.mInputs = [NSMutableArray array]; self.mOutputs = [NSMutableArray array]; @@ -94,19 +100,21 @@ - (instancetype)initOnChain:(DSChain *)chain { self.hasUnverifiedInstantSendLock = NO; _lockTime = TX_LOCKTIME; self.blockHeight = TX_UNCONFIRMED; - self.sourceBlockchainIdentities = [NSSet set]; - self.destinationBlockchainIdentities = [NSSet set]; + self.sourceIdentities = [NSSet set]; + self.destinationIdentities = [NSSet set]; + self.cachedDirection = DSTransactionDirection_NotAccountFunds; + self.cachedDashAmount = UINT64_MAX; return self; } - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { if (!(self = [self initOnChain:chain])) return nil; - + NSString *address = nil; NSNumber *l = 0; uint32_t off = 0; uint64_t count = 0; - + @autoreleasepool { self.chain = chain; _version = [message UInt16AtOffset:off]; // tx version @@ -118,7 +126,7 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { return nil; // at least one input is required } off += l.unsignedIntegerValue; - + for (NSUInteger i = 0; i < count; i++) { // inputs UInt256 hash = [message UInt256AtOffset:off]; // input hash off += sizeof(UInt256); @@ -132,10 +140,10 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { DSTransactionInput *transactionInput = [DSTransactionInput transactionInputWithHash:hash index:index inScript:inScript signature:signature sequence:sequence]; [self.mInputs addObject:transactionInput]; } - + count = (NSUInteger)[message varIntAtOffset:off length:&l]; // output count off += l.unsignedIntegerValue; - + for (NSUInteger i = 0; i < count; i++) { // outputs uint64_t amount = [message UInt64AtOffset:off]; // output amount off += sizeof(uint64_t); @@ -144,7 +152,7 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { DSTransactionOutput *transactionOutput = [DSTransactionOutput transactionOutputWithAmount:amount outScript:outScript onChain:self.chain]; [self.mOutputs addObject:transactionOutput]; } - + _lockTime = [message UInt32AtOffset:off]; // tx locktime off += 4; _payloadOffset = off; @@ -152,9 +160,9 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { _txHash = self.data.SHA256_2; } } - + if ([self type] != DSTransactionType_Classic) return self; //only classic transactions are shapeshifted - + NSString *outboundShapeshiftAddress = [self shapeshiftOutboundAddress]; if (!outboundShapeshiftAddress) return self; self.associatedShapeshift = [DSShapeshiftEntity shapeshiftHavingWithdrawalAddress:outboundShapeshiftAddress inContext:[NSManagedObjectContext chainContext]]; @@ -168,9 +176,9 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { self.associatedShapeshift.shapeshiftStatus = @(eShapeshiftAddressStatus_NoDeposits); } } - + if (self.associatedShapeshift || ![self.outputs count]) return self; - + NSString *mainOutputAddress = nil; NSMutableArray *allAddresses = [NSMutableArray array]; for (DSAddressEntity *e in [DSAddressEntity allObjectsInContext:chain.chainManagedObjectContext]) { @@ -186,11 +194,14 @@ - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { if (mainOutputAddress) { self.associatedShapeshift = [DSShapeshiftEntity registerShapeshiftWithInputAddress:mainOutputAddress andWithdrawalAddress:outboundShapeshiftAddress withStatus:eShapeshiftAddressStatus_NoDeposits inContext:[NSManagedObjectContext chainContext]]; } - + return self; } -- (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)indexes inputScripts:(NSArray *)scripts inputSequences:(NSArray *)inputSequences +- (instancetype)initWithInputHashes:(NSArray *)hashes + inputIndexes:(NSArray *)indexes + inputScripts:(NSArray *)scripts + inputSequences:(NSArray *)inputSequences outputAddresses:(NSArray *)addresses outputAmounts:(NSArray *)amounts onChain:(DSChain *)chain { @@ -198,9 +209,9 @@ - (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)in if (hashes.count != indexes.count) return nil; if (scripts.count > 0 && hashes.count != scripts.count) return nil; if (addresses.count != amounts.count) return nil; - + if (!(self = [super init])) return nil; - + self.persistenceStatus = DSTransactionPersistenceStatus_NotSaved; self.chain = chain; _version = chain.transactionVersion; @@ -214,7 +225,7 @@ - (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)in NSData *inputScript = (scripts.count > 0) ? [scripts objectAtIndex:i] : nil; [self.mInputs addObject:[DSTransactionInput transactionInputWithHash:inputHash index:index inScript:inputScript signature:nil sequence:inputSequence]]; } - + self.mOutputs = [NSMutableArray array]; for (int i = 0; i < amounts.count; i++) { uint64_t amount = [[amounts objectAtIndex:i] unsignedLongValue]; @@ -227,7 +238,7 @@ - (instancetype)initWithInputHashes:(NSArray *)hashes inputIndexes:(NSArray *)in } [self.mOutputs addObject:[DSTransactionOutput transactionOutputWithAmount:amount outScript:outScript onChain:self.chain]]; } - + _lockTime = TX_LOCKTIME; self.blockHeight = TX_UNCONFIRMED; return self; @@ -314,25 +325,50 @@ - (NSString *)description { - (NSString *)longDescription { NSString *txid = [NSString hexWithData:[NSData dataWithBytes:self.txHash.u8 length:sizeof(UInt256)].reverse]; return [NSString stringWithFormat:@"%@(id=%@, inputs=%@, outputs=%@)", - [[self class] description], txid, - self.inputs, - self.outputs]; + [[self class] description], txid, + self.inputs, + self.outputs]; } -// retuns the amount sent from the wallet by the trasaction (total wallet outputs consumed, change and fee included) -- (uint64_t)amountSent { +- (uint64_t)dashAmount { + if (self.cachedDashAmount != UINT64_MAX) { + return self.cachedDashAmount; + } + uint64_t amount = 0; - for (DSTransactionInput *input in self.inputs) { - UInt256 hash = input.inputHash; - DSTransaction *tx = [self.chain transactionForHash:hash]; - DSAccount *account = [self.chain firstAccountThatCanContainTransaction:tx]; - uint32_t n = input.index; - if (n < tx.outputs.count) { - DSTransactionOutput *output = tx.outputs[n]; - if ([account containsAddress:output.address]) - amount += output.amount; + const uint64_t sent = [self.chain amountSentByTransaction:self]; + const uint64_t received = [self.chain amountReceivedFromTransaction:self]; + uint64_t fee = self.feeUsed; + + if (fee == UINT64_MAX) { + fee = 0; + } + + if (sent > 0 && (received + fee) == sent) { + // moved + amount = 0; + self.cachedDirection = DSTransactionDirection_Moved; + } else if (sent > 0) { + // sent + if (received > sent) { + // NOTE: During the sync we may get an incorrect amount + return UINT64_MAX; } + + self.cachedDirection = DSTransactionDirection_Sent; + amount = sent - received - fee; + } else if (received > 0) { + // received + self.cachedDirection = DSTransactionDirection_Received; + amount = received; + } else { + // no funds moved on this account + self.cachedDirection = DSTransactionDirection_NotAccountFunds; + amount = 0; } + + self.cachedDashAmount = amount; + return amount; } @@ -343,7 +379,7 @@ - (size_t)size { uint32_t inputCount = (uint32_t)self.mInputs.count; uint32_t outputCount = (uint32_t)self.mOutputs.count; return 8 + [NSMutableData sizeOfVarInt:inputCount] + [NSMutableData sizeOfVarInt:outputCount] + - TX_INPUT_SIZE * inputCount + TX_OUTPUT_SIZE * outputCount; + TX_INPUT_SIZE * inputCount + TX_OUTPUT_SIZE * outputCount; } } @@ -380,14 +416,17 @@ - (BOOL)isCoinbaseClassicTransaction { } } -- (BOOL)isCreditFundingTransaction { - for (DSTransactionOutput *output in self.outputs) { - NSData *script = output.outScript; - if ([script UInt8AtOffset:0] == OP_RETURN && script.length == 22) { - return YES; - } - } - return NO; +- (BOOL)isImmatureCoinBase { + // note GetBlocksToMaturity is 0 for non-coinbase tx + return [self getBlocksToMaturity] > 0; +} + +- (int32_t)getBlocksToMaturity { + if (![self isCoinbaseClassicTransaction]) + return 0; + + uint32_t chainDepth = [self confirmations]; + return MAX(0, (COINBASE_MATURITY + 1) - chainDepth); } - (NSUInteger)hash { @@ -398,18 +437,28 @@ - (NSUInteger)hash { // MARK: - Wire Serialization - (NSData *)toData { - return [self toDataWithSubscriptIndex:NSNotFound]; + return [self toData:NO]; +} + +- (NSData *)toData:(BOOL)anyoneCanPay { + return [self toDataWithSubscriptIndex:NSNotFound anyoneCanPay:anyoneCanPay]; } // Returns the binary transaction data that needs to be hashed and signed with the private key for the tx input at // subscriptIndex. A subscriptIndex of NSNotFound will return the entire signed transaction. -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { NSArray *inputs = self.inputs; NSArray *outputs = self.outputs; NSUInteger inputsCount = inputs.count; NSUInteger outputsCount = outputs.count; - BOOL forSigHash = ([self isMemberOfClass:[DSTransaction class]] || [self isMemberOfClass:[DSCreditFundingTransaction class]] || [self isMemberOfClass:[DSAssetUnlockTransaction class]]) && subscriptIndex != NSNotFound; + + if (anyoneCanPay && subscriptIndex < inputsCount) { + inputs = @[inputs[subscriptIndex]]; + inputsCount = 1; + } + + BOOL forSigHash = [self isMemberOfClass:[DSTransaction class]] && subscriptIndex != NSNotFound; NSUInteger dataSize = 8 + [NSMutableData sizeOfVarInt:inputsCount] + [NSMutableData sizeOfVarInt:outputsCount] + TX_INPUT_SIZE * inputsCount + TX_OUTPUT_SIZE * outputsCount + (forSigHash ? 4 : 0); NSMutableData *d = [NSMutableData dataWithCapacity:dataSize]; @@ -424,7 +473,7 @@ - (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { if (subscriptIndex == NSNotFound && input.signature != nil) { [d appendCountedData:input.signature]; - } else if (subscriptIndex == i && input.inScript != nil) { + } else if (anyoneCanPay || (subscriptIndex == i && input.inScript != nil)) { // TODO: to fully match the reference implementation, OP_CODESEPARATOR related checksig logic should go here [d appendCountedData:input.inScript]; } else { @@ -442,7 +491,14 @@ - (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { } [d appendUInt32:self.lockTime]; - if (forSigHash) [d appendUInt32:SIGHASH_ALL]; + if (forSigHash) { + uint8_t sighashFlags = SIGHASH_ALL; + if (anyoneCanPay) { + sighashFlags |= SIGHASH_ANYONECANPAY; + } + + [d appendUInt32:sighashFlags]; + } return [d copy]; } } @@ -563,7 +619,7 @@ - (BOOL)signWithSerializedPrivateKeys:(NSArray *)privateKeys { NSMutableArray *keys = [NSMutableArray arrayWithCapacity:privateKeys.count]; for (NSString *pk in privateKeys) { - OpaqueKey *key = [DSKeyManager keyWithPrivateKeyString:pk ofKeyType:KeyKind_ECDSA forChainType:self.chain.chainType]; + DMaybeOpaqueKey *key = DMaybeOpaqueKeyWithPrivateKey(DKeyKindECDSA(), DChar(pk), self.chain.chainType); if (!key) continue; [keys addObject:[NSValue valueWithPointer:key]]; } @@ -572,72 +628,99 @@ - (BOOL)signWithSerializedPrivateKeys:(NSArray *)privateKeys { } - (BOOL)signWithPrivateKeys:(NSArray *)keys { + return [self signWithPrivateKeys:keys anyoneCanPay:NO]; +} + +- (BOOL)signWithPrivateKeys:(NSArray *)keys anyoneCanPay:(BOOL)anyoneCanPay { NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:keys.count]; // TODO: avoid double looping: defer getting address into signWithPrivateKeys key <-> address - for (NSValue *key in keys) { - [addresses addObject:[DSKeyManager addressForKey:key.pointerValue forChainType:self.chain.chainType]]; + for (NSValue *keyValue in keys) { + DMaybeOpaqueKey *key = (DMaybeOpaqueKey *) keyValue.pointerValue; + [addresses addObject:[DSKeyManager addressForKey:key->ok forChainType:self.chain.chainType]]; } @synchronized (self) { - for (NSUInteger i = 0; i < self.mInputs.count; i++) { + for (NSUInteger i = 0; i < self.mInputs.count; i++) { DSTransactionInput *transactionInput = self.mInputs[i]; - NSString *addr = [DSKeyManager addressWithScriptPubKey:transactionInput.inScript forChain:self.chain]; NSUInteger keyIdx = (addr) ? [addresses indexOfObject:addr] : NSNotFound; - if (keyIdx == NSNotFound) continue; - NSData *data = [self toDataWithSubscriptIndex:i]; - NSMutableData *sig = [NSMutableData data]; - NSValue *keyValue = keys[keyIdx]; - OpaqueKey *key = ((OpaqueKey *) keyValue.pointerValue); - UInt256 hash = data.SHA256_2; - NSData *signedData = [DSKeyManager NSDataFrom:key_ecdsa_sign(key->ecdsa, hash.u8, 32)]; - - NSMutableData *s = [NSMutableData dataWithData:signedData]; - [s appendUInt8:SIGHASH_ALL]; - [sig appendScriptPushData:s]; - NSArray *elem = [transactionInput.inScript scriptElements]; - if (elem.count >= 2 && [elem[elem.count - 2] intValue] == OP_EQUALVERIFY) { // pay-to-pubkey-hash scriptSig - [sig appendScriptPushData:[DSKeyManager publicKeyData:key]]; + if (keyIdx == NSNotFound) { + if (anyoneCanPay && !transactionInput.signature) + transactionInput.signature = [NSData data]; + continue; } - + NSData *data = [self toDataWithSubscriptIndex:i anyoneCanPay:anyoneCanPay]; + uint8_t sighashFlags = SIGHASH_ALL; + if (anyoneCanPay) { + sighashFlags |= SIGHASH_ANYONECANPAY; + } + // TODO: MAKE CORRECT MERGE + NSData *inScript = transactionInput.inScript; + NSData *sig = [DSTransaction signInput:data flags:sighashFlags inputScript:inScript withOpaqueKeyValue:keys[keyIdx]]; transactionInput.signature = sig; } - if (!self.isSigned) return NO; _txHash = self.data.SHA256_2; return YES; } } -- (BOOL)signWithPreorderedPrivateKeys:(NSArray *)keys { +- (BOOL)signWithMaybePrivateKeySets:(NSArray *)keysSets anyoneCanPay:(BOOL)anyoneCanPay { @synchronized (self) { for (NSUInteger i = 0; i < self.mInputs.count; i++) { DSTransactionInput *transactionInput = self.mInputs[i]; - NSMutableData *sig = [NSMutableData data]; - NSData *data = [self toDataWithSubscriptIndex:i]; - UInt256 hash = data.SHA256_2; - NSValue *keyValue = keys[i]; - OpaqueKey *key = ((OpaqueKey *) keyValue.pointerValue); - NSData *signedData = [DSKeyManager NSDataFrom:key_ecdsa_sign(key->ecdsa, hash.u8, 32)]; - NSMutableData *s = [NSMutableData dataWithData:signedData]; - NSArray *elem = [transactionInput.inScript scriptElements]; - - [s appendUInt8:SIGHASH_ALL]; - [sig appendScriptPushData:s]; - - if (elem.count >= 2 && [elem[elem.count - 2] intValue] == OP_EQUALVERIFY) { // pay-to-pubkey-hash scriptSig - [sig appendScriptPushData:[DSKeyManager publicKeyData:key]]; + NSData *inScript = transactionInput.inScript; + for (NSValue *keyValue in keysSets) { + DMaybeOpaqueKeys *maybe_opaque_keys = keyValue.pointerValue; + if (maybe_opaque_keys->ok) { + DOpaqueKey *opaque_key = DOpaqueKeyUsedInTxInputScript(bytes_ctor(inScript), maybe_opaque_keys->ok, self.chain.chainType); + if (opaque_key) { + NSData *data = [self toDataWithSubscriptIndex:i anyoneCanPay:anyoneCanPay]; + NSData *sig = [DSTransaction signInput:data flags:SIGHASH_ALL inputScript:inScript withOpaqueKey:opaque_key]; + DOpaqueKeyDtor(opaque_key); + transactionInput.signature = sig; + } + } } + } + if (!self.isSigned) return NO; + _txHash = [self toData:anyoneCanPay].SHA256_2; + return YES; + } +} +- (BOOL)signWithPreorderedPrivateKeys:(NSArray *)keys { + // TODO: Function isn't used at all except commented out `testIdentityGrindingAttack` + @synchronized (self) { + for (NSUInteger i = 0; i < self.mInputs.count; i++) { + DSTransactionInput *transactionInput = self.mInputs[i]; + NSData *sig = [DSTransaction signInput:[self toDataWithSubscriptIndex:i anyoneCanPay:NO] flags:SIGHASH_ALL inputScript:transactionInput.inScript withOpaqueKeyValue:keys[i]]; transactionInput.signature = sig; } - if (!self.isSigned) return NO; _txHash = self.data.SHA256_2; return YES; } } ++ (NSData *)signInput:(NSData *)data + flags:(uint8_t)flags + inputScript:(NSData *)inputScript + withOpaqueKeyValue:(NSValue *)keyValue { + DMaybeOpaqueKey *key = ((DMaybeOpaqueKey *) keyValue.pointerValue); + return [self signInput:data flags:flags inputScript:inputScript withOpaqueKey:key->ok]; +} ++ (NSData *)signInput:(NSData *)data + flags:(uint8_t)flags + inputScript:(NSData *)inputScript + withOpaqueKey:(DOpaqueKey *)key { + Slice_u8 *input = slice_ctor(data); + Vec_u8 *tx_input_script = bytes_ctor(inputScript); + Vec_u8 *tx_sig = DOpaqueKeyCreateTxSig(key, input, flags, tx_input_script); + NSData *result = [DSKeyManager NSDataFrom:tx_sig]; + return result; +} + // MARK: - Priority (Deprecated) // priority = sum(input_amount_in_satoshis*input_age_in_blocks)/size_in_bytes @@ -715,27 +798,34 @@ - (BOOL)confirmed { // MARK: - Blockchain Identities -- (void)loadBlockchainIdentitiesFromDerivationPaths:(NSArray *)derivationPaths { - NSMutableSet *destinationBlockchainIdentities = [NSMutableSet set]; - NSMutableSet *sourceBlockchainIdentities = [NSMutableSet set]; +- (void)loadIdentitiesFromDerivationPaths:(NSArray *)derivationPaths { + NSMutableSet *destinationIdentities = [NSMutableSet set]; + NSMutableSet *sourceIdentities = [NSMutableSet set]; for (DSTransactionOutput *output in self.outputs) { for (DSFundsDerivationPath *derivationPath in derivationPaths) { if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]] && [derivationPath containsAddress:output.address]) { DSIncomingFundsDerivationPath *incomingFundsDerivationPath = ((DSIncomingFundsDerivationPath *)derivationPath); - DSBlockchainIdentity *destinationBlockchainIdentity = [incomingFundsDerivationPath contactDestinationBlockchainIdentity]; - DSBlockchainIdentity *sourceBlockchainIdentity = [incomingFundsDerivationPath contactSourceBlockchainIdentity]; - if (sourceBlockchainIdentity) { - [destinationBlockchainIdentities addObject:sourceBlockchainIdentity]; //these need to be inverted since the derivation path is incoming + DSIdentity *destinationIdentity = [self.chain identityForUniqueId:incomingFundsDerivationPath.contactDestinationIdentityUniqueId + foundInWallet:nil + includeForeignIdentities:YES]; + + DSIdentity *sourceIdentity = [self.chain identityForUniqueId:incomingFundsDerivationPath.contactSourceIdentityUniqueId + foundInWallet:nil + includeForeignIdentities:YES]; + + + if (sourceIdentity) { + [destinationIdentities addObject:sourceIdentity]; //these need to be inverted since the derivation path is incoming } - if (destinationBlockchainIdentity) { - [sourceBlockchainIdentities addObject:destinationBlockchainIdentity]; //these need to be inverted since the derivation path is incoming + if (destinationIdentity) { + [sourceIdentities addObject:destinationIdentity]; //these need to be inverted since the derivation path is incoming } } } } - self.sourceBlockchainIdentities = [self.sourceBlockchainIdentities setByAddingObjectsFromSet:[sourceBlockchainIdentities copy]]; - self.destinationBlockchainIdentities = [self.destinationBlockchainIdentities setByAddingObjectsFromSet:[destinationBlockchainIdentities copy]]; + self.sourceIdentities = [self.sourceIdentities setByAddingObjectsFromSet:[sourceIdentities copy]]; + self.destinationIdentities = [self.destinationIdentities setByAddingObjectsFromSet:[destinationIdentities copy]]; } // MARK: - Polymorphic data @@ -880,6 +970,27 @@ - (BOOL)saveInitialInContext:(NSManagedObjectContext *)context { @implementation DSTransaction (Extensions) - (DSTransactionDirection)direction { - return [_chain directionOfTransaction: self]; + if (self.cachedDirection != DSTransactionDirection_NotAccountFunds) { + return self.cachedDirection; + } + DSTransactionDirection direction; + const uint64_t sent = [_chain amountSentByTransaction:self]; + const uint64_t received = [_chain amountReceivedFromTransaction:self]; + const uint64_t fee = self.feeUsed; + if (sent > 0 && (received + fee) == sent) { + // moved + direction = DSTransactionDirection_Moved; + } else if (sent > 0) { + // sent + direction = DSTransactionDirection_Sent; + } else if (received > 0) { + // received + direction = DSTransactionDirection_Received; + } else { + // no funds moved on this account + direction = DSTransactionDirection_NotAccountFunds; + } + self.cachedDirection = direction; + return direction; } @end diff --git a/DashSync/shared/Models/Transactions/Base/DSTransactionInput.h b/DashSync/shared/Models/Transactions/Base/DSTransactionInput.h index 337686faf..f87289775 100644 --- a/DashSync/shared/Models/Transactions/Base/DSTransactionInput.h +++ b/DashSync/shared/Models/Transactions/Base/DSTransactionInput.h @@ -29,7 +29,11 @@ typedef union _UInt256 UInt256; @property (nonatomic, strong, nullable) NSData *signature; @property (nonatomic, assign) uint32_t sequence; -+ (instancetype)transactionInputWithHash:(UInt256)inputHash index:(uint32_t)index inScript:(NSData *)inScript signature:(NSData *_Nullable)signature sequence:(uint32_t)sequence; ++ (instancetype)transactionInputWithHash:(UInt256)inputHash + index:(uint32_t)index + inScript:(NSData *)inScript + signature:(NSData *_Nullable)signature + sequence:(uint32_t)sequence; - (NSComparisonResult)compare:(DSTransactionInput *)obj; diff --git a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction+Mndiff.m b/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction+Mndiff.m deleted file mode 100644 index 493351f89..000000000 --- a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction+Mndiff.m +++ /dev/null @@ -1,68 +0,0 @@ -// -// Created by Vladimir Pirogov -// Copyright © 2022 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 "DSCoinbaseTransaction+Mndiff.h" -#import "NSData+Dash.h" - -@implementation DSCoinbaseTransaction (Mndiff) - -/*+ (instancetype)coinbaseTransactionWith:(CoinbaseTransaction *)coinbaseTransaction onChain:(DSChain *)chain { - DSCoinbaseTransaction *ctx = [[DSCoinbaseTransaction alloc] init]; - Transaction *tx = coinbaseTransaction->base; - uintptr_t inputs_count = tx->inputs_count; - TransactionInput **tx_inputs = tx->inputs; - for (NSUInteger i = 0; i < inputs_count; i++) { - TransactionInput *input = tx_inputs[i]; - UInt256 hash = *(UInt256 *)input->input_hash; - uint32_t index = input->index; - uintptr_t script_length = input->script_length; - uintptr_t signature_length = input->signature_length; - NSData *script = script_length > 0 ? [NSData dataWithBytes:input->script length:script_length] : nil; - NSData *signature = signature_length > 0 ? [NSData dataWithBytes:input->signature length:signature_length] : nil; - uint32_t sequence = input->sequence; - [ctx addInputHash:hash - index:index - script:script - signature:signature - sequence:sequence]; - } - uintptr_t outputs_count = tx->outputs_count; - TransactionOutput **tx_outputs = tx->outputs; - for (NSUInteger i = 0; i < outputs_count; i++) { - TransactionOutput *output = tx_outputs[i]; - uint64_t amount = output->amount; - uintptr_t address_length = output->address_length; - uintptr_t script_length = output->script_length; - NSString *address = address_length > 0 ? [NSData dataWithBytes:output->address length:address_length].hexString : nil; - NSData *script = script_length > 0 ? [NSData dataWithBytes:output->script length:script_length] : nil; - [ctx addOutputScript:script - withAddress:address - amount:amount]; - } - ctx.height = coinbaseTransaction->height; - ctx.coinbaseTransactionVersion = coinbaseTransaction->coinbase_transaction_version; - ctx.merkleRootMNList = *(UInt256 *)coinbaseTransaction->merkle_root_mn_list; - ctx.merkleRootLLMQList = *(UInt256 *)coinbaseTransaction->merkle_root_llmq_list; - ctx.lockTime = tx->lock_time; - ctx.version = tx->version; - ctx.txHash = *(UInt256 *)tx->tx_hash; - ctx.type = tx->tx_type; - ctx.blockHeight = tx->block_height; - return ctx; -}*/ - -@end diff --git a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.h b/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.h index 8b504d7f4..1e729338d 100644 --- a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.h +++ b/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.h @@ -7,6 +7,9 @@ #import "DSTransaction.h" +#define COINBASE_TX_CORE_19 2 +#define COINBASE_TX_CORE_20 3 + @interface DSCoinbaseTransaction : DSTransaction @property (nonatomic, assign) uint16_t coinbaseTransactionVersion; diff --git a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.m b/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.m index 3c2370ba6..409107296 100644 --- a/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.m +++ b/DashSync/shared/Models/Transactions/Coinbase/DSCoinbaseTransaction.m @@ -5,12 +5,14 @@ // Created by Sam Westrich on 7/12/18. // +#import "DSChain+Params.h" #import "DSCoinbaseTransaction.h" #import "DSCoinbaseTransactionEntity+CoreDataClass.h" #import "DSTransactionFactory.h" #import "NSData+Dash.h" #import "NSMutableData+Dash.h" + @implementation DSCoinbaseTransaction - (instancetype)initWithMessage:(NSData *)message onChain:(DSChain *)chain { @@ -103,9 +105,10 @@ - (NSData *)payloadData { // Returns the binary transaction data that needs to be hashed and signed with the private key for the tx input at // subscriptIndex. A subscriptIndex of NSNotFound will return the entire signed transaction. -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex + anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; return [data appendCountedData:[self payloadData]]; } } diff --git a/DashSync/shared/Models/Transactions/DSTransactionFactory.m b/DashSync/shared/Models/Transactions/DSTransactionFactory.m index 4bc66e4d9..1153a3cb2 100644 --- a/DashSync/shared/Models/Transactions/DSTransactionFactory.m +++ b/DashSync/shared/Models/Transactions/DSTransactionFactory.m @@ -8,18 +8,12 @@ #import "DSAssetLockTransaction.h" #import "DSAssetUnlockTransaction.h" #import "DSTransactionFactory.h" -#import "DSBlockchainIdentityCloseTransition.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSBlockchainIdentityTopupTransition.h" -#import "DSBlockchainIdentityUpdateTransition.h" #import "DSCoinbaseTransaction.h" -#import "DSCreditFundingTransaction.h" #import "DSProviderRegistrationTransaction.h" #import "DSProviderUpdateRegistrarTransaction.h" #import "DSProviderUpdateRevocationTransaction.h" #import "DSProviderUpdateServiceTransaction.h" #import "DSQuorumCommitmentTransaction.h" -#import "DSTransition.h" #import "NSData+DSHash.h" #import "NSData+Dash.h" @@ -40,14 +34,8 @@ + (DSTransaction *)transactionWithMessage:(NSData *)message onChain:(DSChain *)c type = [message UInt16AtOffset:2]; } switch (type) { - case DSTransactionType_Classic: { - DSTransaction *transaction = [DSTransaction transactionWithMessage:message onChain:chain]; - if ([transaction isCreditFundingTransaction]) { - //replace with credit funding transaction - transaction = [DSCreditFundingTransaction transactionWithMessage:message onChain:chain]; - } - return transaction; - } + case DSTransactionType_Classic: + return [DSTransaction transactionWithMessage:message onChain:chain]; case DSTransactionType_Coinbase: return [DSCoinbaseTransaction transactionWithMessage:message onChain:chain]; case DSTransactionType_ProviderRegistration: diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderRegistrationTransaction.m b/DashSync/shared/Models/Transactions/Provider/DSProviderRegistrationTransaction.m index b49269d17..6d499f773 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderRegistrationTransaction.m +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderRegistrationTransaction.m @@ -6,7 +6,9 @@ // #import "DSProviderRegistrationTransaction.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" +#import "DSChain+Wallet.h" #import "DSChainManager+Protected.h" #import "DSMasternodeManager+LocalMasternode.h" #import "DSProviderRegistrationTransactionEntity+CoreDataClass.h" @@ -177,12 +179,14 @@ - (NSString *)payloadCollateralString { } - (UInt256)payloadCollateralDigest { - return [DSKeyManager proRegTXPayloadCollateralDigest:[self payloadDataForHash] - scriptPayout:self.scriptPayout - reward:self.operatorReward - ownerKeyHash:self.ownerKeyHash - voterKeyHash:self.votingKeyHash - chainType:self.chain.chainType].UInt256; + Slice_u8 *pld = slice_ctor([self payloadDataForHash]); + Slice_u8 *sp = slice_ctor(self.scriptPayout); + u160 *owner_hash = u160_ctor_u(self.ownerKeyHash); + u160 *voter_hash = u160_ctor_u(self.votingKeyHash); + u256 *result = DECDSAKeyProRegTxPayloadCollateralDigest(pld, sp, self.operatorReward, owner_hash, voter_hash, self.chain.chainType); + UInt256 digest = u256_cast(result); + u256_dtor(result); + return digest; } - (BOOL)checkPayloadSignature { @@ -212,6 +216,7 @@ - (NSData *)basePayloadData { [data appendUInt16:CFSwapInt16BigToHost(self.platformP2PPort)]; [data appendUInt16:CFSwapInt16BigToHost(self.platformHTTPPort)]; } +// NSLog(@"basePayloadData: %@", data.hexString); return data; } @@ -226,12 +231,14 @@ - (NSData *)payloadData { [data appendData:[self basePayloadData]]; [data appendUInt8:self.payloadSignature.length]; [data appendData:self.payloadSignature]; +// NSLog(@"payloadData: %@", data.hexString); return data; } -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex + anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; [data appendCountedData:[self payloadData]]; if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; return data; diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.h b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.h index 7ab4fc1ff..15c99f016 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.h +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.h @@ -6,7 +6,7 @@ // #import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSTransaction.h" NS_ASSUME_NONNULL_BEGIN @@ -56,11 +56,11 @@ providerUpdateRegistrarTransactionVersion:(uint16_t)version - (void)updateInputsHash; -- (void)signPayloadWithKey:(OpaqueKey *_Nonnull)privateKey; +- (void)signPayloadWithKey:(DOpaqueKey *_Nonnull)privateKey; - (BOOL)checkPayloadSignature; -- (BOOL)checkPayloadSignature:(OpaqueKey *_Nonnull)publicKey; +- (BOOL)checkPayloadSignature:(DOpaqueKey *_Nonnull)publicKey; @end diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.m b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.m index 07a340f71..604062d1d 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.m +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRegistrarTransaction.m @@ -6,6 +6,7 @@ // #import "DSProviderUpdateRegistrarTransaction.h" +#import "DSChain+Transaction.h" #import "DSChainManager.h" #import "DSLocalMasternode.h" #import "DSMasternodeManager.h" @@ -136,20 +137,35 @@ - (UInt256)payloadHash { return [self payloadDataForHash].SHA256_2; } -- (BOOL)checkPayloadSignature:(OpaqueKey *)providerOwnerPublicKey { - return key_check_payload_signature(providerOwnerPublicKey, self.providerRegistrationTransaction.ownerKeyHash.u8); +- (BOOL)checkPayloadSignature:(DOpaqueKey *)providerOwnerPublicKey { + u160 *owner_key_hash = u160_ctor_u(self.providerRegistrationTransaction.ownerKeyHash); + BOOL result = DOpaqueKeyCheckPayloadSignature(providerOwnerPublicKey, owner_key_hash); + return result; } - (BOOL)checkPayloadSignature { NSData *signature = self.payloadSignature; NSData *payload = [self payloadDataForHash]; - return key_ecdsa_verify_compact_sig(signature.bytes, signature.length, payload.bytes, payload.length, self.providerRegistrationTransaction.ownerKeyHash.u8); + Slice_u8 *slice = slice_ctor(signature); + u256 *digest = u256_ctor(payload); + DMaybeECDSAKey *result = DECDSAKeyWithCompactSig(slice, digest); + if (!result->ok) { + DMaybeECDSAKeyDtor(result); + return NO; + } else { + u160 *hashed = DECDSAKeyPublicKeyHash(result->ok); + BOOL verified = uint160_eq(self.providerRegistrationTransaction.ownerKeyHash, u160_cast(hashed)); + DMaybeECDSAKeyDtor(result); + return verified; + } } -- (void)signPayloadWithKey:(OpaqueKey *)privateKey { +- (void)signPayloadWithKey:(DOpaqueKey *)privateKey { //ATTENTION If this ever changes from ECDSA, change the max signature size defined above //DSLogPrivate(@"Private Key is %@", [privateKey serializedPrivateKeyForChain:self.chain]); - self.payloadSignature = [DSKeyManager NSDataFrom:key_ecdsa_compact_sign(privateKey->ecdsa, [self payloadHash].u8)];; + Arr_u8_65 *sig = DECDSAKeyCompactSign(privateKey->ecdsa, slice_u256_ctor_u([self payloadHash])); + self.payloadSignature = NSDataFromPtr(sig); + Arr_u8_65_destroy(sig); } - (NSData *)basePayloadData { @@ -180,9 +196,10 @@ - (NSData *)payloadData { return data; } -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex + anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; NSData *payloadData = [self payloadData]; [data appendVarInt:payloadData.length]; [data appendData:payloadData]; diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.h b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.h index 4ea6e8d19..b81fd4508 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.h +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.h @@ -6,7 +6,7 @@ // #import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "dash_spv_apple_bindings.h" #import "DSTransaction.h" NS_ASSUME_NONNULL_BEGIN @@ -30,11 +30,11 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateInputsHash; -- (void)signPayloadWithKey:(OpaqueKey *_Nonnull)privateKey; +- (void)signPayloadWithKey:(DOpaqueKey *_Nonnull)privateKey; - (BOOL)checkPayloadSignature; -- (BOOL)checkPayloadSignature:(OpaqueKey *)publicKey; +- (BOOL)checkPayloadSignature:(DOpaqueKey *)publicKey; @end diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.m b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.m index 7de1b9d2c..eee818fea 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.m +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateRevocationTransaction.m @@ -6,6 +6,7 @@ // #import "DSProviderUpdateRevocationTransaction.h" +#import "DSChain+Transaction.h" #import "DSChainManager.h" #import "DSLocalMasternode.h" #import "DSMasternodeManager.h" @@ -104,25 +105,23 @@ - (UInt256)payloadHash { - (BOOL)checkPayloadSignature { NSAssert(self.providerRegistrationTransaction, @"We need a provider registration transaction"); - return key_bls_verify(self.providerRegistrationTransaction.operatorKey.u8, - ![self.providerRegistrationTransaction usesBasicBLS], - [self payloadHash].u8, - [self payloadSignature].bytes); + u384 *pubkey = u384_ctor_u(self.providerRegistrationTransaction.operatorKey); + Slice_u8 *digest = slice_u256_ctor_u([self payloadHash]); + u768 *sig = u768_ctor([self payloadSignature]); + BOOL verified = DBLSKeyVerifySig(pubkey, ![self.providerRegistrationTransaction usesBasicBLS], digest, sig); + return verified; } -- (BOOL)checkPayloadSignature:(OpaqueKey *)publicKey { +- (BOOL)checkPayloadSignature:(DOpaqueKey *)publicKey { return [DSKeyManager verifyMessageDigest:publicKey digest:[self payloadHash] signature:[self payloadSignature]]; } -- (void)signPayloadWithKey:(OpaqueKey *)privateKey { +- (void)signPayloadWithKey:(DOpaqueKey *)privateKey { NSData *payload = [self payloadDataForHash]; - BLSKey *bls; - if (privateKey->tag == OpaqueKey_BLSBasic) - bls = privateKey->bls_basic; - else - bls = privateKey->bls_legacy; - - self.payloadSignature = [DSKeyManager NSDataFrom:key_bls_sign_data(bls, payload.bytes, payload.length)]; + Slice_u8 *p = slice_ctor(payload); + u768 *signed_data = DBLSKeySignData(privateKey->bls, p); + self.payloadSignature = NSDataFromPtr(signed_data); + u768_dtor(signed_data); } - (NSData *)basePayloadData { @@ -148,9 +147,10 @@ - (NSData *)payloadData { return data; } -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex + anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; [data appendCountedData:[self payloadData]]; if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; return data; diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.h b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.h index fd46f5085..b6addfd63 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.h +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.h @@ -6,7 +6,7 @@ // #import "BigIntTypes.h" -#import "dash_shared_core.h" +#import "DSKeyManager.h" #import "DSTransaction.h" NS_ASSUME_NONNULL_BEGIN @@ -53,11 +53,11 @@ providerUpdateServiceTransactionVersion:(uint16_t)version - (void)updateInputsHash; -- (void)signPayloadWithKey:(OpaqueKey *_Nonnull)privateKey; +- (void)signPayloadWithKey:(DOpaqueKey *_Nonnull)privateKey; - (BOOL)checkPayloadSignature; -- (BOOL)checkPayloadSignature:(OpaqueKey *)publicKey; +- (BOOL)checkPayloadSignature:(DOpaqueKey *)publicKey; @end diff --git a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.m b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.m index cbd2ee993..c3d39b1fc 100644 --- a/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.m +++ b/DashSync/shared/Models/Transactions/Provider/DSProviderUpdateServiceTransaction.m @@ -6,6 +6,7 @@ // #import "DSProviderUpdateServiceTransaction.h" +#import "DSChain+Transaction.h" #import "DSChainManager.h" #import "DSLocalMasternode.h" #import "DSMasternodeManager.h" @@ -134,25 +135,23 @@ - (UInt256)payloadHash { - (BOOL)checkPayloadSignature { NSAssert(self.providerRegistrationTransaction, @"We need a provider registration transaction"); - return key_bls_verify(self.providerRegistrationTransaction.operatorKey.u8, - ![self.providerRegistrationTransaction usesBasicBLS], - [self payloadHash].u8, - [self payloadSignature].bytes); + u384 *pub_key = u384_ctor_u(self.providerRegistrationTransaction.operatorKey); + Slice_u8 *digest = slice_u256_ctor_u([self payloadHash]); + u768 *sig = u768_ctor([self payloadSignature]); + BOOL verified = DBLSKeyVerifySig(pub_key, ![self.providerRegistrationTransaction usesBasicBLS], digest, sig); + return verified; } -- (BOOL)checkPayloadSignature:(OpaqueKey *)publicKey { +- (BOOL)checkPayloadSignature:(DOpaqueKey *)publicKey { return [DSKeyManager verifyMessageDigest:publicKey digest:[self payloadHash] signature:[self payloadSignature]]; } -- (void)signPayloadWithKey:(OpaqueKey *)privateKey { +- (void)signPayloadWithKey:(DOpaqueKey *)privateKey { NSData *data = [self payloadDataForHash]; - BLSKey *bls; - if (privateKey->tag == OpaqueKey_BLSBasic) - bls = privateKey->bls_basic; - else - bls = privateKey->bls_legacy; - - self.payloadSignature = [DSKeyManager NSDataFrom:key_bls_sign_data(bls, data.bytes, data.length)]; + Slice_u8 *slice = slice_ctor(data); + u768 *sig = DBLSKeySignData(privateKey->bls, slice); + self.payloadSignature = NSDataFromPtr(sig); + u768_dtor(sig); } - (NSString *_Nullable)payoutAddress { @@ -197,9 +196,10 @@ - (NSData *)payloadData { return data; } -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex + anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; [data appendCountedData:[self payloadData]]; if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; return data; diff --git a/DashSync/shared/Models/Transactions/Quorums/DSQuorumCommitmentTransaction.m b/DashSync/shared/Models/Transactions/Quorums/DSQuorumCommitmentTransaction.m index 7b4325e65..500e09d95 100644 --- a/DashSync/shared/Models/Transactions/Quorums/DSQuorumCommitmentTransaction.m +++ b/DashSync/shared/Models/Transactions/Quorums/DSQuorumCommitmentTransaction.m @@ -132,9 +132,10 @@ - (NSData *)payloadData { return data; } -- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex { +- (NSData *)toDataWithSubscriptIndex:(NSUInteger)subscriptIndex + anyoneCanPay:(BOOL)anyoneCanPay { @synchronized(self) { - NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex] mutableCopy]; + NSMutableData *data = [[super toDataWithSubscriptIndex:subscriptIndex anyoneCanPay:anyoneCanPay] mutableCopy]; [data appendCountedData:[self payloadData]]; if (subscriptIndex != NSNotFound) [data appendUInt32:SIGHASH_ALL]; return data; diff --git a/DashSync/shared/Models/Wallet/DSAccount.h b/DashSync/shared/Models/Wallet/DSAccount.h index 3db72ad65..3bf196bc0 100644 --- a/DashSync/shared/Models/Wallet/DSAccount.h +++ b/DashSync/shared/Models/Wallet/DSAccount.h @@ -23,6 +23,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#import "DSAssetLockTransaction.h" #import "DSFundsDerivationPath.h" #import "DSIncomingFundsDerivationPath.h" #import "DSTransaction.h" @@ -35,151 +36,166 @@ NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *_Nonnull const DSAccountNewAccountFromTransactionNotification; FOUNDATION_EXPORT NSString *_Nonnull const DSAccountNewAccountShouldBeAddedFromTransactionNotification; -@class DSFundsDerivationPath, DSIncomingFundsDerivationPathDSWallet, DSBlockchainIdentityRegistrationTransition, DSBlockchainIdentityUpdateTransition, DSCreditFundingTransaction; -@class DSCoinbaseTransaction, DSPotentialOneWayFriendship; +@class DSFundsDerivationPath, DSIncomingFundsDerivationPathDSWallet, DSCoinbaseTransaction, DSCoinControl, DSPotentialOneWayFriendship; @interface DSAccount : NSObject // BIP 43 derivation paths @property (nullable, nonatomic, readonly) NSArray *fundDerivationPaths; - @property (nullable, nonatomic, readonly) NSArray *outgoingFundDerivationPaths; - @property (nullable, nonatomic, strong) DSFundsDerivationPath *defaultDerivationPath; - @property (nullable, nonatomic, readonly) DSFundsDerivationPath *bip44DerivationPath; - @property (nullable, nonatomic, readonly) DSFundsDerivationPath *bip32DerivationPath; - @property (nullable, nonatomic, readonly) DSDerivationPath *masterContactsDerivationPath; - +@property (nullable, nonatomic, readonly) DSFundsDerivationPath *coinJoinDerivationPath; @property (nullable, nonatomic, weak) DSWallet *wallet; - @property (nonatomic, readonly) NSString *uniqueID; - @property (nonatomic, readonly) uint32_t accountNumber; - @property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext; - // current wallet balance excluding transactions known to be invalid @property (nonatomic, readonly) uint64_t balance; - // NSValue objects containing UTXO structs @property (nonatomic, readonly) NSArray *unspentOutputs; - // latest 100 transactions sorted by date, most recent first @property (atomic, readonly) NSArray *recentTransactions; - // latest 100 transactions sorted by date, most recent first @property (atomic, readonly) NSArray *recentTransactionsWithInternalOutput; - // all wallet transactions sorted by date, most recent first @property (atomic, readonly) NSArray *allTransactions; - // all wallet transactions sorted by date, most recent first @property (atomic, readonly) NSArray *coinbaseTransactions; - // Does this account have any coinbase rewards @property (nonatomic, readonly) BOOL hasCoinbaseTransaction; - // returns the first unused external address @property (nullable, nonatomic, readonly) NSString *receiveAddress; - // returns the first unused internal address @property (nullable, nonatomic, readonly) NSString *changeAddress; - // all previously generated external addresses @property (nonatomic, readonly) NSArray *externalAddresses; - // all previously generated internal addresses @property (nonatomic, readonly) NSArray *internalAddresses; - +// returns the first unused coinjoin address +@property (nullable, nonatomic, readonly) NSString *coinJoinReceiveAddress; +// returns the first unused coinjoin internal address +@property (nullable, nonatomic, readonly) NSString *coinJoinChangeAddress; +// returns all issued CoinJoin receive addresses +@property (nullable, nonatomic, readonly) NSArray *usedCoinJoinReceiveAddresses; +// returns all used CoinJoin receive addresses +@property (nullable, nonatomic, readonly) NSArray *allCoinJoinReceiveAddresses; // all the contacts for an account @property (nonatomic, readonly) NSArray *_Nonnull contacts; - // has an extended public key missing in one of the account derivation paths @property (nonatomic, readonly) BOOL hasAnExtendedPublicKeyMissing; -- (NSArray *_Nullable)registerAddressesWithGapLimit:(NSUInteger)gapLimit unusedAccountGapLimit:(NSUInteger)unusedAccountGapLimit dashpayGapLimit:(NSUInteger)dashpayGapLimit internal:(BOOL)internal error:(NSError **)error; - -+ (DSAccount *)accountWithAccountNumber:(uint32_t)accountNumber withDerivationPaths:(NSArray *)derivationPaths inContext:(NSManagedObjectContext *_Nullable)context; - -+ (NSArray *)standardAccountsToAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain inContext:(NSManagedObjectContext *_Nullable)context; - -- (instancetype)initWithAccountNumber:(uint32_t)accountNumber withDerivationPaths:(NSArray *)derivationPaths inContext:(NSManagedObjectContext *_Nullable)context; - -- (instancetype)initAsViewOnlyWithAccountNumber:(uint32_t)accountNumber withDerivationPaths:(NSArray *)derivationPaths inContext:(NSManagedObjectContext *_Nullable)context; - +- (NSArray *_Nullable)registerAddressesWithInitialGapLimit; +- (NSArray *_Nullable)registerAddressesWithProlongGapLimit; + ++ (DSAccount *)accountWithAccountNumber:(uint32_t)accountNumber + withDerivationPaths:(NSArray *)derivationPaths + inContext:(NSManagedObjectContext *_Nullable)context; + ++ (NSArray *)standardAccountsToAccountNumber:(uint32_t)accountNumber + onChain:(DSChain *)chain + inContext:(NSManagedObjectContext *_Nullable)context; +- (instancetype)initWithAccountNumber:(uint32_t)accountNumber + withDerivationPaths:(NSArray *)derivationPaths + inContext:(NSManagedObjectContext *_Nullable)context; +- (instancetype)initAsViewOnlyWithAccountNumber:(uint32_t)accountNumber + withDerivationPaths:(NSArray *)derivationPaths + inContext:(NSManagedObjectContext *_Nullable)context; - (void)removeDerivationPath:(DSDerivationPath *)derivationPath; - - (DSIncomingFundsDerivationPath *)derivationPathForFriendshipWithIdentifier:(NSData *)friendshipIdentifier; - - (void)removeIncomingDerivationPathForFriendshipWithIdentifier:(NSData *)friendshipIdentifier; - - (void)addDerivationPath:(DSDerivationPath *)derivationPath; +- (void)addIncomingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath + forFriendshipIdentifier:(NSData *)friendshipIdentifier + inContext:(NSManagedObjectContext *)context; -- (void)addIncomingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath forFriendshipIdentifier:(NSData *)friendshipIdentifier inContext:(NSManagedObjectContext *)context; - -- (void)addOutgoingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath forFriendshipIdentifier:(NSData *)friendshipIdentifier inContext:(NSManagedObjectContext *)context; - -- (void)addDerivationPathsFromArray:(NSArray *)derivationPaths; +- (void)addOutgoingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath + forFriendshipIdentifier:(NSData *)friendshipIdentifier + inContext:(NSManagedObjectContext *)context; // largest amount that can be sent from the account after fees @property (nonatomic, readonly) uint64_t maxOutputAmount; -- (uint64_t)maxOutputAmountWithConfirmationCount:(uint64_t)confirmationCount returnInputCount:(uint32_t *_Nullable)rInputCount; - +- (uint64_t)maxOutputAmountWithConfirmationCount:(uint64_t)confirmationCount + returnInputCount:(uint32_t *_Nullable)rInputCount; // true if the address is controlled by the wallet - (BOOL)containsAddress:(NSString *)address; +// true if the coinjoin address is controlled by the wallet +- (BOOL)containsCoinJoinAddress:(NSString *)coinJoinAddress; + // true if the address is internal and is controlled by the wallet - (BOOL)containsInternalAddress:(NSString *)address; - // true if the address is external and is controlled by the wallet - (BOOL)containsExternalAddress:(NSString *)address; - // true if the address is controlled by the wallet except for evolution addresses - (BOOL)baseDerivationPathsContainAddress:(NSString *)address; - // the high level (hardened) derivation path containing the address - (DSDerivationPath *_Nullable)derivationPathContainingAddress:(NSString *)address; - // the high level (hardened) derivation path containing the address that is external to the wallet, basically a friend's address - (DSIncomingFundsDerivationPath *_Nullable)externalDerivationPathContainingAddress:(NSString *)address; - - (BOOL)transactionAddressAlreadySeenInOutputs:(NSString *)address; - // true if the address was previously used as an input or output in any wallet transaction (from this wallet only) - (BOOL)addressIsUsed:(NSString *)address; - // returns an unsigned transaction that sends the specified amount from the wallet to the given address -- (DSTransaction *_Nullable)transactionFor:(uint64_t)amount to:(NSString *)address withFee:(BOOL)fee; - +- (DSTransaction *_Nullable)transactionFor:(uint64_t)amount + to:(NSString *)address + withFee:(BOOL)fee; // returns an unsigned transaction that sends the specified amount from the wallet to the given address intended for conversion to L2 credits -- (DSCreditFundingTransaction *_Nullable)creditFundingTransactionFor:(uint64_t)amount to:(NSString *)address withFee:(BOOL)fee; +- (DSAssetLockTransaction *)assetLockTransactionFor:(uint64_t)amount + to:(NSString *)address + withFee:(BOOL)fee; // returns an unsigned transaction that sends the specified amounts from the wallet to the specified output scripts - (DSTransaction *_Nullable)transactionForAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee; -// returns an unsigned transaction that sends the specified amounts from the wallet to the specified output scripts -- (DSTransaction *_Nullable)transactionForAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee toShapeshiftAddress:(NSString *_Nullable)shapeshiftAddress; - -- (DSTransaction *)updateTransaction:(DSTransaction *)transaction forAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee; +- (DSTransaction *)transactionForAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee + coinControl:(DSCoinControl *)coinControl; +// returns an unsigned transaction that sends the specified amounts from the wallet to the specified output scripts +- (DSTransaction *_Nullable)transactionForAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee + toShapeshiftAddress:(NSString *_Nullable)shapeshiftAddress; + +- (DSTransaction *)updateTransaction:(DSTransaction *)transaction + forAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee; +- (DSTransaction *)updateTransaction:(DSTransaction *)transaction + forAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee + sortType:(DSTransactionSortType)sortType; -- (DSTransaction *)updateTransaction:(DSTransaction *)transaction forAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee sortType:(DSTransactionSortType)sortType; +/// Sign any inputs in the given transaction that can be signed using private keys from the wallet +/// +/// - Parameters: +/// - transaction: Instance of `DSTransaction` you want to sign +/// +/// - Returns: boolean value indicating if the transaction was signed +/// +/// - Note: Using this method to sign a tx doesn't present pin controller, use this method carefully from UI +/// +- (BOOL)signTransaction:(DSTransaction *)transaction; /// Sign any inputs in the given transaction that can be signed using private keys from the wallet /// /// - Parameters: /// - transaction: Instance of `DSTransaction` you want to sign -/// - completion: Completion block that has type `TransactionValidityCompletionBlock` +/// - anyoneCanPay: apply SIGHASH_ANYONECANPAY signature type +/// +/// - Returns: boolean value indicating if the transaction was signed /// /// - Note: Using this method to sign a tx doesn't present pin controller, use this method carefully from UI /// -- (void)signTransaction:(DSTransaction *)transaction completion:(_Nonnull TransactionValidityCompletionBlock)completion; +- (BOOL)signTransaction:(DSTransaction *)transaction anyoneCanPay:(BOOL)anyoneCanPay; /// Sign any inputs in the given transaction that can be signed using private keys from the wallet /// @@ -189,25 +205,32 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSAccountNewAccountShouldBeAddedFromT /// /// - Note: Using this method to sign a tx presents pin controller for auth purpose /// -- (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *_Nullable)authprompt completion:(_Nonnull TransactionValidityCompletionBlock)completion; +- (void)signTransaction:(DSTransaction *)transaction + withPrompt:(NSString *_Nullable)authprompt + completion:(_Nonnull TransactionValidityCompletionBlock)completion; // true if the given transaction is associated with the account (even if it hasn't been registered), false otherwise - (BOOL)canContainTransaction:(DSTransaction *)transaction; +//- (BOOL)canContainRustTransaction:(Result_ok_dashcore_blockdata_transaction_Transaction_err_dash_spv_platform_error_Error *)transaction; // adds a transaction to the account, or returns false if it isn't associated with the account -- (BOOL)registerTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately; +- (BOOL)registerTransaction:(DSTransaction *)transaction + saveImmediately:(BOOL)saveImmediately; // this is used to save transactions atomically with the block, needs to be called before switching threads to save the block - (void)prepareForIncomingTransactionPersistenceForBlockSaveWithNumber:(uint32_t)blockNumber; // this is used to save transactions atomically with the block -- (void)persistIncomingTransactionsAttributesForBlockSaveWithNumber:(uint32_t)blockNumber inContext:(NSManagedObjectContext *)context; +- (void)persistIncomingTransactionsAttributesForBlockSaveWithNumber:(uint32_t)blockNumber + inContext:(NSManagedObjectContext *)context; // removes a transaction from the account along with any transactions that depend on its outputs, returns TRUE if a transaction was removed -- (BOOL)removeTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately; +- (BOOL)removeTransaction:(DSTransaction *)transaction + saveImmediately:(BOOL)saveImmediately; // removes a transaction by hash from the account along with any transactions that depend on its outputs, returns TRUE if a transaction was removed -- (BOOL)removeTransactionWithHash:(UInt256)txHash saveImmediately:(BOOL)saveImmediately; +- (BOOL)removeTransactionWithHash:(UInt256)txHash + saveImmediately:(BOOL)saveImmediately; // returns the transaction with the given hash if it's been registered in the account (might also return non-registered) - (DSTransaction *_Nullable)transactionForHash:(UInt256)txHash; @@ -215,6 +238,11 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSAccountNewAccountShouldBeAddedFromT // true if no previous account transaction spends any of the given transaction's inputs, and no inputs are invalid - (BOOL)transactionIsValid:(DSTransaction *)transaction; +- (BOOL)isSpent:(NSValue *)output; + +// returns input value if no previous account transaction spends this input, and the input is valid, -1 otherwise. +- (int64_t)inputValue:(UInt256)txHash inputIndex:(uint32_t)index; + // received, sent or moved inside an account - (DSTransactionDirection)directionOfTransaction:(DSTransaction *)transaction; @@ -242,18 +270,20 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSAccountNewAccountShouldBeAddedFromT // retuns the amount sent from the account by the trasaction (total account outputs consumed, change and fee included) - (uint64_t)amountSentByTransaction:(DSTransaction *)transaction; +// Returns the amounts sent by the transaction +- (NSArray *)amountsSentByTransaction:(DSTransaction *)transaction; + // returns the external (receive) addresses of a transaction - (NSArray *)externalAddressesOfTransaction:(DSTransaction *)transaction; // returns the fee for the given transaction if all its inputs are from wallet transactions, UINT64_MAX otherwise - (uint64_t)feeForTransaction:(DSTransaction *)transaction; -// historical wallet balance after the given transaction, or current balance if transaction is not registered in wallet -- (uint64_t)balanceAfterTransaction:(DSTransaction *)transaction; - - (void)chainUpdatedBlockHeight:(int32_t)height; -- (NSArray *)setBlockHeight:(int32_t)height andTimestamp:(NSTimeInterval)timestamp forTransactionHashes:(NSArray *)txHashes; +- (NSArray *)setBlockHeight:(int32_t)height + andTimestamp:(NSTimeInterval)timestamp + forTransactionHashes:(NSArray *)txHashes; // This loads the derivation paths addresses once the account is set to a wallet - (void)loadDerivationPaths; @@ -269,7 +299,8 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSAccountNewAccountShouldBeAddedFromT // given a private key, queries api.dashwallet.com for unspent outputs and calls the completion block with a signed // transaction that will sweep the balance into wallet (doesn't publish the tx) -- (void)sweepPrivateKey:(NSString *)privKey withFee:(BOOL)fee +- (void)sweepPrivateKey:(NSString *)privKey + withFee:(BOOL)fee completion:(void (^_Nonnull)(DSTransaction *_Nonnull tx, uint64_t fee, NSError *_Null_unspecified error))completion; @end diff --git a/DashSync/shared/Models/Wallet/DSAccount.m b/DashSync/shared/Models/Wallet/DSAccount.m index b0a9fe375..dd338bb47 100644 --- a/DashSync/shared/Models/Wallet/DSAccount.m +++ b/DashSync/shared/Models/Wallet/DSAccount.m @@ -24,14 +24,13 @@ // THE SOFTWARE. #import "DSAccount.h" +#import "DSChain+Identity.h" +#import "DSChain+Params.h" #import "DSChain+Protected.h" #import "DSFundsDerivationPath.h" +#import "DSGapLimit.h" #import "DSWallet+Protected.h" -#import "DSBlockchainIdentityCloseTransition.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSBlockchainIdentityTopupTransition.h" -#import "DSBlockchainIdentityUpdateTransition.h" #import "DSProviderRegistrationTransaction.h" #import "DSProviderUpdateRegistrarTransaction.h" #import "DSProviderUpdateRevocationTransaction.h" @@ -49,8 +48,8 @@ #import "DSBIP39Mnemonic.h" #import "DSChainEntity+CoreDataClass.h" #import "DSCoinbaseTransaction.h" -#import "DSCreditFundingTransaction.h" #import "DSDerivationPathEntity+CoreDataClass.h" +#import "DSDerivationPathFactory.h" #import "DSGovernanceSyncManager.h" #import "DSIncomingFundsDerivationPath.h" #import "DSInsightManager.h" @@ -61,6 +60,7 @@ #import "DSTransactionFactory.h" #import "DSTransactionInput.h" #import "DSTransactionOutput.h" +#import "DSCoinControl.h" #import "NSData+Dash.h" #import "NSDate+Utils.h" #import "NSError+Dash.h" @@ -73,6 +73,12 @@ #define AUTH_SWEEP_KEY @"AUTH_SWEEP_KEY" #define AUTH_SWEEP_FEE @"AUTH_SWEEP_FEE" +#define ERROR_CANT_CREATE_KEY [NSError errorWithCode:187 localizedDescriptionKey:@"Can't create key"] +#define ERROR_INVALID_PRIVATE_KEY [NSError errorWithCode:187 localizedDescriptionKey:@"Not a valid private key"] +#define ERROR_PRIVATE_KEY_ALREADY_IN_WALLET [NSError errorWithCode:187 localizedDescriptionKey:@"This private key is already in your wallet"] +#define ERROR_EMPTY_PRIVATE_KEY [NSError errorWithCode:417 localizedDescriptionKey:@"This private key is empty"] +#define ERROR_NO_FUNDS_FOR_FEE [NSError errorWithCode:417 localizedDescriptionKey: @"Transaction fees would cost more than the funds available on this private key (due to tiny \"dust\" deposits)"] +#define ERROR_SIGNING [NSError errorWithCode:401 localizedDescriptionKey:@"Error signing transaction"] @class DSFundsDerivationPath, DSIncomingFundsDerivationPath, DSAccount; @@ -109,6 +115,8 @@ @interface DSAccount () @property (nonatomic, strong) DSDerivationPath *masterContactsDerivationPath; +@property (nonatomic, strong) DSFundsDerivationPath *coinJoinDerivationPath; + @property (nonatomic, assign) BOOL isViewOnlyAccount; @property (nonatomic, assign) UInt256 firstTransactionHash; @@ -120,11 +128,17 @@ @implementation DSAccount : NSObject // MARK: - Initiation -+ (DSAccount *)accountWithAccountNumber:(uint32_t)accountNumber withDerivationPaths:(NSArray *)derivationPaths inContext:(NSManagedObjectContext *_Nullable)context { - return [[self alloc] initWithAccountNumber:accountNumber withDerivationPaths:derivationPaths inContext:context]; ++ (DSAccount *)accountWithAccountNumber:(uint32_t)accountNumber + withDerivationPaths:(NSArray *)derivationPaths + inContext:(NSManagedObjectContext *_Nullable)context { + return [[self alloc] initWithAccountNumber:accountNumber + withDerivationPaths:derivationPaths + inContext:context]; } -+ (NSArray *)standardAccountsToAccountNumber:(uint32_t)accountNumber onChain:(DSChain *)chain inContext:(NSManagedObjectContext *_Nullable)context { ++ (NSArray *)standardAccountsToAccountNumber:(uint32_t)accountNumber + onChain:(DSChain *)chain + inContext:(NSManagedObjectContext *_Nullable)context { NSMutableArray *accounts = [NSMutableArray array]; for (uint32_t i = 0; i < accountNumber + 1; i++) { [accounts addObject:[self accountWithAccountNumber:i withDerivationPaths:[chain standardDerivationPathsForAccountNumber:i] inContext:context]]; @@ -160,7 +174,13 @@ - (void)verifyAndAssignAddedDerivationPaths:(NSArray *)deriv NSAssert(TRUE, @"There should only be one master contacts derivation path"); } self.masterContactsDerivationPath = derivationPath; + } else if (derivationPath.reference == DSDerivationPathReference_CoinJoin) { + if (self.coinJoinDerivationPath) { + NSAssert(TRUE, @"There should only be one CoinJoin derivation path"); + } + self.coinJoinDerivationPath = (DSFundsDerivationPath *)derivationPath; } + for (int j = i + 1; j < [derivationPaths count]; j++) { DSDerivationPath *derivationPath2 = [derivationPaths objectAtIndex:j]; NSAssert([derivationPath isDerivationPathEqual:derivationPath2] == NO, @"Derivation paths should all be different"); @@ -172,9 +192,11 @@ - (void)verifyAndAssignAddedDerivationPaths:(NSArray *)deriv } } -- (instancetype)initWithAccountNumber:(uint32_t)accountNumber withDerivationPaths:(NSArray *)derivationPaths inContext:(NSManagedObjectContext *)context { +- (instancetype)initWithAccountNumber:(uint32_t)accountNumber + withDerivationPaths:(NSArray *)derivationPaths + inContext:(NSManagedObjectContext *)context { NSParameterAssert(derivationPaths); - + if (!(self = [super init])) return nil; _accountNumber = accountNumber; [self verifyAndAssignAddedDerivationPaths:derivationPaths]; @@ -198,12 +220,12 @@ - (instancetype)initWithAccountNumber:(uint32_t)accountNumber withDerivationPath - (instancetype)initAsViewOnlyWithAccountNumber:(uint32_t)accountNumber withDerivationPaths:(NSArray *)derivationPaths inContext:(NSManagedObjectContext *)context { NSParameterAssert(derivationPaths); - + if (!(self = [self initWithAccountNumber:accountNumber withDerivationPaths:derivationPaths inContext:context])) return nil; self.isViewOnlyAccount = TRUE; self.transactionsToSave = [NSMutableArray array]; self.transactionsToSaveInBlockSave = [NSMutableDictionary dictionary]; - + return self; } @@ -228,17 +250,17 @@ - (void)loadTransactions { DSLogPrivate(@"[%@] Transaction %@", _wallet.chain.name, [transaction longDescription]); } #endif - + NSUInteger transactionCount = [DSTransactionEntity countObjectsInContext:self.managedObjectContext matching:@"transactionHash.chain == %@", [self.wallet.chain chainEntityInContext:self.managedObjectContext]]; if (transactionCount > self.allTx.count) { // pre-fetch transaction inputs and outputs @autoreleasepool { NSFetchRequest *fetchRequest = [DSTxOutputEntity fetchRequest]; - + //for some reason it is faster to search by the wallet unique id on the account, then it is by the account itself, this might change if there are more than 1 account; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"account.walletUniqueID = %@ && account.index = %@", self.wallet.uniqueIDString, @(self.accountNumber)]; [fetchRequest setRelationshipKeyPathsForPrefetching:@[@"transaction.inputs", @"transaction.outputs", @"transaction.transactionHash", @"spentInInput.transaction.inputs", @"spentInInput.transaction.outputs", @"spentInInput.transaction.transactionHash"]]; - + NSError *fetchRequestError = nil; //NSDate *transactionOutputsStartTime = [NSDate date]; NSArray *transactionOutputs = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchRequestError]; @@ -283,12 +305,12 @@ - (void)loadDerivationPaths { } } } else { - for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { + for (DSDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { - [derivationPath registerAddressesWithGapLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL internal:NO error:nil]; + [derivationPath registerAddressesWithSettings:[DSGapLimit withLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL]]; } else { - [derivationPath registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INITIAL internal:YES error:nil]; - [derivationPath registerAddressesWithGapLimit:SEQUENCE_GAP_LIMIT_INITIAL internal:NO error:nil]; + [derivationPath registerAddressesWithSettings:[DSGapLimitFunds internal:SEQUENCE_GAP_LIMIT_INITIAL]]; + [derivationPath registerAddressesWithSettings:[DSGapLimitFunds external:SEQUENCE_GAP_LIMIT_INITIAL]]; } } } @@ -323,7 +345,6 @@ - (NSString *)uniqueID { - (uint32_t)blockHeight { static uint32_t height = 0; uint32_t h = self.wallet.chain.lastSyncBlockHeight; - if (h > height) height = h; return height; } @@ -338,6 +359,32 @@ - (NSString *)changeAddress { return self.defaultDerivationPath.changeAddress; } +// returns the first unused coinjoin address +- (NSString *)coinJoinReceiveAddress { + NSString *address = self.coinJoinDerivationPath.receiveAddress; + dispatch_sync(self.wallet.chain.networkingQueue, ^{ + [self.coinJoinDerivationPath registerTransactionAddress:address]; + }); + return address; +} + +// returns the first unused coinjoin address +- (NSString *)coinJoinChangeAddress { + NSString *address = self.coinJoinDerivationPath.changeAddress; + dispatch_sync(self.wallet.chain.networkingQueue, ^{ + [self.coinJoinDerivationPath registerTransactionAddress:address]; + }); + return address; +} + +- (NSArray *)allCoinJoinReceiveAddresses { + return [self.coinJoinDerivationPath allReceiveAddresses]; +} + +- (NSArray *)usedCoinJoinReceiveAddresses { + return [self.coinJoinDerivationPath usedReceiveAddresses]; +} + // NSData objects containing serialized UTXOs - (NSArray *)unspentOutputs { return self.utxos.array; @@ -347,74 +394,58 @@ - (NSArray *)unspentOutputs { - (void)removeDerivationPath:(DSDerivationPath *)derivationPath { NSParameterAssert(derivationPath); - - if ([self.mFundDerivationPaths containsObject:derivationPath]) { + + if ([self.mFundDerivationPaths containsObject:derivationPath]) [self.mFundDerivationPaths removeObject:derivationPath]; - } } - (void)removeIncomingDerivationPathForFriendshipWithIdentifier:(NSData *)friendshipIdentifier { NSParameterAssert(friendshipIdentifier); DSIncomingFundsDerivationPath *derivationPath = [self.mContactIncomingFundDerivationPathsDictionary objectForKey:friendshipIdentifier]; - if (derivationPath) { + if (derivationPath) [self removeDerivationPath:derivationPath]; - } } - (DSIncomingFundsDerivationPath *)derivationPathForFriendshipWithIdentifier:(NSData *)friendshipIdentifier { NSParameterAssert(friendshipIdentifier); DSIncomingFundsDerivationPath *derivationPath = [self.mContactIncomingFundDerivationPathsDictionary objectForKey:friendshipIdentifier]; - if (derivationPath) return derivationPath; - derivationPath = [self.mContactOutgoingFundDerivationPathsDictionary objectForKey:friendshipIdentifier]; - return derivationPath; + return derivationPath ?: [self.mContactOutgoingFundDerivationPathsDictionary objectForKey:friendshipIdentifier]; } - (void)addDerivationPath:(DSDerivationPath *)derivationPath { NSParameterAssert(derivationPath); - - if (!_isViewOnlyAccount) { + if (!_isViewOnlyAccount) [self verifyAndAssignAddedDerivationPaths:@[derivationPath]]; - } - if ([self verifyDerivationPathNotAlreadyPresent:derivationPath]) { + if ([self verifyDerivationPathNotAlreadyPresent:derivationPath]) [self.mFundDerivationPaths addObject:derivationPath]; - } } -- (void)addIncomingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath forFriendshipIdentifier:(NSData *)friendshipIdentifier inContext:(NSManagedObjectContext *)context { +- (void)addIncomingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath + forFriendshipIdentifier:(NSData *)friendshipIdentifier + inContext:(NSManagedObjectContext *)context { NSParameterAssert(derivationPath); NSParameterAssert(friendshipIdentifier); NSAssert(derivationPath.length, @"derivation path must have a length"); derivationPath.account = self; [self addDerivationPath:derivationPath]; [self.mContactIncomingFundDerivationPathsDictionary setObject:derivationPath forKey:friendshipIdentifier]; - if ([derivationPath hasExtendedPublicKey]) { + if ([derivationPath hasExtendedPublicKey]) [derivationPath loadAddressesInContext:context]; - } [self updateBalance]; } -- (void)addOutgoingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath forFriendshipIdentifier:(NSData *)friendshipIdentifier inContext:(NSManagedObjectContext *)context { +- (void)addOutgoingDerivationPath:(DSIncomingFundsDerivationPath *)derivationPath + forFriendshipIdentifier:(NSData *)friendshipIdentifier + inContext:(NSManagedObjectContext *)context { NSParameterAssert(derivationPath); NSParameterAssert(friendshipIdentifier); - NSAssert(derivationPath.sourceIsLocal || !derivationPath.length, @"derivation path must not have a length unless it is on device"); + + BOOL sourceIsLocal = !![derivationPath.chain identityForUniqueId:derivationPath.contactSourceIdentityUniqueId]; + NSAssert(sourceIsLocal || !derivationPath.length, @"derivation path must not have a length unless it is on device"); derivationPath.account = self; [self.mContactOutgoingFundDerivationPathsDictionary setObject:derivationPath forKey:friendshipIdentifier]; - if ([derivationPath hasExtendedPublicKey]) { + if ([derivationPath hasExtendedPublicKey]) [derivationPath loadAddressesInContext:context]; - } -} - -- (void)addDerivationPathsFromArray:(NSArray *)derivationPaths { - NSParameterAssert(derivationPaths); - - if (!_isViewOnlyAccount) { - [self verifyAndAssignAddedDerivationPaths:derivationPaths]; - } - for (DSDerivationPath *derivationPath in derivationPaths) { - if ([self verifyDerivationPathNotAlreadyPresent:derivationPath]) { - [self.mFundDerivationPaths addObject:derivationPath]; - } - } } - (NSArray *)fundDerivationPaths { @@ -446,18 +477,42 @@ - (BOOL)hasAnExtendedPublicKeyMissing { return NO; } -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit unusedAccountGapLimit:(NSUInteger)unusedAccountGapLimit dashpayGapLimit:(NSUInteger)dashpayGapLimit internal:(BOOL)internal error:(NSError **)error { +- (NSArray *)registerAddressesWithInitialGapLimit { NSMutableArray *mArray = [NSMutableArray array]; for (DSDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { - DSFundsDerivationPath *fundsDerivationPath = (DSFundsDerivationPath *)derivationPath; - NSUInteger registerGapLimit = [fundsDerivationPath shouldUseReducedGapLimit] ? unusedAccountGapLimit : gapLimit; - [mArray addObjectsFromArray:[fundsDerivationPath registerAddressesWithGapLimit:registerGapLimit internal:internal error:error]]; - } else if (!internal && [derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { - [mArray addObjectsFromArray:[(DSIncomingFundsDerivationPath *)derivationPath registerAddressesWithGapLimit:dashpayGapLimit error:error]]; + DSFundsDerivationPath *path = (DSFundsDerivationPath *)derivationPath; + BOOL useReduced = [path shouldUseReducedGapLimit]; + BOOL isAnonymous = path.type == DSDerivationPathType_AnonymousFunds; + NSUInteger limit = useReduced ? SEQUENCE_UNUSED_GAP_LIMIT_INITIAL : (isAnonymous ? SEQUENCE_GAP_LIMIT_INITIAL_COINJOIN : SEQUENCE_GAP_LIMIT_INITIAL); + [mArray addObjectsFromArray:[path registerAddressesWithSettings:[DSGapLimitFunds external:limit]]]; + [mArray addObjectsFromArray:[path registerAddressesWithSettings:[DSGapLimitFunds internal:limit]]]; + } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { + NSArray *addresses = [derivationPath registerAddressesWithSettings:[DSGapLimit withLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INITIAL]]; + [mArray addObjectsFromArray:addresses]; + } + } + return [mArray copy]; +} + +- (NSArray *)registerAddressesWithProlongGapLimit { + NSMutableArray *mArray = [NSMutableArray array]; + for (DSDerivationPath *derivationPath in self.fundDerivationPaths) { + if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { + DSFundsDerivationPath *path = (DSFundsDerivationPath *)derivationPath; + BOOL useReduced = [path shouldUseReducedGapLimit]; + BOOL isAnonymous = path.type == DSDerivationPathType_AnonymousFunds; + NSUInteger externalLimit = useReduced ? SEQUENCE_UNUSED_GAP_LIMIT_EXTERNAL : (isAnonymous ? SEQUENCE_GAP_LIMIT_INITIAL_COINJOIN : SEQUENCE_GAP_LIMIT_EXTERNAL); + NSUInteger internalLimit = useReduced ? SEQUENCE_GAP_LIMIT_INTERNAL : (isAnonymous ? SEQUENCE_GAP_LIMIT_INITIAL_COINJOIN : SEQUENCE_GAP_LIMIT_INTERNAL); + [mArray addObjectsFromArray:[path registerAddressesWithSettings:[DSGapLimitFunds external:externalLimit]]]; + [mArray addObjectsFromArray:[path registerAddressesWithSettings:[DSGapLimitFunds internal:internalLimit]]]; + } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { + NSArray *addresses = [derivationPath registerAddressesWithSettings:[DSGapLimit withLimit:SEQUENCE_DASHPAY_GAP_LIMIT_INCOMING]]; + [mArray addObjectsFromArray:addresses]; } } return mArray; + } // all previously generated external addresses @@ -509,13 +564,26 @@ - (BOOL)containsAddress:(NSString *)address { //in case address is of type [NSNull null] return FALSE; } - + for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath containsAddress:address]) return TRUE; } return FALSE; } +// true if the coinjoin address is controlled by the wallet +- (BOOL)containsCoinJoinAddress:(NSString *)coinJoinAddress { + NSParameterAssert(coinJoinAddress); + if (![coinJoinAddress isKindOfClass:[NSString class]]) { + //in case address is of type [NSNull null] + return FALSE; + } + + if ([self.coinJoinDerivationPath containsAddress:coinJoinAddress]) return TRUE; + + return FALSE; +} + // true if the address is controlled by the wallet - (BOOL)containsInternalAddress:(NSString *)address { NSParameterAssert(address); @@ -523,7 +591,7 @@ - (BOOL)containsInternalAddress:(NSString *)address { //in case address is of type [NSNull null] return FALSE; } - + for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]] && [derivationPath containsChangeAddress:address]) { return TRUE; @@ -538,7 +606,7 @@ - (BOOL)baseDerivationPathsContainAddress:(NSString *)address { //in case address is of type [NSNull null] return FALSE; } - + for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]] && [derivationPath containsAddress:address]) { return TRUE; @@ -554,7 +622,7 @@ - (BOOL)containsExternalAddress:(NSString *)address { //in case address is of type [NSNull null] return FALSE; } - + for (DSDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { if ([(DSFundsDerivationPath *)derivationPath containsReceiveAddress:address]) return TRUE; @@ -571,7 +639,7 @@ - (DSIncomingFundsDerivationPath *)externalDerivationPathContainingAddress:(NSSt //in case address is of type [NSNull null] return nil; } - + for (DSIncomingFundsDerivationPath *derivationPath in self.mContactOutgoingFundDerivationPathsDictionary.allValues) { if ([derivationPath containsAddress:address]) return derivationPath; } @@ -585,7 +653,7 @@ - (BOOL)addressIsUsed:(NSString *)address { //in case address is of type [NSNull null] return FALSE; } - + for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { if ([derivationPath addressIsUsed:address]) return TRUE; } @@ -598,11 +666,11 @@ - (BOOL)transactionAddressAlreadySeenInOutputs:(NSString *)address { //in case address is of type [NSNull null] return FALSE; } - + for (DSTransaction *transaction in self.allTransactions) { if ([transaction.outputs indexOfObjectPassingTest:^BOOL(DSTransactionOutput *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - return [obj.address isEqual:address]; - }] != NSNotFound) return TRUE; + return [obj.address isEqual:address]; + }] != NSNotFound) return TRUE; } return FALSE; } @@ -614,13 +682,12 @@ - (void)updateBalance { NSMutableOrderedSet *utxos = [NSMutableOrderedSet orderedSet]; NSMutableSet *spentOutputs = [NSMutableSet set], *invalidTx = [NSMutableSet set], *pendingTransactionHashes = [NSMutableSet set]; NSMutableDictionary *pendingCoinbaseLockedTransactionHashes = [NSMutableDictionary dictionary]; - NSMutableArray *balanceHistory = [NSMutableArray array]; uint32_t now = [NSDate timeIntervalSince1970]; - + for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { derivationPath.balance = 0; } - + for (DSTransaction *tx in [self.transactions reverseObjectEnumerator]) { #if LOG_BALANCE_UPDATE DSLogPrivate(@"updating balance after transaction %@", [NSData dataWithUInt256:tx.txHash].reverse.hexString); @@ -630,7 +697,7 @@ - (void)updateBalance { NSSet *inputs; uint32_t n = 0; BOOL pending = NO; - + if (!tx.isCoinbaseClassicTransaction && ![tx isKindOfClass:[DSCoinbaseTransaction class]]) { NSMutableArray *rHashes = [NSMutableArray array]; @@ -644,25 +711,24 @@ - (void)updateBalance { if (tx.blockHeight == TX_UNCONFIRMED && ([spent intersectsSet:spentOutputs] || [inputs intersectsSet:invalidTx])) { [invalidTx addObject:uint256_obj(tx.txHash)]; - [balanceHistory insertObject:@(balance) atIndex:0]; continue; } } else { inputs = [NSSet set]; } - + [spentOutputs unionSet:spent]; // add inputs to spent output set n = 0; - + // check if any inputs are pending if (tx.blockHeight == TX_UNCONFIRMED) { if (tx.size > TX_MAX_SIZE) { pending = YES; // check transaction size is under TX_MAX_SIZE } - + for (DSTransactionInput *input in tx.inputs) { if (input.sequence == UINT32_MAX) continue; - + if (tx.lockTime < TX_MAX_LOCK_HEIGHT && tx.lockTime > self.wallet.chain.bestBlockHeight + 1) { pending = YES; // future lockTime @@ -682,7 +748,7 @@ - (void)updateBalance { #endif } } - + for (DSTransactionOutput *output in tx.outputs) { // check that no outputs are dust if (output.amount < TX_MIN_OUTPUT_AMOUNT) { pending = YES; @@ -694,24 +760,22 @@ - (void)updateBalance { } } } - + if (pending || [inputs intersectsSet:pendingTransactionHashes]) { [pendingTransactionHashes addObject:uint256_obj(tx.txHash)]; - [balanceHistory insertObject:@(balance) atIndex:0]; continue; } - + uint32_t lockedBlockHeight = [self transactionOutputsAreLockedTill:tx]; - + if (lockedBlockHeight) { if (![pendingCoinbaseLockedTransactionHashes objectForKey:@(lockedBlockHeight)]) { pendingCoinbaseLockedTransactionHashes[@(lockedBlockHeight)] = [NSMutableSet set]; } [((NSMutableSet *)pendingCoinbaseLockedTransactionHashes[@(lockedBlockHeight)]) addObject:uint256_obj(tx.txHash)]; - [balanceHistory insertObject:@(balance) atIndex:0]; continue; } - + //TODO: don't add outputs below TX_MIN_OUTPUT_AMOUNT //TODO: don't add coin generation outputs < 100 blocks deep //NOTE: balance/UTXOs will then need to be recalculated when last block changes @@ -729,15 +793,15 @@ - (void)updateBalance { } n++; } - + // transaction ordering is not guaranteed, so check the entire UTXO set against the entire spent output set [spent setSet:utxos.set]; [spent intersectSet:spentOutputs]; - + for (NSValue *output in spent) { // remove any spent outputs from UTXO set DSTransaction *transaction; DSUTXO o; - + [output getValue:&o]; transaction = self.allTx[uint256_obj(o.hash)]; [utxos removeObject:output]; @@ -751,10 +815,9 @@ - (void)updateBalance { } } } - + if (prevBalance < balance) totalReceived += balance - prevBalance; if (balance < prevBalance) totalSent += prevBalance - balance; - [balanceHistory insertObject:@(balance) atIndex:0]; prevBalance = balance; #if LOG_BALANCE_UPDATE DSLog(@"===UTXOS==="); @@ -772,19 +835,18 @@ - (void)updateBalance { #endif } } - + self.invalidTransactionHashes = invalidTx; self.pendingTransactionHashes = pendingTransactionHashes; self.pendingCoinbaseLockedTransactionHashes = pendingCoinbaseLockedTransactionHashes; self.spentOutputs = spentOutputs; self.utxos = utxos; - self.balanceHistory = balanceHistory; _totalSent = totalSent; _totalReceived = totalReceived; - + if (balance != _balance) { _balance = balance; - + dispatch_async(dispatch_get_main_queue(), ^{ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(postBalanceDidChangeNotification) object:nil]; [self performSelector:@selector(postBalanceDidChangeNotification) withObject:nil afterDelay:0.1]; @@ -792,16 +854,6 @@ - (void)updateBalance { } } -// historical wallet balance after the given transaction, or current balance if transaction is not registered in wallet -- (uint64_t)balanceAfterTransaction:(DSTransaction *)transaction { - NSParameterAssert(transaction); - - NSUInteger i = [self.transactions indexOfObject:transaction]; - - return (i < self.balanceHistory.count) ? [self.balanceHistory[i] unsignedLongLongValue] : self.balance; -} - - - (void)postBalanceDidChangeNotification { [[NSNotificationCenter defaultCenter] postNotificationName:DSWalletBalanceDidChangeNotification object:nil]; } @@ -816,7 +868,7 @@ static NSUInteger transactionAddressIndex(DSTransaction *transaction, NSArray *a NSUInteger i = [addressChain indexOfObject:output.address]; if (i != NSNotFound) return i; } - + return NSNotFound; } @@ -824,38 +876,38 @@ static NSUInteger transactionAddressIndex(DSTransaction *transaction, NSArray *a // each block, however correct transaction ordering cannot be relied upon for determining wallet balance or UTXO set - (void)sortTransactions { @synchronized (self) { - BOOL (^isAscending)(id, id); - __block __weak BOOL (^_isAscending)(id, id) = isAscending = ^BOOL(DSTransaction *tx1, DSTransaction *tx2) { + __block BOOL (^isAscending)(id, id) = ^BOOL(DSTransaction *tx1, DSTransaction *tx2) { if (!tx1 || !tx2) return NO; if (tx1.blockHeight > tx2.blockHeight) return YES; if (tx1.blockHeight < tx2.blockHeight) return NO; NSValue *hash1 = uint256_obj(tx1.txHash), *hash2 = uint256_obj(tx2.txHash); if ([tx1.inputs indexOfObjectPassingTest:^BOOL(DSTransactionInput *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - return uint256_eq(obj.inputHash, tx2.txHash); - }] != NSNotFound) return YES; + return uint256_eq(obj.inputHash, tx2.txHash); + }] != NSNotFound) return YES; if ([tx2.inputs indexOfObjectPassingTest:^BOOL(DSTransactionInput *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - return uint256_eq(obj.inputHash, tx1.txHash); - }] != NSNotFound) return NO; + return uint256_eq(obj.inputHash, tx1.txHash); + }] != NSNotFound) return NO; if ([self.invalidTransactionHashes containsObject:hash1] && ![self.invalidTransactionHashes containsObject:hash2]) return YES; if ([self.pendingTransactionHashes containsObject:hash1] && ![self.pendingTransactionHashes containsObject:hash2]) return YES; - for (DSTransactionInput *input in tx1.inputs) { - if (_isAscending(self.allTx[uint256_obj(input.inputHash)], tx2)) return YES; - } + return NO; }; - + + NSArray *externalAddresses = self.externalAddresses; + NSArray *internalAddresses = self.internalAddresses; + [self.transactions sortWithOptions:NSSortStable usingComparator:^NSComparisonResult(id tx1, id tx2) { - if (isAscending(tx1, tx2)) return NSOrderedAscending; - if (isAscending(tx2, tx1)) return NSOrderedDescending; - - NSUInteger i = transactionAddressIndex(tx1, self.internalAddresses); - NSUInteger j = transactionAddressIndex(tx2, (i == NSNotFound) ? self.externalAddresses : self.internalAddresses); - - if (i == NSNotFound && j != NSNotFound) i = transactionAddressIndex(tx1, self.externalAddresses); - if (i == NSNotFound || j == NSNotFound || i == j) return NSOrderedSame; - return (i > j) ? NSOrderedAscending : NSOrderedDescending; - }]; + if (isAscending(tx1, tx2)) return NSOrderedAscending; + if (isAscending(tx2, tx1)) return NSOrderedDescending; + + NSUInteger i = transactionAddressIndex(tx1, internalAddresses); + NSUInteger j = transactionAddressIndex(tx2, (i == NSNotFound) ? externalAddresses : internalAddresses); + + if (i == NSNotFound && j != NSNotFound) i = transactionAddressIndex(tx1, externalAddresses); + if (i == NSNotFound || j == NSNotFound || i == j) return NSOrderedSame; + return (i > j) ? NSOrderedAscending : NSOrderedDescending; + }]; } } @@ -873,7 +925,7 @@ - (DSTransaction *)transactionForHash:(UInt256)txHash { // last 100 transactions sorted by date, most recent first - (NSArray *)recentTransactions { return [self.transactions.array subarrayWithRange:NSMakeRange(0, (self.transactions.count > 100) ? 100 : - self.transactions.count)]; + self.transactions.count)]; } // last 100 transactions sorted by date, most recent first @@ -931,16 +983,18 @@ - (BOOL)canContainTransaction:(DSTransaction *)transaction { return YES; } if ([transaction isKindOfClass:[DSProviderRegistrationTransaction class]]) { - DSProviderRegistrationTransaction *providerRegistrationTransaction = (DSProviderRegistrationTransaction *)transaction; - if ([self containsAddress:providerRegistrationTransaction.payoutAddress]) return YES; + DSProviderRegistrationTransaction *tx = (DSProviderRegistrationTransaction *)transaction; + if ([self containsAddress:tx.payoutAddress]) return YES; } else if ([transaction isKindOfClass:[DSProviderUpdateServiceTransaction class]]) { - DSProviderUpdateServiceTransaction *providerUpdateServiceTransaction = (DSProviderUpdateServiceTransaction *)transaction; - NSString *payoutAddress = providerUpdateServiceTransaction.payoutAddress; + DSProviderUpdateServiceTransaction *tx = (DSProviderUpdateServiceTransaction *)transaction; + NSString *payoutAddress = tx.payoutAddress; if (payoutAddress && [self containsAddress:payoutAddress]) return YES; } else if ([transaction isKindOfClass:[DSProviderUpdateRegistrarTransaction class]]) { - DSProviderUpdateRegistrarTransaction *providerUpdateRegistrarTransaction = (DSProviderUpdateRegistrarTransaction *)transaction; - if ([self containsAddress:providerUpdateRegistrarTransaction.payoutAddress]) return YES; + DSProviderUpdateRegistrarTransaction *tx = (DSProviderUpdateRegistrarTransaction *)transaction; + if ([self containsAddress:tx.payoutAddress]) return YES; } + // TODO: asset locks/unlocks/transitions? +// else if ([transaction isKindOfClass:[DSAs]]) return NO; } @@ -948,19 +1002,22 @@ - (BOOL)canContainTransaction:(DSTransaction *)transaction { - (BOOL)checkIsFirstTransaction:(DSTransaction *)transaction { NSParameterAssert(transaction); - - for (DSDerivationPath *derivationPath in self.fundDerivationPaths) { - if ([derivationPath type] & DSDerivationPathType_IsForFunds) { - NSString *firstAddress; - if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { - firstAddress = [(DSFundsDerivationPath *)derivationPath addressAtIndex:0 internal:NO]; - } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { - firstAddress = [(DSIncomingFundsDerivationPath *)derivationPath addressAtIndex:0]; + for (DSDerivationPath *path in self.fundDerivationPaths) { + if ([path type] & DSDerivationPathType_IsForFunds) { + NSIndexPath *indexPath; + if ([path isKindOfClass:[DSFundsDerivationPath class]]) { + indexPath = [NSIndexPath indexPathWithIndexes:(const NSUInteger[]){0, 0} length:2]; + } else if ([path isKindOfClass:[DSIncomingFundsDerivationPath class]]) { + indexPath = [NSIndexPath indexPathWithIndexes:(const NSUInteger[]){0} length:1]; } - if ([transaction.outputs indexOfObjectPassingTest:^BOOL(DSTransactionOutput *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + if (indexPath) { + NSData *firstPubKeyData = [path publicKeyDataAtIndexPath:indexPath]; + NSString *firstAddress = [DSKeyManager ecdsaKeyAddressFromPublicKeyData:firstPubKeyData forChainType:path.chain.chainType]; + if ([transaction.outputs indexOfObjectPassingTest:^BOOL(DSTransactionOutput *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { return [obj.address isEqual:firstAddress]; }] != NSNotFound) { - return TRUE; + return TRUE; + } } } } @@ -970,54 +1027,95 @@ - (BOOL)checkIsFirstTransaction:(DSTransaction *)transaction { // MARK: = Creation // returns an unsigned transaction that sends the specified amount from the wallet to the given address -- (DSTransaction *)transactionFor:(uint64_t)amount to:(NSString *)address withFee:(BOOL)fee { +- (DSTransaction *)transactionFor:(uint64_t)amount + to:(NSString *)address + withFee:(BOOL)fee { NSParameterAssert(address); NSData *script = [DSKeyManager scriptPubKeyForAddress:address forChain:self.wallet.chain]; - return [self transactionForAmounts:@[@(amount)] toOutputScripts:@[script] withFee:fee]; + return [self transactionForAmounts:@[@(amount)] + toOutputScripts:@[script] + withFee:fee]; } // returns an unsigned transaction that sends the specified amount from the wallet to the given address -- (DSCreditFundingTransaction *)creditFundingTransactionFor:(uint64_t)amount to:(NSString *)address withFee:(BOOL)fee { +- (DSAssetLockTransaction *)assetLockTransactionFor:(uint64_t)amount + to:(NSString *)address + withFee:(BOOL)fee { NSParameterAssert(address); - - NSMutableData *script = [NSMutableData data]; - - [script appendCreditBurnScriptPubKeyForAddress:address forChain:self.wallet.chain]; - - DSCreditFundingTransaction *transaction = [[DSCreditFundingTransaction alloc] initOnChain:self.wallet.chain]; - return (DSCreditFundingTransaction *)[self updateTransaction:transaction forAmounts:@[@(amount)] toOutputScripts:@[script] withFee:fee sortType:DSTransactionSortType_BIP69]; + NSData *script = [NSData scriptPubKeyForAddress:address forChain:self.wallet.chain]; + DSTransactionOutput *creditOutput = [DSTransactionOutput transactionOutputWithAmount:amount outScript:script onChain:self.wallet.chain]; + DSAssetLockTransaction *transaction = [[DSAssetLockTransaction alloc] initOnChain:self.wallet.chain withCreditOutputs:@[creditOutput]]; + return (DSAssetLockTransaction *)[self updateTransaction:transaction + forAmounts:@[@(amount)] + toOutputScripts:@[[NSData assetLockOutputScript]] + withFee:fee + sortType:DSTransactionSortType_BIP69]; +} +- (nonnull DSTransaction *)transactionForAmounts:(nonnull NSArray *)amounts + toOutputScripts:(nonnull NSArray *)scripts + withFee:(BOOL)fee + coinControl:(nonnull DSCoinControl *)coinControl { + return [self transactionForAmounts:amounts + toOutputScripts:scripts + withFee:fee + toShapeshiftAddress:nil + coinControl:coinControl]; + +} +- (DSTransaction * _Nullable)transactionForAmounts:(nonnull NSArray *)amounts + toOutputScripts:(nonnull NSArray *)scripts + withFee:(BOOL)fee + toShapeshiftAddress:(NSString * _Nullable)shapeshiftAddress { + return [self transactionForAmounts:amounts + toOutputScripts:scripts + withFee:fee + toShapeshiftAddress:shapeshiftAddress + coinControl:nil]; } - // returns an unsigned transaction that sends the specified amounts from the wallet to the specified output scripts -- (DSTransaction *)transactionForAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee { - return [self transactionForAmounts:amounts toOutputScripts:scripts withFee:fee toShapeshiftAddress:nil]; -} - -- (DSTransaction *)transactionForAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee toShapeshiftAddress:(NSString *)shapeshiftAddress { +- (DSTransaction *)transactionForAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee { + return [self transactionForAmounts:amounts + toOutputScripts:scripts + withFee:fee + toShapeshiftAddress:nil + coinControl:nil]; +} + +- (DSTransaction *)transactionForAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee + toShapeshiftAddress:(NSString *)shapeshiftAddress + coinControl:(DSCoinControl *)coinControl { NSParameterAssert(amounts); NSParameterAssert(scripts); DSTransaction *transaction = [[DSTransaction alloc] initOnChain:self.wallet.chain]; - return [self updateTransaction:transaction forAmounts:amounts toOutputScripts:scripts withFee:fee toShapeshiftAddress:shapeshiftAddress sortType:DSTransactionSortType_BIP69]; + return [self updateTransaction:transaction forAmounts:amounts toOutputScripts:scripts withFee:fee toShapeshiftAddress:shapeshiftAddress sortType:DSTransactionSortType_BIP69 coinControl:coinControl]; } // MARK: == Proposal Transaction Creation - (DSTransaction *)proposalCollateralTransactionWithData:(NSData *)data { NSParameterAssert(data); - NSMutableData *script = [NSMutableData data]; - [script appendProposalInfo:data]; - return [self transactionForAmounts:@[@(PROPOSAL_COST)] toOutputScripts:@[script] withFee:TRUE]; } // MARK: = Update // returns an unsigned transaction that sends the specified amounts from the wallet to the specified output scripts -- (DSTransaction *)updateTransaction:(DSTransaction *)transaction forAmounts:(NSArray *)amounts toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee { - return [self updateTransaction:transaction forAmounts:amounts toOutputScripts:scripts withFee:fee sortType:DSTransactionSortType_BIP69]; +- (DSTransaction *)updateTransaction:(DSTransaction *)transaction + forAmounts:(NSArray *)amounts + toOutputScripts:(NSArray *)scripts + withFee:(BOOL)fee { + return [self updateTransaction:transaction + forAmounts:amounts + toOutputScripts:scripts + withFee:fee + sortType:DSTransactionSortType_BIP69]; } - (DSTransaction *)updateTransaction:(DSTransaction *)transaction @@ -1025,7 +1123,13 @@ - (DSTransaction *)updateTransaction:(DSTransaction *)transaction toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee sortType:(DSTransactionSortType)sortType { - return [self updateTransaction:transaction forAmounts:amounts toOutputScripts:scripts withFee:fee toShapeshiftAddress:nil sortType:sortType]; + return [self updateTransaction:transaction + forAmounts:amounts + toOutputScripts:scripts + withFee:fee + toShapeshiftAddress:nil + sortType:sortType + coinControl:nil]; } // returns an unsigned transaction that sends the specified amounts from the wallet to the specified output scripts @@ -1034,7 +1138,8 @@ - (DSTransaction *)updateTransaction:(DSTransaction *)transaction toOutputScripts:(NSArray *)scripts withFee:(BOOL)fee toShapeshiftAddress:(NSString *)shapeshiftAddress - sortType:(DSTransactionSortType)sortType { + sortType:(DSTransactionSortType)sortType + coinControl:(DSCoinControl *)coinControl { NSParameterAssert(transaction); NSParameterAssert(amounts); NSParameterAssert(scripts); @@ -1052,24 +1157,24 @@ - (DSTransaction *)updateTransaction:(DSTransaction *)transaction //TODO: use up all UTXOs for all used addresses to avoid leaving funds in addresses whose public key is revealed //TODO: avoid combining addresses in a single transaction when possible to reduce information leakage //TODO: use up UTXOs received from any of the output scripts that this transaction sends funds to, to mitigate an - // attacker double spending and requesting a refund + // attacker double spending and requesting a refund for (NSValue *output in self.utxos) { [output getValue:&o]; + + if (coinControl && ![coinControl isSelected:o]) continue; tx = self.allTx[uint256_obj(o.hash)]; if ([self transactionOutputsAreLocked:tx]) continue; if (!tx) continue; if ([transaction isMemberOfClass:[DSProviderRegistrationTransaction class]]) { DSProviderRegistrationTransaction *providerRegistrationTransaction = (DSProviderRegistrationTransaction *)transaction; - if (dsutxo_eq(providerRegistrationTransaction.collateralOutpoint, o)) { + if (dsutxo_eq(providerRegistrationTransaction.collateralOutpoint, o)) continue; //don't spend the collateral - } DSUTXO reversedCollateral = (DSUTXO){.hash = uint256_reverse(providerRegistrationTransaction.collateralOutpoint.hash), providerRegistrationTransaction.collateralOutpoint.n}; - - if (dsutxo_eq(reversedCollateral, o)) { + if (dsutxo_eq(reversedCollateral, o)) continue; //don't spend the collateral - } } + [transaction addInputHash:tx.txHash index:o.n script:tx.outputs[o.n].outScript]; @@ -1113,34 +1218,37 @@ - (DSTransaction *)updateTransaction:(DSTransaction *)transaction if (balance == amount + feeAmount || balance >= amount + feeAmount + self.wallet.chain.minOutputAmount) break; } - if (!feeAmount) { + if (!feeAmount) feeAmount = [self.wallet.chain feeForTxSize:transaction.size + TX_OUTPUT_SIZE + cpfpSize]; // assume we will add a change output - } if (balance < amount + feeAmount) { // insufficient funds DSLog(@"[%@] Insufficient funds. %llu is less than transaction amount:%llu", self.wallet.chain.name, balance, amount + feeAmount); return nil; } - if (shapeshiftAddress) { + if (shapeshiftAddress) [transaction addOutputShapeshiftAddress:shapeshiftAddress]; - } BOOL followBIP69sorting = sortType == DSTransactionSortType_BIP69; - if (followBIP69sorting) { + if (followBIP69sorting) [transaction sortInputsAccordingToBIP69]; - } - if (balance - (amount + feeAmount) >= self.wallet.chain.minOutputAmount) { - [transaction addOutputAddress:self.changeAddress amount:balance - (amount + feeAmount)]; + NSString *changeAddress; + + if (coinControl.destChange) { + changeAddress = coinControl.destChange; + } else { + changeAddress = self.changeAddress; + } + + [transaction addOutputAddress:changeAddress amount:balance - (amount + feeAmount)]; + if (followBIP69sorting) { [transaction sortOutputsAccordingToBIP69]; } else if (sortType == DSTransactionSortType_Shuffle) { [transaction shuffleOutputOrder]; } } - [transaction hasSetInputsAndOutputs]; - return transaction; } } @@ -1153,7 +1261,9 @@ - (void)chainUpdatedBlockHeight:(int32_t)height { // set the block heights and timestamps for the given transactions, use a height of TX_UNCONFIRMED and timestamp of 0 to // indicate a transaction and it's dependents should remain marked as unverified (not 0-conf safe) -- (NSArray *)setBlockHeight:(int32_t)height andTimestamp:(NSTimeInterval)timestamp forTransactionHashes:(NSArray *)txHashes { +- (NSArray *)setBlockHeight:(int32_t)height + andTimestamp:(NSTimeInterval)timestamp + forTransactionHashes:(NSArray *)txHashes { @synchronized (self) { NSMutableArray *hashes = [NSMutableArray array], *updated = [NSMutableArray array]; BOOL needsUpdate = NO; @@ -1199,7 +1309,8 @@ - (NSArray *)setBlockHeight:(int32_t)height andTimestamp:(NSTimeInterval)timesta // MARK: = Removal // removes a transaction from the wallet along with any transactions that depend on its outputs -- (BOOL)removeTransactionWithHash:(UInt256)txHash saveImmediately:(BOOL)saveImmediately { +- (BOOL)removeTransactionWithHash:(UInt256)txHash + saveImmediately:(BOOL)saveImmediately { @synchronized (self) { DSTransaction *transaction = self.allTx[uint256_obj(txHash)]; if (!transaction) return FALSE; @@ -1207,7 +1318,8 @@ - (BOOL)removeTransactionWithHash:(UInt256)txHash saveImmediately:(BOOL)saveImme } } -- (BOOL)removeTransaction:(DSTransaction *)baseTransaction saveImmediately:(BOOL)saveImmediately { +- (BOOL)removeTransaction:(DSTransaction *)baseTransaction + saveImmediately:(BOOL)saveImmediately { NSParameterAssert(baseTransaction); @synchronized (self) { NSMutableSet *dependentTransactions = [NSMutableSet set]; @@ -1245,12 +1357,10 @@ - (BOOL)removeTransaction:(DSTransaction *)baseTransaction saveImmediately:(BOOL - (NSArray *)usedDerivationPathsForTransaction:(DSTransaction *)transaction { NSMutableArray *usedDerivationPaths = [NSMutableArray array]; - for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { - NSMutableOrderedSet *externalIndexes = [NSMutableOrderedSet orderedSet], - *internalIndexes = [NSMutableOrderedSet orderedSet]; + if (!(derivationPath.type == DSDerivationPathType_ClearFunds || derivationPath.type == DSDerivationPathType_AnonymousFunds)) continue; + NSMutableOrderedSet *externalIndexes = [NSMutableOrderedSet orderedSet], *internalIndexes = [NSMutableOrderedSet orderedSet]; for (NSString *addr in transaction.inputAddresses) { - if (!(derivationPath.type == DSDerivationPathType_ClearFunds || derivationPath.type == DSDerivationPathType_AnonymousFunds)) continue; if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { NSInteger index = [derivationPath.allChangeAddresses indexOfObject:addr]; if (index != NSNotFound) { @@ -1264,54 +1374,43 @@ - (NSArray *)usedDerivationPathsForTransaction:(DSTransaction *)transaction { continue; } } - if ([externalIndexes count] || [internalIndexes count]) { - [usedDerivationPaths addObject:@{@"derivationPath": derivationPath, @"externalIndexes": externalIndexes, @"internalIndexes": internalIndexes}]; - } + if ([externalIndexes count] || [internalIndexes count]) + [usedDerivationPaths addObject:@{ + @"derivationPath": derivationPath, + @"externalIndexes": externalIndexes, + @"internalIndexes": internalIndexes + }]; } return usedDerivationPaths; } -- (void)signTransaction:(DSTransaction *)transaction completion:(_Nonnull TransactionValidityCompletionBlock)completion { - NSParameterAssert(transaction); +- (BOOL)signTransaction:(DSTransaction *)transaction { + return [self signTransaction:transaction anyoneCanPay:NO]; +} - if (_isViewOnlyAccount) return; +- (BOOL)signTransaction:(DSTransaction *)transaction anyoneCanPay:(BOOL)anyoneCanPay { + NSParameterAssert(transaction); - //int64_t amount = [self amountSentByTransaction:transaction] - [self amountReceivedFromTransaction:transaction]; + if (_isViewOnlyAccount) return NO; NSArray *usedDerivationPaths = [self usedDerivationPathsForTransaction:transaction]; - + @autoreleasepool { // @autoreleasepool ensures sensitive data will be dealocated immediately - self.wallet.seedRequestBlock(^void(NSData *_Nullable seed, BOOL cancelled) { - if (!seed) { - if (completion) completion(NO, YES); - } else { - NSMutableArray *privkeys = [NSMutableArray array]; - for (NSDictionary *dictionary in usedDerivationPaths) { - DSDerivationPath *derivationPath = dictionary[@"derivationPath"]; - NSMutableOrderedSet *externalIndexes = dictionary[@"externalIndexes"], - *internalIndexes = dictionary[@"internalIndexes"]; - if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { - DSFundsDerivationPath *fundsDerivationPath = (DSFundsDerivationPath *)derivationPath; - [privkeys addObjectsFromArray:[fundsDerivationPath privateKeys:externalIndexes.array internal:NO fromSeed:seed]]; - [privkeys addObjectsFromArray:[fundsDerivationPath privateKeys:internalIndexes.array internal:YES fromSeed:seed]]; - } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { - DSIncomingFundsDerivationPath *incomingFundsDerivationPath = (DSIncomingFundsDerivationPath *)derivationPath; - [privkeys addObjectsFromArray:[incomingFundsDerivationPath privateKeys:externalIndexes.array fromSeed:seed]]; - } else { - NSAssert(FALSE, @"The derivation path must be a normal or incoming funds derivation path"); - } - } - - BOOL signedSuccessfully = [transaction signWithPrivateKeys:privkeys]; - if (completion) completion(signedSuccessfully, NO); - } - }); + NSData *seed = [self.wallet requestSeedNoAuth]; + + if (!seed) { + return NO; + } else { + return [self collectPrivateKeySetsAndSignTransaction:usedDerivationPaths fromSeed:seed transaction:transaction anyoneCanPay:anyoneCanPay]; + } } } // sign any inputs in the given transaction that can be signed using private keys from the wallet -- (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *_Nullable)authprompt completion:(TransactionValidityCompletionBlock)completion { +- (void)signTransaction:(DSTransaction *)transaction + withPrompt:(NSString *_Nullable)authprompt + completion:(TransactionValidityCompletionBlock)completion { NSParameterAssert(transaction); if (_isViewOnlyAccount) return; @@ -1324,32 +1423,93 @@ - (void)signTransaction:(DSTransaction *)transaction withPrompt:(NSString *_Null if (!seed) { if (completion) completion(NO, YES); } else { - NSMutableArray *privkeys = [NSMutableArray array]; - for (NSDictionary *dictionary in usedDerivationPaths) { - DSDerivationPath *derivationPath = dictionary[@"derivationPath"]; - NSMutableOrderedSet *externalIndexes = dictionary[@"externalIndexes"], - *internalIndexes = dictionary[@"internalIndexes"]; - if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { - DSFundsDerivationPath *fundsDerivationPath = (DSFundsDerivationPath *)derivationPath; - [privkeys addObjectsFromArray:[fundsDerivationPath privateKeys:externalIndexes.array internal:NO fromSeed:seed]]; - [privkeys addObjectsFromArray:[fundsDerivationPath privateKeys:internalIndexes.array internal:YES fromSeed:seed]]; - } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { - DSIncomingFundsDerivationPath *incomingFundsDerivationPath = (DSIncomingFundsDerivationPath *)derivationPath; - [privkeys addObjectsFromArray:[incomingFundsDerivationPath privateKeys:externalIndexes.array fromSeed:seed]]; - } else { - NSAssert(FALSE, @"The derivation path must be a normal or incoming funds derivation path"); - } - } - - BOOL signedSuccessfully = [transaction signWithPrivateKeys:privkeys]; - if (completion) completion(signedSuccessfully, NO); + BOOL success = [self collectPrivateKeySetsAndSignTransaction:usedDerivationPaths fromSeed:seed transaction:transaction anyoneCanPay:NO]; + if (completion) completion(success, NO); } }); } } +- (BOOL)collectPrivateKeySetsAndSignTransaction:(NSArray *)usedDerivationPaths + fromSeed:(NSData *)seed + transaction:(DSTransaction *)transaction + anyoneCanPay:(BOOL)anyoneCanPay { + NSMutableArray *privkeys = [NSMutableArray array]; + for (NSDictionary *dictionary in usedDerivationPaths) { + DSDerivationPath *derivationPath = dictionary[@"derivationPath"]; + NSMutableOrderedSet *externalIndexes = dictionary[@"externalIndexes"], + *internalIndexes = dictionary[@"internalIndexes"]; + if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { + // TODO: before we migrated high-level models for derivation paths into rust + // TODO: in order to avoid mess while dealing with nested c-structures + // TODO: we wrap DMaybeOpaqueKeys into NSValue + + DSFundsDerivationPath *fundsDerivationPath = (DSFundsDerivationPath *)derivationPath; + + NSMutableArray *externalArray = [NSMutableArray array]; + for (NSNumber *index in externalIndexes.array) { + [externalArray addObject:[NSIndexPath indexPathWithIndexes:(NSUInteger []){0, index.unsignedIntValue} length:2]]; + } + DMaybeOpaqueKeys *external = [DSDerivationPathFactory privateKeysAtIndexPaths:externalArray + fromSeed:seed + derivationPath:fundsDerivationPath]; + NSMutableArray *internalArray = [NSMutableArray array]; + for (NSNumber *index in internalIndexes.array) { + [internalArray addObject:[NSIndexPath indexPathWithIndexes:(NSUInteger []){1, index.unsignedIntValue} length:2]]; + } + DMaybeOpaqueKeys *internal = [DSDerivationPathFactory privateKeysAtIndexPaths:internalArray + fromSeed:seed + derivationPath:fundsDerivationPath]; + + [privkeys addObject:[NSValue valueWithPointer:external]]; + [privkeys addObject:[NSValue valueWithPointer:internal]]; + } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { + DSIncomingFundsDerivationPath *incomingFundsDerivationPath = (DSIncomingFundsDerivationPath *)derivationPath; + + NSMutableArray *mArray = [NSMutableArray array]; + for (NSNumber *index in externalIndexes.array) { + [mArray addObject:[NSIndexPath indexPathWithIndexes:(NSUInteger []){index.unsignedIntValue} length:1]]; + } + DMaybeOpaqueKeys *keys = [DSDerivationPathFactory privateKeysAtIndexPaths:mArray + fromSeed:seed + derivationPath:incomingFundsDerivationPath]; + [privkeys addObject:[NSValue valueWithPointer:keys]]; + } else { + NSAssert(FALSE, @"The derivation path must be a normal or incoming funds derivation path"); + } + } + + return [transaction signWithMaybePrivateKeySets:privkeys anyoneCanPay:anyoneCanPay]; +} + +//- (BOOL)signTxWithDerivationPaths:(NSArray *)usedDerivationPaths tx:(DSTransaction *)tx seed:(NSData *)seed { +// return [self signTxWithDerivationPaths:usedDerivationPaths tx:tx seed:seed anyoneCanPay:NO]; +//} +// +//- (BOOL)signTxWithDerivationPaths:(NSArray *)usedDerivationPaths tx:(DSTransaction *)tx seed:(NSData *)seed anyoneCanPay:(BOOL)anyoneCanPay { +// NSMutableArray *privkeys = [NSMutableArray array]; +// for (NSDictionary *dictionary in usedDerivationPaths) { +// DSDerivationPath *derivationPath = dictionary[@"derivationPath"]; +// NSMutableOrderedSet *externalIndexes = dictionary[@"externalIndexes"], +// *internalIndexes = dictionary[@"internalIndexes"]; +// if ([derivationPath isKindOfClass:[DSFundsDerivationPath class]]) { +// DSFundsDerivationPath *fundsDerivationPath = (DSFundsDerivationPath *)derivationPath; +// [privkeys addObjectsFromArray:[fundsDerivationPath privateKeys:externalIndexes.array internal:NO fromSeed:seed]]; +// [privkeys addObjectsFromArray:[fundsDerivationPath privateKeys:internalIndexes.array internal:YES fromSeed:seed]]; +// } else if ([derivationPath isKindOfClass:[DSIncomingFundsDerivationPath class]]) { +// DSIncomingFundsDerivationPath *incomingFundsDerivationPath = (DSIncomingFundsDerivationPath *)derivationPath; +// [privkeys addObjectsFromArray:[incomingFundsDerivationPath privateKeys:externalIndexes.array fromSeed:seed]]; +// } else { +// NSAssert(FALSE, @"The derivation path must be a normal or incoming funds derivation path"); +// } +// } +// +// return [tx signWithPrivateKeys:privkeys anyoneCanPay:anyoneCanPay]; +//} // sign any inputs in the given transaction that can be signed using private keys from the wallet -- (void)signTransactions:(NSArray *)transactions withPrompt:(NSString *)authprompt completion:(TransactionValidityCompletionBlock)completion { +- (void)signTransactions:(NSArray *)transactions + withPrompt:(NSString *)authprompt + completion:(TransactionValidityCompletionBlock)completion { if (_isViewOnlyAccount) return; int64_t amount = 0; @@ -1360,10 +1520,11 @@ - (void)signTransactions:(NSArray *)transactions withPrompt:(NS for (DSTransaction *transaction in transactions) { NSMutableArray *usedDerivationPaths = [NSMutableArray array]; for (DSFundsDerivationPath *derivationPath in self.fundDerivationPaths) { + if (!(derivationPath.type == DSDerivationPathType_ClearFunds || derivationPath.type == DSDerivationPathType_AnonymousFunds)) continue; + NSMutableOrderedSet *externalIndexes = [NSMutableOrderedSet orderedSet], *internalIndexes = [NSMutableOrderedSet orderedSet]; for (NSString *addr in transaction.inputAddresses) { - if (!(derivationPath.type == DSDerivationPathType_ClearFunds || derivationPath.type == DSDerivationPathType_AnonymousFunds)) continue; NSInteger index = [derivationPath.allChangeAddresses indexOfObject:addr]; if (index != NSNotFound) { [internalIndexes addObject:@(index)]; @@ -1388,8 +1549,18 @@ - (void)signTransactions:(NSArray *)transactions withPrompt:(NS DSFundsDerivationPath *derivationPath = dictionary[@"derivationPath"]; NSMutableOrderedSet *externalIndexes = dictionary[@"externalIndexes"], *internalIndexes = dictionary[@"internalIndexes"]; - [privkeys addObjectsFromArray:[derivationPath serializedPrivateKeys:externalIndexes.array internal:NO fromSeed:seed]]; - [privkeys addObjectsFromArray:[derivationPath serializedPrivateKeys:internalIndexes.array internal:YES fromSeed:seed]]; + + NSMutableArray *externalArray = [NSMutableArray array]; + NSMutableArray *internalArray = [NSMutableArray array]; + for (NSNumber *index in externalIndexes) { + [externalArray addObject:[NSIndexPath indexPathWithIndexes:(NSUInteger[]){0, index.unsignedIntValue} length:2]]; + } + for (NSNumber *index in internalIndexes) { + [internalArray addObject:[NSIndexPath indexPathWithIndexes:(NSUInteger[]){1, index.unsignedIntValue} length:2]]; + } + + [privkeys addObjectsFromArray:[DSDerivationPathFactory serializedPrivateKeysAtIndexPaths:externalArray fromSeed:seed derivationPath:derivationPath]]; + [privkeys addObjectsFromArray:[DSDerivationPathFactory serializedPrivateKeysAtIndexPaths:internalArray fromSeed:seed derivationPath:derivationPath]]; } BOOL signedSuccessfully = [transaction signWithSerializedPrivateKeys:privkeys]; @@ -1403,7 +1574,8 @@ - (void)signTransactions:(NSArray *)transactions withPrompt:(NS // MARK: = Registration // records the transaction in the account, or returns false if it isn't associated with the wallet -- (BOOL)registerTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)saveImmediately { +- (BOOL)registerTransaction:(DSTransaction *)transaction + saveImmediately:(BOOL)saveImmediately { NSParameterAssert(transaction); #if DEBUG DSLogPrivate(@"[%@] [DSAccount] registering transaction %@", self.wallet.chain.name, transaction); @@ -1449,8 +1621,8 @@ - (BOOL)registerTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)s [derivationPath registerTransactionAddress:output.address]; //only will register if derivation path contains address } } - [transaction loadBlockchainIdentitiesFromDerivationPaths:self.fundDerivationPaths]; - [transaction loadBlockchainIdentitiesFromDerivationPaths:self.outgoingFundDerivationPaths]; + [transaction loadIdentitiesFromDerivationPaths:self.fundDerivationPaths]; + [transaction loadIdentitiesFromDerivationPaths:self.outgoingFundDerivationPaths]; [self updateBalance]; if (saveImmediately) { if (!self.wallet.isTransient) { @@ -1468,7 +1640,8 @@ - (void)prepareForIncomingTransactionPersistenceForBlockSaveWithNumber:(uint32_t [self.transactionsToSave removeAllObjects]; } -- (void)persistIncomingTransactionsAttributesForBlockSaveWithNumber:(uint32_t)blockNumber inContext:(NSManagedObjectContext *)context { +- (void)persistIncomingTransactionsAttributesForBlockSaveWithNumber:(uint32_t)blockNumber + inContext:(NSManagedObjectContext *)context { for (DSTransaction *transaction in self.transactionsToSaveInBlockSave[@(blockNumber)]) { [transaction setInitialPersistentAttributesInContext:context]; } @@ -1486,7 +1659,8 @@ - (BOOL)transactionIsValid:(DSTransaction *)transaction { @synchronized (self) { if (transaction.blockHeight != TX_UNCONFIRMED) return YES; if (self.allTx[uint256_obj(transaction.txHash)] != nil) { - return ![self.invalidTransactionHashes containsObject:uint256_obj(transaction.txHash)]; + BOOL invalid = [self.invalidTransactionHashes containsObject:uint256_obj(transaction.txHash)]; + return !invalid; } for (DSTransactionInput *input in transaction.inputs) { UInt256 h = input.inputHash; @@ -1502,6 +1676,30 @@ - (BOOL)transactionIsValid:(DSTransaction *)transaction { } } +- (BOOL)isSpent:(NSValue *)output { + if (!output) { + return false; + } + + return [self.spentOutputs containsObject:output]; +} + +- (int64_t)inputValue:(UInt256)txHash inputIndex:(uint32_t)index { + NSValue *hash = uint256_obj(txHash); + DSTransaction *tx = self.allTx[hash]; + + if (tx == NULL) { + return -1; + } + + if (![self transactionIsValid:tx] || + [self.spentOutputs containsObject:dsutxo_obj(((DSUTXO){txHash, index}))]) { + return -1; + } + + return (int64_t)tx.outputs[index].amount; +} + // true if transaction cannot be immediately spent (i.e. if it or an input tx can be replaced-by-fee) - (BOOL)transactionIsPending:(DSTransaction *)transaction { NSParameterAssert(transaction); @@ -1635,6 +1833,24 @@ - (uint64_t)amountSentByTransaction:(DSTransaction *)transaction { return amount; } +// Returns the amounts sent by the transaction +- (NSArray *)amountsSentByTransaction:(DSTransaction *)transaction { + NSMutableArray *amounts = [NSMutableArray array]; + + for (DSTransactionInput *input in transaction.inputs) { + DSTransaction *tx = self.allTx[uint256_obj(input.inputHash)]; + uint64_t amount = tx.outputs[input.index].amount; + + if (amount > 0) { + [amounts addObject:@(amount)]; + } else { + [amounts addObject:@(0)]; + } + } + + return amounts; +} + // MARK: = Addresses - (NSArray *)externalAddressesOfTransaction:(DSTransaction *)transaction { @@ -1782,18 +1998,30 @@ - (void)sweepPrivateKey:(NSString *)privKey withFee:(BOOL)fee } cancel:^{}]; return; } - - ECDSAKey *key = key_ecdsa_with_private_key([privKey UTF8String], self.wallet.chain.chainType); - NSString *address = [DSKeyManager NSStringFrom:address_for_ecdsa_key(key, self.wallet.chain.chainType)]; - NSData *publicKeyData = [DSKeyManager NSDataFrom:key_ecdsa_public_key_data(key)]; - processor_destroy_ecdsa_key(key); + DChainType *chain_type = self.wallet.chain.chainType; + DMaybeECDSAKey *result = DMaybeECDSAKeyWithPrivateKey(DChar(privKey), chain_type); + if (!result) { + completion(nil, 0, ERROR_CANT_CREATE_KEY); + return; + } + if (!result->ok) { + DMaybeECDSAKeyDtor(result); + completion(nil, 0, ERROR_CANT_CREATE_KEY); + return; + } + + char *addr = DECDSAKeyPubAddress(result->ok, self.wallet.chain.chainType); + NSString *address = [DSKeyManager NSStringFrom:addr]; + Vec_u8 *public_key_data = DECDSAKeyPublicKeyData(result->ok); + NSData *publicKeyData = [DSKeyManager NSDataFrom:public_key_data]; + DMaybeECDSAKeyDtor(result); if (!address) { - completion(nil, 0, [NSError errorWithCode:187 localizedDescriptionKey:@"Not a valid private key"]); + completion(nil, 0, ERROR_INVALID_PRIVATE_KEY); return; } if ([self.wallet containsAddress:address]) { - completion(nil, 0, [NSError errorWithCode:187 localizedDescriptionKey:@"This private key is already in your wallet"]); + completion(nil, 0, ERROR_PRIVATE_KEY_ALREADY_IN_WALLET); return; } @@ -1819,7 +2047,7 @@ - (void)sweepPrivateKey:(NSString *)privKey withFee:(BOOL)fee } if (balance == 0) { - completion(nil, 0, [NSError errorWithCode:417 localizedDescriptionKey:@"This private key is empty"]); + completion(nil, 0, ERROR_EMPTY_PRIVATE_KEY); return; } @@ -1827,16 +2055,14 @@ - (void)sweepPrivateKey:(NSString *)privKey withFee:(BOOL)fee if (fee) feeAmount = [self.wallet.chain feeForTxSize:tx.size + 34 + (publicKeyData.length - 33) * tx.inputs.count]; //input count doesn't matter for non instant transactions if (feeAmount + self.wallet.chain.minOutputAmount > balance) { - completion(nil, 0, [NSError errorWithCode:417 localizedDescriptionKey: - @"Transaction fees would cost more than the funds available on this " - "private key (due to tiny \"dust\" deposits)"]); + completion(nil, 0, ERROR_NO_FUNDS_FOR_FEE); return; } [tx addOutputAddress:self.receiveAddress amount:balance - feeAmount]; if (![tx signWithSerializedPrivateKeys:@[privKey]]) { - completion(nil, 0, [NSError errorWithCode:401 localizedDescriptionKey:@"Error signing transaction"]); + completion(nil, 0, ERROR_SIGNING); return; } @@ -1844,4 +2070,6 @@ - (void)sweepPrivateKey:(NSString *)privKey withFee:(BOOL)fee }]; } + + @end diff --git a/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.h b/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.h index b3ffdda15..f78ee83cb 100644 --- a/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.h +++ b/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.h @@ -9,7 +9,7 @@ #import -@class DSWallet, DSTransaction, DSCreditFundingTransaction, DSBlockchainIdentityRegistrationTransition, DSBlockchainIdentityUpdateTransition; +@class DSWallet, DSTransaction; NS_ASSUME_NONNULL_BEGIN @@ -25,17 +25,15 @@ NS_ASSUME_NONNULL_BEGIN - (void)removeAllTransactions; -- (DSCreditFundingTransaction *)creditFundingTransactionForBlockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId; - //// This gets a blockchain user registration transaction that has a specific public key hash (will change to BLS pub key) -//- (DSBlockchainIdentityRegistrationTransition*)blockchainIdentityRegistrationTransactionForPublicKeyHash:(UInt160)publicKeyHash; +//- (DSIdentityRegistrationTransition*)identityRegistrationTransactionForPublicKeyHash:(UInt160)publicKeyHash; // //// This gets a blockchain user reset transaction that has a specific public key hash (will change to BLS pub key) -//- (DSBlockchainIdentityUpdateTransition*)blockchainIdentityResetTransactionForPublicKeyHash:(UInt160)publicKeyHash; +//- (DSIdentityUpdateTransition*)identityResetTransactionForPublicKeyHash:(UInt160)publicKeyHash; // -//- (NSArray*)identityTransitionsForRegistrationTransitionHash:(UInt256)blockchainIdentityRegistrationTransactionHash; +//- (NSArray*)identityTransitionsForRegistrationTransitionHash:(UInt256)identityRegistrationTransactionHash; // -//- (UInt256)lastSubscriptionTransactionHashForRegistrationTransactionHash:(UInt256)blockchainIdentityRegistrationTransactionHash; +//- (UInt256)lastSubscriptionTransactionHashForRegistrationTransactionHash:(UInt256)identityRegistrationTransactionHash; // this is used to save transactions atomically with the block, needs to be called before switching threads to save the block - (void)prepareForIncomingTransactionPersistenceForBlockSaveWithNumber:(uint32_t)blockNumber; diff --git a/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.m b/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.m index 31a6a65a1..8006e7c90 100644 --- a/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.m +++ b/DashSync/shared/Models/Wallet/DSSpecialTransactionsWalletHolder.m @@ -10,7 +10,7 @@ #import "DSSpecialTransactionsWalletHolder.h" #import "DSAddressEntity+CoreDataClass.h" #import "DSChain.h" -#import "DSCreditFundingTransaction.h" +#import "DSChain+Params.h" #import "DSDerivationPath.h" #import "DSDerivationPathEntity+CoreDataClass.h" #import "DSDerivationPathFactory.h" @@ -38,7 +38,6 @@ @interface DSSpecialTransactionsWalletHolder () @property (nonatomic, strong) NSMutableDictionary *providerUpdateServiceTransactions; @property (nonatomic, strong) NSMutableDictionary *providerUpdateRegistrarTransactions; @property (nonatomic, strong) NSMutableDictionary *providerUpdateRevocationTransactions; -@property (nonatomic, strong) NSMutableDictionary *creditFundingTransactions; @property (nonatomic, strong) NSMutableDictionary *assetLockTransactions; @property (nonatomic, strong) NSMutableDictionary *assetUnlockTransactions; @property (nonatomic, strong) NSMutableArray *transactionsToSave; @@ -59,7 +58,6 @@ - (instancetype)initWithWallet:(DSWallet *)wallet inContext:(NSManagedObjectCont self.providerUpdateRevocationTransactions = [NSMutableDictionary dictionary]; self.assetLockTransactions = [NSMutableDictionary dictionary]; self.assetUnlockTransactions = [NSMutableDictionary dictionary]; - self.creditFundingTransactions = [NSMutableDictionary dictionary]; self.managedObjectContext = [NSManagedObjectContext chainContext]; self.wallet = wallet; self.transactionsToSave = [NSMutableArray array]; @@ -68,7 +66,14 @@ - (instancetype)initWithWallet:(DSWallet *)wallet inContext:(NSManagedObjectCont } - (NSArray *)transactionDictionaries { - return @[self.providerRegistrationTransactions, self.providerUpdateServiceTransactions, self.providerUpdateRegistrarTransactions, self.providerUpdateRevocationTransactions, self.creditFundingTransactions, self.assetLockTransactions, self.assetUnlockTransactions]; + return @[ + self.providerRegistrationTransactions, + self.providerUpdateServiceTransactions, + self.providerUpdateRegistrarTransactions, + self.providerUpdateRevocationTransactions, + self.assetLockTransactions, + self.assetUnlockTransactions + ]; } @@ -154,12 +159,6 @@ - (BOOL)registerTransaction:(DSTransaction *)transaction saveImmediately:(BOOL)s [self.providerUpdateRevocationTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; added = TRUE; } - } else if ([transaction isMemberOfClass:[DSCreditFundingTransaction class]]) { - DSCreditFundingTransaction *creditFundingTransaction = (DSCreditFundingTransaction *)transaction; - if (![self.creditFundingTransactions objectForKey:uint256_data(creditFundingTransaction.creditBurnIdentityIdentifier)]) { - [self.creditFundingTransactions setObject:transaction forKey:uint256_data(creditFundingTransaction.creditBurnIdentityIdentifier)]; - added = TRUE; - } } else if ([transaction isMemberOfClass:[DSAssetLockTransaction class]]) { DSAssetLockTransaction *assetLockTransaction = (DSAssetLockTransaction *)transaction; if (![self.assetLockTransactions objectForKey:uint256_data(assetLockTransaction.txHash)]) { @@ -213,15 +212,10 @@ - (void)loadTransactions { [self.providerUpdateServiceTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; } else if ([transaction isMemberOfClass:[DSProviderUpdateRegistrarTransaction class]]) { [self.providerUpdateRegistrarTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; - } else if ([transaction isMemberOfClass:[DSCreditFundingTransaction class]]) { - DSCreditFundingTransaction *creditFundingTransaction = (DSCreditFundingTransaction *)transaction; - [self.creditFundingTransactions setObject:transaction forKey:uint256_data(creditFundingTransaction.creditBurnIdentityIdentifier)]; } else if ([transaction isMemberOfClass:[DSAssetLockTransaction class]]) { - DSAssetLockTransaction *assetLockTransaction = (DSAssetLockTransaction *)transaction; - [self.assetLockTransactions setObject:transaction forKey:uint256_data(assetLockTransaction.txHash)]; + [self.assetLockTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; } else if ([transaction isMemberOfClass:[DSAssetUnlockTransaction class]]) { - DSAssetUnlockTransaction *assetUnlockTransaction = (DSAssetUnlockTransaction *)transaction; - [self.assetUnlockTransactions setObject:transaction forKey:uint256_data(assetUnlockTransaction.txHash)]; + [self.assetUnlockTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; } else { //the other ones don't have addresses in payload NSAssert(FALSE, @"Unknown special transaction type"); } @@ -253,33 +247,33 @@ - (void)loadTransactions { } } - // NSArray * blockchainIdentityRegistrationTransactions = [self.blockchainIdentityRegistrationTransactions allValues]; + // NSArray * identityRegistrationTransactions = [self.identityRegistrationTransactions allValues]; // - // for (DSBlockchainIdentityRegistrationTransition * blockchainIdentityRegistrationTransaction in blockchainIdentityRegistrationTransactions) { - // NSArray* blockchainIdentityResetTransactions = [DSBlockchainIdentityResetTransactionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(blockchainIdentityRegistrationTransaction.txHash)]; - // for (DSBlockchainIdentityResetTransitionEntity *e in blockchainIdentityResetTransactions) { + // for (DSIdentityRegistrationTransition * identityRegistrationTransaction in identityRegistrationTransactions) { + // NSArray* identityResetTransactions = [DSBlockchainIdentityResetTransactionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(identityRegistrationTransaction.txHash)]; + // for (DSBlockchainIdentityResetTransitionEntity *e in identityResetTransactions) { // DSTransaction *transaction = [e transactionForChain:self.wallet.chain]; // // if (! transaction) continue; - // [self.blockchainIdentityResetTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; + // [self.identityResetTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; // } // - // NSArray* blockchainIdentityCloseTransactions = [DSBlockchainIdentityCloseTransactionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(blockchainIdentityRegistrationTransaction.txHash)]; - // for (DSBlockchainIdentityCloseTransitionEntity *e in blockchainIdentityCloseTransactions) { + // NSArray* identityCloseTransactions = [DSBlockchainIdentityCloseTransactionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(identityRegistrationTransaction.txHash)]; + // for (DSBlockchainIdentityCloseTransitionEntity *e in identityCloseTransactions) { // DSTransaction *transaction = [e transactionForChain:self.wallet.chain]; // // if (! transaction) continue; - // [self.blockchainIdentityCloseTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; + // [self.identityCloseTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; // } // - // NSArray* blockchainIdentityTopupTransactions = [DSBlockchainIdentityTopupTransitionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(blockchainIdentityRegistrationTransaction.txHash)]; - // for (DSBlockchainIdentityTopupTransitionEntity *e in blockchainIdentityTopupTransactions) { + // NSArray* identityTopupTransactions = [DSBlockchainIdentityTopupTransitionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(identityRegistrationTransaction.txHash)]; + // for (DSBlockchainIdentityTopupTransitionEntity *e in identityTopupTransactions) { // DSTransaction *transaction = [e transactionForChain:self.wallet.chain]; // // if (! transaction) continue; - // [self.blockchainIdentityTopupTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; + // [self.identityTopupTransactions setObject:transaction forKey:uint256_data(transaction.txHash)]; // } - // NSArray* transitions = [DSTransitionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(blockchainIdentityRegistrationTransaction.txHash)]; + // NSArray* transitions = [DSTransitionEntity objectsInContext:context matching:@"registrationTransactionHash == %@",uint256_data(identityRegistrationTransaction.txHash)]; // for (DSTransitionEntity *e in transitions) { // DSTransaction *transaction = [e transactionForChain:self.wallet.chain]; // @@ -290,78 +284,74 @@ - (void)loadTransactions { }]; } -- (DSCreditFundingTransaction *)creditFundingTransactionForBlockchainIdentityUniqueId:(UInt256)blockchainIdentityUniqueId { - return [self.creditFundingTransactions objectForKey:uint256_data(blockchainIdentityUniqueId)]; -} - //// MARK: == Blockchain Identities Transaction Retrieval // -//-(DSBlockchainIdentityRegistrationTransition*)blockchainIdentityRegistrationTransactionForPublicKeyHash:(UInt160)publicKeyHash { -// for (DSBlockchainIdentityRegistrationTransition * blockchainIdentityRegistrationTransaction in [self.blockchainIdentityRegistrationTransactions allValues]) { -// if (uint160_eq(blockchainIdentityRegistrationTransaction.pubkeyHash, publicKeyHash)) { -// return blockchainIdentityRegistrationTransaction; +//-(DSIdentityRegistrationTransition*)identityRegistrationTransactionForPublicKeyHash:(UInt160)publicKeyHash { +// for (DSIdentityRegistrationTransition * identityRegistrationTransaction in [self.identityRegistrationTransactions allValues]) { +// if (uint160_eq(identityRegistrationTransaction.pubkeyHash, publicKeyHash)) { +// return identityRegistrationTransaction; // } // } // return nil; //} // -//- (DSBlockchainIdentityUpdateTransition*)blockchainIdentityResetTransactionForPublicKeyHash:(UInt160)publicKeyHash { -// for (DSBlockchainIdentityResetTransition * blockchainIdentityResetTransaction in [self.blockchainIdentityResetTransactions allValues]) { -// if (uint160_eq(blockchainIdentityResetTransaction.replacementPublicKeyHash, publicKeyHash)) { -// return blockchainIdentityResetTransaction; +//- (DSIdentityUpdateTransition*)identityResetTransactionForPublicKeyHash:(UInt160)publicKeyHash { +// for (DSIdentityResetTransition * identityResetTransaction in [self.identityResetTransactions allValues]) { +// if (uint160_eq(identityResetTransaction.replacementPublicKeyHash, publicKeyHash)) { +// return identityResetTransaction; // } // } // return nil; //} // -//-(NSArray*)identityTransitionsForRegistrationTransitionHash:(UInt256)blockchainIdentityRegistrationTransactionHash { -// NSLog(@"blockchainIdentityRegistrationTransactionHash %@",uint256_hex(blockchainIdentityRegistrationTransactionHash)); +//-(NSArray*)identityTransitionsForRegistrationTransitionHash:(UInt256)identityRegistrationTransactionHash { +// NSLog(@"identityRegistrationTransactionHash %@",uint256_hex(identityRegistrationTransactionHash)); // NSMutableArray * subscriptionTransactions = [NSMutableArray array]; -// for (DSBlockchainIdentityTopupTransition * blockchainIdentityTopupTransaction in [self.blockchainIdentityTopupTransactions allValues]) { -// if (uint256_eq(blockchainIdentityTopupTransaction.registrationTransactionHash, blockchainIdentityRegistrationTransactionHash)) { -// [subscriptionTransactions addObject:blockchainIdentityTopupTransaction]; +// for (DSIdentityTopupTransition * identityTopupTransaction in [self.identityTopupTransactions allValues]) { +// if (uint256_eq(identityTopupTransaction.registrationTransactionHash, identityRegistrationTransactionHash)) { +// [subscriptionTransactions addObject:identityTopupTransaction]; // } // } -// for (DSBlockchainIdentityResetTransition * blockchainIdentityResetTransaction in [self.blockchainIdentityResetTransactions allValues]) { -// if (uint256_eq(blockchainIdentityResetTransaction.registrationTransactionHash, blockchainIdentityRegistrationTransactionHash)) { -// [subscriptionTransactions addObject:blockchainIdentityResetTransaction]; +// for (DSIdentityResetTransition * identityResetTransaction in [self.identityResetTransactions allValues]) { +// if (uint256_eq(identityResetTransaction.registrationTransactionHash, identityRegistrationTransactionHash)) { +// [subscriptionTransactions addObject:identityResetTransaction]; // } // } -// for (DSBlockchainIdentityCloseTransition * blockchainIdentityCloseTransaction in [self.blockchainIdentityCloseTransactions allValues]) { -// if (uint256_eq(blockchainIdentityCloseTransaction.registrationTransactionHash, blockchainIdentityRegistrationTransactionHash)) { -// [subscriptionTransactions addObject:blockchainIdentityCloseTransaction]; +// for (DSIdentityCloseTransition * identityCloseTransaction in [self.identityCloseTransactions allValues]) { +// if (uint256_eq(identityCloseTransaction.registrationTransactionHash, identityRegistrationTransactionHash)) { +// [subscriptionTransactions addObject:identityCloseTransaction]; // } // } // for (DSTransition * transition in [self.transitions allValues]) { -// NSLog(@"transition blockchainIdentityRegistrationTransactionHash %@",uint256_hex(transition.registrationTransactionHash)); -// if (uint256_eq(transition.registrationTransactionHash, blockchainIdentityRegistrationTransactionHash)) { +// NSLog(@"transition identityRegistrationTransactionHash %@",uint256_hex(transition.registrationTransactionHash)); +// if (uint256_eq(transition.registrationTransactionHash, identityRegistrationTransactionHash)) { // [subscriptionTransactions addObject:transition]; // } // } // return [subscriptionTransactions copy]; //} // -//-(UInt256)lastSubscriptionTransactionHashForRegistrationTransactionHash:(UInt256)blockchainIdentityRegistrationTransactionHash { -// NSMutableOrderedSet * subscriptionTransactions = [NSMutableOrderedSet orderedSetWithArray:[self identityTransitionsForRegistrationTransitionHash:blockchainIdentityRegistrationTransactionHash]]; -// UInt256 lastSubscriptionTransactionHash = blockchainIdentityRegistrationTransactionHash; +//-(UInt256)lastSubscriptionTransactionHashForRegistrationTransactionHash:(UInt256)identityRegistrationTransactionHash { +// NSMutableOrderedSet * subscriptionTransactions = [NSMutableOrderedSet orderedSetWithArray:[self identityTransitionsForRegistrationTransitionHash:identityRegistrationTransactionHash]]; +// UInt256 lastSubscriptionTransactionHash = identityRegistrationTransactionHash; // while ([subscriptionTransactions count]) { // BOOL found = FALSE; // for (DSTransaction * transaction in [subscriptionTransactions copy]) { -// if ([transaction isKindOfClass:[DSBlockchainIdentityTopupTransition class]]) { +// if ([transaction isKindOfClass:[DSIdentityTopupTransition class]]) { // [subscriptionTransactions removeObject:transaction]; //remove topups -// } else if ([transaction isKindOfClass:[DSBlockchainIdentityUpdateTransition class]]) { -// DSBlockchainIdentityUpdateTransition * blockchainIdentityResetTransaction = (DSBlockchainIdentityUpdateTransition*)transaction; -// if (uint256_eq(blockchainIdentityResetTransaction.previousBlockchainIdentityTransactionHash, lastSubscriptionTransactionHash)) { -// lastSubscriptionTransactionHash = blockchainIdentityResetTransaction.txHash; +// } else if ([transaction isKindOfClass:[DSIdentityUpdateTransition class]]) { +// DSIdentityUpdateTransition * transition = (DSIdentityUpdateTransition*)transaction; +// if (uint256_eq(transition.previousIdentityTransactionHash, lastSubscriptionTransactionHash)) { +// lastSubscriptionTransactionHash = transition.txHash; // found = TRUE; -// [subscriptionTransactions removeObject:blockchainIdentityResetTransaction]; +// [subscriptionTransactions removeObject:transition]; // } -// } else if ([transaction isKindOfClass:[DSBlockchainIdentityCloseTransition class]]) { -// DSBlockchainIdentityCloseTransition * blockchainIdentityCloseTransaction = (DSBlockchainIdentityCloseTransition*)transaction; -// if (uint256_eq(blockchainIdentityCloseTransaction.previousBlockchainIdentityTransactionHash, lastSubscriptionTransactionHash)) { -// lastSubscriptionTransactionHash = blockchainIdentityCloseTransaction.txHash; +// } else if ([transaction isKindOfClass:[DSIdentityCloseTransition class]]) { +// DSIdentityCloseTransition * transition = (DSIdentityCloseTransition*)transaction; +// if (uint256_eq(transition.previousIdentityTransactionHash, lastSubscriptionTransactionHash)) { +// lastSubscriptionTransactionHash = transition.txHash; // found = TRUE; -// [subscriptionTransactions removeObject:blockchainIdentityCloseTransaction]; +// [subscriptionTransactions removeObject:transition]; // } // } else if ([transaction isKindOfClass:[DSTransition class]]) { // DSTransition * transition = (DSTransition*)transaction; diff --git a/DashSync/shared/Models/Wallet/DSWallet+Identity.h b/DashSync/shared/Models/Wallet/DSWallet+Identity.h new file mode 100644 index 000000000..fada98161 --- /dev/null +++ b/DashSync/shared/Models/Wallet/DSWallet+Identity.h @@ -0,0 +1,68 @@ +// +// 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 "DSIdentity.h" +#import "DSWallet.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DSWallet (Identity) + +@property (nonatomic, readonly) NSDictionary *identities; +@property (nonatomic, readonly, nullable) DSIdentity *defaultIdentity; +@property (nonatomic, readonly) NSArray *identityAddresses; +// the first unused index for blockchain identity registration funding +@property (nonatomic, readonly) uint32_t unusedIdentityIndex; +// the amount of known blockchain identities +@property (nonatomic, readonly) uint32_t identitiesCount; + +- (void)setup; +- (void)setupIdentities; +- (void)loadIdentities; + +- (void)unregisterIdentity:(DSIdentity *)identity; +- (void)addIdentities:(NSArray *)identities; +- (void)addIdentity:(DSIdentity *)identity; + +// Verify makes sure the keys for the blockchain identity are good +- (BOOL)registerIdentities:(NSArray *)identities verify:(BOOL)verify; +- (BOOL)registerIdentity:(DSIdentity *)identity verify:(BOOL)verify; +- (BOOL)registerIdentity:(DSIdentity *)identity; +- (BOOL)containsIdentity:(DSIdentity *)identity; + +- (DSIdentity *)createIdentity; +- (DSIdentity *)createIdentityUsingDerivationIndex:(uint32_t)index; +- (DSIdentity *)createIdentityForUsername:(NSString *_Nullable)username; +- (DSIdentity *)createIdentityForUsername:(NSString *_Nullable)username usingDerivationIndex:(uint32_t)index; +- (DSIdentity *_Nullable)identityThatCreatedContract:(DDataContract *)contract withContractId:(UInt256)contractId; +- (DSIdentity *_Nullable)identityForUniqueId:(UInt256)uniqueId; +- (DSIdentity *_Nullable)identityForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key; +- (DMaybeOpaqueKey *_Nullable)identityPrivateKeyForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key; + +//- (NSUInteger)indexOfIdentityAuthenticationHash:(UInt160)hash; +- (NSUInteger)indexOfIdentityAssetLockRegistrationHash:(UInt160)hash; +- (NSUInteger)indexOfIdentityAssetLockTopupHash:(UInt160)hash; +- (NSUInteger)indexOfIdentityAssetLockInvitationHash:(UInt160)hash; + +// Protected +- (void)wipeIdentitiesInContext:(NSManagedObjectContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Wallet/DSWallet+Identity.m b/DashSync/shared/Models/Wallet/DSWallet+Identity.m new file mode 100644 index 000000000..802829540 --- /dev/null +++ b/DashSync/shared/Models/Wallet/DSWallet+Identity.m @@ -0,0 +1,401 @@ +// +// 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 "DSAccount.h" +#import "DSAccountEntity+CoreDataClass.h" +#import "DSAssetLockTransactionEntity+CoreDataClass.h" +#import "DSAuthenticationKeysDerivationPath.h" +#import "DSAssetLockDerivationPath.h" +#import "DSIdentity+Protected.h" +#import "DSIdentity+Username.h" +#import "DSBlockchainIdentityEntity+CoreDataClass.h" +#import "DSChain+Params.h" +#import "DSDashpayUserEntity+CoreDataClass.h" +#import "DSDerivationPathFactory.h" +#import "DSDerivationPathEntity+CoreDataClass.h" +#import "DSFriendRequestEntity+CoreDataClass.h" +#import "DSIncomingFundsDerivationPath.h" +#import "DSWallet+Identity.h" +#import "NSData+Dash.h" +#import "NSManagedObject+Sugar.h" +#import + +#define WALLET_BLOCKCHAIN_USERS_KEY @"WALLET_BLOCKCHAIN_USERS_KEY" +#define IDENTITY_INDEX_KEY @"IDENTITY_INDEX_KEY" +#define IDENTITY_LOCKED_OUTPUT_KEY @"IDENTITY_LOCKED_OUTPUT_KEY" + +NSString const *mIdentitiesDictionaryKey = @"mIdentitiesDictionaryKey"; +NSString const *defaultIdentityKey = @"defaultIdentityKey"; + +@implementation DSWallet (Identity) + +- (void)setMIdentities:(NSMutableDictionary *)dictionary { + objc_setAssociatedObject(self, &mIdentitiesDictionaryKey, dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSMutableDictionary *)mIdentities { + return objc_getAssociatedObject(self, &mIdentitiesDictionaryKey); +} + +- (DSIdentity *)defaultIdentity { + return objc_getAssociatedObject(self, &defaultIdentityKey); +} + +- (void)setDefaultIdentity:(DSIdentity *)defaultIdentity { + objc_setAssociatedObject(self, &defaultIdentityKey, defaultIdentity, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)setup { + self.mIdentities = nil; + self.mIdentities = [NSMutableDictionary dictionary]; +} + +- (void)setupIdentities { + self.mIdentities = nil; + [self identities]; +} + +- (NSString *)walletIdentitiesKey { + return [NSString stringWithFormat:@"%@_%@", WALLET_BLOCKCHAIN_USERS_KEY, [self uniqueIDString]]; +} + +- (NSString *)walletIdentitiesDefaultIndexKey { + return [NSString stringWithFormat:@"%@_%@_DEFAULT_INDEX", WALLET_BLOCKCHAIN_USERS_KEY, [self uniqueIDString]]; +} + +- (void)loadIdentities { + [self.chain.chainManagedObjectContext performBlockAndWait:^{ + NSMutableArray *usedFriendshipIdentifiers = [NSMutableArray array]; + for (NSData *identityData in self.mIdentities) { + DSIdentity *identity = [self.mIdentities objectForKey:identityData]; + NSSet *outgoingRequests = [identity matchingDashpayUserInContext:self.chain.chainManagedObjectContext].outgoingRequests; + for (DSFriendRequestEntity *request in outgoingRequests) { + DSAccount *account = [self accountWithNumber:request.account.index]; + UInt256 destinationIdentityID = request.destinationContact.associatedBlockchainIdentity.uniqueID.UInt256; + UInt256 sourceIdentityID = identity.uniqueID; + DSIncomingFundsDerivationPath *path = [DSIncomingFundsDerivationPath contactBasedDerivationPathWithDestinationIdentityUniqueId:destinationIdentityID + sourceIdentityUniqueId:sourceIdentityID + forAccount:account + onChain:self.chain]; + path.standaloneExtendedPublicKeyUniqueID = request.derivationPath.publicKeyIdentifier; + path.wallet = self; + [account addIncomingDerivationPath:path forFriendshipIdentifier:request.friendshipIdentifier inContext:self.chain.chainManagedObjectContext]; + [usedFriendshipIdentifiers addObject:request.friendshipIdentifier]; + } + } + + for (NSData *identityUniqueIdData in self.mIdentities) { + DSIdentity *identity = [self.mIdentities objectForKey:identityUniqueIdData]; + NSSet *incomingRequests = [identity matchingDashpayUserInContext:self.chain.chainManagedObjectContext].incomingRequests; + for (DSFriendRequestEntity *request in incomingRequests) { + DSAccount *account = [self accountWithNumber:request.account.index]; + DSIncomingFundsDerivationPath *fundsDerivationPath = [account derivationPathForFriendshipWithIdentifier:request.friendshipIdentifier]; + if (fundsDerivationPath) { + //both contacts are on device + [account addOutgoingDerivationPath:fundsDerivationPath + forFriendshipIdentifier:request.friendshipIdentifier + inContext:self.chain.chainManagedObjectContext]; + } else { + NSString *derivationPathPublicKeyIdentifier = request.derivationPath.publicKeyIdentifier; + UInt256 destinationIdentityID = request.destinationContact.associatedBlockchainIdentity.uniqueID.UInt256; + UInt256 sourceIdentityID = request.sourceContact.associatedBlockchainIdentity.uniqueID.UInt256; + DSIncomingFundsDerivationPath *path = [DSIncomingFundsDerivationPath externalDerivationPathWithExtendedPublicKeyUniqueID:derivationPathPublicKeyIdentifier + withDestinationIdentityUniqueId:destinationIdentityID + sourceIdentityUniqueId:sourceIdentityID + onChain:self.chain]; + path.wallet = self; + path.account = account; + [account addOutgoingDerivationPath:path + forFriendshipIdentifier:request.friendshipIdentifier + inContext:self.chain.chainManagedObjectContext]; + } + } + } + + //this adds the extra information to the transaction and must come after loading all blockchain identities. + for (DSAccount *account in self.accounts) { + for (DSTransaction *transaction in account.allTransactions) { + [transaction loadIdentitiesFromDerivationPaths:account.fundDerivationPaths]; + [transaction loadIdentitiesFromDerivationPaths:account.outgoingFundDerivationPaths]; + } + } + }]; +} +// MARK: - Identities + +- (NSArray *)identityAddresses { + DSAuthenticationKeysDerivationPath *derivationPath = [[DSDerivationPathFactory sharedInstance] identityBLSKeysDerivationPathForWallet:self]; + return derivationPath.hasExtendedPublicKey ? [derivationPath addressesToIndex:[self unusedIdentityIndex] + 10 useCache:YES addToCache:YES] : @[]; +} + +- (void)unregisterIdentity:(DSIdentity *)identity { + NSParameterAssert(identity); + NSAssert(identity.wallet == self, @"the identity you are trying to remove is not in this wallet"); + [self.mIdentities removeObjectForKey:identity.uniqueIDData]; + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletIdentitiesKey, @[[NSNumber class], [NSData class], [NSString class]], &error) mutableCopy]; + if (keyChainDictionary) + [keyChainDictionary removeObjectForKey:identity.uniqueIDData]; + else + keyChainDictionary = [NSMutableDictionary dictionary]; + setKeychainDict(keyChainDictionary, self.walletIdentitiesKey, NO); +} + +- (void)addIdentities:(NSArray *)identities { + for (DSIdentity *identity in identities) { + [self addIdentity:identity]; + } +} + +- (void)addIdentity:(DSIdentity *)identity { + NSParameterAssert(identity); + NSAssert(uint256_is_not_zero(identity.uniqueID), @"The identity unique ID must be set"); + [self.mIdentities setObject:identity forKey:identity.uniqueIDData]; +} + +- (BOOL)containsIdentity:(DSIdentity *)identity { + return identity.lockedOutpointData && ([self.mIdentities objectForKey:identity.uniqueIDData] != nil); +} + +- (BOOL)registerIdentities:(NSArray *)identities + verify:(BOOL)verify { + for (DSIdentity *identity in identities) { + if (![self registerIdentity:identity verify:verify]) + return FALSE; + } + return TRUE; +} + +- (BOOL)registerIdentity:(DSIdentity *)identity { + return [self registerIdentity:identity verify:NO]; +} + +- (BOOL)registerIdentity:(DSIdentity *)identity + verify:(BOOL)verify { + NSParameterAssert(identity); + if (verify && ![identity verifyKeysForWallet:self]) { + identity.isLocal = FALSE; + return FALSE; + } + if ([self.mIdentities objectForKey:identity.uniqueIDData] == nil) + [self addIdentity:identity]; + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletIdentitiesKey, @[[NSNumber class], [NSData class], [NSString class]], &error) mutableCopy]; + if (error) return FALSE; + if (!keyChainDictionary) + keyChainDictionary = [NSMutableDictionary dictionary]; + NSAssert(uint256_is_not_zero(identity.uniqueID), @"registrationTransactionHashData must not be null"); + keyChainDictionary[identity.uniqueIDData] = uint256_is_zero(identity.lockedOutpointData.transactionOutpoint.hash) + ? @{IDENTITY_INDEX_KEY: @(identity.index)} + : @{IDENTITY_INDEX_KEY: @(identity.index), IDENTITY_LOCKED_OUTPUT_KEY: identity.lockedOutpointData}; + setKeychainDict(keyChainDictionary, self.walletIdentitiesKey, NO); + if (!self.defaultIdentity && (identity.index == 0)) + self.defaultIdentity = identity; + return TRUE; +} + +- (void)wipeIdentitiesInContext:(NSManagedObjectContext *)context { + for (DSIdentity *identity in [self.mIdentities allValues]) { + [self unregisterIdentity:identity]; + [identity deletePersistentObjectAndSave:NO inContext:context]; + } + self.defaultIdentity = nil; +} + +- (DSIdentity *_Nullable)identityThatCreatedContract:(DDataContract *)contract + withContractId:(UInt256)contractId { + NSParameterAssert(contract); + NSAssert(uint256_is_not_zero(contractId), @"contractId must not be null"); + DSIdentity *foundIdentity = nil; + for (DSIdentity *identity in [self.mIdentities allValues]) { + if (uint256_eq([identity contractIdIfRegistered:contract], contractId)) + foundIdentity = identity; + } + return foundIdentity; +} + +- (DSIdentity *)identityForUniqueId:(UInt256)uniqueId { + NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); + DSIdentity *foundIdentity = nil; + for (DSIdentity *identity in [self.mIdentities allValues]) { + if (uint256_eq([identity uniqueID], uniqueId)) + foundIdentity = identity; + } + return foundIdentity; +} + +- (DSIdentity *)identityForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key { + DSIdentity *foundIdentity = nil; + for (DSIdentity *identity in [self.mIdentities allValues]) { + if ([identity containsPublicKey:identity_public_key]) + foundIdentity = identity; + } + return foundIdentity; +} + +- (DMaybeOpaqueKey *)identityPrivateKeyForIdentityPublicKey:(dpp_identity_identity_public_key_IdentityPublicKey *)identity_public_key { + switch (identity_public_key->tag) { + case dpp_identity_identity_public_key_IdentityPublicKey_V0: { + dpp_identity_identity_public_key_v0_IdentityPublicKeyV0 *v0 = identity_public_key->v0; + uint32_t key_index = v0->id->_0; + for (DSIdentity *identity in [self.mIdentities allValues]) { + DOpaqueKey *key = [identity keyAtIndex:key_index]; + if (key && DOpaqueKeyPublicKeyDataEqualTo(key, v0->data->_0)) + return [identity privateKeyAtIndex:key_index ofType:dash_spv_platform_identity_manager_key_kind_from_key_type(v0->key_type)]; + } + return nil; + } + default: return nil; + } +} + +- (uint32_t)identitiesCount { + return (uint32_t)[self.mIdentities count]; +} + +- (BOOL)upgradeIdentityKeyChain { + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletIdentitiesKey, @[[NSNumber class], [NSData class], [NSString class]], &error) mutableCopy]; + NSAssert(error == nil, @"There should be no error during upgrade"); + if (error) return FALSE; + NSMutableDictionary *updated = [NSMutableDictionary dictionary]; + for (NSData *identityLockedOutpoint in keyChainDictionary) { + [updated setObject:@{IDENTITY_INDEX_KEY: keyChainDictionary[identityLockedOutpoint], IDENTITY_LOCKED_OUTPUT_KEY: identityLockedOutpoint} + forKey:uint256_data([identityLockedOutpoint SHA256_2])]; + } + setKeychainDict(updated, self.walletIdentitiesKey, NO); + return TRUE; +} + + +//This loads all the identities that the wallet knows about. If the app was deleted and reinstalled the identity information will remain from the keychain but must be reaquired from the network. +- (NSMutableDictionary *)identities { + //setKeychainDict(@{}, self.walletIdentitiesKey, NO); + if (self.mIdentities) return self.mIdentities; + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletIdentitiesKey, @[[NSNumber class], [NSData class], [NSString class]], &error) mutableCopy]; + if (error) return nil; + uint64_t defaultIndex = getKeychainInt(self.walletIdentitiesDefaultIndexKey, &error); + if (error) return nil; + NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; + if (keyChainDictionary && keyChainDictionary.count) { + if ([[[keyChainDictionary allValues] firstObject] isKindOfClass:[NSNumber class]]) + return [self upgradeIdentityKeyChain] ? (NSMutableDictionary *) [self identities] : nil; + for (NSData *uniqueIdData in keyChainDictionary) { + NSDictionary *dict = keyChainDictionary[uniqueIdData]; + uint32_t index = [[dict objectForKey:IDENTITY_INDEX_KEY] unsignedIntValue]; + // either the identity is known in core data (and we can pull it) or the wallet has been wiped and we need to get it from DAPI (the unique Id was saved in the keychain, so we don't need to resync) + //TODO: get the identity from core data + + NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; //shouldn't matter what context is used + + [context performBlockAndWait:^{ + NSUInteger identityEntitiesCount = [DSBlockchainIdentityEntity countObjectsInContext:context matching:@"chain == %@ && isLocal == TRUE", [self.chain chainEntityInContext:context]]; + if (identityEntitiesCount != keyChainDictionary.count) + DSLog(@"[%@] Unmatching blockchain entities count", self.chain.name); + DSBlockchainIdentityEntity *entity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", uniqueIdData]; + DSIdentity *identity = nil; + NSDictionary *dict = keyChainDictionary[uniqueIdData]; + NSData *lockedOutpointData = [dict objectForKey:IDENTITY_LOCKED_OUTPUT_KEY]; + if (entity) { + if (lockedOutpointData) { + identity = [[DSIdentity alloc] initAtIndex:index withLockedOutpoint:lockedOutpointData.transactionOutpoint inWallet:self withIdentityEntity:entity]; + } else { + identity = [[DSIdentity alloc] initAtIndex:index withUniqueId:uniqueIdData.UInt256 inWallet:self withIdentityEntity:entity]; + } + } else if (lockedOutpointData) { + //No blockchain identity is known in core data + NSData *transactionHashData = uint256_data(uint256_reverse(lockedOutpointData.transactionOutpoint.hash)); + DSAssetLockTransactionEntity *creditRegitrationTransactionEntity = [DSAssetLockTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", transactionHashData]; + if (creditRegitrationTransactionEntity) { + // The registration funding transaction exists + // Weird but we should recover in this situation + DSAssetLockTransaction *assetLockTransaction = (DSAssetLockTransaction *)[creditRegitrationTransactionEntity transactionForChain:self.chain]; + BOOL correctIndex = [assetLockTransaction checkDerivationPathIndexForWallet:self isIndex:index]; + if (!correctIndex) { + DSLog(@"%@: AssetLockTX: IncorrectIndex %u (%@)", self.chain.name, index, assetLockTransaction.toData.hexString); +// NSAssert(FALSE, @"We should implement this"); + } else { + identity = [[DSIdentity alloc] initAtIndex:index withAssetLockTransaction:assetLockTransaction inWallet:self]; + [identity registerInWallet]; + } + } else { + // We also don't have the registration funding transaction + identity = [[DSIdentity alloc] initAtIndex:index withUniqueId:uniqueIdData.UInt256 inWallet:self]; + [identity registerInWalletForIdentityUniqueId:uniqueIdData.UInt256]; + } + } else { + identity = [[DSIdentity alloc] initAtIndex:index withUniqueId:uniqueIdData.UInt256 inWallet:self]; + [identity registerInWalletForIdentityUniqueId:uniqueIdData.UInt256]; + } + if (identity) { + rDictionary[uniqueIdData] = identity; + if (index == defaultIndex) + self.defaultIdentity = identity; + } + }]; + } + } + self.mIdentities = rDictionary; + return self.mIdentities; +} + +- (uint32_t)unusedIdentityIndex { + NSArray *identities = [self.mIdentities allValues]; + NSNumber *max = [identities valueForKeyPath:@"index.@max.intValue"]; + return max != nil ? ([max unsignedIntValue] + 1) : 0; +} + +- (DSIdentity *)createIdentity { + return [[DSIdentity alloc] initAtIndex:[self unusedIdentityIndex] inWallet:self]; +} + +- (DSIdentity *)createIdentityUsingDerivationIndex:(uint32_t)index { + return [[DSIdentity alloc] initAtIndex:index inWallet:self]; +} + +- (DSIdentity *)createIdentityForUsername:(NSString *)username { + DSIdentity *identity = [self createIdentity]; + [identity addDashpayUsername:username save:NO]; + return identity; +} + +- (DSIdentity *)createIdentityForUsername:(NSString *)username + usingDerivationIndex:(uint32_t)index { + DSIdentity *identity = [self createIdentityUsingDerivationIndex:index]; + [identity addDashpayUsername:username save:NO]; + return identity; +} + + +//- (NSUInteger)indexOfIdentityAuthenticationHash:(UInt160)hash { +// return [[DSAuthenticationKeysDerivationPath identitiesBLSKeysDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; +//} + +- (NSUInteger)indexOfIdentityAssetLockRegistrationHash:(UInt160)hash { + return [[DSAssetLockDerivationPath identityRegistrationFundingDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; +} + +- (NSUInteger)indexOfIdentityAssetLockTopupHash:(UInt160)hash { + return [[DSAssetLockDerivationPath identityTopupFundingDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; +} + +- (NSUInteger)indexOfIdentityAssetLockInvitationHash:(UInt160)hash { + return [[DSAssetLockDerivationPath identityInvitationFundingDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; +} + +@end diff --git a/DashSync/shared/Models/Wallet/DSWallet+Invitation.h b/DashSync/shared/Models/Wallet/DSWallet+Invitation.h new file mode 100644 index 000000000..d9cfcc697 --- /dev/null +++ b/DashSync/shared/Models/Wallet/DSWallet+Invitation.h @@ -0,0 +1,45 @@ +// +// 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 "DSInvitation.h" +#import "DSWallet.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSWallet (Invitation) + +@property (nonatomic, readonly) NSDictionary *invitations; +// the first unused index for invitations +@property (nonatomic, readonly) uint32_t unusedInvitationIndex; +// the amount of known blockchain invitations +@property (nonatomic, readonly) uint32_t invitationsCount; + +- (void)setupInvitations; + +- (void)unregisterInvitation:(DSInvitation *)invitation; +- (void)addInvitation:(DSInvitation *)invitation; +- (void)registerInvitation:(DSInvitation *)invitation; +- (BOOL)containsInvitation:(DSInvitation *)invitation; +- (DSInvitation *)createInvitation; +- (DSInvitation *)createInvitationUsingDerivationIndex:(uint32_t)index; +- (DSInvitation *_Nullable)invitationForUniqueId:(UInt256)uniqueId; +- (void)wipeInvitationsInContext:(NSManagedObjectContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Wallet/DSWallet+Invitation.m b/DashSync/shared/Models/Wallet/DSWallet+Invitation.m new file mode 100644 index 000000000..e8fb87e8e --- /dev/null +++ b/DashSync/shared/Models/Wallet/DSWallet+Invitation.m @@ -0,0 +1,182 @@ +// +// 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 "DSInvitation+Protected.h" +#import "DSChain+Params.h" +#import "DSAssetLockTransactionEntity+CoreDataClass.h" +#import "DSWallet+Invitation.h" +#import "NSManagedObject+Sugar.h" +#import + +#define WALLET_BLOCKCHAIN_INVITATIONS_KEY @"WALLET_BLOCKCHAIN_INVITATIONS_KEY" +NSString const *mInvitationsDictionaryKey = @"mInvitationsDictionaryKey"; + +@interface DSWallet () + +@property (nonatomic, strong) NSMutableDictionary *mInvitations; + +@end + +@implementation DSWallet (Invitation) + +- (void)setMInvitations:(NSMutableDictionary *)dictionary { + objc_setAssociatedObject(self, &mInvitationsDictionaryKey, dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSMutableDictionary *)mInvitations { + return objc_getAssociatedObject(self, &mInvitationsDictionaryKey); +} + +- (void)setupInvitations { + self.mInvitations = nil; + [self invitations]; +} + +- (NSString *)walletInvitationsKey { + return [NSString stringWithFormat:@"%@_%@", WALLET_BLOCKCHAIN_INVITATIONS_KEY, [self uniqueIDString]]; +} + +// MARK: - Invitations + + +- (uint32_t)invitationsCount { + return (uint32_t)[self.mInvitations count]; +} + + +//This loads all the identities that the wallet knows about. If the app was deleted and reinstalled the identity information will remain from the keychain but must be reaquired from the network. +- (NSMutableDictionary *)invitations { + //setKeychainDict(@{}, self.walletInvitationsKey, NO); + if (self.mInvitations) return self.mInvitations; + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletInvitationsKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; + if (error) return nil; + NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; + if (keyChainDictionary) { + for (NSData *invitationLockedOutpointData in keyChainDictionary) { + uint32_t index = [keyChainDictionary[invitationLockedOutpointData] unsignedIntValue]; + DSUTXO invitationLockedOutpoint = invitationLockedOutpointData.transactionOutpoint; + //either the identity is known in core data (and we can pull it) or the wallet has been wiped and we need to get it from DAPI (the unique Id was saved in the keychain, so we don't need to resync) + //TODO: get the identity from core data + NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; //shouldn't matter what context is used + [context performBlockAndWait:^{ + NSUInteger invitationEntitiesCount = [DSBlockchainInvitationEntity countObjectsInContext:context matching:@"chain == %@", [self.chain chainEntityInContext:context]]; + if (invitationEntitiesCount != keyChainDictionary.count) + DSLog(@"[%@] Unmatching blockchain invitations count", self.chain.name); + NSData *identityID = uint256_data([dsutxo_data(invitationLockedOutpoint) SHA256_2]); + DSBlockchainInvitationEntity *invitationEntity = [DSBlockchainInvitationEntity anyObjectInContext:context matching:@"blockchainIdentity.uniqueID == %@", identityID]; + DSInvitation *invitation = nil; + if (invitationEntity) { + invitation = [[DSInvitation alloc] initAtIndex:index withLockedOutpoint:invitationLockedOutpoint inWallet:self withInvitationEntity:invitationEntity]; + } else { + //No blockchain identity is known in core data + NSData *transactionHashData = uint256_data(uint256_reverse(invitationLockedOutpoint.hash)); + DSAssetLockTransactionEntity *creditRegitrationTransactionEntity = [DSAssetLockTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", transactionHashData]; + if (creditRegitrationTransactionEntity) { + //The registration funding transaction exists + //Weird but we should recover in this situation + DSAssetLockTransaction *registrationTransaction = (DSAssetLockTransaction *)[creditRegitrationTransactionEntity transactionForChain:self.chain]; + + BOOL correctIndex = [registrationTransaction checkInvitationDerivationPathIndexForWallet:self isIndex:index]; + if (!correctIndex) { + NSAssert(FALSE, @"We should implement this"); + } else { + invitation = [[DSInvitation alloc] initAtIndex:index withAssetLockTransaction:registrationTransaction inWallet:self]; + [invitation registerInWallet]; + } + } else { + //We also don't have the registration funding transaction + invitation = [[DSInvitation alloc] initAtIndex:index withLockedOutpoint:invitationLockedOutpoint inWallet:self]; + [invitation registerInWalletForIdentityUniqueId:[dsutxo_data(invitationLockedOutpoint) SHA256_2]]; + } + } + if (invitation) + rDictionary[invitationLockedOutpointData] = invitation; + }]; + } + } + self.mInvitations = rDictionary; + return self.mInvitations; +} + +- (DSInvitation *)invitationForUniqueId:(UInt256)uniqueId { + NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); + DSInvitation *foundInvitation = nil; + for (DSInvitation *invitation in [self.mInvitations allValues]) { + if (uint256_eq([invitation.identity uniqueID], uniqueId)) + foundInvitation = invitation; + } + return foundInvitation; +} + +- (uint32_t)unusedInvitationIndex { + NSArray *invitations = [self.mInvitations allValues]; + NSNumber *max = [invitations valueForKeyPath:@"identity.index.@max.intValue"]; + return max != nil ? ([max unsignedIntValue] + 1) : 0; +} + +- (DSInvitation *)createInvitation { + return [[DSInvitation alloc] initAtIndex:[self unusedInvitationIndex] inWallet:self]; +} + +- (DSInvitation *)createInvitationUsingDerivationIndex:(uint32_t)index { + return [[DSInvitation alloc] initAtIndex:index inWallet:self]; +} + +- (void)unregisterInvitation:(DSInvitation *)invitation { + NSParameterAssert(invitation); + NSAssert(invitation.wallet == self, @"the invitation you are trying to remove is not in this wallet"); + NSAssert(invitation.identity != nil, @"the invitation you are trying to remove has no identity"); + [self.mInvitations removeObjectForKey:invitation.identity.lockedOutpointData]; + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletInvitationsKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; + if (!keyChainDictionary) keyChainDictionary = [NSMutableDictionary dictionary]; + [keyChainDictionary removeObjectForKey:invitation.identity.lockedOutpointData]; + setKeychainDict(keyChainDictionary, self.walletInvitationsKey, NO); +} + +- (void)addInvitation:(DSInvitation *)invitation { + NSParameterAssert(invitation); + [self.mInvitations setObject:invitation forKey:invitation.identity.lockedOutpointData]; +} + +- (void)registerInvitation:(DSInvitation *)invitation { + NSParameterAssert(invitation); + NSAssert(invitation.identity != nil, @"the invitation you are trying to remove has no identity"); + if ([self.mInvitations objectForKey:invitation.identity.lockedOutpointData] == nil) + [self addInvitation:invitation]; + NSError *error = nil; + NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletInvitationsKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; + if (!keyChainDictionary) + keyChainDictionary = [NSMutableDictionary dictionary]; + NSAssert(uint256_is_not_zero(invitation.identity.uniqueID), @"registrationTransactionHashData must not be null"); + keyChainDictionary[invitation.identity.lockedOutpointData] = @(invitation.identity.index); + setKeychainDict(keyChainDictionary, self.walletInvitationsKey, NO); +} + +- (BOOL)containsInvitation:(DSInvitation *)invitation { + return invitation.identity.lockedOutpointData && ([self.mInvitations objectForKey:invitation.identity.lockedOutpointData] != nil); +} + +- (void)wipeInvitationsInContext:(NSManagedObjectContext *)context { + for (DSInvitation *invitation in [self.mInvitations allValues]) { + [self unregisterInvitation:invitation]; + [invitation deletePersistentObjectAndSave:NO inContext:context]; + } +} + +@end diff --git a/DashSync/shared/Models/Wallet/DSWallet+Protected.h b/DashSync/shared/Models/Wallet/DSWallet+Protected.h index db2561f98..8bac2b905 100644 --- a/DashSync/shared/Models/Wallet/DSWallet+Protected.h +++ b/DashSync/shared/Models/Wallet/DSWallet+Protected.h @@ -25,13 +25,16 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSString *creationTimeUniqueID; -@property (nonatomic, strong) SeedRequestBlock seedRequestBlock; - @property (nonatomic, strong) SecureSeedRequestBlock secureSeedRequestBlock; @property (nonatomic, readonly) BOOL hasAnExtendedPublicKeyMissing; -@property (nonatomic, strong) NSData *transientDerivedKeyData; +- (instancetype)initWithUniqueID:(NSString *)uniqueID + andAccounts:(NSArray *)accounts + forChain:(DSChain *)chain + storeSeedPhrase:(BOOL)store + isTransient:(BOOL)isTransient; + //this is used from the account to help determine best start sync position for future resync - (void)setGuessedWalletCreationTime:(NSTimeInterval)guessedWalletCreationTime; @@ -46,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSData *)chainSynchronizationFingerprintForBlockZones:(NSOrderedSet *)blockHeightZones forChainHeight:(uint32_t)chainHeight; -- (void)loadBlockchainIdentities; +- (NSData *_Nullable)requestSeedNoAuth; @end diff --git a/DashSync/shared/Models/Wallet/DSWallet+Tests.h b/DashSync/shared/Models/Wallet/DSWallet+Tests.h new file mode 100644 index 000000000..bcaf97118 --- /dev/null +++ b/DashSync/shared/Models/Wallet/DSWallet+Tests.h @@ -0,0 +1,39 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "DSWallet.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DSWallet (Tests) + +@property (nonatomic, strong) NSData *transientDerivedKeyData; + ++ (DSWallet *)transientWalletWithDerivedKeyData:(NSData *)derivedData + forChain:(DSChain *)chain; ++ (DSWallet *_Nullable)standardWalletWithRandomSeedPhraseForChain:(DSChain *)chain + storeSeedPhrase:(BOOL)store + isTransient:(BOOL)isTransient; ++ (DSWallet *_Nullable)standardWalletWithRandomSeedPhraseInLanguage:(DSBIP39Language)language + forChain:(DSChain *)chain + storeSeedPhrase:(BOOL)store + isTransient:(BOOL)isTransient; + +@end + +NS_ASSUME_NONNULL_END diff --git a/DashSync/shared/Models/Wallet/DSWallet+Tests.m b/DashSync/shared/Models/Wallet/DSWallet+Tests.m new file mode 100644 index 000000000..de6c4a05f --- /dev/null +++ b/DashSync/shared/Models/Wallet/DSWallet+Tests.m @@ -0,0 +1,88 @@ +// +// Created by Vladimir Pirogov +// Copyright © 2025 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 "DSAccount.h" +#import "DSChain+Params.h" +#import "DSWallet+Protected.h" +#import "DSWallet+Tests.h" +#import "NSDate+Utils.h" +#import + +//this is for testing purposes only +NSString const *transientDerivedKeyDataKey = @"transientDerivedKeyDataKey"; + +@implementation DSWallet (Tests) + +- (NSData *)transientDerivedKeyData { + return objc_getAssociatedObject(self, &transientDerivedKeyDataKey); +} + +- (void)setTransientDerivedKeyData:(NSData *)transientDerivedKeyData { + objc_setAssociatedObject(self, &transientDerivedKeyDataKey, transientDerivedKeyData, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + ++ (DSWallet *)transientWalletWithDerivedKeyData:(NSData *)derivedData forChain:(DSChain *)chain { + NSParameterAssert(derivedData); + NSParameterAssert(chain); + + DSAccount *account = [DSAccount accountWithAccountNumber:0 withDerivationPaths:[chain standardDerivationPathsForAccountNumber:0] inContext:chain.chainManagedObjectContext]; + + + NSString *uniqueId = [self setTransientDerivedKeyData:derivedData withAccounts:@[account] forChain:chain]; //make sure we can create the wallet first + if (!uniqueId) return nil; + //[self registerSpecializedDerivationPathsForSeedPhrase:seedPhrase underUniqueId:uniqueId onChain:chain]; + DSWallet *wallet = [[DSWallet alloc] initWithUniqueID:uniqueId andAccounts:@[account] forChain:chain storeSeedPhrase:NO isTransient:YES]; + + wallet.transientDerivedKeyData = derivedData; + + return wallet; +} + ++ (DSWallet *)standardWalletWithRandomSeedPhraseForChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient { + NSParameterAssert(chain); + + return [self standardWalletWithRandomSeedPhraseInLanguage:DSBIP39Language_Default forChain:chain storeSeedPhrase:store isTransient:isTransient]; +} + ++ (DSWallet *)standardWalletWithRandomSeedPhraseInLanguage:(DSBIP39Language)language forChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient { + NSParameterAssert(chain); + + return [self standardWalletWithSeedPhrase:[self generateRandomSeedPhraseForLanguage:language] setCreationDate:[NSDate timeIntervalSince1970] forChain:chain storeSeedPhrase:store isTransient:isTransient]; +} + ++ (NSString *)setTransientDerivedKeyData:(NSData *)derivedKeyData withAccounts:(NSArray *)accounts forChain:(DSChain *)chain { + if (!derivedKeyData) return nil; + NSString *uniqueID = nil; + @autoreleasepool { // @autoreleasepool ensures sensitive data will be deallocated immediately + // we store the wallet creation time on the keychain because keychain data persists even when an app is deleted + Slice_u8 *derived_key_data = slice_ctor(derivedKeyData); + uint64_t unique_id = DECDSAPublicKeyUniqueIdFromDerivedKeyData(derived_key_data, chain.chainType); + uniqueID = [NSString stringWithFormat:@"%0llx", unique_id]; + for (DSAccount *account in accounts) { + for (DSDerivationPath *derivationPath in account.fundDerivationPaths) { + [derivationPath generateExtendedPublicKeyFromSeed:derivedKeyData storeUnderWalletUniqueId:nil]; + } + if ([chain isEvolutionEnabled]) { + [account.masterContactsDerivationPath generateExtendedPublicKeyFromSeed:derivedKeyData storeUnderWalletUniqueId:nil]; + } + } + } + return uniqueID; +} + + +@end diff --git a/DashSync/shared/Models/Wallet/DSWallet.h b/DashSync/shared/Models/Wallet/DSWallet.h index f6a3d6993..2a6ade3cf 100644 --- a/DashSync/shared/Models/Wallet/DSWallet.h +++ b/DashSync/shared/Models/Wallet/DSWallet.h @@ -24,7 +24,7 @@ #import "BigIntTypes.h" #import "DSBIP39Mnemonic.h" -#import "DSBlockchainIdentity.h" +#import "DSIdentity.h" #import NS_ASSUME_NONNULL_BEGIN @@ -38,34 +38,17 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSWalletBalanceDidChangeNotification; #define DUFFS 100000000LL #define MAX_MONEY (21000000LL * DUFFS) -@class DSChain, DSAccount, DSTransaction, DSDerivationPath, DSLocalMasternode, DSSpecialTransactionsWalletHolder, DSBlockchainInvitation; +@class DSChain, DSAccount, DSTransaction, DSDerivationPath, DSLocalMasternode, DSSpecialTransactionsWalletHolder, DSInvitation; @interface DSWallet : NSObject @property (nonatomic, readonly) NSDictionary *orderedAccounts; - @property (nonatomic, readonly) uint32_t lastAccountNumber; - @property (nonatomic, readonly) NSArray *accounts; - @property (nonatomic, readonly) DSSpecialTransactionsWalletHolder *specialTransactionsHolder; - -@property (nonatomic, readonly) NSDictionary *blockchainIdentities; - -@property (nonatomic, readonly) NSDictionary *blockchainInvitations; - -@property (nonatomic, readonly, nullable) DSBlockchainIdentity *defaultBlockchainIdentity; - -- (void)setDefaultBlockchainIdentity:(DSBlockchainIdentity *)defaultBlockchainIdentity; - -@property (nonatomic, readonly) NSArray *blockchainIdentityAddresses; - @property (nonatomic, readonly) NSArray *providerOwnerAddresses; - @property (nonatomic, readonly) NSArray *providerVotingAddresses; - @property (nonatomic, readonly) NSArray *providerOperatorAddresses; - @property (nonatomic, readonly) NSArray *platformNodeAddresses; //This is unique among all wallets and all chains @@ -103,29 +86,19 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSWalletBalanceDidChangeNotification; // the total amount received by the wallet (excluding change) @property (nonatomic, readonly) uint64_t totalReceived; -// the first unused index for blockchain identity registration funding -@property (nonatomic, readonly) uint32_t unusedBlockchainIdentityIndex; - -// the first unused index for invitations -@property (nonatomic, readonly) uint32_t unusedBlockchainInvitationIndex; - -// the amount of known blockchain identities -@property (nonatomic, readonly) uint32_t blockchainIdentitiesCount; - -// the amount of known blockchain invitations -@property (nonatomic, readonly) uint32_t blockchainInvitationsCount; - // The fingerprint for currentTransactions @property (nonatomic, readonly) NSData *chainSynchronizationFingerprint; - (void)authPrivateKey:(void (^_Nullable)(NSString *_Nullable authKey))completion; -+ (DSWallet *_Nullable)standardWalletWithSeedPhrase:(NSString *)seedPhrase setCreationDate:(NSTimeInterval)creationDate forChain:(DSChain *)chain storeSeedPhrase:(BOOL)storeSeedPhrase isTransient:(BOOL)isTransient; -+ (DSWallet *_Nullable)standardWalletWithRandomSeedPhraseForChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient; -+ (DSWallet *_Nullable)standardWalletWithRandomSeedPhraseInLanguage:(DSBIP39Language)language forChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient; -+ (DSWallet *_Nullable)transientWalletWithDerivedKeyData:(NSData *)derivedData forChain:(DSChain *)chain; ++ (DSWallet *_Nullable)standardWalletWithSeedPhrase:(NSString *)seedPhrase + setCreationDate:(NSTimeInterval)creationDate + forChain:(DSChain *)chain + storeSeedPhrase:(BOOL)storeSeedPhrase + isTransient:(BOOL)isTransient; -- (instancetype)initWithUniqueID:(NSString *_Nonnull)uniqueID forChain:(DSChain *_Nonnull)chain; +- (instancetype)initWithUniqueID:(NSString *_Nonnull)uniqueID + forChain:(DSChain *_Nonnull)chain; // true if the address is controlled by the wallet - (BOOL)containsAddress:(NSString *)address; @@ -134,14 +107,10 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSWalletBalanceDidChangeNotification; - (BOOL)accountsBaseDerivationPathsContainAddress:(NSString *)address; - (DSAccount *_Nullable)accountForAddress:(NSString *)address; - - (DSAccount *_Nullable)accountForDashpayExternalDerivationPathAddress:(NSString *)address; - // true if the address was previously used as an input or output for this wallet - (BOOL)addressIsUsed:(NSString *)address; - - (BOOL)transactionAddressAlreadySeenInOutputs:(NSString *)address; - - (void)chainUpdatedBlockHeight:(int32_t)height; // sets the block heights and timestamps for the given transactions, and returns an array of hashes of the updated tx @@ -170,12 +139,15 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSWalletBalanceDidChangeNotification; - (NSArray *)accountsThatCanContainTransaction:(DSTransaction *)transaction; // returns an account to which the given transaction hash is associated with, no account if the transaction hash is not associated with the wallet -- (DSAccount *_Nullable)accountForTransactionHash:(UInt256)txHash transaction:(DSTransaction *_Nullable __autoreleasing *_Nullable)transaction; +- (DSAccount *_Nullable)accountForTransactionHash:(UInt256)txHash + transaction:(DSTransaction *_Nullable __autoreleasing *_Nullable)transaction; // returns the transaction with the given hash if it's been registered in the wallet (might also return non-registered) - (DSTransaction *_Nullable)transactionForHash:(UInt256)txHash; -- (NSArray *)registerAddressesWithGapLimit:(NSUInteger)gapLimit unusedAccountGapLimit:(NSUInteger)unusedAccountGapLimit dashpayGapLimit:(NSUInteger)dashpayGapLimit internal:(BOOL)internal error:(NSError *_Nullable *_Nullable)error; +- (NSArray *)allAddresses; +- (NSArray *_Nullable)registerAddressesWithInitialGapLimit; +- (NSArray *_Nullable)registerAddressesWithProlongGapLimit; // returns the amount received by the wallet from the transaction (total outputs to change and/or receive addresses) - (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction; @@ -189,20 +161,27 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSWalletBalanceDidChangeNotification; // true if no previous wallet transaction spends any of the given transaction's inputs, and no inputs are invalid - (BOOL)transactionIsValid:(DSTransaction *)transaction; +// returns input value if no previous wallet transaction spends this input, and the input is valid, -1 otherwise. +- (int64_t)inputValue:(UInt256)txHash inputIndex:(uint32_t)index; + // this is used to save transactions atomically with the block, needs to be called before switching threads to save the block - (void)prepareForIncomingTransactionPersistenceForBlockSaveWithNumber:(uint32_t)blockNumber; // this is used to save transactions atomically with the block -- (void)persistIncomingTransactionsAttributesForBlockSaveWithNumber:(uint32_t)blockNumber inContext:(NSManagedObjectContext *)context; +- (void)persistIncomingTransactionsAttributesForBlockSaveWithNumber:(uint32_t)blockNumber + inContext:(NSManagedObjectContext *)context; //returns the seed phrase after authenticating - (void)seedPhraseAfterAuthentication:(void (^_Nullable)(NSString *_Nullable seedPhrase))completion; -- (void)seedPhraseAfterAuthenticationWithPrompt:(NSString *_Nullable)authprompt completion:(void (^_Nullable)(NSString *_Nullable seedPhrase))completion; +- (void)seedPhraseAfterAuthenticationWithPrompt:(NSString *_Nullable)authprompt + completion:(void (^_Nullable)(NSString *_Nullable seedPhrase))completion; - (NSString *_Nullable)seedPhraseIfAuthenticated; -- (OpaqueKey *_Nullable)privateKeyForAddress:(NSString *_Nonnull)address fromSeed:(NSData *_Nonnull)seed; -- (NSString *_Nullable)privateKeyAddressForAddress:(NSString *)address fromSeed:(NSData *)seed; +- (DMaybeOpaqueKey *_Nullable)privateKeyForAddress:(NSString *_Nonnull)address + fromSeed:(NSData *_Nonnull)seed; +- (NSString *_Nullable)privateKeyAddressForAddress:(NSString *)address + fromSeed:(NSData *)seed; //generate a random Mnemonic seed + (NSString *_Nullable)generateRandomSeedPhrase; @@ -222,66 +201,41 @@ FOUNDATION_EXPORT NSString *_Nonnull const DSWalletBalanceDidChangeNotification; //Recreate derivation paths and addresses - (void)reloadDerivationPaths; -- (void)unregisterBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; -- (void)addBlockchainIdentities:(NSArray *)blockchainIdentities; -- (void)addBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; - -//Verify makes sure the keys for the blockchain identity are good -- (BOOL)registerBlockchainIdentities:(NSArray *)blockchainIdentities verify:(BOOL)verify; -- (BOOL)registerBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity verify:(BOOL)verify; -- (BOOL)registerBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; -- (BOOL)containsBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity; - -- (void)unregisterBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation; -- (void)addBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation; -- (void)registerBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation; -- (BOOL)containsBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation; - -- (DSBlockchainIdentity *)createBlockchainIdentity; -- (DSBlockchainIdentity *)createBlockchainIdentityUsingDerivationIndex:(uint32_t)index; -- (DSBlockchainIdentity *)createBlockchainIdentityForUsername:(NSString *_Nullable)username; -- (DSBlockchainIdentity *)createBlockchainIdentityForUsername:(NSString *_Nullable)username usingDerivationIndex:(uint32_t)index; - -- (DSBlockchainInvitation *)createBlockchainInvitation; -- (DSBlockchainInvitation *)createBlockchainInvitationUsingDerivationIndex:(uint32_t)index; - -- (DSBlockchainIdentity *_Nullable)blockchainIdentityThatCreatedContract:(DPContract *)contract withContractId:(UInt256)contractId; - -- (DSBlockchainIdentity *_Nullable)blockchainIdentityForUniqueId:(UInt256)uniqueId; - -- (DSBlockchainInvitation *_Nullable)blockchainInvitationForUniqueId:(UInt256)uniqueId; -- (void)seedWithPrompt:(NSString *_Nullable)authprompt forAmount:(uint64_t)amount completion:(_Nullable SeedCompletionBlock)completion; +- (void)seedWithPrompt:(NSString *_Nullable)authprompt + forAmount:(uint64_t)amount + completion:(_Nullable SeedCompletionBlock)completion; -- (void)copyForChain:(DSChain *)chain completion:(void (^_Nonnull)(DSWallet *_Nullable copiedWallet))completion; +- (void)copyForChain:(DSChain *)chain + completion:(void (^_Nonnull)(DSWallet *_Nullable copiedWallet))completion; - (void)registerMasternodeOperator:(DSLocalMasternode *)masternode; //will use indexes -- (void)registerMasternodeOperator:(DSLocalMasternode *)masternode withOperatorPublicKey:(OpaqueKey *)operatorKey; //will use defined key +- (void)registerMasternodeOperator:(DSLocalMasternode *)masternode + withOperatorPublicKey:(DOpaqueKey *)operatorKey; //will use defined key - (void)registerMasternodeOwner:(DSLocalMasternode *)masternode; -- (void)registerMasternodeOwner:(DSLocalMasternode *)masternode withOwnerPrivateKey:(OpaqueKey *)ownerKey; //will use defined key +- (void)registerMasternodeOwner:(DSLocalMasternode *)masternode + withOwnerPrivateKey:(DOpaqueKey *)ownerKey; //will use defined key - (void)registerMasternodeVoter:(DSLocalMasternode *)masternode; -- (void)registerMasternodeVoter:(DSLocalMasternode *)masternode withVotingKey:(OpaqueKey *)votingKey; //will use defined key +- (void)registerMasternodeVoter:(DSLocalMasternode *)masternode + withVotingKey:(DOpaqueKey *)votingKey; //will use defined key - (void)registerPlatformNode:(DSLocalMasternode *)masternode; -- (void)registerPlatformNode:(DSLocalMasternode *)masternode withKey:(OpaqueKey *)key; //will use defined key +- (void)registerPlatformNode:(DSLocalMasternode *)masternode + withKey:(DOpaqueKey *)key; //will use defined key -- (BOOL)containsProviderVotingAuthenticationHash:(UInt160)votingAuthenticationHash; -- (BOOL)containsProviderOwningAuthenticationHash:(UInt160)owningAuthenticationHash; -- (BOOL)containsProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey; -- (BOOL)containsBlockchainIdentityBLSAuthenticationHash:(UInt160)blockchainIdentityAuthenticationHash; +//- (BOOL)containsProviderVotingAuthenticationHash:(UInt160)hash; +//- (BOOL)containsProviderOwningAuthenticationHash:(UInt160)hash; +//- (BOOL)containsProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey; +//- (BOOL)containsIdentityBLSAuthenticationHash:(UInt160)hash; - (BOOL)containsHoldingAddress:(NSString *)holdingAddress; -- (NSUInteger)indexOfProviderVotingAuthenticationHash:(UInt160)votingAuthenticationHash; -- (NSUInteger)indexOfProviderOwningAuthenticationHash:(UInt160)owningAuthenticationHash; +- (NSUInteger)indexOfProviderVotingAuthenticationHash:(UInt160)hash; +- (NSUInteger)indexOfProviderOwningAuthenticationHash:(UInt160)hash; - (NSUInteger)indexOfProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey; -- (NSUInteger)indexOfPlatformNodeAuthenticationHash:(UInt160)platformNodeAuthenticationHash; +- (NSUInteger)indexOfPlatformNodeAuthenticationHash:(UInt160)hash; - (NSUInteger)indexOfHoldingAddress:(NSString *)holdingAddress; -- (NSUInteger)indexOfBlockchainIdentityAuthenticationHash:(UInt160)blockchainIdentityAuthenticationHash; -- (NSUInteger)indexOfBlockchainIdentityCreditFundingRegistrationHash:(UInt160)creditFundingRegistrationHash; -- (NSUInteger)indexOfBlockchainIdentityCreditFundingTopupHash:(UInt160)creditFundingTopupHash; -- (NSUInteger)indexOfBlockchainIdentityCreditFundingInvitationHash:(UInt160)creditFundingInvitationHash; @end diff --git a/DashSync/shared/Models/Wallet/DSWallet.m b/DashSync/shared/Models/Wallet/DSWallet.m index 9eee22251..355ea2e04 100644 --- a/DashSync/shared/Models/Wallet/DSWallet.m +++ b/DashSync/shared/Models/Wallet/DSWallet.m @@ -23,41 +23,22 @@ // THE SOFTWARE. #import "DSAccount.h" -#import "DSAccountEntity+CoreDataClass.h" -#import "DSAddressEntity+CoreDataProperties.h" +#import "DSAssetLockDerivationPath+Protected.h" #import "DSAuthenticationKeysDerivationPath+Protected.h" #import "DSAuthenticationManager+Private.h" -#import "DSAuthenticationManager.h" -#import "DSBIP39Mnemonic.h" -#import "DSBlockchainIdentity+Protected.h" -#import "DSBlockchainIdentityEntity+CoreDataClass.h" -#import "DSBlockchainIdentityKeyPathEntity+CoreDataClass.h" -#import "DSBlockchainIdentityRegistrationTransition.h" -#import "DSBlockchainIdentityUpdateTransition.h" -#import "DSBlockchainIdentityUsernameEntity+CoreDataClass.h" -#import "DSBlockchainInvitation+Protected.h" -#import "DSChain+Protected.h" +#import "DSChain+Params.h" +#import "DSChain+Wallet.h" #import "DSChainsManager.h" -#import "DSCreditFundingDerivationPath+Protected.h" -#import "DSCreditFundingTransaction.h" -#import "DSCreditFundingTransactionEntity+CoreDataClass.h" -#import "DSDashpayUserEntity+CoreDataClass.h" -#import "DSDerivationPathEntity+CoreDataClass.h" #import "DSDerivationPathFactory.h" -#import "DSEnvironment.h" -#import "DSFriendRequestEntity+CoreDataClass.h" -#import "DSIncomingFundsDerivationPath.h" #import "DSLocalMasternode.h" #import "DSMasternodeHoldingsDerivationPath+Protected.h" #import "DSOptionsManager.h" -#import "DSPriceManager.h" #import "DSProviderRegistrationTransaction.h" #import "DSSpecialTransactionsWalletHolder.h" -#import "DSTransactionEntity+CoreDataProperties.h" +#import "DSWallet+Identity.h" +#import "DSWallet+Invitation.h" #import "DSWallet+Protected.h" -#import "NSData+Dash.h" #import "NSDate+Utils.h" -#import "NSManagedObject+Sugar.h" #import "NSMutableData+Dash.h" #define SEED_ENTROPY_LENGTH (128 / 8) @@ -66,8 +47,6 @@ #define AUTH_PRIVKEY_KEY @"authprivkey" #define WALLET_MNEMONIC_KEY @"WALLET_MNEMONIC_KEY" #define WALLET_MASTER_PUBLIC_KEY @"WALLET_MASTER_PUBLIC_KEY" -#define WALLET_BLOCKCHAIN_USERS_KEY @"WALLET_BLOCKCHAIN_USERS_KEY" -#define WALLET_BLOCKCHAIN_INVITATIONS_KEY @"WALLET_BLOCKCHAIN_INVITATIONS_KEY" #define WALLET_ACCOUNTS_KNOWN_KEY @"WALLET_ACCOUNTS_KNOWN_KEY" @@ -79,9 +58,6 @@ #define VERIFIED_WALLET_CREATION_TIME_KEY @"VERIFIED_WALLET_CREATION_TIME" #define REFERENCE_DATE_2001 978307200 -#define IDENTITY_INDEX_KEY @"IDENTITY_INDEX_KEY" -#define IDENTITY_LOCKED_OUTPUT_KEY @"IDENTITY_LOCKED_OUTPUT_KEY" - @interface DSWallet () { NSTimeInterval _lGuessedWalletCreationTime; } @@ -105,8 +81,6 @@ @interface DSWallet () { @property (nonatomic, strong) NSMutableDictionary *mPlatformNodeKeyLocations; @property (nonatomic, assign, getter=isTransient) BOOL transient; -@property (nonatomic, strong) NSMutableDictionary *mBlockchainIdentities; -@property (nonatomic, strong) NSMutableDictionary *mBlockchainInvitations; @end @@ -126,36 +100,6 @@ + (DSWallet *)standardWalletWithSeedPhrase:(NSString *)seedPhrase setCreationDat return wallet; } -+ (DSWallet *)standardWalletWithRandomSeedPhraseForChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient { - NSParameterAssert(chain); - - return [self standardWalletWithRandomSeedPhraseInLanguage:DSBIP39Language_Default forChain:chain storeSeedPhrase:store isTransient:isTransient]; -} - -+ (DSWallet *)standardWalletWithRandomSeedPhraseInLanguage:(DSBIP39Language)language forChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient { - NSParameterAssert(chain); - - return [self standardWalletWithSeedPhrase:[self generateRandomSeedPhraseForLanguage:language] setCreationDate:[NSDate timeIntervalSince1970] forChain:chain storeSeedPhrase:store isTransient:isTransient]; -} - -//this is for testing purposes only -+ (DSWallet *)transientWalletWithDerivedKeyData:(NSData *)derivedData forChain:(DSChain *)chain { - NSParameterAssert(derivedData); - NSParameterAssert(chain); - - DSAccount *account = [DSAccount accountWithAccountNumber:0 withDerivationPaths:[chain standardDerivationPathsForAccountNumber:0] inContext:chain.chainManagedObjectContext]; - - - NSString *uniqueId = [self setTransientDerivedKeyData:derivedData withAccounts:@[account] forChain:chain]; //make sure we can create the wallet first - if (!uniqueId) return nil; - //[self registerSpecializedDerivationPathsForSeedPhrase:seedPhrase underUniqueId:uniqueId onChain:chain]; - DSWallet *wallet = [[DSWallet alloc] initWithUniqueID:uniqueId andAccounts:@[account] forChain:chain storeSeedPhrase:NO isTransient:YES]; - - wallet.transientDerivedKeyData = derivedData; - - return wallet; -} - - (instancetype)initWithChain:(DSChain *)chain { NSParameterAssert(chain); @@ -163,7 +107,6 @@ - (instancetype)initWithChain:(DSChain *)chain { self.transient = FALSE; self.mAccounts = [NSMutableDictionary dictionary]; self.chain = chain; - self.mBlockchainIdentities = [NSMutableDictionary dictionary]; self.mMasternodeOwnerIndexes = [NSMutableDictionary dictionary]; self.mMasternodeVoterIndexes = [NSMutableDictionary dictionary]; self.mMasternodeOperatorIndexes = [NSMutableDictionary dictionary]; @@ -173,10 +116,15 @@ - (instancetype)initWithChain:(DSChain *)chain { self.checkedWalletCreationTime = NO; self.checkedGuessedWalletCreationTime = NO; self.checkedVerifyWalletCreationTime = NO; + [self setup]; return self; } -- (instancetype)initWithUniqueID:(NSString *)uniqueID andAccounts:(NSArray *)accounts forChain:(DSChain *)chain storeSeedPhrase:(BOOL)store isTransient:(BOOL)isTransient { +- (instancetype)initWithUniqueID:(NSString *)uniqueID + andAccounts:(NSArray *)accounts + forChain:(DSChain *)chain + storeSeedPhrase:(BOOL)store + isTransient:(BOOL)isTransient { NSParameterAssert(uniqueID); NSParameterAssert(accounts); NSParameterAssert(chain); @@ -185,12 +133,6 @@ - (instancetype)initWithUniqueID:(NSString *)uniqueID andAccounts:(NSArray *)allAddresses { + NSMutableArray *allAddressesArray = [NSMutableArray array]; + NSSet *addresses = [self.allReceiveAddresses setByAddingObjectsFromSet:self.allChangeAddresses]; + [allAddressesArray addObjectsFromArray:[addresses allObjects]]; + [allAddressesArray addObjectsFromArray:[self providerOwnerAddresses]]; + [allAddressesArray addObjectsFromArray:[self providerVotingAddresses]]; + [allAddressesArray addObjectsFromArray:[self providerOperatorAddresses]]; + [allAddressesArray addObjectsFromArray:[self platformNodeAddresses]]; + //we should also add the blockchain user public keys to the filter + if (self.chain.isEvolutionEnabled) + [allAddressesArray addObjectsFromArray:[self identityAddresses]]; + return allAddressesArray; +} + +- (NSArray *)registerAddressesWithInitialGapLimit { + NSMutableArray *mArray = [NSMutableArray array]; + for (DSAccount *account in self.accounts) { + [mArray addObjectsFromArray:[account registerAddressesWithInitialGapLimit]]; + } + return [mArray copy]; +} + +- (NSArray *)registerAddressesWithProlongGapLimit { NSMutableArray *mArray = [NSMutableArray array]; for (DSAccount *account in self.accounts) { - [mArray addObjectsFromArray:[account registerAddressesWithGapLimit:gapLimit unusedAccountGapLimit:unusedAccountGapLimit dashpayGapLimit:dashpayGapLimit internal:internal error:error]]; + [mArray addObjectsFromArray:[account registerAddressesWithProlongGapLimit]]; } return [mArray copy]; } - (DSAccount *)firstAccountThatCanContainTransaction:(DSTransaction *)transaction { NSParameterAssert(transaction); - for (DSAccount *account in self.accounts) { if ([account canContainTransaction:transaction]) return account; } @@ -935,7 +799,6 @@ - (NSArray *)unspentOutputs { // true if the address is controlled by the wallet, this can also be for paths that are not accounts (todo) - (BOOL)containsAddress:(NSString *)address { NSParameterAssert(address); - for (DSAccount *account in self.accounts) { if ([account containsAddress:address]) return TRUE; } @@ -945,7 +808,6 @@ - (BOOL)containsAddress:(NSString *)address { // true if the address is controlled by the wallet, this can also be for paths that are not accounts (todo) - (BOOL)accountsBaseDerivationPathsContainAddress:(NSString *)address { NSParameterAssert(address); - for (DSAccount *account in self.accounts) { if ([account baseDerivationPathsContainAddress:address]) return TRUE; } @@ -962,7 +824,6 @@ - (DSAccount *_Nullable)firstAccountWithBalance { - (DSAccount *)accountForAddress:(NSString *)address { NSParameterAssert(address); - for (DSAccount *account in self.accounts) { if ([account containsAddress:address]) return account; } @@ -971,7 +832,6 @@ - (DSAccount *)accountForAddress:(NSString *)address { - (DSAccount *)accountForDashpayExternalDerivationPathAddress:(NSString *)address { NSParameterAssert(address); - for (DSAccount *account in self.accounts) { if ([account externalDerivationPathContainingAddress:address]) return account; } @@ -981,7 +841,6 @@ - (DSAccount *)accountForDashpayExternalDerivationPathAddress:(NSString *)addres // true if the address was previously used as an input or output in any wallet transaction - (BOOL)addressIsUsed:(NSString *)address { NSParameterAssert(address); - for (DSAccount *account in self.accounts) { if ([account addressIsUsed:address]) return TRUE; } @@ -990,7 +849,6 @@ - (BOOL)addressIsUsed:(NSString *)address { - (BOOL)transactionAddressAlreadySeenInOutputs:(NSString *)address { NSParameterAssert(address); - for (DSAccount *account in self.accounts) { if ([account transactionAddressAlreadySeenInOutputs:address]) return TRUE; } @@ -1000,7 +858,6 @@ - (BOOL)transactionAddressAlreadySeenInOutputs:(NSString *)address { // returns the amount received by the wallet from the transaction (total outputs to change and/or receive addresses) - (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction { NSParameterAssert(transaction); - uint64_t received = 0; for (DSAccount *account in self.accounts) { received += [account amountReceivedFromTransaction:transaction]; @@ -1011,7 +868,6 @@ - (uint64_t)amountReceivedFromTransaction:(DSTransaction *)transaction { // retuns the amount sent from the wallet by the trasaction (total wallet outputs consumed, change and fee included) - (uint64_t)amountSentByTransaction:(DSTransaction *)transaction { NSParameterAssert(transaction); - uint64_t sent = 0; for (DSAccount *account in self.accounts) { sent += [account amountSentByTransaction:transaction]; @@ -1084,7 +940,15 @@ - (BOOL)transactionIsValid:(DSTransaction *_Nonnull)transaction { return TRUE; } -- (OpaqueKey *)privateKeyForAddress:(NSString *)address fromSeed:(NSData *)seed { +- (int64_t)inputValue:(UInt256)txHash inputIndex:(uint32_t)index { + for (DSAccount *account in self.accounts) { + int64_t value = [account inputValue:txHash inputIndex:index]; + if (value != -1) return value; + } + return -1; +} + +- (DMaybeOpaqueKey *)privateKeyForAddress:(NSString *)address fromSeed:(NSData *)seed { NSParameterAssert(address); NSParameterAssert(seed); @@ -1097,9 +961,19 @@ - (OpaqueKey *)privateKeyForAddress:(NSString *)address fromSeed:(NSData *)seed } - (NSString *)privateKeyAddressForAddress:(NSString *)address fromSeed:(NSData *)seed { - OpaqueKey *key = [self privateKeyForAddress:address fromSeed:seed]; - NSString *addressString = [DSKeyManager addressForKey:key forChainType:self.chain.chainType]; - return addressString; + DMaybeOpaqueKey *result = [self privateKeyForAddress:address fromSeed:seed]; + NSString *keyAddress = NULL; + if (result) { + if (result->ok) { + char *c_string = DOpaqueKeyPubAddress(result->ok, self.chain.chainType); + keyAddress = NSStringFromPtr(c_string); + if (c_string) { + DCharDtor(c_string); + } + } + DMaybeOpaqueKeyDtor(result); + } + return keyAddress; } - (void)reloadDerivationPaths { @@ -1132,8 +1006,8 @@ - (void)wipeBlockchainInfoInContext:(NSManagedObjectContext *)context { [account wipeBlockchainInfo]; } [self.specialTransactionsHolder removeAllTransactions]; - [self wipeBlockchainIdentitiesInContext:context]; - [self wipeBlockchainInvitationsInContext:context]; + [self wipeIdentitiesInContext:context]; + [self wipeInvitationsInContext:context]; } - (void)wipeBlockchainExtraAccountsInContext:(NSManagedObjectContext *)context { @@ -1147,416 +1021,6 @@ - (void)wipeBlockchainExtraAccountsInContext:(NSManagedObjectContext *)context { } } -// MARK: - Blockchain Identities - -- (NSArray *)blockchainIdentityAddresses { - DSAuthenticationKeysDerivationPath *derivationPath = [[DSDerivationPathFactory sharedInstance] blockchainIdentityBLSKeysDerivationPathForWallet:self]; - if (!derivationPath.hasExtendedPublicKey) return @[]; - return [derivationPath addressesToIndex:[self unusedBlockchainIdentityIndex] + 10 useCache:YES addToCache:YES]; -} - -- (void)unregisterBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - NSParameterAssert(blockchainIdentity); - NSAssert(blockchainIdentity.wallet == self, @"the blockchainIdentity you are trying to remove is not in this wallet"); - - [self.mBlockchainIdentities removeObjectForKey:blockchainIdentity.uniqueIDData]; - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainIdentitiesKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - if (!keyChainDictionary) keyChainDictionary = [NSMutableDictionary dictionary]; - [keyChainDictionary removeObjectForKey:blockchainIdentity.uniqueIDData]; - setKeychainDict(keyChainDictionary, self.walletBlockchainIdentitiesKey, NO); -} - -- (void)addBlockchainIdentities:(NSArray *)blockchainIdentities { - for (DSBlockchainIdentity *identity in blockchainIdentities) { - [self addBlockchainIdentity:identity]; - } -} - -- (void)addBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - NSParameterAssert(blockchainIdentity); - NSAssert(uint256_is_not_zero(blockchainIdentity.uniqueID), @"The blockchain identity unique ID must be set"); - [self.mBlockchainIdentities setObject:blockchainIdentity forKey:blockchainIdentity.uniqueIDData]; -} - -- (BOOL)containsBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - if (blockchainIdentity.lockedOutpointData) { - return ([self.mBlockchainIdentities objectForKey:blockchainIdentity.uniqueIDData] != nil); - } else { - return FALSE; - } -} - -- (BOOL)registerBlockchainIdentities:(NSArray *)blockchainIdentities verify:(BOOL)verify { - for (DSBlockchainIdentity *identity in blockchainIdentities) { - BOOL success = [self registerBlockchainIdentity:identity verify:verify]; - if (!success) { - return FALSE; - } - } - return TRUE; -} - -- (BOOL)registerBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity { - return [self registerBlockchainIdentity:blockchainIdentity verify:NO]; -} - -- (BOOL)registerBlockchainIdentity:(DSBlockchainIdentity *)blockchainIdentity verify:(BOOL)verify { - NSParameterAssert(blockchainIdentity); - if (verify) { - BOOL verified = [blockchainIdentity verifyKeysForWallet:self]; - if (!verified) { - blockchainIdentity.isLocal = FALSE; - return FALSE; - } - } - - if ([self.mBlockchainIdentities objectForKey:blockchainIdentity.uniqueIDData] == nil) { - [self addBlockchainIdentity:blockchainIdentity]; - } - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainIdentitiesKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - - if (error) return FALSE; - - if (!keyChainDictionary) keyChainDictionary = [NSMutableDictionary dictionary]; - - NSAssert(uint256_is_not_zero(blockchainIdentity.uniqueID), @"registrationTransactionHashData must not be null"); - if (uint256_is_zero(blockchainIdentity.lockedOutpointData.transactionOutpoint.hash)) { - keyChainDictionary[blockchainIdentity.uniqueIDData] = @{IDENTITY_INDEX_KEY: @(blockchainIdentity.index)}; - } else { - keyChainDictionary[blockchainIdentity.uniqueIDData] = @{IDENTITY_INDEX_KEY: @(blockchainIdentity.index), IDENTITY_LOCKED_OUTPUT_KEY: blockchainIdentity.lockedOutpointData}; - } - setKeychainDict(keyChainDictionary, self.walletBlockchainIdentitiesKey, NO); - - if (!_defaultBlockchainIdentity && (blockchainIdentity.index == 0)) { - _defaultBlockchainIdentity = blockchainIdentity; - } - return TRUE; -} - -- (void)wipeBlockchainIdentitiesInContext:(NSManagedObjectContext *)context { - for (DSBlockchainIdentity *blockchainIdentity in [_mBlockchainIdentities allValues]) { - [self unregisterBlockchainIdentity:blockchainIdentity]; - [blockchainIdentity deletePersistentObjectAndSave:NO inContext:context]; - } - _defaultBlockchainIdentity = nil; -} - -- (DSBlockchainIdentity *_Nullable)blockchainIdentityThatCreatedContract:(DPContract *)contract withContractId:(UInt256)contractId { - NSParameterAssert(contract); - NSAssert(uint256_is_not_zero(contractId), @"contractId must not be null"); - DSBlockchainIdentity *foundBlockchainIdentity = nil; - for (DSBlockchainIdentity *blockchainIdentity in [_mBlockchainIdentities allValues]) { - if (uint256_eq([contract contractIdIfRegisteredByBlockchainIdentity:blockchainIdentity], contractId)) { - foundBlockchainIdentity = blockchainIdentity; - } - } - return foundBlockchainIdentity; -} - -- (DSBlockchainIdentity *)blockchainIdentityForUniqueId:(UInt256)uniqueId { - NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); - DSBlockchainIdentity *foundBlockchainIdentity = nil; - for (DSBlockchainIdentity *blockchainIdentity in [_mBlockchainIdentities allValues]) { - if (uint256_eq([blockchainIdentity uniqueID], uniqueId)) { - foundBlockchainIdentity = blockchainIdentity; - } - } - return foundBlockchainIdentity; -} - -- (uint32_t)blockchainIdentitiesCount { - return (uint32_t)[self.mBlockchainIdentities count]; -} - -- (BOOL)upgradeIdentityKeyChain { - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainIdentitiesKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - NSAssert(error == nil, @"There should be no error during upgrade"); - if (error) return FALSE; - NSMutableDictionary *updatedKeyChainDictionary = [NSMutableDictionary dictionary]; - for (NSData *blockchainIdentityLockedOutpoint in keyChainDictionary) { - NSData *uniqueIdData = uint256_data([blockchainIdentityLockedOutpoint SHA256_2]); - [updatedKeyChainDictionary setObject:@{IDENTITY_INDEX_KEY: keyChainDictionary[blockchainIdentityLockedOutpoint], IDENTITY_LOCKED_OUTPUT_KEY: blockchainIdentityLockedOutpoint} forKey:uniqueIdData]; - } - setKeychainDict(updatedKeyChainDictionary, self.walletBlockchainIdentitiesKey, NO); - return TRUE; -} - - -//This loads all the identities that the wallet knows about. If the app was deleted and reinstalled the identity information will remain from the keychain but must be reaquired from the network. -- (NSMutableDictionary *)blockchainIdentities { - //setKeychainDict(@{}, self.walletBlockchainIdentitiesKey, NO); - if (_mBlockchainIdentities) return _mBlockchainIdentities; - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainIdentitiesKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - if (error) { - return nil; - } - uint64_t defaultIndex = getKeychainInt(self.walletBlockchainIdentitiesDefaultIndexKey, &error); - if (error) { - return nil; - } - NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; - - if (keyChainDictionary && keyChainDictionary.count) { - if ([[[keyChainDictionary allValues] firstObject] isKindOfClass:[NSNumber class]]) { - BOOL upgraded = [self upgradeIdentityKeyChain]; - if (!upgraded) { - return nil; - } else { - return (NSMutableDictionary *) [self blockchainIdentities]; - } - } - for (NSData *uniqueIdData in keyChainDictionary) { - uint32_t index = [[keyChainDictionary[uniqueIdData] objectForKey:IDENTITY_INDEX_KEY] unsignedIntValue]; - //DSLogPrivate(@"Blockchain identity unique Id is %@",uint256_hex(blockchainIdentityUniqueId)); - // UInt256 lastTransitionHash = [self.specialTransactionsHolder lastSubscriptionTransactionHashForRegistrationTransactionHash:registrationTransactionHash]; - // DSLogPrivate(@"reg %@ last %@",uint256_hex(registrationTransactionHash),uint256_hex(lastTransitionHash)); - // DSBlockchainIdentityRegistrationTransition * blockchainIdentityRegistrationTransaction = [self blockchainIdentityRegistrationTransactionForIndex:index]; - - //either the identity is known in core data (and we can pull it) or the wallet has been wiped and we need to get it from DAPI (the unique Id was saved in the keychain, so we don't need to resync) - //TODO: get the identity from core data - - NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; //shouldn't matter what context is used - - [context performBlockAndWait:^{ - NSUInteger blockchainIdentityEntitiesCount = [DSBlockchainIdentityEntity countObjectsInContext:context matching:@"chain == %@ && isLocal == TRUE", [self.chain chainEntityInContext:context]]; - if (blockchainIdentityEntitiesCount != keyChainDictionary.count) { - DSLog(@"[%@] Unmatching blockchain entities count", self.chain.name); - } - DSBlockchainIdentityEntity *blockchainIdentityEntity = [DSBlockchainIdentityEntity anyObjectInContext:context matching:@"uniqueID == %@", uniqueIdData]; - DSBlockchainIdentity *blockchainIdentity = nil; - NSData *lockedOutpointData = [keyChainDictionary[uniqueIdData] objectForKey:IDENTITY_LOCKED_OUTPUT_KEY]; - if (blockchainIdentityEntity) { - if (lockedOutpointData) { - blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index withLockedOutpoint:lockedOutpointData.transactionOutpoint inWallet:self withBlockchainIdentityEntity:blockchainIdentityEntity]; - } else { - blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index withUniqueId:uniqueIdData.UInt256 inWallet:self withBlockchainIdentityEntity:blockchainIdentityEntity]; - } - } else if (lockedOutpointData) { - //No blockchain identity is known in core data - NSData *transactionHashData = uint256_data(uint256_reverse(lockedOutpointData.transactionOutpoint.hash)); - DSTransactionEntity *creditRegitrationTransactionEntity = [DSTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", transactionHashData]; - if (creditRegitrationTransactionEntity) { - //The registration funding transaction exists - //Weird but we should recover in this situation - DSCreditFundingTransaction *registrationTransaction = (DSCreditFundingTransaction *)[creditRegitrationTransactionEntity transactionForChain:self.chain]; - - BOOL correctIndex = [registrationTransaction checkDerivationPathIndexForWallet:self isIndex:index]; - if (!correctIndex) { - NSAssert(FALSE, @"We should implement this"); - } else { - blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index withFundingTransaction:registrationTransaction withUsernameDictionary:nil inWallet:self]; - [blockchainIdentity registerInWallet]; - } - } else { - //We also don't have the registration funding transaction - blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index withUniqueId:uniqueIdData.UInt256 inWallet:self]; - [blockchainIdentity registerInWalletForBlockchainIdentityUniqueId:uniqueIdData.UInt256]; - } - } else { - blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index withUniqueId:uniqueIdData.UInt256 inWallet:self]; - [blockchainIdentity registerInWalletForBlockchainIdentityUniqueId:uniqueIdData.UInt256]; - } - if (blockchainIdentity) { - rDictionary[uniqueIdData] = blockchainIdentity; - if (index == defaultIndex) { - _defaultBlockchainIdentity = blockchainIdentity; - } - } - }]; - } - } - _mBlockchainIdentities = rDictionary; - return _mBlockchainIdentities; -} - -- (void)setDefaultBlockchainIdentity:(DSBlockchainIdentity *)defaultBlockchainIdentity { - if (![[self.blockchainIdentities allValues] containsObject:defaultBlockchainIdentity]) return; - _defaultBlockchainIdentity = defaultBlockchainIdentity; - setKeychainInt(defaultBlockchainIdentity.index, self.walletBlockchainIdentitiesDefaultIndexKey, NO); -} - -- (uint32_t)unusedBlockchainIdentityIndex { - NSArray *blockchainIdentities = [_mBlockchainIdentities allValues]; - NSNumber *max = [blockchainIdentities valueForKeyPath:@"index.@max.intValue"]; - return max != nil ? ([max unsignedIntValue] + 1) : 0; -} - -- (DSBlockchainIdentity *)createBlockchainIdentity { - DSBlockchainIdentity *blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:[self unusedBlockchainIdentityIndex] inWallet:self]; - return blockchainIdentity; -} - -- (DSBlockchainIdentity *)createBlockchainIdentityUsingDerivationIndex:(uint32_t)index { - DSBlockchainIdentity *blockchainIdentity = [[DSBlockchainIdentity alloc] initAtIndex:index inWallet:self]; - return blockchainIdentity; -} - -- (DSBlockchainIdentity *)createBlockchainIdentityForUsername:(NSString *)username { - DSBlockchainIdentity *blockchainIdentity = [self createBlockchainIdentity]; - [blockchainIdentity addDashpayUsername:username save:NO]; - return blockchainIdentity; -} - -- (DSBlockchainIdentity *)createBlockchainIdentityForUsername:(NSString *)username usingDerivationIndex:(uint32_t)index { - DSBlockchainIdentity *blockchainIdentity = [self createBlockchainIdentityUsingDerivationIndex:index]; - [blockchainIdentity addDashpayUsername:username save:NO]; - return blockchainIdentity; -} - -// MARK: - Invitations - - -- (uint32_t)blockchainInvitationsCount { - return (uint32_t)[self.mBlockchainInvitations count]; -} - - -//This loads all the identities that the wallet knows about. If the app was deleted and reinstalled the identity information will remain from the keychain but must be reaquired from the network. -- (NSMutableDictionary *)blockchainInvitations { - //setKeychainDict(@{}, self.walletBlockchainInvitationsKey, NO); - if (_mBlockchainInvitations) return _mBlockchainInvitations; - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainInvitationsKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - if (error) { - return nil; - } - NSMutableDictionary *rDictionary = [NSMutableDictionary dictionary]; - - if (keyChainDictionary) { - for (NSData *blockchainInvitationLockedOutpointData in keyChainDictionary) { - uint32_t index = [keyChainDictionary[blockchainInvitationLockedOutpointData] unsignedIntValue]; - DSUTXO blockchainInvitationLockedOutpoint = blockchainInvitationLockedOutpointData.transactionOutpoint; - //DSLogPrivate(@"Blockchain identity unique Id is %@",uint256_hex(blockchainInvitationUniqueId)); - // UInt256 lastTransitionHash = [self.specialTransactionsHolder lastSubscriptionTransactionHashForRegistrationTransactionHash:registrationTransactionHash]; - // DSLogPrivate(@"reg %@ last %@",uint256_hex(registrationTransactionHash),uint256_hex(lastTransitionHash)); - // DSBlockchainInvitationRegistrationTransition * blockchainInvitationRegistrationTransaction = [self blockchainInvitationRegistrationTransactionForIndex:index]; - - //either the identity is known in core data (and we can pull it) or the wallet has been wiped and we need to get it from DAPI (the unique Id was saved in the keychain, so we don't need to resync) - //TODO: get the identity from core data - - NSManagedObjectContext *context = [NSManagedObjectContext chainContext]; //shouldn't matter what context is used - - [context performBlockAndWait:^{ - NSUInteger blockchainInvitationEntitiesCount = [DSBlockchainInvitationEntity countObjectsInContext:context matching:@"chain == %@", [self.chain chainEntityInContext:context]]; - if (blockchainInvitationEntitiesCount != keyChainDictionary.count) { - DSLog(@"[%@] Unmatching blockchain invitations count", self.chain.name); - } - DSBlockchainInvitationEntity *blockchainInvitationEntity = [DSBlockchainInvitationEntity anyObjectInContext:context matching:@"blockchainIdentity.uniqueID == %@", uint256_data([dsutxo_data(blockchainInvitationLockedOutpoint) SHA256_2])]; - DSBlockchainInvitation *blockchainInvitation = nil; - if (blockchainInvitationEntity) { - blockchainInvitation = [[DSBlockchainInvitation alloc] initAtIndex:index withLockedOutpoint:blockchainInvitationLockedOutpoint inWallet:self withBlockchainInvitationEntity:blockchainInvitationEntity]; - } else { - //No blockchain identity is known in core data - NSData *transactionHashData = uint256_data(uint256_reverse(blockchainInvitationLockedOutpoint.hash)); - DSTransactionEntity *creditRegitrationTransactionEntity = [DSTransactionEntity anyObjectInContext:context matching:@"transactionHash.txHash == %@", transactionHashData]; - if (creditRegitrationTransactionEntity) { - //The registration funding transaction exists - //Weird but we should recover in this situation - DSCreditFundingTransaction *registrationTransaction = (DSCreditFundingTransaction *)[creditRegitrationTransactionEntity transactionForChain:self.chain]; - - BOOL correctIndex = [registrationTransaction checkInvitationDerivationPathIndexForWallet:self isIndex:index]; - if (!correctIndex) { - NSAssert(FALSE, @"We should implement this"); - } else { - blockchainInvitation = [[DSBlockchainInvitation alloc] initAtIndex:index withFundingTransaction:registrationTransaction inWallet:self]; - [blockchainInvitation registerInWallet]; - } - } else { - //We also don't have the registration funding transaction - blockchainInvitation = [[DSBlockchainInvitation alloc] initAtIndex:index withLockedOutpoint:blockchainInvitationLockedOutpoint inWallet:self]; - [blockchainInvitation registerInWalletForBlockchainIdentityUniqueId:[dsutxo_data(blockchainInvitationLockedOutpoint) SHA256_2]]; - } - } - if (blockchainInvitation) { - rDictionary[blockchainInvitationLockedOutpointData] = blockchainInvitation; - } - }]; - } - } - _mBlockchainInvitations = rDictionary; - return _mBlockchainInvitations; -} - -- (DSBlockchainInvitation *)blockchainInvitationForUniqueId:(UInt256)uniqueId { - NSAssert(uint256_is_not_zero(uniqueId), @"uniqueId must not be null"); - DSBlockchainInvitation *foundBlockchainInvitation = nil; - for (DSBlockchainInvitation *blockchainInvitation in [_mBlockchainInvitations allValues]) { - if (uint256_eq([blockchainInvitation.identity uniqueID], uniqueId)) { - foundBlockchainInvitation = blockchainInvitation; - } - } - return foundBlockchainInvitation; -} - -- (uint32_t)unusedBlockchainInvitationIndex { - NSArray *blockchainInvitations = [_mBlockchainInvitations allValues]; - NSNumber *max = [blockchainInvitations valueForKeyPath:@"identity.index.@max.intValue"]; - return max != nil ? ([max unsignedIntValue] + 1) : 0; -} - -- (DSBlockchainInvitation *)createBlockchainInvitation { - DSBlockchainInvitation *blockchainInvitation = [[DSBlockchainInvitation alloc] initAtIndex:[self unusedBlockchainInvitationIndex] inWallet:self]; - return blockchainInvitation; -} - -- (DSBlockchainInvitation *)createBlockchainInvitationUsingDerivationIndex:(uint32_t)index { - DSBlockchainInvitation *blockchainInvitation = [[DSBlockchainInvitation alloc] initAtIndex:index inWallet:self]; - return blockchainInvitation; -} - -- (void)unregisterBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation { - NSParameterAssert(blockchainInvitation); - NSAssert(blockchainInvitation.wallet == self, @"the blockchainInvitation you are trying to remove is not in this wallet"); - NSAssert(blockchainInvitation.identity != nil, @"the blockchainInvitation you are trying to remove has no identity"); - - [self.mBlockchainInvitations removeObjectForKey:blockchainInvitation.identity.lockedOutpointData]; - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainInvitationsKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - if (!keyChainDictionary) keyChainDictionary = [NSMutableDictionary dictionary]; - [keyChainDictionary removeObjectForKey:blockchainInvitation.identity.lockedOutpointData]; - setKeychainDict(keyChainDictionary, self.walletBlockchainInvitationsKey, NO); -} - -- (void)addBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation { - NSParameterAssert(blockchainInvitation); - [self.mBlockchainInvitations setObject:blockchainInvitation forKey:blockchainInvitation.identity.lockedOutpointData]; -} - -- (void)registerBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation { - NSParameterAssert(blockchainInvitation); - NSAssert(blockchainInvitation.identity != nil, @"the blockchainInvitation you are trying to remove has no identity"); - - if ([self.mBlockchainInvitations objectForKey:blockchainInvitation.identity.lockedOutpointData] == nil) { - [self addBlockchainInvitation:blockchainInvitation]; - } - NSError *error = nil; - NSMutableDictionary *keyChainDictionary = [getKeychainDict(self.walletBlockchainInvitationsKey, @[[NSNumber class], [NSData class]], &error) mutableCopy]; - if (!keyChainDictionary) keyChainDictionary = [NSMutableDictionary dictionary]; - - NSAssert(uint256_is_not_zero(blockchainInvitation.identity.uniqueID), @"registrationTransactionHashData must not be null"); - keyChainDictionary[blockchainInvitation.identity.lockedOutpointData] = @(blockchainInvitation.identity.index); - setKeychainDict(keyChainDictionary, self.walletBlockchainInvitationsKey, NO); -} - -- (BOOL)containsBlockchainInvitation:(DSBlockchainInvitation *)blockchainInvitation { - if (blockchainInvitation.identity.lockedOutpointData) { - return ([self.mBlockchainInvitations objectForKey:blockchainInvitation.identity.lockedOutpointData] != nil); - } else { - return FALSE; - } -} - -- (void)wipeBlockchainInvitationsInContext:(NSManagedObjectContext *)context { - for (DSBlockchainInvitation *blockchainInvitation in [_mBlockchainInvitations allValues]) { - [self unregisterBlockchainInvitation:blockchainInvitation]; - [blockchainInvitation deletePersistentObjectAndSave:NO inContext:context]; - } -} - // MARK: - Masternodes (Providers) @@ -1620,7 +1084,7 @@ - (void)registerMasternodeOperator:(DSLocalMasternode *)masternode { } } -- (void)registerMasternodeOperator:(DSLocalMasternode *)masternode withOperatorPublicKey:(OpaqueKey *)operatorKey { +- (void)registerMasternodeOperator:(DSLocalMasternode *)masternode withOperatorPublicKey:(DOpaqueKey *)operatorKey { NSParameterAssert(masternode); if ([self.mMasternodeOperatorPublicKeyLocations objectForKey:uint256_data(masternode.providerRegistrationTransaction.txHash)] == nil) { NSData *publicKeyData = [DSKeyManager publicKeyData:operatorKey]; @@ -1648,7 +1112,7 @@ - (void)registerMasternodeOwner:(DSLocalMasternode *)masternode { } } -- (void)registerMasternodeOwner:(DSLocalMasternode *)masternode withOwnerPrivateKey:(OpaqueKey *)ownerKey { +- (void)registerMasternodeOwner:(DSLocalMasternode *)masternode withOwnerPrivateKey:(DOpaqueKey *)ownerKey { NSParameterAssert(masternode); if ([self.mMasternodeOwnerPrivateKeyLocations objectForKey:uint256_data(masternode.providerRegistrationTransaction.txHash)] == nil) { @@ -1677,7 +1141,7 @@ - (void)registerMasternodeVoter:(DSLocalMasternode *)masternode { } } -- (void)registerMasternodeVoter:(DSLocalMasternode *)masternode withVotingKey:(OpaqueKey *)votingKey { +- (void)registerMasternodeVoter:(DSLocalMasternode *)masternode withVotingKey:(DOpaqueKey *)votingKey { if ([self.mMasternodeVoterKeyLocations objectForKey:uint256_data(masternode.providerRegistrationTransaction.txHash)] == nil) { NSData *publicKeyData = [DSKeyManager publicKeyData:votingKey]; NSData *hashedVoterKey = [NSData dataWithUInt256:publicKeyData.SHA256]; @@ -1708,7 +1172,7 @@ - (void)registerPlatformNode:(DSLocalMasternode *)masternode { } } -- (void)registerPlatformNode:(DSLocalMasternode *)masternode withKey:(OpaqueKey *)key { +- (void)registerPlatformNode:(DSLocalMasternode *)masternode withKey:(DOpaqueKey *)key { NSParameterAssert(masternode); if ([self.mPlatformNodeKeyLocations objectForKey:uint256_data(masternode.providerRegistrationTransaction.txHash)] == nil) { @@ -1726,95 +1190,51 @@ - (void)registerPlatformNode:(DSLocalMasternode *)masternode withKey:(OpaqueKey } } -- (BOOL)containsProviderVotingAuthenticationHash:(UInt160)votingAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:votingAuthenticationHash forChain:self.chain]; - return [derivationPath containsAddress:address]; -} - -- (BOOL)containsProviderOwningAuthenticationHash:(UInt160)owningAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:owningAuthenticationHash forChain:self.chain]; - return [derivationPath containsAddress:address]; -} - -- (BOOL)containsProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:[[NSData dataWithUInt384:providerOperatorAuthenticationKey] hash160] forChain:self.chain]; - return [derivationPath containsAddress:address]; -} - -- (BOOL)containsPlatformNodeAuthenticationHash:(UInt160)platformNodeAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:platformNodeAuthenticationHash forChain:self.chain]; - return [derivationPath containsAddress:address]; -} - -- (BOOL)containsBlockchainIdentityBLSAuthenticationHash:(UInt160)blockchainIdentityAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentitiesBLSKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:blockchainIdentityAuthenticationHash forChain:self.chain]; - return [derivationPath containsAddress:address]; -} +//- (BOOL)containsProviderVotingAuthenticationHash:(UInt160)hash { +// return [[DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self] containsAddressHash:hash]; +//} +// +//- (BOOL)containsProviderOwningAuthenticationHash:(UInt160)hash { +// return [[DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self] containsAddressHash:hash]; +//} +// +//- (BOOL)containsProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey { +// UInt160 hash = [[NSData dataWithUInt384:providerOperatorAuthenticationKey] hash160]; +// return [[DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self] containsAddressHash:hash]; +//} +// +//- (BOOL)containsPlatformNodeAuthenticationHash:(UInt160)hash { +// return [[DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self] containsAddressHash:hash]; +//} +// +//- (BOOL)containsIdentityBLSAuthenticationHash:(UInt160)hash { +// return [[DSAuthenticationKeysDerivationPath identitiesBLSKeysDerivationPathForWallet:self] containsAddressHash:hash]; +//} - (BOOL)containsHoldingAddress:(NSString *)holdingAddress { NSParameterAssert(holdingAddress); - - DSMasternodeHoldingsDerivationPath *derivationPath = [DSMasternodeHoldingsDerivationPath providerFundsDerivationPathForWallet:self]; - return [derivationPath containsAddress:holdingAddress]; + return [[DSMasternodeHoldingsDerivationPath providerFundsDerivationPathForWallet:self] containsAddress:holdingAddress]; } -- (NSUInteger)indexOfProviderVotingAuthenticationHash:(UInt160)votingAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:votingAuthenticationHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; +- (NSUInteger)indexOfProviderVotingAuthenticationHash:(UInt160)hash { + return [[DSAuthenticationKeysDerivationPath providerVotingKeysDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; } -- (NSUInteger)indexOfProviderOwningAuthenticationHash:(UInt160)owningAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:owningAuthenticationHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; +- (NSUInteger)indexOfProviderOwningAuthenticationHash:(UInt160)hash { + return [[DSAuthenticationKeysDerivationPath providerOwnerKeysDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; } - (NSUInteger)indexOfProviderOperatorAuthenticationKey:(UInt384)providerOperatorAuthenticationKey { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:[[NSData dataWithUInt384:providerOperatorAuthenticationKey] hash160] forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; + return [[DSAuthenticationKeysDerivationPath providerOperatorKeysDerivationPathForWallet:self] indexOfKnownAddressHash:[[NSData dataWithUInt384:providerOperatorAuthenticationKey] hash160]]; } -- (NSUInteger)indexOfPlatformNodeAuthenticationHash:(UInt160)platformNodeAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:platformNodeAuthenticationHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; +- (NSUInteger)indexOfPlatformNodeAuthenticationHash:(UInt160)hash { + return [[DSAuthenticationKeysDerivationPath platformNodeKeysDerivationPathForWallet:self] indexOfKnownAddressHash:hash]; } - (NSUInteger)indexOfHoldingAddress:(NSString *)holdingAddress { NSParameterAssert(holdingAddress); - DSMasternodeHoldingsDerivationPath *derivationPath = [DSMasternodeHoldingsDerivationPath providerFundsDerivationPathForWallet:self]; - return [derivationPath indexOfKnownAddress:holdingAddress]; -} - -- (NSUInteger)indexOfBlockchainIdentityAuthenticationHash:(UInt160)blockchainIdentityAuthenticationHash { - DSAuthenticationKeysDerivationPath *derivationPath = [DSAuthenticationKeysDerivationPath blockchainIdentitiesBLSKeysDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:blockchainIdentityAuthenticationHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; -} - -- (NSUInteger)indexOfBlockchainIdentityCreditFundingRegistrationHash:(UInt160)creditFundingRegistrationHash { - DSCreditFundingDerivationPath *derivationPath = [DSCreditFundingDerivationPath blockchainIdentityRegistrationFundingDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:creditFundingRegistrationHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; -} - -- (NSUInteger)indexOfBlockchainIdentityCreditFundingTopupHash:(UInt160)creditFundingTopupHash { - DSCreditFundingDerivationPath *derivationPath = [DSCreditFundingDerivationPath blockchainIdentityTopupFundingDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:creditFundingTopupHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; -} - -- (NSUInteger)indexOfBlockchainIdentityCreditFundingInvitationHash:(UInt160)creditFundingInvitationHash { - DSCreditFundingDerivationPath *derivationPath = [DSCreditFundingDerivationPath blockchainIdentityInvitationFundingDerivationPathForWallet:self]; - NSString *address = [DSKeyManager addressFromHash160:creditFundingInvitationHash forChain:self.chain]; - return [derivationPath indexOfKnownAddress:address]; + return [[DSMasternodeHoldingsDerivationPath providerFundsDerivationPathForWallet:self] indexOfKnownAddress:holdingAddress]; } @end diff --git a/DashSync/shared/Models/Wallet/DSWalletConstants.m b/DashSync/shared/Models/Wallet/DSWalletConstants.m index 8392923de..5582cc93d 100644 --- a/DashSync/shared/Models/Wallet/DSWalletConstants.m +++ b/DashSync/shared/Models/Wallet/DSWalletConstants.m @@ -79,30 +79,30 @@ NSString *const DSContractUpdateNotificationKey = @"DSContractUpdateNotificationKey"; -NSString *const DSBlockchainIdentityDidUpdateNotification = @"DSBlockchainIdentitiesDidUpdateNotification"; +NSString *const DSIdentityDidUpdateNotification = @"DSIdentitiesDidUpdateNotification"; -NSString *const DSBlockchainIdentityDidUpdateUsernameStatusNotification = @"DSBlockchainIdentityDidUpdateUsernameStatusNotification"; +NSString *const DSIdentityDidUpdateUsernameStatusNotification = @"DSIdentityDidUpdateUsernameStatusNotification"; -NSString *const DSBlockchainIdentityKey = @"DSBlockchainIdentityKey"; +NSString *const DSIdentityKey = @"DSIdentityKey"; -NSString *const DSBlockchainIdentityUsernameKey = @"DSBlockchainIdentityUsernameKey"; +NSString *const DSIdentityUsernameKey = @"DSIdentityUsernameKey"; -NSString *const DSBlockchainIdentityUsernameDomainKey = @"DSBlockchainIdentityUsernameDomainKey"; +NSString *const DSIdentityUsernameDomainKey = @"DSIdentityUsernameDomainKey"; -NSString *const DSBlockchainIdentityUpdateEvents = @"DSBlockchainIdentityUpdateEvents"; +NSString *const DSIdentityUpdateEvents = @"DSIdentityUpdateEvents"; -NSString *const DSBlockchainIdentityUpdateEventKeyUpdate = @"DSBlockchainIdentityUpdateEventKeyUpdate"; +NSString *const DSIdentityUpdateEventKeyUpdate = @"DSIdentityUpdateEventKeyUpdate"; -NSString *const DSBlockchainIdentityUpdateEventRegistration = @"DSBlockchainIdentityUpdateEventRegistration"; +NSString *const DSIdentityUpdateEventRegistration = @"DSIdentityUpdateEventRegistration"; -NSString *const DSBlockchainIdentityUpdateEventCreditBalance = @"DSBlockchainIdentityUpdateEventCreditBalance"; +NSString *const DSIdentityUpdateEventCreditBalance = @"DSIdentityUpdateEventCreditBalance"; -NSString *const DSBlockchainIdentityUpdateEventDashpaySyncronizationBlockHash = @"DSBlockchainIdentityUpdateEventDashpaySyncronizationBlockHash"; +NSString *const DSIdentityUpdateEventDashpaySyncronizationBlockHash = @"DSIdentityUpdateEventDashpaySyncronizationBlockHash"; -NSString *const DSBlockchainInvitationDidUpdateNotification = @"DSBlockchainInvitationDidUpdateNotification"; +NSString *const DSInvitationDidUpdateNotification = @"DSInvitationDidUpdateNotification"; -NSString *const DSBlockchainInvitationKey = @"DSBlockchainInvitationKey"; +NSString *const DSInvitationKey = @"DSInvitationKey"; -NSString *const DSBlockchainInvitationUpdateEvents = @"DSBlockchainInvitationUpdateEvents"; +NSString *const DSInvitationUpdateEvents = @"DSInvitationUpdateEvents"; -NSString *const DSBlockchainInvitationUpdateEventLink = @"DSBlockchainInvitationUpdateEventLink"; +NSString *const DSInvitationUpdateEventLink = @"DSInvitationUpdateEventLink"; diff --git a/Example/DashSync.xcodeproj/project.pbxproj b/Example/DashSync.xcodeproj/project.pbxproj index 53cc4cd0a..9ce7e5e99 100644 --- a/Example/DashSync.xcodeproj/project.pbxproj +++ b/Example/DashSync.xcodeproj/project.pbxproj @@ -66,9 +66,12 @@ 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; 65F8D081B17C5F08E6BBF4A8 /* libPods-DashSync-DashSync_Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F6B6FEB4981DE6989D82EB0 /* libPods-DashSync-DashSync_Example.a */; }; 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; }; + 757323562C5A384800EF5FC0 /* DSCoinJoinSessionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 757323542C5A381600EF5FC0 /* DSCoinJoinSessionTest.m */; }; + 758719952B3ECB7A004C0E2D /* DSCoinJoinViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 758719942B3ECB7A004C0E2D /* DSCoinJoinViewController.m */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; 93099D7228E7275400160709 /* MNLIST_1746460.dat in Resources */ = {isa = PBXBuildFile; fileRef = 93099D7028E7275300160709 /* MNLIST_1746460.dat */; }; 93099D7328E7275400160709 /* MNL_1746460_1746516.dat in Resources */ = {isa = PBXBuildFile; fileRef = 93099D7128E7275400160709 /* MNL_1746460_1746516.dat */; }; + 9311F51D2D2082DB0039D778 /* DSNetworkActivityView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9311F51C2D2082DB0039D778 /* DSNetworkActivityView.m */; }; 937619A526E22E360023BE64 /* BackgroundTasks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 937619A326E22DCD0023BE64 /* BackgroundTasks.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B8D9D66B2CB5A33300D289A9 /* libDashSync-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8D9D66A2CB5A33300D289A9 /* libDashSync-macOS.a */; }; D233D44D64AE2AA230F44AA7 /* libPods-DashSync_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DE90F593715182564EB9A7E8 /* libPods-DashSync_Tests.a */; }; @@ -77,9 +80,9 @@ FB0F15A521049432000272E6 /* DSTransactionStatusTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15A421049432000272E6 /* DSTransactionStatusTableViewCell.m */; }; FB0F15A821049505000272E6 /* DSTransactionAmountTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15A721049505000272E6 /* DSTransactionAmountTableViewCell.m */; }; FB0F15AB21049598000272E6 /* DSTransactionIdentifierTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15AA21049598000272E6 /* DSTransactionIdentifierTableViewCell.m */; }; - FB0F15B721090445000272E6 /* DSBlockchainIdentitiesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15B621090445000272E6 /* DSBlockchainIdentitiesViewController.m */; }; - FB0F15BE210A0208000272E6 /* DSBlockchainIdentityTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15BD210A0208000272E6 /* DSBlockchainIdentityTableViewCell.m */; }; - FB0F15C1210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15C0210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.m */; }; + FB0F15B721090445000272E6 /* DSIdentitiesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15B621090445000272E6 /* DSIdentitiesViewController.m */; }; + FB0F15BE210A0208000272E6 /* DSIdentityTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15BD210A0208000272E6 /* DSIdentityTableViewCell.m */; }; + FB0F15C1210ACD52000272E6 /* DSCreateIdentityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15C0210ACD52000272E6 /* DSCreateIdentityViewController.m */; }; FB0F15C4210AD9A1000272E6 /* DSWalletChooserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0F15C3210AD9A0000272E6 /* DSWalletChooserViewController.m */; }; FB12BDBF25239CD70027CD64 /* DSIdentityAuthenticationDerivationPathsAddressesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB12BDBD25239CD60027CD64 /* DSIdentityAuthenticationDerivationPathsAddressesViewController.m */; }; FB12FEE320CD3BD100C1E0F0 /* DSMasternodeTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB12FEE220CD3BD100C1E0F0 /* DSMasternodeTableViewCell.m */; }; @@ -374,11 +377,11 @@ FB83C2E721B91A8D0057A0EF /* DSTransactionFloodingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB83C2E621B91A8D0057A0EF /* DSTransactionFloodingViewController.m */; }; FB85000D261A75700002E5EA /* DSInvitationDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB85000C261A75700002E5EA /* DSInvitationDetailViewController.m */; }; FB850013261A7B9A0002E5EA /* DSIdentityChooserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB850012261A7B9A0002E5EA /* DSIdentityChooserViewController.m */; }; - FB850051261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB850050261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.m */; }; + FB850051261AA82F0002E5EA /* DSIdentityChooserTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB850050261AA82F0002E5EA /* DSIdentityChooserTableViewCell.m */; }; FB87A0CE24B7C28700C22DF7 /* DSMiningTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB87A0CD24B7C28600C22DF7 /* DSMiningTests.m */; }; FB88ECB02649647500CDD987 /* DSInvitationsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB88ECAF2649647400CDD987 /* DSInvitationsTests.m */; }; FB88ECB6264B8BB800CDD987 /* DSDAPIGetTransactionInformationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB88ECB5264B8BB800CDD987 /* DSDAPIGetTransactionInformationViewController.m */; }; - FB88ECBC264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB88ECBB264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.m */; }; + FB88ECBC264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB88ECBB264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.m */; }; FB89F2D722CC875800CD0232 /* MNL_0_1094976.dat in Resources */ = {isa = PBXBuildFile; fileRef = FB89F2D622CC875800CD0232 /* MNL_0_1094976.dat */; }; FB89F2D922CD82EB00CD0232 /* MNL_0_1095720.dat in Resources */ = {isa = PBXBuildFile; fileRef = FB89F2D822CD82EB00CD0232 /* MNL_0_1095720.dat */; }; FB90D2B420BD2BDD002B7956 /* DSSporksViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB90D2B320BD2BDD002B7956 /* DSSporksViewController.m */; }; @@ -398,8 +401,8 @@ FB97502725B3AFB000A1DCE7 /* DSTestnetSyncTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB97502625B3AFAF00A1DCE7 /* DSTestnetSyncTests.m */; }; FB97502B25B3BC2D00A1DCE7 /* DSMainnetSyncTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB97502A25B3BC2D00A1DCE7 /* DSMainnetSyncTests.m */; }; FB9762F724BC4C3E00CF28EC /* DSMiningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB9762F624BC4C3E00CF28EC /* DSMiningViewController.m */; }; - FB988321241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB988320241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.m */; }; - FB988324241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB988323241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.m */; }; + FB988321241D64FA00B39F02 /* DSIdentityKeysViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB988320241D64FA00B39F02 /* DSIdentityKeysViewController.m */; }; + FB988324241D6C3300B39F02 /* DSIdentityKeyTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB988323241D6C3300B39F02 /* DSIdentityKeyTableViewCell.m */; }; FB995D9A21101C4200AC37D0 /* DSPeersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB995D9921101C4200AC37D0 /* DSPeersViewController.m */; }; FB995D9D21101C9600AC37D0 /* DSPeerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FB995D9C21101C9600AC37D0 /* DSPeerTableViewCell.m */; }; FB9A106620D7A72D006883E2 /* DSAddressesExporterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB9A106520D7A72D006883E2 /* DSAddressesExporterViewController.m */; }; @@ -429,7 +432,7 @@ FBB5D729221DF9390021764A /* DSUpdateMasternodeServiceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBB5D728221DF9390021764A /* DSUpdateMasternodeServiceViewController.m */; }; FBB5D73C221FE5F40021764A /* DSUpdateMasternodeRegistrarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBB5D73B221FE5F40021764A /* DSUpdateMasternodeRegistrarViewController.m */; }; FBBDF423226B6E770081D673 /* DSBigNumberTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBDF422226B6E770081D673 /* DSBigNumberTests.m */; }; - FBBDF426226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBDF425226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.m */; }; + FBBDF426226C843C0081D673 /* DSIdentityTransitionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBDF425226C843C0081D673 /* DSIdentityTransitionsViewController.m */; }; FBBDF429226C8ECA0081D673 /* DSTransitionTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBDF428226C8ECA0081D673 /* DSTransitionTableViewCell.m */; }; FBBDF42C226EDF600081D673 /* DSContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBDF42B226EDF600081D673 /* DSContactTableViewCell.m */; }; FBC2EAF8260F842B00B53BDB /* Invitations.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBC2EAF7260F842B00B53BDB /* Invitations.storyboard */; }; @@ -437,12 +440,12 @@ FBC2EB08260F89A900B53BDB /* DSInvitationTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC2EB07260F89A900B53BDB /* DSInvitationTableViewCell.m */; }; FBC2EB0E260F8B4600B53BDB /* DSCreateInvitationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC2EB0D260F8B4600B53BDB /* DSCreateInvitationViewController.m */; }; FBC508C62426F33D00EB3DAB /* SearchIdentity.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBC508C52426F33D00EB3DAB /* SearchIdentity.storyboard */; }; - FBC508CB2426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC508CA2426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.m */; }; - FBC508CE2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC508CD2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.m */; }; + FBC508CB2426F42500EB3DAB /* DSSearchIdentitiesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC508CA2426F42500EB3DAB /* DSSearchIdentitiesViewController.m */; }; + FBC508CE2427363C00EB3DAB /* DSIdentitySearchTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC508CD2427363C00EB3DAB /* DSIdentitySearchTableViewCell.m */; }; FBC9248E2414EF4000005D0D /* DSAttackTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBC9248C2414EF0600005D0D /* DSAttackTests.m */; }; FBD696EC2291A1800012FF51 /* DSDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD696EB2291A1800012FF51 /* DSDataTests.m */; }; - FBD967A72125719E005CBE0B /* DSBlockchainIdentityActionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD967A62125719E005CBE0B /* DSBlockchainIdentityActionsViewController.m */; }; - FBD967AA212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD967A9212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.m */; }; + FBD967A72125719E005CBE0B /* DSIdentityActionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD967A62125719E005CBE0B /* DSIdentityActionsViewController.m */; }; + FBD967AA212571DC005CBE0B /* DSTopupIdentityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD967A9212571DC005CBE0B /* DSTopupIdentityViewController.m */; }; FBD967AD21270DD6005CBE0B /* DSUsernameTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD967AC21270DD6005CBE0B /* DSUsernameTableViewCell.m */; }; FBDB9B7E20FF7AB900AD61B3 /* DSDeterministicMasternodeListTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDB9B7D20FF7AB900AD61B3 /* DSDeterministicMasternodeListTests.m */; }; FBE07DD124673AC200A1079C /* DSMainnetMetricSyncTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBE07DD024673AC200A1079C /* DSMainnetMetricSyncTests.m */; }; @@ -615,14 +618,19 @@ 64A245673BBAE1DA7DAB4907 /* DashSync.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = DashSync.podspec; path = ../DashSync.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 6F6B6FEB4981DE6989D82EB0 /* libPods-DashSync-DashSync_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DashSync-DashSync_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 757323532C5A329600EF5FC0 /* CoinJoinTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CoinJoinTests.xctestplan; sourceTree = ""; }; + 757323542C5A381600EF5FC0 /* DSCoinJoinSessionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCoinJoinSessionTest.m; sourceTree = ""; }; + 758719932B3ECB28004C0E2D /* DSCoinJoinViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSCoinJoinViewController.h; sourceTree = ""; }; + 758719942B3ECB7A004C0E2D /* DSCoinJoinViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCoinJoinViewController.m; sourceTree = ""; }; 77374E9036662C743187A1DA /* Pods-DashSync_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashSync_Tests.release.xcconfig"; path = "Target Support Files/Pods-DashSync_Tests/Pods-DashSync_Tests.release.xcconfig"; sourceTree = ""; }; 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 93099D7028E7275300160709 /* MNLIST_1746460.dat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MNLIST_1746460.dat; sourceTree = ""; }; 93099D7128E7275400160709 /* MNL_1746460_1746516.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = MNL_1746460_1746516.dat; sourceTree = ""; }; + 9311F51B2D2082DB0039D778 /* DSNetworkActivityView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSNetworkActivityView.h; sourceTree = ""; }; + 9311F51C2D2082DB0039D778 /* DSNetworkActivityView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSNetworkActivityView.m; sourceTree = ""; }; 937619A326E22DCD0023BE64 /* BackgroundTasks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTasks.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.3.sdk/System/Library/Frameworks/BackgroundTasks.framework; sourceTree = DEVELOPER_DIR; }; B8D9D6682CB5A11B00D289A9 /* libDashSync-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libDashSync-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B8D9D66A2CB5A33300D289A9 /* libDashSync-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libDashSync-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - B8D9D66C2CB5A36600D289A9 /* DashSharedCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DashSharedCore.xcframework; path = "../../dash-shared-core/dash-spv-apple-bindings/DashSharedCore/framework/DashSharedCore.xcframework"; sourceTree = ""; }; BD44FCB7175424B2BE7993BB /* Pods-DashSync-DashSync_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashSync-DashSync_Example.release.xcconfig"; path = "Target Support Files/Pods-DashSync-DashSync_Example/Pods-DashSync-DashSync_Example.release.xcconfig"; sourceTree = ""; }; D97E3B2F9438560328BFE431 /* Pods-NetworkInfo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkInfo.release.xcconfig"; path = "Target Support Files/Pods-NetworkInfo/Pods-NetworkInfo.release.xcconfig"; sourceTree = ""; }; DE90F593715182564EB9A7E8 /* libPods-DashSync_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DashSync_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -637,12 +645,12 @@ FB0F15A721049505000272E6 /* DSTransactionAmountTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSTransactionAmountTableViewCell.m; sourceTree = ""; }; FB0F15A921049598000272E6 /* DSTransactionIdentifierTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSTransactionIdentifierTableViewCell.h; sourceTree = ""; }; FB0F15AA21049598000272E6 /* DSTransactionIdentifierTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSTransactionIdentifierTableViewCell.m; sourceTree = ""; }; - FB0F15B521090445000272E6 /* DSBlockchainIdentitiesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentitiesViewController.h; sourceTree = ""; }; - FB0F15B621090445000272E6 /* DSBlockchainIdentitiesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentitiesViewController.m; sourceTree = ""; }; - FB0F15BC210A0208000272E6 /* DSBlockchainIdentityTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentityTableViewCell.h; sourceTree = ""; }; - FB0F15BD210A0208000272E6 /* DSBlockchainIdentityTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentityTableViewCell.m; sourceTree = ""; }; - FB0F15BF210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSCreateBlockchainIdentityViewController.h; sourceTree = ""; }; - FB0F15C0210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCreateBlockchainIdentityViewController.m; sourceTree = ""; }; + FB0F15B521090445000272E6 /* DSIdentitiesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentitiesViewController.h; sourceTree = ""; }; + FB0F15B621090445000272E6 /* DSIdentitiesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentitiesViewController.m; sourceTree = ""; }; + FB0F15BC210A0208000272E6 /* DSIdentityTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityTableViewCell.h; sourceTree = ""; }; + FB0F15BD210A0208000272E6 /* DSIdentityTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityTableViewCell.m; sourceTree = ""; }; + FB0F15BF210ACD52000272E6 /* DSCreateIdentityViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSCreateIdentityViewController.h; sourceTree = ""; }; + FB0F15C0210ACD52000272E6 /* DSCreateIdentityViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCreateIdentityViewController.m; sourceTree = ""; }; FB0F15C2210AD9A0000272E6 /* DSWalletChooserViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSWalletChooserViewController.h; sourceTree = ""; }; FB0F15C3210AD9A0000272E6 /* DSWalletChooserViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSWalletChooserViewController.m; sourceTree = ""; }; FB12BDBD25239CD60027CD64 /* DSIdentityAuthenticationDerivationPathsAddressesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DSIdentityAuthenticationDerivationPathsAddressesViewController.m; sourceTree = ""; }; @@ -981,14 +989,14 @@ FB85000C261A75700002E5EA /* DSInvitationDetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSInvitationDetailViewController.m; sourceTree = ""; }; FB850011261A7B9A0002E5EA /* DSIdentityChooserViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityChooserViewController.h; sourceTree = ""; }; FB850012261A7B9A0002E5EA /* DSIdentityChooserViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityChooserViewController.m; sourceTree = ""; }; - FB85004F261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentityChooserTableViewCell.h; sourceTree = ""; }; - FB850050261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentityChooserTableViewCell.m; sourceTree = ""; }; + FB85004F261AA82F0002E5EA /* DSIdentityChooserTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityChooserTableViewCell.h; sourceTree = ""; }; + FB850050261AA82F0002E5EA /* DSIdentityChooserTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityChooserTableViewCell.m; sourceTree = ""; }; FB87A0CD24B7C28600C22DF7 /* DSMiningTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSMiningTests.m; sourceTree = ""; }; FB88ECAF2649647400CDD987 /* DSInvitationsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSInvitationsTests.m; sourceTree = ""; }; FB88ECB4264B8BB800CDD987 /* DSDAPIGetTransactionInformationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSDAPIGetTransactionInformationViewController.h; sourceTree = ""; }; FB88ECB5264B8BB800CDD987 /* DSDAPIGetTransactionInformationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSDAPIGetTransactionInformationViewController.m; sourceTree = ""; }; - FB88ECBA264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSCreateBlockchainIdentityFromInvitationViewController.h; sourceTree = ""; }; - FB88ECBB264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCreateBlockchainIdentityFromInvitationViewController.m; sourceTree = ""; }; + FB88ECBA264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSCreateIdentityFromInvitationViewController.h; sourceTree = ""; }; + FB88ECBB264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCreateIdentityFromInvitationViewController.m; sourceTree = ""; }; FB89F2D622CC875800CD0232 /* MNL_0_1094976.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = MNL_0_1094976.dat; sourceTree = ""; }; FB89F2D822CD82EB00CD0232 /* MNL_0_1095720.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = MNL_0_1095720.dat; sourceTree = ""; }; FB90D2B220BD2BDD002B7956 /* DSSporksViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSSporksViewController.h; sourceTree = ""; }; @@ -1042,10 +1050,10 @@ FB97502A25B3BC2D00A1DCE7 /* DSMainnetSyncTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DSMainnetSyncTests.m; sourceTree = ""; }; FB9762F524BC4C3E00CF28EC /* DSMiningViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSMiningViewController.h; sourceTree = ""; }; FB9762F624BC4C3E00CF28EC /* DSMiningViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSMiningViewController.m; sourceTree = ""; }; - FB98831F241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentityKeysViewController.h; sourceTree = ""; }; - FB988320241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentityKeysViewController.m; sourceTree = ""; }; - FB988322241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentityKeyTableViewCell.h; sourceTree = ""; }; - FB988323241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentityKeyTableViewCell.m; sourceTree = ""; }; + FB98831F241D64FA00B39F02 /* DSIdentityKeysViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityKeysViewController.h; sourceTree = ""; }; + FB988320241D64FA00B39F02 /* DSIdentityKeysViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityKeysViewController.m; sourceTree = ""; }; + FB988322241D6C3300B39F02 /* DSIdentityKeyTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityKeyTableViewCell.h; sourceTree = ""; }; + FB988323241D6C3300B39F02 /* DSIdentityKeyTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityKeyTableViewCell.m; sourceTree = ""; }; FB995D9821101C4200AC37D0 /* DSPeersViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPeersViewController.h; sourceTree = ""; }; FB995D9921101C4200AC37D0 /* DSPeersViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPeersViewController.m; sourceTree = ""; }; FB995D9B21101C9600AC37D0 /* DSPeerTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPeerTableViewCell.h; sourceTree = ""; }; @@ -1104,8 +1112,8 @@ FBBB464C25C0547F00ACDF35 /* e2eTestsTestnet.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; name = e2eTestsTestnet.yml; path = ../.github/workflows/e2eTestsTestnet.yml; sourceTree = ""; }; FBBB464D25C054DE00ACDF35 /* TestnetE2ETests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestnetE2ETests.xctestplan; sourceTree = ""; }; FBBDF422226B6E770081D673 /* DSBigNumberTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBigNumberTests.m; sourceTree = ""; }; - FBBDF424226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentityTransitionsViewController.h; sourceTree = ""; }; - FBBDF425226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentityTransitionsViewController.m; sourceTree = ""; }; + FBBDF424226C843C0081D673 /* DSIdentityTransitionsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityTransitionsViewController.h; sourceTree = ""; }; + FBBDF425226C843C0081D673 /* DSIdentityTransitionsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityTransitionsViewController.m; sourceTree = ""; }; FBBDF427226C8ECA0081D673 /* DSTransitionTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSTransitionTableViewCell.h; sourceTree = ""; }; FBBDF428226C8ECA0081D673 /* DSTransitionTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSTransitionTableViewCell.m; sourceTree = ""; }; FBBDF42A226EDF600081D673 /* DSContactTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSContactTableViewCell.h; sourceTree = ""; }; @@ -1118,16 +1126,16 @@ FBC2EB0C260F8B4600B53BDB /* DSCreateInvitationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSCreateInvitationViewController.h; sourceTree = ""; }; FBC2EB0D260F8B4600B53BDB /* DSCreateInvitationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSCreateInvitationViewController.m; sourceTree = ""; }; FBC508C52426F33D00EB3DAB /* SearchIdentity.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SearchIdentity.storyboard; sourceTree = ""; }; - FBC508C92426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSSearchBlockchainIdentitiesViewController.h; sourceTree = ""; }; - FBC508CA2426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSSearchBlockchainIdentitiesViewController.m; sourceTree = ""; }; - FBC508CC2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentitySearchTableViewCell.h; sourceTree = ""; }; - FBC508CD2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentitySearchTableViewCell.m; sourceTree = ""; }; + FBC508C92426F42500EB3DAB /* DSSearchIdentitiesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSSearchIdentitiesViewController.h; sourceTree = ""; }; + FBC508CA2426F42500EB3DAB /* DSSearchIdentitiesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSSearchIdentitiesViewController.m; sourceTree = ""; }; + FBC508CC2427363C00EB3DAB /* DSIdentitySearchTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentitySearchTableViewCell.h; sourceTree = ""; }; + FBC508CD2427363C00EB3DAB /* DSIdentitySearchTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentitySearchTableViewCell.m; sourceTree = ""; }; FBC9248C2414EF0600005D0D /* DSAttackTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSAttackTests.m; sourceTree = ""; }; FBD696EB2291A1800012FF51 /* DSDataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSDataTests.m; sourceTree = ""; }; - FBD967A52125719E005CBE0B /* DSBlockchainIdentityActionsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSBlockchainIdentityActionsViewController.h; sourceTree = ""; }; - FBD967A62125719E005CBE0B /* DSBlockchainIdentityActionsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBlockchainIdentityActionsViewController.m; sourceTree = ""; }; - FBD967A8212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSTopupBlockchainIdentityViewController.h; sourceTree = ""; }; - FBD967A9212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSTopupBlockchainIdentityViewController.m; sourceTree = ""; }; + FBD967A52125719E005CBE0B /* DSIdentityActionsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSIdentityActionsViewController.h; sourceTree = ""; }; + FBD967A62125719E005CBE0B /* DSIdentityActionsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSIdentityActionsViewController.m; sourceTree = ""; }; + FBD967A8212571DC005CBE0B /* DSTopupIdentityViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSTopupIdentityViewController.h; sourceTree = ""; }; + FBD967A9212571DC005CBE0B /* DSTopupIdentityViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSTopupIdentityViewController.m; sourceTree = ""; }; FBD967AB21270DD6005CBE0B /* DSUsernameTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSUsernameTableViewCell.h; sourceTree = ""; }; FBD967AC21270DD6005CBE0B /* DSUsernameTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSUsernameTableViewCell.m; sourceTree = ""; }; FBDB9B7D20FF7AB900AD61B3 /* DSDeterministicMasternodeListTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSDeterministicMasternodeListTests.m; sourceTree = ""; }; @@ -1393,7 +1401,6 @@ 6003F58C195388D20070C39A /* Frameworks */ = { isa = PBXGroup; children = ( - B8D9D66C2CB5A36600D289A9 /* DashSharedCore.xcframework */, B8D9D66A2CB5A33300D289A9 /* libDashSync-macOS.a */, B8D9D6682CB5A11B00D289A9 /* libDashSync-macOS.a */, 937619A326E22DCD0023BE64 /* BackgroundTasks.framework */, @@ -1459,6 +1466,7 @@ FBD45D1B24DF1A6200168EBC /* LibraryTests */, FBD45D1924DF1A0900168EBC /* L1Tests */, FB76C5A822BD7B9700DF2B29 /* MasternodeLists */, + 757323522C5A31ED00EF5FC0 /* CoinJoin */, FBE155FF22B99368009CA817 /* MasternodeList1088800.dat */, 2A5C601322BFC65E00C6003F /* ML_at_122088.dat */, 6003F5B6195388D20070C39A /* Supporting Files */, @@ -1489,25 +1497,33 @@ name = "Podspec Metadata"; sourceTree = ""; }; + 757323522C5A31ED00EF5FC0 /* CoinJoin */ = { + isa = PBXGroup; + children = ( + 757323542C5A381600EF5FC0 /* DSCoinJoinSessionTest.m */, + ); + name = CoinJoin; + sourceTree = ""; + }; FB0F15B421090405000272E6 /* Blockchain Identities */ = { isa = PBXGroup; children = ( FBC508C82426F3DE00EB3DAB /* Searching */, 2A1BDBE7225B855B0028F6DE /* Contracts */, - FB0F15B521090445000272E6 /* DSBlockchainIdentitiesViewController.h */, - FB0F15B621090445000272E6 /* DSBlockchainIdentitiesViewController.m */, - FB0F15BF210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.h */, - FB0F15C0210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.m */, - FB88ECBA264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.h */, - FB88ECBB264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.m */, - FBD967A52125719E005CBE0B /* DSBlockchainIdentityActionsViewController.h */, - FBD967A62125719E005CBE0B /* DSBlockchainIdentityActionsViewController.m */, - FBD967A8212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.h */, - FBD967A9212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.m */, - FBBDF424226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.h */, - FBBDF425226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.m */, - FB98831F241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.h */, - FB988320241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.m */, + FB0F15B521090445000272E6 /* DSIdentitiesViewController.h */, + FB0F15B621090445000272E6 /* DSIdentitiesViewController.m */, + FB0F15BF210ACD52000272E6 /* DSCreateIdentityViewController.h */, + FB0F15C0210ACD52000272E6 /* DSCreateIdentityViewController.m */, + FB88ECBA264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.h */, + FB88ECBB264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.m */, + FBD967A52125719E005CBE0B /* DSIdentityActionsViewController.h */, + FBD967A62125719E005CBE0B /* DSIdentityActionsViewController.m */, + FBD967A8212571DC005CBE0B /* DSTopupIdentityViewController.h */, + FBD967A9212571DC005CBE0B /* DSTopupIdentityViewController.m */, + FBBDF424226C843C0081D673 /* DSIdentityTransitionsViewController.h */, + FBBDF425226C843C0081D673 /* DSIdentityTransitionsViewController.m */, + FB98831F241D64FA00B39F02 /* DSIdentityKeysViewController.h */, + FB988320241D64FA00B39F02 /* DSIdentityKeysViewController.m */, ); name = "Blockchain Identities"; sourceTree = ""; @@ -1848,6 +1864,8 @@ FB5252FE20F2377D009A2BF1 /* BRCopyLabel.m */, FB2B21AA20D6FC0B000DF537 /* BRBubbleView.h */, FB2B21A920D6FC0B000DF537 /* BRBubbleView.m */, + 9311F51B2D2082DB0039D778 /* DSNetworkActivityView.h */, + 9311F51C2D2082DB0039D778 /* DSNetworkActivityView.m */, ); name = Views; sourceTree = ""; @@ -1921,8 +1939,8 @@ FB0F15A721049505000272E6 /* DSTransactionAmountTableViewCell.m */, FB0F15A921049598000272E6 /* DSTransactionIdentifierTableViewCell.h */, FB0F15AA21049598000272E6 /* DSTransactionIdentifierTableViewCell.m */, - FB0F15BC210A0208000272E6 /* DSBlockchainIdentityTableViewCell.h */, - FB0F15BD210A0208000272E6 /* DSBlockchainIdentityTableViewCell.m */, + FB0F15BC210A0208000272E6 /* DSIdentityTableViewCell.h */, + FB0F15BD210A0208000272E6 /* DSIdentityTableViewCell.m */, FB995D9B21101C9600AC37D0 /* DSPeerTableViewCell.h */, FB995D9C21101C9600AC37D0 /* DSPeerTableViewCell.m */, FBD967AB21270DD6005CBE0B /* DSUsernameTableViewCell.h */, @@ -1945,14 +1963,14 @@ FBBDF42B226EDF600081D673 /* DSContactTableViewCell.m */, FB702C6323EDC18D0099030C /* DSContractTableViewCell.h */, FB702C6423EDC18D0099030C /* DSContractTableViewCell.m */, - FB988322241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.h */, - FB988323241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.m */, - FBC508CC2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.h */, - FBC508CD2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.m */, + FB988322241D6C3300B39F02 /* DSIdentityKeyTableViewCell.h */, + FB988323241D6C3300B39F02 /* DSIdentityKeyTableViewCell.m */, + FBC508CC2427363C00EB3DAB /* DSIdentitySearchTableViewCell.h */, + FBC508CD2427363C00EB3DAB /* DSIdentitySearchTableViewCell.m */, FBC2EB06260F89A900B53BDB /* DSInvitationTableViewCell.h */, FBC2EB07260F89A900B53BDB /* DSInvitationTableViewCell.m */, - FB85004F261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.h */, - FB850050261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.m */, + FB85004F261AA82F0002E5EA /* DSIdentityChooserTableViewCell.h */, + FB850050261AA82F0002E5EA /* DSIdentityChooserTableViewCell.m */, ); name = Cells; sourceTree = ""; @@ -2036,6 +2054,8 @@ FB83C2E621B91A8D0057A0EF /* DSTransactionFloodingViewController.m */, FB9762F524BC4C3E00CF28EC /* DSMiningViewController.h */, FB9762F624BC4C3E00CF28EC /* DSMiningViewController.m */, + 758719932B3ECB28004C0E2D /* DSCoinJoinViewController.h */, + 758719942B3ECB7A004C0E2D /* DSCoinJoinViewController.m */, ); name = Actions; sourceTree = ""; @@ -2138,6 +2158,7 @@ FB97500625B3876D00A1DCE7 /* TestnetSyncTests.xctestplan */, FB97501F25B3AD1400A1DCE7 /* Metrics.xctestplan */, FBBB464D25C054DE00ACDF35 /* TestnetE2ETests.xctestplan */, + 757323532C5A329600EF5FC0 /* CoinJoinTests.xctestplan */, ); name = TestPlans; sourceTree = ""; @@ -2232,8 +2253,8 @@ FBC508C82426F3DE00EB3DAB /* Searching */ = { isa = PBXGroup; children = ( - FBC508C92426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.h */, - FBC508CA2426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.m */, + FBC508C92426F42500EB3DAB /* DSSearchIdentitiesViewController.h */, + FBC508CA2426F42500EB3DAB /* DSSearchIdentitiesViewController.m */, ); name = Searching; sourceTree = ""; @@ -2402,7 +2423,7 @@ ORGANIZATIONNAME = "Dash Core Group"; TargetAttributes = { 6003F589195388D20070C39A = { - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; 6003F5AD195388D20070C39A = { DevelopmentTeam = 44RJ69WHFF; @@ -2773,14 +2794,10 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-NetworkInfo/Pods-NetworkInfo-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/DashSync-macOS/DashSync.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Protobuf-macOS/Protobuf_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/gRPC-macOS/gRPCCertificates.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DashSync.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Protobuf_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2817,14 +2834,10 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-DashSync-DashSync_Example/Pods-DashSync-DashSync_Example-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/DashSync-iOS/DashSync.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Protobuf-iOS/Protobuf_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DashSync.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Protobuf_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2901,12 +2914,12 @@ files = ( FB9A107220D836BB006883E2 /* DSAddDevnetAddIPAddressTableViewCell.m in Sources */, FBC2EB0E260F8B4600B53BDB /* DSCreateInvitationViewController.m in Sources */, - FB850051261AA82F0002E5EA /* DSBlockchainIdentityChooserTableViewCell.m in Sources */, + FB850051261AA82F0002E5EA /* DSIdentityChooserTableViewCell.m in Sources */, 2AF8A93C2283539800F8C72D /* DSFetchedResultsTableViewController.m in Sources */, FB763C652089B0A700CDBA57 /* DSAddressTableViewCell.m in Sources */, FB702C6523EDC18D0099030C /* DSContractTableViewCell.m in Sources */, 2A3B3EB820F893F4002962FB /* SwitcherFormTableViewCell.m in Sources */, - FB988321241D64FA00B39F02 /* DSBlockchainIdentityKeysViewController.m in Sources */, + FB988321241D64FA00B39F02 /* DSIdentityKeysViewController.m in Sources */, FB0F15A521049432000272E6 /* DSTransactionStatusTableViewCell.m in Sources */, 6003F59E195388D20070C39A /* DSAppDelegate.m in Sources */, FB5252F620EE159B009A2BF1 /* DSAccountChooserViewController.m in Sources */, @@ -2933,7 +2946,7 @@ FB6B78492278D5210005C9DB /* DSContactSentTransactionsTableViewController.m in Sources */, FBAA7749222B241D001B3756 /* DSProviderUpdateRegistrarTransactionsViewController.m in Sources */, FB0955CC244BBDC400A93675 /* DSContactRelationshipInfoViewController.m in Sources */, - FB88ECBC264BFC7D00CDD987 /* DSCreateBlockchainIdentityFromInvitationViewController.m in Sources */, + FB88ECBC264BFC7D00CDD987 /* DSCreateIdentityFromInvitationViewController.m in Sources */, FBE2B7A524C9E481000F1108 /* DSRegisterTLDViewController.m in Sources */, FB763C622089AF9500CDBA57 /* DSStandaloneAddressesViewController.m in Sources */, 6003F5A7195388D20070C39A /* DSSyncViewController.m in Sources */, @@ -2944,22 +2957,23 @@ FBEB7E8F220EEA7B0013F619 /* DSRegisterMasternodeViewController.m in Sources */, FB0F15A821049505000272E6 /* DSTransactionAmountTableViewCell.m in Sources */, FB6DC62520AB63AC00E35EED /* DSSettingsViewController.m in Sources */, + 9311F51D2D2082DB0039D778 /* DSNetworkActivityView.m in Sources */, FB12FEED20CD520700C1E0F0 /* DSStandaloneDerivationPathKeyInputViewController.m in Sources */, 2A3B3EB420F893F4002962FB /* SelectorFormCellModel.m in Sources */, FBB5D73C221FE5F40021764A /* DSUpdateMasternodeRegistrarViewController.m in Sources */, FBB4E386215057AA008040B7 /* DSAddDAPViewController.m in Sources */, FBB5D729221DF9390021764A /* DSUpdateMasternodeServiceViewController.m in Sources */, - FB0F15BE210A0208000272E6 /* DSBlockchainIdentityTableViewCell.m in Sources */, + FB0F15BE210A0208000272E6 /* DSIdentityTableViewCell.m in Sources */, FBB4E383214BC7C3008040B7 /* DSDAPIGetUserInfoViewController.m in Sources */, FBEB7E92220F0DE50013F619 /* DSAccountChooserTableViewCell.m in Sources */, - FBBDF426226C843C0081D673 /* DSBlockchainIdentityTransitionsViewController.m in Sources */, + FBBDF426226C843C0081D673 /* DSIdentityTransitionsViewController.m in Sources */, FB9A106F20D83683006883E2 /* DSAddDevnetIPAddressTableViewCell.m in Sources */, FBB4E380214A9F01008040B7 /* DSDAPIGetAddressSummaryViewController.m in Sources */, FBB4E3732145B5B3008040B7 /* DSDAPListViewController.m in Sources */, FB85000D261A75700002E5EA /* DSInvitationDetailViewController.m in Sources */, FBB4E37C214A937D008040B7 /* DSDAPICallsViewController.m in Sources */, FB9A106920D833F4006883E2 /* DSAddDevnetViewController.m in Sources */, - FBD967AA212571DC005CBE0B /* DSTopupBlockchainIdentityViewController.m in Sources */, + FBD967AA212571DC005CBE0B /* DSTopupIdentityViewController.m in Sources */, 2A9FFEC622328D0E00956D5F /* DSContactsViewController.m in Sources */, FB2B21AB20D6FC0B000DF537 /* BRBubbleView.m in Sources */, FBB25BF020C5CDC60037F5BE /* DSBlockchainExplorerViewController.m in Sources */, @@ -2977,15 +2991,15 @@ FB5252FC20F232C9009A2BF1 /* DSTransactionDetailViewController.m in Sources */, FB6B78462278D50A0005C9DB /* DSContactReceivedTransactionsTableViewController.m in Sources */, FBF31B68222F784E00393301 /* DSSpecializedDerivationPathsViewController.m in Sources */, - FB988324241D6C3300B39F02 /* DSBlockchainIdentityKeyTableViewCell.m in Sources */, + FB988324241D6C3300B39F02 /* DSIdentityKeyTableViewCell.m in Sources */, FB6B784C2278FB3A0005C9DB /* DSContactSendDashViewController.m in Sources */, - FBD967A72125719E005CBE0B /* DSBlockchainIdentityActionsViewController.m in Sources */, + FBD967A72125719E005CBE0B /* DSIdentityActionsViewController.m in Sources */, FBBDF429226C8ECA0081D673 /* DSTransitionTableViewCell.m in Sources */, FB5252FF20F2377E009A2BF1 /* BRCopyLabel.m in Sources */, 2A3B3EC220F893F4002962FB /* BaseFormCellModel.m in Sources */, 2A3B3EB320F893F4002962FB /* FormTableViewController.m in Sources */, FB0F15AB21049598000272E6 /* DSTransactionIdentifierTableViewCell.m in Sources */, - FB0F15B721090445000272E6 /* DSBlockchainIdentitiesViewController.m in Sources */, + FB0F15B721090445000272E6 /* DSIdentitiesViewController.m in Sources */, FB44D628228C77E400354A4D /* DSQuorumListViewController.m in Sources */, FB12FEE320CD3BD100C1E0F0 /* DSMasternodeTableViewCell.m in Sources */, FB3E970420DC21EB00D9B0CB /* DSAddressesTransactionsViewController.m in Sources */, @@ -2997,15 +3011,16 @@ FB90D2D520C40DD3002B7956 /* DSDoubleDerivationPathsAddressesViewController.m in Sources */, FBF31B65222F74B400393301 /* DSWalletDetailViewController.m in Sources */, 2AF8A95D2284820F00F8C72D /* DSContactProfileAvatarView.m in Sources */, - FB0F15C1210ACD52000272E6 /* DSCreateBlockchainIdentityViewController.m in Sources */, + FB0F15C1210ACD52000272E6 /* DSCreateIdentityViewController.m in Sources */, FBBDF42C226EDF600081D673 /* DSContactTableViewCell.m in Sources */, FB83C2E721B91A8D0057A0EF /* DSTransactionFloodingViewController.m in Sources */, FB12FEEA20CD50AC00C1E0F0 /* DSStandaloneDerivationPathViewController.m in Sources */, + 758719952B3ECB7A004C0E2D /* DSCoinJoinViewController.m in Sources */, FB9762F724BC4C3E00CF28EC /* DSMiningViewController.m in Sources */, FB850013261A7B9A0002E5EA /* DSIdentityChooserViewController.m in Sources */, FB90D2CC20C3F0D3002B7956 /* DSAccountTableViewCell.m in Sources */, FB6DC62920AB64F400E35EED /* DSChainsViewController.m in Sources */, - FBC508CE2427363C00EB3DAB /* DSBlockchainIdentitySearchTableViewCell.m in Sources */, + FBC508CE2427363C00EB3DAB /* DSIdentitySearchTableViewCell.m in Sources */, 2AF8A94F2284745700F8C72D /* TextViewFormTableViewCell.m in Sources */, FB23895B20D3035D00921B89 /* DSProposalTableViewCell.m in Sources */, 6003F59A195388D20070C39A /* main.m in Sources */, @@ -3024,7 +3039,7 @@ FB88ECB6264B8BB800CDD987 /* DSDAPIGetTransactionInformationViewController.m in Sources */, FBC2EB02260F886700B53BDB /* DSInvitationsViewController.m in Sources */, FB83C2E421B7B7560057A0EF /* DSActionsViewController.m in Sources */, - FBC508CB2426F42500EB3DAB /* DSSearchBlockchainIdentitiesViewController.m in Sources */, + FBC508CB2426F42500EB3DAB /* DSSearchIdentitiesViewController.m in Sources */, FBEB7E95220F0DFA0013F619 /* DSWalletChooserTableViewCell.m in Sources */, FB12FEE720CD3EC100C1E0F0 /* DSMasternodeViewController.m in Sources */, 2AE125EE223B9B0600379C6F /* DSOutgoingContactsTableViewController.m in Sources */, @@ -3058,6 +3073,7 @@ 2A1ECBD920D8B327000177D8 /* DSHashTests.m in Sources */, FB91B2D425325EB100511A44 /* MDCDamerauLevenshteinDistanceTests.m in Sources */, 2A7DCCB420DA198F0097049F /* DSPaymentProtocolTests.m in Sources */, + 757323562C5A384800EF5FC0 /* DSCoinJoinSessionTest.m in Sources */, FBAF79792385CA0200F4944E /* DSSparseMerkleTreeTests.m in Sources */, FB97501025B3888800A1DCE7 /* DSMainnetE2ETests.m in Sources */, FB97502B25B3BC2D00A1DCE7 /* DSMainnetSyncTests.m in Sources */, @@ -3161,6 +3177,7 @@ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_FAST_MATH = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -3216,6 +3233,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_FAST_MATH = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -3235,40 +3253,37 @@ baseConfigurationReference = 61730B1B3737895F96BE6A54 /* Pods-DashSync-DashSync_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Sign to Run Locally"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; EXCLUDED_ARCHS = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "DashSync/DashSync-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "DPP_STATE_TRANSITIONS=1", + "PLATFORM_VALUE_STD=1", + ); INFOPLIST_FILE = "DashSync/DashSync-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; 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\"", @@ -3284,7 +3299,8 @@ "\"SystemConfiguration\"", "-framework", "\"UIKit\"", - "-ld_classic", + "-ld64", + "-DPP_STATE_TRANSITIONS", ); PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3298,39 +3314,37 @@ baseConfigurationReference = BD44FCB7175424B2BE7993BB /* Pods-DashSync-DashSync_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; EXCLUDED_ARCHS = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "DashSync/DashSync-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "$(inherited)", + "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "DPP_STATE_TRANSITIONS=1", + "PLATFORM_VALUE_STD=1", + ); INFOPLIST_FILE = "DashSync/DashSync-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; 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\"", @@ -3346,7 +3360,8 @@ "\"SystemConfiguration\"", "-framework", "\"UIKit\"", - "-ld_classic", + "-ld64", + "-DPP_STATE_TRANSITIONS", ); PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3369,6 +3384,8 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", + "DPP_STATE_TRANSITIONS=1", + "DASHCORE_MESSAGE_VERIFICATION=1", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -3388,6 +3405,12 @@ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "DPP_STATE_TRANSITIONS=1", + "DASHCORE_MESSAGE_VERIFICATION=1", + ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; diff --git a/Example/DashSync.xcodeproj/xcshareddata/xcschemes/DashSync-Example.xcscheme b/Example/DashSync.xcodeproj/xcshareddata/xcschemes/DashSync-Example.xcscheme index 88c79c32d..b86cbac27 100644 --- a/Example/DashSync.xcodeproj/xcshareddata/xcschemes/DashSync-Example.xcscheme +++ b/Example/DashSync.xcodeproj/xcshareddata/xcschemes/DashSync-Example.xcscheme @@ -132,6 +132,9 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + enableAddressSanitizer = "YES" + enableASanStackUseAfterReturn = "YES" + enableUBSanitizer = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -161,19 +164,24 @@ + + + + - - - + - + + @@ -20,14 +21,14 @@ - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + @@ -117,7 +141,7 @@ - + @@ -131,14 +155,14 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/DashSync/BRCopyLabel.m b/Example/DashSync/BRCopyLabel.m index 3fefc20a2..4b6c990b7 100644 --- a/Example/DashSync/BRCopyLabel.m +++ b/Example/DashSync/BRCopyLabel.m @@ -110,8 +110,10 @@ - (void)toggleCopyMenu { self.highlight.alpha = 1.0; }]; [self becomeFirstResponder]; - [[UIMenuController sharedMenuController] setTargetRect:self.copyableFrame inView:self]; - [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES]; +// showMenuFromView:rect + [[UIMenuController sharedMenuController] showMenuFromView:self rect:self.copyableFrame]; +// [[UIMenuController sharedMenuController] setTargetRect:self.copyableFrame inView:self]; +// [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES]; if (!self.menuHideObserver) { self.menuHideObserver = diff --git a/Example/DashSync/Base.lproj/Main.storyboard b/Example/DashSync/Base.lproj/Main.storyboard index d07e791d3..51812501d 100644 --- a/Example/DashSync/Base.lproj/Main.storyboard +++ b/Example/DashSync/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -121,10 +121,10 @@ - + - + - + - + - + - + - + - + - + - +