diff --git a/.gitignore b/.gitignore index 1cdacb9e..faba1bc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ +# OS X +.DS_Store + # Xcode -.bundle/ -build/* +build/ *.pbxuser !default.pbxuser *.mode1v3 @@ -9,24 +11,18 @@ build/* !default.mode2v3 *.perspectivev3 !default.perspectivev3 -!default.xcworkspace -xcuserdata +xcuserdata/ +*.xccheckout profile *.moved-aside -Pods/* -!Podfile.lock -.idea/ -.DS_Store -/.irb-history -/screenshot_*.png - -history_bash.txt - -#Fastlane - -fastlane/report.xml -fastlane/README.md +DerivedData +*.hmap +*.ipa -# Cuckoo +# Bundler +.bundle +_Pods.xcodeproj +Pods/ +.idea GeneratedMocks.swift diff --git a/BitcoinCashKit.swift.podspec b/BitcoinCashKit.swift.podspec index aaea0c60..72470f83 100644 --- a/BitcoinCashKit.swift.podspec +++ b/BitcoinCashKit.swift.podspec @@ -1,29 +1,34 @@ -Pod::Spec.new do |spec| - spec.name = 'BitcoinCashKit.swift' - spec.module_name = "BitcoinCashKit" - spec.version = '0.6' - spec.summary = 'BitcoinCash library for Swift' - spec.description = <<-DESC - BitcoinCashKit implements BitcoinCash protocol in Swift. It is an implementation of the BitcoinCash SPV protocol written (almost) entirely in swift. - ``` - DESC - spec.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' - spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' } - spec.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } - spec.social_media_url = 'http://horizontalsystems.io/' +Pod::Spec.new do |s| + s.name = 'BitcoinCashKit.swift' + s.module_name = 'BitcoinCashKit' + s.version = '0.18' + s.summary = 'BitcoinCash library for Swift.' - spec.requires_arc = true - spec.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "#{spec.version}" } - spec.source_files = 'BitcoinCashKit/BitcoinCashKit/**/*.{h,m,swift}' - spec.ios.deployment_target = '11.0' - spec.swift_version = '5' + s.description = <<-DESC +BitcoinCashKit implements BitcoinCash protocol in Swift. It is an implementation of the BitcoinCash SPV protocol written (almost) entirely in swift. + DESC - spec.dependency 'BitcoinCore.swift', '~> 0.6' - spec.dependency 'HSCryptoKit', '~> 1.4' - spec.dependency 'HSHDWalletKit', '~> 1.1' - spec.dependency 'Alamofire', '~> 4.0' - spec.dependency 'ObjectMapper', '~> 3.0' - spec.dependency 'RxSwift', '~> 5.0' - spec.dependency 'BigInt', '~> 4.0' - spec.dependency 'GRDB.swift', '~> 4.0' + s.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } + s.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "bitcoin-cash-#{s.version}" } + s.social_media_url = 'http://horizontalsystems.io/' + + s.ios.deployment_target = '13.0' + s.swift_version = '5' + + s.source_files = 'BitcoinCashKit/Classes/**/*' + s.resource_bundle = { 'BitcoinCashKit' => 'BitcoinCashKit/Assets/Checkpoints/*' } + + s.requires_arc = true + + s.dependency 'BitcoinCore.swift', '~> 0.18' + s.dependency 'OpenSslKit.swift', '~> 1.0' + s.dependency 'Secp256k1Kit.swift', '~> 1.0' + s.dependency 'HdWalletKit.swift', '~> 1.5' + + s.dependency 'ObjectMapper', '~> 4.0' + s.dependency 'RxSwift', '~> 5.0' + s.dependency 'BigInt', '~> 5.0' + s.dependency 'GRDB.swift', '~> 5.0' end diff --git a/BitcoinCashKit/Assets/Checkpoints/MainNet-bip44.checkpoint b/BitcoinCashKit/Assets/Checkpoints/MainNet-bip44.checkpoint new file mode 100644 index 00000000..148a4e8b --- /dev/null +++ b/BitcoinCashKit/Assets/Checkpoints/MainNet-bip44.checkpoint @@ -0,0 +1,147 @@ +02000000ba3f2b4208ec0495b2e3743465cae2b44d8f1c778b44cf6b0000000000000000d287e52e8045c060c1cee47d1cc7559c7b8ab8db580539fb55fc579a998ea14efe0e50538c9d001926c0c180a08504003f72e59e0db5b38e5210369dc2fb4831ab1e81f3b5dbec3d0000000000000000 +0200000088c03613a752946272147792ee524af4acbffc3c1a70204b00000000000000005b679afd22900abbf4221db5401ee867c99264e13f754777ccaf1fd92f1fa4fb3a0d5053aab3001931afde449f850400ba3f2b4208ec0495b2e3743465cae2b44d8f1c778b44cf6b0000000000000000 +02000000de6076c4ebaf8a99d1aed6600b702499921d606e5b7df60500000000000000005aedac698f670b8c787f2339575bd4466e558adc6e0f6e8e0373005618c672820a0c5053aab300196bf4edac9e85040088c03613a752946272147792ee524af4acbffc3c1a70204b0000000000000000 +02000000f308b5ad14372c12cd08c584596e0790a7fed9f2846b8e170000000000000000a69485840909da4d1236721b721f2638a7d1c716f085f099cc21c705bdeef8494f085053aab300190efbed749d850400de6076c4ebaf8a99d1aed6600b702499921d606e5b7df6050000000000000000 +02000000ebb127579225c0b7c9556364ee197eff8d84c2c474cbe0090000000000000000bf62b8c209d82909d5883e8095e8cbabab5545ad29839b25c973944fd161ed44a5055053aab30019058e37689c850400f308b5ad14372c12cd08c584596e0790a7fed9f2846b8e170000000000000000 +02000000a8ed0256dd77435126cffa9d9d6550046a45ff63643c492200000000000000002546a265189fd9caf283386a90dc53a15d7a9caf8daff4b4b741851bb8a6421173015053aab30019effe96599b850400ebb127579225c0b7c9556364ee197eff8d84c2c474cbe0090000000000000000 +0200000012a3a9812e4b2f1311576e9abf834e7b247c139453da7b4300000000000000003a54f400aee6cef8fc999858ca83ea110b2a71da91c53061e736dc0f8fe5438fca005053aab3001951fab8bd9a850400a8ed0256dd77435126cffa9d9d6550046a45ff63643c49220000000000000000 +020000001bdf3c4a0aaf48ad2b6fe321f9ca2687fc77b7dcffe0c54b00000000000000005e1932b8b416ffb592c6c6a900016eb76a5ff1ef264823a8dbea2a5506ae5aa472ff4f53aab3001951c856699985040012a3a9812e4b2f1311576e9abf834e7b247c139453da7b430000000000000000 +0200000045c081445f0c886c0e9c06100a032f899a0ca02981c7242c00000000000000007404f4844f9ea3bf199e9b0879359a682442805f36df07f07705a52e818479dc57fc4f53aab30019b4de0dde988504001bdf3c4a0aaf48ad2b6fe321f9ca2687fc77b7dcffe0c54b0000000000000000 +02000000edeafbbb92af050ce4fdff9b2e42c7c7e09a1fb7b3420e81000000000000000061b816aeadc6d8060be33252375f320f73f6b2c7385b517525574dde8bfd1770e2fa4f53aab30019a29707c59785040045c081445f0c886c0e9c06100a032f899a0ca02981c7242c0000000000000000 +02000000223f307aadd3b7f79b72f224a5b5b5df31722cec5f48c51e000000000000000047ea9aa89e6ecc12e573daa8efb6727cfeda897455bbc0a6b2f69c2d209f4b71f5f94f53aab3001907fad61a96850400edeafbbb92af050ce4fdff9b2e42c7c7e09a1fb7b3420e810000000000000000 +02000000b09566bca15f31a35345a5bc315b2957b3efdd75bc7c4c6400000000000000006f5d1c5a30362de6e2bcb95aab111149a77cba7f9f72c925a82b0348500d217748f64f53aab30019f6a067c695850400223f307aadd3b7f79b72f224a5b5b5df31722cec5f48c51e0000000000000000 +020000003209dff479cfc22384c25fe241f907e24ba8e29e103d3c390000000000000000c0ebec8579d6a66721537ec2425c7083b9da1ed863585725b7680b1f6d2072dce1f34f53aab30019365408bc94850400b09566bca15f31a35345a5bc315b2957b3efdd75bc7c4c640000000000000000 +02000000e589a04528b496bb1bd163424583ec8c3a3eebd64decbcaa000000000000000092c84273d629837a781f62df4ff1e98735bae5e69d6680421d3d106a501add60d5f24f53aab300190d2711dc938504003209dff479cfc22384c25fe241f907e24ba8e29e103d3c390000000000000000 +0200000031ad2c1506d203293dd6348d5f54ff5b14629186933f58620000000000000000227058a7137843a516996ce7ce984beab4fd5e9099709bdbb1bc98f3517c3c56e3ee4f53aab3001992a4e3f792850400e589a04528b496bb1bd163424583ec8c3a3eebd64decbcaa0000000000000000 +02000000bc7498364b22f646653e7753fdd49013e51bc7c5be5b8a4e0000000000000000d98447ac6d93c629fc81af9a49a074d7db60b90c5790fce9990f9114a493194affed4f53aab30019d81bf0829185040031ad2c1506d203293dd6348d5f54ff5b14629186933f58620000000000000000 +02000000910bd6e05b76fb5ed472b76c07a28313da789d778ad2e57700000000000000008380c76e3c4b8f48c2965c4c4c16653e85e99329780b92dc12bea667f041e3164aed4f53aab30019aa7b9ff690850400bc7498364b22f646653e7753fdd49013e51bc7c5be5b8a4e0000000000000000 +02000000ba34128aadc2e73a6d9bbe328cd660b517752fa57ee411600000000000000000f1042e7234a288d31ef80f8893481272bcd6eea0fa513dfaebf3ac5a92f37e7c12ec4f53aab300190c5f41688f850400910bd6e05b76fb5ed472b76c07a28313da789d778ad2e5770000000000000000 +02000000bd6fd95daaccf77098024073cc5dededffad394ae025844b00000000000000005d77b6ee34ad3a5363f58cfafb015e3d72efd8e918cde7029345b385f30d16513ae94f53aab30019bb8afec88e850400ba34128aadc2e73a6d9bbe328cd660b517752fa57ee411600000000000000000 +02000000ae7c78592678f6ba5065bd9a0662271013134a89434fd8a900000000000000004759f0afc2f484916fb6a082338323be7c2cd2803b76abc375153df5d808733966e74f53aab300195dcfb6778d850400bd6fd95daaccf77098024073cc5dededffad394ae025844b0000000000000000 +020000009ee688465fe1174c750089d5195f8348cd439e60c930c74a000000000000000006a33880503c7f14c144f037f2ac1819b27160bde2722c995e39be1218d3ecc79ee54f53aab30019a193cd438c850400ae7c78592678f6ba5065bd9a0662271013134a89434fd8a90000000000000000 +02000000e077c2bab49992492d535b1b60549fc97593011717c03580000000000000000065abe8c39e5bf51cf5d87373f6366b87e1974802dd801f1af5516f2cd8bfbddce5e34f53aab30019e34ea8c38b8504009ee688465fe1174c750089d5195f8348cd439e60c930c74a0000000000000000 +020000007a8cb9e2ea2f7fcc1d0195ddb2175965bf45f2812cacea3c0000000000000000304629e4aa6d3c307a719db55ee29bb073048b003e759118a3acbbe02497ca5848df4f53aab30019dbf02e988a850400e077c2bab49992492d535b1b60549fc97593011717c035800000000000000000 +02000000578569a85a532af78d9f2bb448a439c376e916782b212d0f00000000000000006f504b91547c7fd4cf0a99b3ac4fdcee30a779cce3c099819d43be85b838bd5168dd4f53aab30019c54e259b898504007a8cb9e2ea2f7fcc1d0195ddb2175965bf45f2812cacea3c0000000000000000 +02000000f0f00b72042032823151b265c854cced592cf73d8638b94c0000000000000000a34029fd1f9dfe44947178ae0d4491289ddacf75989b06b90d0142eb3d86536c2fd94f53aab30019f08e0c4688850400578569a85a532af78d9f2bb448a439c376e916782b212d0f0000000000000000 +0200000038375a7969c498dab5b388afbc1c3d374bd9210425f4c074000000000000000008b8a73fbb942d2e35f7faaacbe76aa63193b9c36a2f39d0698b1e23a924363333d64f53aab30019a07c2e2b87850400f0f00b72042032823151b265c854cced592cf73d8638b94c0000000000000000 +0200000011355cd4abdbce81ff0583e7317f35329acbf14a57214b85000000000000000070702c32c6a515e0b167e642db4409f537b5addd63c2e41630635aae8499456fb0d54f53aab30019f3b8fbf48685040038375a7969c498dab5b388afbc1c3d374bd9210425f4c0740000000000000000 +02000000a091e9e5205df3c26a27b8499c26b7bfd1e75b7bdb8522a60000000000000000c4d57b53d1ee807ffee287f0a11fe1b24c3324442863ddae9036a8951558d4acf6d34f53aab300194cb573318585040011355cd4abdbce81ff0583e7317f35329acbf14a57214b850000000000000000 +020000007ac6087351f59c8730e7109cef79a2118d4e9788cb09732e0000000000000000b09c72103aa7e6cb81faef18027c57e700aa33a9f02b95013b605605763d02b2bdce4f53aab300190e0c313e84850400a091e9e5205df3c26a27b8499c26b7bfd1e75b7bdb8522a60000000000000000 +02000000543a88c9ead76ff8c031d558b6214f37d2258a057288a98200000000000000004a7420c2953accf8630c75fca3821ade63aafc1d99b476aeb78f1f6c7044044f33cd4f53aab30019cfdad2b6838504007ac6087351f59c8730e7109cef79a2118d4e9788cb09732e0000000000000000 +02000000c281b9672067629d19873f9e50b52e5c6d713f1535cdc6770000000000000000ece1a30937fe7a6478df3283167980b6440a36bfc79fd5ed876aec9a718527d4a1cc4f53aab30019b5a9e54c82850400543a88c9ead76ff8c031d558b6214f37d2258a057288a9820000000000000000 +02000000148699aa20a44895895985d5e3d9f31f0e59e626540b7d840000000000000000dcad236ea06fd34e30ae519f477dc084bf75552f85578e0a8b9c6cc804dcab8e08c94f53aab3001997fd95e881850400c281b9672067629d19873f9e50b52e5c6d713f1535cdc6770000000000000000 +020000008b4317c07870ee8b16e1c279a0a9405e539249d12032b02a00000000000000000b9b5d0f7351b5bc05f2bdc9557963d80a50660dc3aec57fb6fe2432b7ce4b5dacc84f53aab30019486b48f680850400148699aa20a44895895985d5e3d9f31f0e59e626540b7d840000000000000000 +02000000a35cbb341f4dc369daf1db4aa6c7b6deb906f21431fade65000000000000000064014a614d54ad4f58430a53336480ca53fee63b3a24032708c7ca41887f92b07dc84f53aab30019770e1ccb7f8504008b4317c07870ee8b16e1c279a0a9405e539249d12032b02a0000000000000000 +020000000f217937f5acb6f5ee6b1046391fc8dafc7d14f45b543b190000000000000000bc3dadbb38272b5516159c21f3254d4535b9de7e45d142f932295953b953e9788ec54f53aab300197d5dd9e07e850400a35cbb341f4dc369daf1db4aa6c7b6deb906f21431fade650000000000000000 +020000003dfecf95a637d583ed7d83e60c337cce28184b369c8055190000000000000000ed988bc76a1ace4d5af85c915b6b6247fcadb679daeba41e871852899a646ced13c54f53aab3001913012e107d8504000f217937f5acb6f5ee6b1046391fc8dafc7d14f45b543b190000000000000000 +0200000042feba1f0689217040e87a2d5c8003f62054ca70526020870000000000000000854c2a9323d372c80ccd60b8fe93bbf1335e5596dae48aa9a8e85f988c6ab68607c54f53aab30019625b285a7c8504003dfecf95a637d583ed7d83e60c337cce28184b369c8055190000000000000000 +0200000006eed96748eb886c2dd80a66929b6d436d88f8178ce89ab00000000000000000e76ca180a82080b3a12ab75ff1349fb5291ff66e3e061c3bcd2af0937a32cf4c55c24f53aab300190711fa907b85040042feba1f0689217040e87a2d5c8003f62054ca70526020870000000000000000 +0200000060f84899b9dc1829026b42c8a869c3f3cbfe48f44c5fe5740000000000000000f19c0030b60d269e1c1eb2c8c5663b5d848e3e3a8cdb95c40e6cc807cb29aab855bd4f53aab30019cd5a218a7a85040006eed96748eb886c2dd80a66929b6d436d88f8178ce89ab00000000000000000 +020000003874f8b15e3878126e2ab6ada58c4ccb5e9f7ae66006b78700000000000000001a9e63d440c243638fa43d841a6556244b071c51400eaa35f58ab897052bdf5cc6bd4f53aab300195338dba17985040060f84899b9dc1829026b42c8a869c3f3cbfe48f44c5fe5740000000000000000 +0200000048833a11adfe8ca520774d04c30b9c1145d0a0ef7e9d392d0000000000000000f80f0c720479c629e2d8ded2e1835d4871d6efe6489e80a2c7b6b599dd5faa4b35bb4f53aab3001927672991788504003874f8b15e3878126e2ab6ada58c4ccb5e9f7ae66006b7870000000000000000 +0200000027ab9802f9fca4a65bb4ed509f475ec85dced21f5d0337920000000000000000f91e0f223667c1872506d8a658e24d5374b0cc802b2fa552d2fc68f72343aa2e28ba4f53aab300191894e0a87785040048833a11adfe8ca520774d04c30b9c1145d0a0ef7e9d392d0000000000000000 +020000009afe656e0f5922610f0ce98ef4c28e0787674a55189b3619000000000000000039886ac4637bfb353647625edf6b0a6726fa8879c72abb5505022c0fef7bec3c21b94f53aab300197ace04de7685040027ab9802f9fca4a65bb4ed509f475ec85dced21f5d0337920000000000000000 +020000009dd8612854be8ca84cd2cd26de8b43cf5339428a261aed8800000000000000003668922c235558f89ad6a4f0e8b5a20e29aebfdf693df11b302fe523729af35dcfb34f53aab30019337ed7ea758504009afe656e0f5922610f0ce98ef4c28e0787674a55189b36190000000000000000 +02000000b93523be114558c5d172c44d9d1c3f420c9dd4e49d1467a7000000000000000069d1718c45b6cf2fcb253cd14efc6690b5d9afd89f7246daf0e3be736579a94ac4b14f53aab30019024a2f9e748504009dd8612854be8ca84cd2cd26de8b43cf5339428a261aed880000000000000000 +0200000075cbd526a5327ec10cd9bceae74892b180d27a689f153682000000000000000056e857744cc3fb9ac15423b9d23daf69603975ee83876e020fefd946ed6e7bc75bb14f53aab30019ef2968c073850400b93523be114558c5d172c44d9d1c3f420c9dd4e49d1467a70000000000000000 +02000000cb0eddbea32985fc9ee1be87ec23ece886358219536adf440000000000000000653d36f8ffc0a89d2e12358b580cd6e1b0156389fd2d27b54d4107bb9d858f325cb04f53aab300191177f5287285040075cbd526a5327ec10cd9bceae74892b180d27a689f1536820000000000000000 +0200000017fa90299ce9d234cf73c46cb8c4e7480454ec9167b4bd50000000000000000047b38999f9af6fee382fb1b098fe469fc0ab777ab1bc7a73b14bde7673932688deaf4f53aab300194a3cc36671850400cb0eddbea32985fc9ee1be87ec23ece886358219536adf440000000000000000 +020000003268f352f71040e8a15f9c8062d3d5005715436536ba293d00000000000000009e3f8bc6691440d64a681c0dfc9ec2fd459797d7e3e6259362d656daa7374628b8ac4f53aab300197987a3617085040017fa90299ce9d234cf73c46cb8c4e7480454ec9167b4bd500000000000000000 +02000000d2a652ae1b7e57e072f5e3743136dd46ada4d073a3a06039000000000000000079e27bc7faaa03a719d1491ad6dad19ba0b0cb17f443de7c461247c3f47ec45982ab4f53aab30019e804deb86f8504003268f352f71040e8a15f9c8062d3d5005715436536ba293d0000000000000000 +020000006ce2aba8861ec16c546d23fa8af538ce6112f109a9f412360000000000000000497e72f1c1fad86b7e35e230c11e76f635e5c6e809223423f478c39b685b0c615da94f53aab30019f29795776e850400d2a652ae1b7e57e072f5e3743136dd46ada4d073a3a060390000000000000000 +02000000cd27f3686735c946a32f079fea0aff1dc7f8dfe9ada6ecb00000000000000000dd504f552b7f71b6685a8d2dbff92d7c2a62344ce87b801ba4f3c274ca547f7851a94f53aab300194ddb49a06d8504006ce2aba8861ec16c546d23fa8af538ce6112f109a9f412360000000000000000 +0200000089b96fd7b5009cbfdd98322574e1c160f89a1cb7a9bc7fb30000000000000000e3c124151985dd04e2c32ba5e241abd5b22ffd479869259246e29c26a3a9164809a54f53aab30019e177d7866c850400cd27f3686735c946a32f079fea0aff1dc7f8dfe9ada6ecb00000000000000000 +02000000cc8480971555c86209a87f72f51f407ca4dad13fa00079020000000000000000d1ecb901afaaf70e7725f25b444bae44071b6219d50a9c940a556091577a35a50ea44f53aab30019aca3f9ce6b85040089b96fd7b5009cbfdd98322574e1c160f89a1cb7a9bc7fb30000000000000000 +020000004b5b861369e67a4d87657dfffe85253f23fe13815f8e07080000000000000000850ac13a6bf15fe700a8da6f0ec852039a6630a4d474b1b1929db160086ebd1edfa14f53aab30019ce0868a96a850400cc8480971555c86209a87f72f51f407ca4dad13fa00079020000000000000000 +0200000026ba9cc1ff8180ffabee37b7f7682e2dbe38a21762edf53e0000000000000000887b0db8b15a3275165919cdc23ebcfc46b758ce96ecd1ac8c7c8c93a9ec445b88a04f53aab3001973a47edc698504004b5b861369e67a4d87657dfffe85253f23fe13815f8e07080000000000000000 +0200000090bcbfd0a472558833ce464368e939768752e568a93c343500000000000000000716a874daf6c450b277db0baf34463aebb14a4e0e79270e21d4e6c583d33ef0129f4f53aab30019ed605ce76885040026ba9cc1ff8180ffabee37b7f7682e2dbe38a21762edf53e0000000000000000 +020000005f9c242c6b61d0782af687009b48ef873961c8190a052d310000000000000000ed13b2a231aa9fad7154f558202dd7d3ea40105f6d8a1a351043e5a130e8fb2f889e4f53aab30019d8a34f2e6785040090bcbfd0a472558833ce464368e939768752e568a93c34350000000000000000 +02000000be15ccaa2406d7ce2190b4afcb338c49fa3faaa5b6fcd7060000000000000000b2ac4861093a0d319985089c50739d4e8d3e54d0adfc16b4e60f200bcaf8ea43d59d4f53aab30019cdf6810c668504005f9c242c6b61d0782af687009b48ef873961c8190a052d310000000000000000 +02000000c77835534d0a487d00de30f7b40370c958eaa50f1a21089d0000000000000000c26a86958b0dbebe7f7f91e51c0f59486864214b56a1a9c01628a3dad832ca88e09c4f53aab300195365730965850400be15ccaa2406d7ce2190b4afcb338c49fa3faaa5b6fcd7060000000000000000 +02000000429c00b13583ac149843442aa129cf2d44ce396f518e186b00000000000000002796c110ead15a0a3b6b6e84048c821d0edc94a1aaf9eaf08d1ddd6c285927fa4d9b4f53aab30019d8a2f7ff64850400c77835534d0a487d00de30f7b40370c958eaa50f1a21089d0000000000000000 +0200000057415e3cf946d26fad4a69de1832a90937a361a9e8e2b94700000000000000003979bdd7c50be7a00c0d5af930dc11fd21bff44342c09a4d959f60a268f38b5838964f53aab30019c13a8d5663850400429c00b13583ac149843442aa129cf2d44ce396f518e186b0000000000000000 +02000000a1423eae0bbdf430c71516721db80190448f1a9a97e2e2600000000000000000c594080c3ca9c1b1d960025ee20da0e9530cec194c811f728e61bfdb74c308f907944f53aab30019d57d9c066285040057415e3cf946d26fad4a69de1832a90937a361a9e8e2b9470000000000000000 +02000000233b174e68901ebfae050b981ae24d40390d61d26cb7ff720000000000000000b09410313b3c343d16240637ec98993f5c266d1d30a82801a874499b68605bb673934f53aab3001970c6192861850400a1423eae0bbdf430c71516721db80190448f1a9a97e2e2600000000000000000 +02000000f490c3382fac48e779350c67e10072983f5faae76cebaa960000000000000000fb3ed4c465d037d7e0f320702eb52586e95ed052c241266e5a00f04d8d46e013f3924f53aab3001994e10afb60850400233b174e68901ebfae050b981ae24d40390d61d26cb7ff720000000000000000 +02000000a72f696a171bd2c58c7e3eea3ff05be15808d7d9d6860e7b0000000000000000ef70e9f7a49418e55b471de2c416f5d42bc8053972c21b5071e28363bc7085c128904f53aab3001990682ba15f850400f490c3382fac48e779350c67e10072983f5faae76cebaa960000000000000000 +02000000a3f72083fa8cfc8883ef1fb9cf33e719db6c03046f323d6a0000000000000000b213c8c36e257ac08c71f1c11c4b3bbb8e278e3f4f6d96413660a75736fa9ff2018e4f53aab3001925b485255e850400a72f696a171bd2c58c7e3eea3ff05be15808d7d9d6860e7b0000000000000000 +02000000ff3728a8389cd1c1a2ca8a2e825fc1803fd41fca1b41712400000000000000001010056c33aa93d7fe00344d33a90d8c250aaa8cbb7d8a197135a8392fe75c441e8d4f53aab300194839ccd85d850400a3f72083fa8cfc8883ef1fb9cf33e719db6c03046f323d6a0000000000000000 +02000000e1e4f9bfff2512284a9fc44bd35cd2fe9e3deb6d179903660000000000000000f0ae9d62b2e6489d07ab442123e79ebaf87016edf7165f667a1386d1af28bd277d894f53aab300197e4bb89b5c850400ff3728a8389cd1c1a2ca8a2e825fc1803fd41fca1b4171240000000000000000 +020000006b6b77ad6ca3ca34259d6788fdadeef1547a0fa84d731845000000000000000024dcb54597ce76f1d34053d1173092e1a66959c5fc46ddd61e6d1cef2336ac7e27874f53aab3001903c4d70f5b850400e1e4f9bfff2512284a9fc44bd35cd2fe9e3deb6d179903660000000000000000 +020000000df888a842be963a78d897be945d3b6b13c337bd3d99645c0000000000000000d695710e85c2d259b073efc5505d8a33d3065044fe2420263fafd7e165f0accd89874f53aab30019b192482a5a8504006b6b77ad6ca3ca34259d6788fdadeef1547a0fa84d7318450000000000000000 +020000002d420e24ba7ec1fea33a8c317c0f5fc3cb601e28398d219d000000000000000060b3be45558b170a3c5bba932a058f4dab252a2f679021613713eeb80779629386874f53aab3001998924e3f598504000df888a842be963a78d897be945d3b6b13c337bd3d99645c0000000000000000 +02000000764b23552e51c6cfda870eac15f990dac9413932015cc066000000000000000036d520c80b481c1ab56e93466cc6afe2aea1724744ef1bbd7ce09f5f0050a9555e874f53aab300192c47769e588504002d420e24ba7ec1fea33a8c317c0f5fc3cb601e28398d219d0000000000000000 +020000000d1805f9cd06b1a95d282eb025d5df977d95fd48ae6b6d1d0000000000000000beb93f892f198a8a68eec93fe96895c212e78b7f395e1dfeb878a59bbe9562702e864f53aab30019b0cc06b457850400764b23552e51c6cfda870eac15f990dac9413932015cc0660000000000000000 +02000000e3690d5b97db4837340c4b868fa67c9ef0fbd455c05676060000000000000000cd037bce20c7fec31ac599c5ae9b8f585d2cfb722dff5cc6e2a564335f558c1ce2834f53aab300194cfd7f34568504000d1805f9cd06b1a95d282eb025d5df977d95fd48ae6b6d1d0000000000000000 +020000002f7007a253d1c19df4ba90e727bc2074b5a86d19055128730000000000000000fb4a40c6ebe74bdb5ce8bf10360d041995bfc5ded3e9fe775b8b2ee0df2751e3df824f53aab300199dc17fb655850400e3690d5b97db4837340c4b868fa67c9ef0fbd455c05676060000000000000000 +0200000065ce6aee748fa5cf93e4f58a5e27efbf36e808ffdf7f89b100000000000000003918448585c6c551ef5cc3718aa05920832799ab2139e7b1bb27a1952b38f6ba4f814f53aab30019cf84687f548504002f7007a253d1c19df4ba90e727bc2074b5a86d19055128730000000000000000 +0200000050f5d898a6863e578b33cdbf4e0a450ddb5974e6e7cc87a10000000000000000c9bd0e56b8d6ae7ed84cba97b154d14a225b57d494865a398b4cd7df3ae0887893804f53aab300192ca5a1ba5385040065ce6aee748fa5cf93e4f58a5e27efbf36e808ffdf7f89b10000000000000000 +020000001885ce9ecedd1ad9a984a99cb0892e05e76ce4f9a4b0959100000000000000009f99224474cf0aafb9c896ffc8ad292efb26138ff8d3d205401111bdf093c70aed7d4f53aab30019322dad085285040050f5d898a6863e578b33cdbf4e0a450ddb5974e6e7cc87a10000000000000000 +020000003dc4683a79dbebfae6fae6dab9485332c2b4e3104582ee2800000000000000007585606dc0e260afcf37207053d9d20a227052b9c4c08250e365892147aa6704097d4f53aab30019d05817a7518504001885ce9ecedd1ad9a984a99cb0892e05e76ce4f9a4b095910000000000000000 +02000000d4aa5b768011c87fb2988beefc1dfc3c0c27b5445d4ae8060000000000000000b4c2433ae9881920487f975725beabd79399cdb6acd6a57933e9e1dec0261dc5117f4f53aab300191240ee49508504003dc4683a79dbebfae6fae6dab9485332c2b4e3104582ee280000000000000000 +020000001a593e49d585a5987f78c5b141d74238f953dcb25ce2eb680000000000000000bd54cff7a4d185ccad60eb31cf7a9b83256ec0742ebc6e2682f1ada5977a22e69d794f53aab3001916a2d12e4f850400d4aa5b768011c87fb2988beefc1dfc3c0c27b5445d4ae8060000000000000000 +02000000bee844fc7a75db08426459904d2a223bd3c80252ebd18c1f0000000000000000adb779bbc3e7af5f724290c5e102a4d8eae315f8d0b5de6240fdbe7372fdbbed69794f53aab30019d603a6674e8504001a593e49d585a5987f78c5b141d74238f953dcb25ce2eb680000000000000000 +02000000caef89dccbfd7e8a6e14ef44f43f8c16201fb8cbb72a797c0000000000000000770552d3ffa346313864ad81af4e179bb2c6e502a51f34db4c30edb3847de8d441774f53aab30019a642fc8b4d850400bee844fc7a75db08426459904d2a223bd3c80252ebd18c1f0000000000000000 +0200000033a8f19953f1c6b6ece262e4d26a9a262cb598e852cef99a0000000000000000bdf4f023f783249f866adc74e8b633022f1afe71e15f47c4ad03d20bdf79ab9f0a774f53aab30019eb8b93754c850400caef89dccbfd7e8a6e14ef44f43f8c16201fb8cbb72a797c0000000000000000 +0200000034f5b6c08a27e092aeb4adb5370c016cc79a88b09412db4500000000000000003ac289bfdec3d121cc8f2e31404d3c2da154238dbcd424a71cc007b9eff13c3e99754f53aab300195bcd03924b85040033a8f19953f1c6b6ece262e4d26a9a262cb598e852cef99a0000000000000000 +020000005b6639ac9d2483341c991d6c999e415961502bc5c31a168a000000000000000026fc89c24402c97a8cdc70d202014b3f02585b7fedb514a5e4db6a7fa81cd5d9b7744f53aab300198130df8d4a85040034f5b6c08a27e092aeb4adb5370c016cc79a88b09412db450000000000000000 +02000000a64a8d644080e215b4007f64acf883c1877a4e912c83e69e0000000000000000b589f128dd342e7d2acdb1c9471dcbe0c7b9e184ac232b8d3cc4a00bff8042d2766f4f53aab300194ee5562b498504005b6639ac9d2483341c991d6c999e415961502bc5c31a168a0000000000000000 +020000003d0fe23a5aa47fae62dede20794661fba6943c9fe25764a30000000000000000e264374685cafd7b6e4aa6ec65fee7d5f2f2bed349fbaa2b6529823df816bcc1016f4f53aab30019bb976a1048850400a64a8d644080e215b4007f64acf883c1877a4e912c83e69e0000000000000000 +02000000d6daa7600fbeb2e44e30962670ec38573dc4f96e7bda02950000000000000000cb95ae12c3e77829e9eb65f184997ff269a246ef3adf77629b4c467236e20624e86e4f53aab30019be628613478504003d0fe23a5aa47fae62dede20794661fba6943c9fe25764a30000000000000000 +0200000029ed27f5cc527daacf035aa17c6814fd936d9ddf9723d03200000000000000008f836090c6d0b4bb8c3e0d8e79717018e8b9bf0d3613ead3fc003120a82e10324d6e4f53aab30019481e0bc146850400d6daa7600fbeb2e44e30962670ec38573dc4f96e7bda02950000000000000000 +020000009d85a7a41cf63d3a14e0c02b22df37afdf4dafd68578a26a0000000000000000bdeceb9a02027fafd09ce755e1f5f87b31a0bbf27106c6adc990ea40dfeef9e54f694f53aab300196640e5564585040029ed27f5cc527daacf035aa17c6814fd936d9ddf9723d0320000000000000000 +02000000a5cbf81c4d5d3e89669ca406fd9b8fdde99c63e67a7759aa00000000000000007d2107d5f8603e077561f0e9b5eac5936e3919ed12d13afdc3e4eecb9e22c4b2ba694f53aab3001963041b86448504009d85a7a41cf63d3a14e0c02b22df37afdf4dafd68578a26a0000000000000000 +02000000606ded4b084da4f712e562b081408b28584c0ebc05f7bd210000000000000000f4ef310e4649682bc300d2e53d137dc7c2a78c89c6b312d68ba7b2a8a170ddab05694f53aab30019c8dbb3e543850400a5cbf81c4d5d3e89669ca406fd9b8fdde99c63e67a7759aa0000000000000000 +020000002e8da06450c35d020ab2a4d9e510ee89fca196e77f12f0560000000000000000184797be67e17ad2e9fe4eb5d6cb2dd6cb593e28cac759f0048ecd1f1a80600b87694f53aab30019b8a802a842850400606ded4b084da4f712e562b081408b28584c0ebc05f7bd210000000000000000 +02000000ccc36e54810b1412ced7a9a00b37223074c6080ea0dd189c00000000000000000b0cced8fe8361aa7f219649df4daffc0044a4f48e002e17ca6624596401acd0e9664f53aab3001935ee7e1f418504002e8da06450c35d020ab2a4d9e510ee89fca196e77f12f0560000000000000000 +0200000038393b82255dfeadb0fac022ce3e1783eec78ac504b50d9c0000000000000000cb8eb7cf71aeeeb94ec62e30cfef0eb203a8231e22b1ed1eb2ef263f1e4892a586604f53aab30019cd94b47140850400ccc36e54810b1412ced7a9a00b37223074c6080ea0dd189c0000000000000000 +020000000d0d2b4e81465971b908d12a4213a9298acfa7e5c64552910000000000000000cbc469f068c7b093db78b4560f87aa426de78c2a7a1dc4e2b3d070d9c71b3fe85d5f4f53aab30019a3008f9c3f85040038393b82255dfeadb0fac022ce3e1783eec78ac504b50d9c0000000000000000 +02000000be5a12d77b6f57d35657352ca97e6f63cf7c96418b85c279000000000000000029ed471aa72e61952ec049a6e017feab2719c2759fb57e8deb5a8cb440e2d3fbb15c4f53aab3001958d29efb3e8504000d0d2b4e81465971b908d12a4213a9298acfa7e5c64552910000000000000000 +0200000081727d1330040849bde55ef61312bd3cc56a5fd25c338f1b0000000000000000e0fe553450d2009fb94e859fa5304c6bdf6e1a5ca504bc29ec00c49920e0a8cc665c4f53aab30019af038f383d850400be5a12d77b6f57d35657352ca97e6f63cf7c96418b85c2790000000000000000 +02000000fb0307d0fb53e09602225ac23bbe7ed41956ecb7d24baa1e000000000000000060d1ded77639c5d5d3206fa93382ff97af2d73f50d30200bb186dbcf3fd9d3bff85a4f53aab300198fadf0773c85040081727d1330040849bde55ef61312bd3cc56a5fd25c338f1b0000000000000000 +02000000f22bef36ce9113bcbcfed19855f41b892368be6ee4a303a20000000000000000df62f043351ec709ac1b9c4c5bc803aad1a200e55e32d98b876bd9ffbf5c11ffed5a4f53aab30019f624ac1d3b850400fb0307d0fb53e09602225ac23bbe7ed41956ecb7d24baa1e0000000000000000 +02000000e6fc23bf68a531067151ac200b6c17f31ef36ea75ebf054800000000000000000b4cc592c0f058cd48c0f8deced0b419167481fac90e7dddc03666bcd5d0bae77e534f53aab30019101824fd3a850400f22bef36ce9113bcbcfed19855f41b892368be6ee4a303a20000000000000000 +02000000cd4e3a43fcf50fa19ca3ca122bd17741ac6f85a6aa961ab10000000000000000e89391c419414cce2df6c0ae00f5b39d04019a112327942a0187451c80dd6e53ea4f4f53aab3001908489b5439850400e6fc23bf68a531067151ac200b6c17f31ef36ea75ebf05480000000000000000 +020000007102b8fd502967b883062d558bbcb1934de7792aec9413720000000000000000c5676ae74f68105d8d671b3990773a0ec8d6ec3bc663a91c5ae4ea64d6d3347f81494f53aab3001923d85ce638850400cd4e3a43fcf50fa19ca3ca122bd17741ac6f85a6aa961ab10000000000000000 +020000002f15862adb55833c2773f570de340c49db7722809fd3f0940000000000000000b3ed418a2282f811e3d2a90b734f1b639a6ed64a98c01e0ba032ce914ab9b73d73484f53aab30019e2163656378504007102b8fd502967b883062d558bbcb1934de7792aec9413720000000000000000 +02000000101d5109c4b77b74f9fa469d665b68b3dd80d1d84dde19170000000000000000512c5c78a4f53886740ddfcd89f1387d419c0be3f97642465628683c830841f70e484f53aab30019de598b35368504002f15862adb55833c2773f570de340c49db7722809fd3f0940000000000000000 +020000001e6ea1cce073efbfcd5fd45232129a7efeca5c5a43ebe02d00000000000000003b24db33c7b9b256f84c5f934d5f504d885c11b6bd5bb1ce7f14a921992dd274c1434f53aab30019012fb91735850400101d5109c4b77b74f9fa469d665b68b3dd80d1d84dde19170000000000000000 +02000000877999b7a3cb95fa7729a4f5e22109cae34ee4e941c2e7a300000000000000000387ffc351b3167742fd1bf42525d8c33e0c45d5117eec38720a3ac17a21729cef414f53aab3001949b1f27b348504001e6ea1cce073efbfcd5fd45232129a7efeca5c5a43ebe02d0000000000000000 +02000000f70e98f57e4af2a8a7deb52792a250e02d30a68d23d793ad00000000000000003fbf0eb8beca9bd4eed150de6f370b8610579a8d22debfffdbb02f9a183483a319404f53aab3001921d9c2c833850400877999b7a3cb95fa7729a4f5e22109cae34ee4e941c2e7a30000000000000000 +02000000ce7ac68dc3123b45436cef787e839b9eedc7c963da4989ab00000000000000000e246ec6bd3d3321242604f6703e87d59c7642daf414fd5e95e1778a125ec0f542404f53aab30019d8e62d8732850400f70e98f57e4af2a8a7deb52792a250e02d30a68d23d793ad0000000000000000 +0200000062122d2b42728d0b320aabf1732b31b55fad13dd32ccf77700000000000000006258cc658d5427533d85a3252d8778d550fdc15c72a7ade86a16c12459a817bc9e3f4f53aab30019e4f5551131850400ce7ac68dc3123b45436cef787e839b9eedc7c963da4989ab0000000000000000 +020000009423cfc6819c7020bd42ac27e80b1c7c4f730aaea42fd013000000000000000067ba626bd952462953728eb9cb471846cbae71327a36e6a613ab8317995f577e9b3e4f53aab30019e0dcbfac3085040062122d2b42728d0b320aabf1732b31b55fad13dd32ccf7770000000000000000 +02000000ccf2187358b9f1eaaa7fcf58ec2d9fb600348dfa6c2bb8350000000000000000e9b32f7cec2ca0499488cd8159d9e1be2794b20ad5d2b97b5ee9bfdd3a7c320bdd3d4f53aab300196dd6199f2f8504009423cfc6819c7020bd42ac27e80b1c7c4f730aaea42fd0130000000000000000 +0200000065a11cfbe57eb93892b8a6a3c14080c483c2e67ae685f1580000000000000000fe252a834d8b62231f9b01ba6a48227a47fbc22f8007e9728636b11dd6b43409343c4f53aab30019a32afb6e2e850400ccf2187358b9f1eaaa7fcf58ec2d9fb600348dfa6c2bb8350000000000000000 +02000000b04fc8a8dc72a839990c1146a8ab753d96598e1802365a8a000000000000000071bcfd14c366a576b3b05500f9e7052f7704696608272f0b61f86f6c6d469874063b4f53aab30019b5578ed32d85040065a11cfbe57eb93892b8a6a3c14080c483c2e67ae685f1580000000000000000 +02000000c0d2fa1356fecf883d9a905a379d71acaa413368f2f4cb3b0000000000000000400253656db80a89b86b8f685be54be1571a4ea40540773d9101dba015f736d4223a4f53aab30019d88a29842c850400b04fc8a8dc72a839990c1146a8ab753d96598e1802365a8a0000000000000000 +02000000ec1242bd5fe1fdd1b9f3736ba2eae1200cb03235c1d927060000000000000000ad21d25ddca5da312acaf1c50fec67bb18c04f2bc212e64f74c2ce0347375bc9c6384f53aab300199d0e666c2b850400c0d2fa1356fecf883d9a905a379d71acaa413368f2f4cb3b0000000000000000 +0200000044e6a3529e8b18c6449f9e3e1efbda878af990fe5e4aa90500000000000000003174e12d9fa1a7aeae40d2cc8af5b2b31223dc3dbb5ea94ca1cc8bb684b908713f384f53aab30019065bd7292a850400ec1242bd5fe1fdd1b9f3736ba2eae1200cb03235c1d927060000000000000000 +020000004105a5c0ea0853a02d20af3cbd042feafb8dc2f79315522f0000000000000000a1a2dea77a9d186e336ade332fc6b011c83fc44add2d83b4bded99946040aba53a384f53aab300195341aa282985040044e6a3529e8b18c6449f9e3e1efbda878af990fe5e4aa9050000000000000000 +020000002d008795f0acd1e7dcdc5aa76afaba1ba31c271aab32f738000000000000000004dbad9b0b50922c369acd1c1e35bd28bb3deb59f20b2221a7ef78d417e01bd3a3324f53aab30019ac9159d5288504004105a5c0ea0853a02d20af3cbd042feafb8dc2f79315522f0000000000000000 +02000000f8cbbfcbd9a5709910c5f79056ff83e9ffc43e03940a96730000000000000000db2e57c047b670986ac8f7f40992003dd711690f9ba3fe6f0967203e1bf76490e62e4f53aab30019558f91fa278504002d008795f0acd1e7dcdc5aa76afaba1ba31c271aab32f7380000000000000000 +020000001e63ba192d148693cd116c484d602d5b60ae4210c9445d5d0000000000000000185caf72267125f82ea2a8d75690a6250d82739a18f8915a55cbe33a3da598d006284f53aab30019a7ee66ad26850400f8cbbfcbd9a5709910c5f79056ff83e9ffc43e03940a96730000000000000000 +0200000021b148c8fc7f61a8cbab86fa917d3c4c7f6183139b034931000000000000000045e0e292469d9911d8f01d97bb83338f1583625382305441dd9bebf694a80f2cad284f53aab30019c4744f67258504001e63ba192d148693cd116c484d602d5b60ae4210c9445d5d0000000000000000 +020000000248cf85be1d7ba49a2013dde6e2ebe3a8a46e342b16fa3d0000000000000000ee1f75947dff4dea62a76b6b0c60e2cc76cac26a2beacb6c5bc6788828dbbe7008254f53aab30019c9ea1c532485040021b148c8fc7f61a8cbab86fa917d3c4c7f6183139b0349310000000000000000 +0200000022a85507651f1129652a25b54a284d43d1d755ee2d2ce17900000000000000005f25d77d06d7fc6aa3bb0cec575a1b9d68ee120ca6a261e0057240eff8acaa78f4244f53aab30019b82048dc238504000248cf85be1d7ba49a2013dde6e2ebe3a8a46e342b16fa3d0000000000000000 +02000000e1c354e61233452e21c7d7419c9a31fbcb4ccc3c9bbf00600000000000000000a103278c41b8d1e5f9ac5f177005529b1f1ad2d56681ddc03003e07a3223ee1981244f53aab30019a95b4c9c2285040022a85507651f1129652a25b54a284d43d1d755ee2d2ce1790000000000000000 +02000000d44d7f012a71a227c5f500e88e67966b9e0f0e104f2af62d000000000000000046b84388d0c0786b398f7a116e480ed9b945989ae8482a2d5c62b39cb2177fa59f204f53aab3001935ab54c821850400e1c354e61233452e21c7d7419c9a31fbcb4ccc3c9bbf00600000000000000000 +0200000017aad17f4acfdc72e3bbd8e9fc885e3ccef06274e86c5b6a0000000000000000adeb010e3be1306361545949e16dfabad1275361550061801de0227470c383886e1b4f53aab30019b5b7442f20850400d44d7f012a71a227c5f500e88e67966b9e0f0e104f2af62d0000000000000000 +02000000981200022c790a6d2da10be7b2e3d057885c57706f9884100000000000000000a592635e157a7ac121e4ebe71a7e2d8baaa40ea06186997a961de473bcc0e43545194f53aab30019001cdd5a1f85040017aad17f4acfdc72e3bbd8e9fc885e3ccef06274e86c5b6a0000000000000000 +020000008c7c954557748ce467a11742bccf733e5df09aef26cb36080000000000000000677e01646e62e466d8a30870638848f5f2682935047da6ad9b5cbef55af050cdbb174f53aab300198d0c43221e850400981200022c790a6d2da10be7b2e3d057885c57706f9884100000000000000000 +02000000bf4c372f972fbd1beeb682bb5f84169fef89cfa1ff9b322700000000000000006d9a0c989b1532ca9ed7cef4ea8c738822604c276339bad06b828a8fd6ed2ce2f2164f53aab30019923733f91d8504008c7c954557748ce467a11742bccf733e5df09aef26cb36080000000000000000 +0200000054fc78066d71e469c398be1525587527302faab49f81979000000000000000004cc800d9ad02a4180a5ef6e96f13bad059c7a7fd7ae49a3e4c51d4d47963d4cfeb144f53aab30019831103731c850400bf4c372f972fbd1beeb682bb5f84169fef89cfa1ff9b32270000000000000000 +02000000739d7509fc558e59272558711e7560dd3eae023f1a3f484700000000000000008981569b49984136e549bfc2af562057b01e53f3977e472e8bf1152671d6c478d1144f53aab3001930a1a7ab1b85040054fc78066d71e469c398be1525587527302faab49f8197900000000000000000 +02000000a30f864fb3282f0bb533bfaf294fa71ea6d24e9a6a9bfd7d0000000000000000fc56b108ee29b70c7eff51f8ae8a31a76ecfde8cec57e32b90d29336bfe9835330114f53aab30019d28c826c1a850400739d7509fc558e59272558711e7560dd3eae023f1a3f48470000000000000000 +020000003bb767b1cccc63d26553d2ffac3bf8d38648b986e0f7c17f00000000000000004af836f012bf26e6cd03c6d844c7c321aeaadf571f1556fad1fd5f9ffc1c5628e70d4f53aab30019714eb9e219850400a30f864fb3282f0bb533bfaf294fa71ea6d24e9a6a9bfd7d0000000000000000 +0200000039a5d05c2762372ddafba55b2f57406cb3e6c874fc770e950000000000000000327ffc418d5daeec95a0bf5e07eb55171636e62ca9d539302d4fb7b4f79a38ecd90c4f53aab300196d1fd401188504003bb767b1cccc63d26553d2ffac3bf8d38648b986e0f7c17f0000000000000000 +020000003b9343ae2bad5197438df09269076707f328d806f3c0d51c00000000000000009fd2c1de375bdfb2ace14b0236a14bef281fd8d404944053a9fda7a4a376b44f2c0a4f53aab30019237a8cf11785040039a5d05c2762372ddafba55b2f57406cb3e6c874fc770e950000000000000000 +02000000a079768528b9ef1b365b313553337d5fc6e403fb5d3de73100000000000000003992d23c20b895f9fe71994bd03f885c13dff7141117dae130a5f7e3f6c0e45725094f53aab300194843f437168504003b9343ae2bad5197438df09269076707f328d806f3c0d51c0000000000000000 +02000000423eb273b0cf153fbe42b6cfe544f48f1c4991f6fc3562660000000000000000cfc95644d97a0e9e2f04b7139fa3ad891f9fc5ce7fc63ff95cf59430623950e369084f53aab300190c5670c415850400a079768528b9ef1b365b313553337d5fc6e403fb5d3de7310000000000000000 +02000000af28abbb9ed1305d9aaa0f6efeec9021b864480b5e7935a40000000000000000bda0ab7dd66310642e6b39f135f5303f1872e3b3b5d0ce4b1d7423af5e95dd150b004f53aab30019e5b545e214850400423eb273b0cf153fbe42b6cfe544f48f1c4991f6fc3562660000000000000000 +02000000331a74f700fefabfdf1f1093bd36576bb00d7c6d9dd8713a0000000000000000482eecd9a424899a7eb6ae85bc0b9d5519f5fb87739ea6c13f9668a40ef17a39e4fd4e53aab300196e5bce5c13850400af28abbb9ed1305d9aaa0f6efeec9021b864480b5e7935a40000000000000000 +02000000a0310d422e8a7f095ef38b461631a772eb8d1f9dc4bcae3a0000000000000000a6a4469562add71b42286a3033f27d8d027c186b1640735ae206536825b79b0152fd4e53aab300190b4a4cbd12850400331a74f700fefabfdf1f1093bd36576bb00d7c6d9dd8713a0000000000000000 +020000005cfeea1fc4b068f75b30b35edd263a8ffeb90778f6de1c20000000000000000069c107d2d425598fc8352a050d2827b6657e02a0eded7bca667b10910ad64a8f30fd4e53aab300192c5b667611850400a0310d422e8a7f095ef38b461631a772eb8d1f9dc4bcae3a0000000000000000 +02000000c495295ebb9eec9333aceb2c124cae92a3b43c3a4b60a28000000000000000008e189eeab9d432eb27d4a35fc84c538031f2397c8df52d6eb242be1c659c03e89efc4e53aab3001994c92316108504005cfeea1fc4b068f75b30b35edd263a8ffeb90778f6de1c200000000000000000 +02000000ad09610c309bd28cc9c9d036f6ece1797bf6f5be681394790000000000000000ae9e1173bd110c3c9abd91f8a078949e221b3bebbddb0070626aa061056ce29d6ef64e53aab3001963bc9bac0f850400c495295ebb9eec9333aceb2c124cae92a3b43c3a4b60a2800000000000000000 +02000000ebe9e64039e239a5c40498e886b0498d6903a8d364df89200000000000000000a15f0a52681c1bc1c7ad2fa1ef32047d60b3d07ae852fd1ea8b73acca9fb1fea51f64e53aab3001923e35fdc0e850400ad09610c309bd28cc9c9d036f6ece1797bf6f5be681394790000000000000000 diff --git a/BitcoinCashKit/Assets/Checkpoints/MainNet-last.checkpoint b/BitcoinCashKit/Assets/Checkpoints/MainNet-last.checkpoint new file mode 100644 index 00000000..c13d65bf --- /dev/null +++ b/BitcoinCashKit/Assets/Checkpoints/MainNet-last.checkpoint @@ -0,0 +1,147 @@ +0000002083c74a77c23ceca80748c7cf8a30ca32e8abe23ff3daf3020000000000000000987ab465e3d21159d140126d5ec7b85fdc08e33231d7e74eb2bbfff9ad5ccea95f2a326302c305188c4328bafd960b0058d882cd0fe28f21ab9eebcf00f4ecc8fcedf8bb290698040000000000000000 +00600020ca1e35b83ae0e8312cc4de7b46532971b84ab6dc02daed000000000000000000cedc357d694c688fa04a1be8003bcd1a632059c71633e0708cff3cd5175291db1929326324b80518485ae7d2fc960b0083c74a77c23ceca80748c7cf8a30ca32e8abe23ff3daf3020000000000000000 +0000c020d70408b323a83ff595c6e4b58d58fae63bd47102809acf030000000000000000b74c87f7cb9860395d3f42acc8dac9fcbca7fbb0de5258dd278d3a2601e263108a1f32639cbb05186ad6e438fb960b00ca1e35b83ae0e8312cc4de7b46532971b84ab6dc02daed000000000000000000 +0000c020dc24dc0a60718dc9553e09e1629c18ebc014f310f91897050000000000000000015d63fe0a28460e7005d0a013b4a4acaddfbada8f2e3451e617b6660d014dce811f326306bf05189818b863fa960b00d70408b323a83ff595c6e4b58d58fae63bd47102809acf030000000000000000 +0060c8278a984896f751245b1cedc053c9abc093a7f88815aef101000000000000000000dd47bc72a57b00d609491de07981bb7a60c4df2554a8034d8d70d0db1b1d0db06c1f32634cc00518415a4641f9960b00dc24dc0a60718dc9553e09e1629c18ebc014f310f91897050000000000000000 +00401429b2f9fe6b8a623eafec3f6d953e5cf6c6145775e264fc67020000000000000000533d7819647447daaecbf0876d39983e5f2c4e7c51aeb7421741ed6b977c1899eb1d3263cec305187aa4af4ef8960b008a984896f751245b1cedc053c9abc093a7f88815aef101000000000000000000 +00006020f02b682cee0176250d903ec980c768d0efb869641e166500000000000000000068e989055160711fc013388836da690f107cb714043506c535500fa3abd6532de71d3263eac605183038b365f7960b00b2f9fe6b8a623eafec3f6d953e5cf6c6145775e264fc67020000000000000000 +00004020cfeab0ae3e2f0d4735550d2c2e2f5b0072bdcb244a4a97000000000000000000ca2e00a44a7624426505e21e5a20e629ec2140a77f3d35b6441389f14265fe139b1d326354ca05181d21289ef6960b00f02b682cee0176250d903ec980c768d0efb869641e1665000000000000000000 +0040442729b91f64013f95c2a9e0cd33de68a30aa28f474f9ee4d4040000000000000000c2474961dd4addfb26ba6c904d6b6045586b22c1dd874da0391f35777eaed9e1841d326389cd0518070650f7f5960b00cfeab0ae3e2f0d4735550d2c2e2f5b0072bdcb244a4a97000000000000000000 +00000020cb58d89fc788af5bfd206165842a32ed9e6790d4738b0b0100000000000000008c60d2ec65ef4beb2a6f11ed5fc3abd932618c84a2cef18e4cda2e826a91da6c461d3263dfcc051885cac89af4960b0029b91f64013f95c2a9e0cd33de68a30aa28f474f9ee4d4040000000000000000 +0000002097c9957cdd634975f2889f5ad67658229bde09579d8082040000000000000000608a44c812dcc3d4a0f93630d2001878bbb4472897c9aae0aa4424999bded9637f1a3263e6ca0518fe7700cdf3960b00cb58d89fc788af5bfd206165842a32ed9e6790d4738b0b010000000000000000 +0000a0206bbdeceea8eac8bbccb26d8702356563c8017c88f4b6260200000000000000007130ca85b1f5f136c9630821d47d0096a8de0f779a55d74a6b8d190c6f64d8d4dd163263e5cd05182d432874f2960b0097c9957cdd634975f2889f5ad67658229bde09579d8082040000000000000000 +00203a23c722eeee9093549abce94c7a9c1ad840abbf4b0e97d11d000000000000000000efd87bb1615b0b30f34c0f28e9fa7041c212daab8961a46746857ff0938764637b16326310d1051865f10585f1960b006bbdeceea8eac8bbccb26d8702356563c8017c88f4b626020000000000000000 +0000402085e873680a1587a58201962e2dde7db03c076479349db603000000000000000046b831c5f96768ef4ef8f17d2867f7306c9b3468cda403407c666faee05d82a3371632637ccb05182b46d6c2f0960b00c722eeee9093549abce94c7a9c1ad840abbf4b0e97d11d000000000000000000 +00c01a2d5feffdccfd7501691673cfac99186cab2ab21e68d2d8050000000000000000001b4e847956572e5b8393560189b3be764659098cc5e51977b3a57180d77bdaa737103263c7cc051850d062d5ef960b0085e873680a1587a58201962e2dde7db03c076479349db6030000000000000000 +00007026a50ce971419a2e638b75b318349b4a1c4e6063e9751de2040000000000000000cb3d1e28a0c0a1011b379a5a405f29ce031aecec04b492065b4740b0331b0373b50e326313d0051879a2d180ee960b005feffdccfd7501691673cfac99186cab2ab21e68d2d805000000000000000000 +00e0ff2f00468d5e03b8ef42d1c0d41f239cfcc12a79869d8570550000000000000000004018723eb7205a7eb087cb2f59ab206438eb71b992caffd2516d3154d035a2038b0e326397d005188e9bcf10ed960b00a50ce971419a2e638b75b318349b4a1c4e6063e9751de2040000000000000000 +00008020631f0fb64a66f927549ad9e34d334e21185087447ac08f03000000000000000038675f53b03928f503c698ebdad5d33086f5e43730a24a0ce3ecdeb5612a3a8d860c3263f8c9051868db2dceec960b0000468d5e03b8ef42d1c0d41f239cfcc12a79869d857055000000000000000000 +00e0ff3fcee11541a0d7fc72d4163cb9c82491bbdebd249f9c496e0000000000000000000259611a17a9a8c370b6a4ed93542e3a846905c2f74f10f87e54f9fe34581500d505326351cb051887638df1eb960b00631f0fb64a66f927549ad9e34d334e21185087447ac08f030000000000000000 +000000207ecc9b01358fd1e9927ec26fd1e15faf0e91bc2efa881e05000000000000000030ceec472bf8166a6cde9828646f4dbe0e509358e550625b0bab42f174f764c162043263eec90518e3cd6e11ea960b00cee11541a0d7fc72d4163cb9c82491bbdebd249f9c496e000000000000000000 +00a05b30d70a4d9d1de84d2f3b2b78ea596ef50589ed5b417150ad0400000000000000007273e9968b67d829d86bc8bdadda0c9829af34f4ad128f05ccc50c3193cbd2d22001326392c90518076092bae9960b007ecc9b01358fd1e9927ec26fd1e15faf0e91bc2efa881e050000000000000000 +0000002091d38739c283957693e367bf253ba60d4b09ada2e62ab4000000000000000000d011fefaf73948c3d3fb263064994c26710fbcce164e1434b86d555e29b84a848cfe316330cc051874ee1454e8960b00d70a4d9d1de84d2f3b2b78ea596ef50589ed5b417150ad040000000000000000 +00a0b7342eaa503a22f873d43c5449375c3fac3de790f6120c908c010000000000000000e7828632e23faef77bdf0cb02a6d95e0a513a22b40730d5615574ac094a5e5aeecfd316341ce05180ac9c42de7960b0091d38739c283957693e367bf253ba60d4b09ada2e62ab4000000000000000000 +00e0bb2755cd636145349760f7301ebbadc42d856f6347b3ba4226030000000000000000b33f3e3a86d32f9942d859566d9b7fdb47b02f9f84c5d3a44000741fe2773b64f1fc3163d8ce05184f91cfc7e6960b002eaa503a22f873d43c5449375c3fac3de790f6120c908c010000000000000000 +0000e02014ffc88c52d0dd7313eadfd8a15ee627974f8cd8decafa040000000000000000b5e8366179ba6851f85c8d925f63755ec71b836855954e974a4e544886a14457fbfa3163bdd005187f80537be5960b0055cd636145349760f7301ebbadc42d856f6347b3ba4226030000000000000000 +000040205f336072b7473dcf9b97dec52ae40261777168c49f062102000000000000000004843961f13d30dea96d2a4b6f3d4a790d639121c60b2fc8674d5e2aebd54b58e0f931636ecf05182de0315ce4960b0014ffc88c52d0dd7313eadfd8a15ee627974f8cd8decafa040000000000000000 +00006020322b7ef0bbebd61ccc0b8af3d572c6514d37e0a4ed124903000000000000000024fcd8cc368c439572240f3ebccf77b37f89cc77c5c52881ff437727b522d713adf63163d3ce05181b106eb9e3960b005f336072b7473dcf9b97dec52ae40261777168c49f0621020000000000000000 +00004020307bfa553585c67db23b575fadacc4f633be0567100b9e000000000000000000e43f039e579e6125498ccc09aa328ff0fea15a184b4202d6d9d3a25f26a12bf7eff331634ad1051890c64f3be2960b00322b7ef0bbebd61ccc0b8af3d572c6514d37e0a4ed1249030000000000000000 +0040e520a4d34f4aa9141845a71897c54f3cce64b29f38e6f0a59a04000000000000000003d94ccfa62271c63ab845004e00b175a7298da38181c087aaf4307523a9865f36f3316362c90518291e387ae1960b00307bfa553585c67db23b575fadacc4f633be0567100b9e000000000000000000 +000000206105be0c53017e6156520c477fc70ac80b361a9a1d5a1d040000000000000000f467a94d19176432fce5cfff7d3366719e6504a20983d783b5cd34de6a7e27c9aaeb316314c90518930f662de0960b00a4d34f4aa9141845a71897c54f3cce64b29f38e6f0a59a040000000000000000 +0000c020ddda2c556f8ec1f0ba13a067d8b9655190766dba017cc003000000000000000076321521ef662839215769266efae6a630188197a64795029991ecf595b54b6120e9316391c8051800b784badf960b006105be0c53017e6156520c477fc70ac80b361a9a1d5a1d040000000000000000 +00e00020f87d4a30abb031b4c2abadb224517cadd746feeac81f9d020000000000000000f13655f3d7a5e7dabd152555f3a64c0c8b53424870f32b81752e857fc214856f73e63163a3cb05187f0a3e69de960b00ddda2c556f8ec1f0ba13a067d8b9655190766dba017cc0030000000000000000 +000040200a74f3ba65684ee4211a70e30aaed805264f408d2c33c6030000000000000000d238ebf93da4ea5721932560b9598abcddc1c51d8b6585d345c24125c5617d4d20e631638bc40518ada2132add960b00f87d4a30abb031b4c2abadb224517cadd746feeac81f9d020000000000000000 +00e00020b1e9342e5590172426ec7d1b8a3613b0742569b137668e0400000000000000000eab06941a90c9ebecc0ecac73c9d6723fcf8d28e6a0be92b751d7b0249969a11bdf3163d2c60518294c116adc960b000a74f3ba65684ee4211a70e30aaed805264f408d2c33c6030000000000000000 +00008020ae5905e64e341e3bc53b0877d57ee26cb9230d66337581050000000000000000ad895f3dee0426d4f7a483d298c6593a41492fb79d92196133b21590053d6f5443de31631ac3051811702452db960b00b1e9342e5590172426ec7d1b8a3613b0742569b137668e040000000000000000 +00000020bcbd93622a9dc2bec6fcfc4724cb5a1b200024d621ca57000000000000000000d3c486bc797949d89e6f61c942cce273713d81456426f6a429de3556aa0b1ea176d93163adc005183a4fedc1da960b00ae5905e64e341e3bc53b0877d57ee26cb9230d66337581050000000000000000 +0000e020e6ccdda9551508a4e79ff8cd933bd930c8595877b59c35050000000000000000497ee47c711d837f41c5862cd552294dfd09932fa6a58f8a2196d05ebd247ce286d5316386b5051887a04bf9d9960b00bcbd93622a9dc2bec6fcfc4724cb5a1b200024d621ca57000000000000000000 +00000020e2c8ed70fae31c4827b1a9b58424b39877c5267057c29c030000000000000000d57785b6c30cd4398c38b8f85ec3cb976d1c1a5b00e3c2a3766a0fa85a172af3c3cb316365b60518d40d62acd8960b00e6ccdda9551508a4e79ff8cd933bd930c8595877b59c35050000000000000000 +00e0ff375d1560dd9d6df4f28b43a12ea9f55412ac5986af7b1673040000000000000000148992475820b2ed3cb01d6f3ad4dea92fc6f49247121d6895b82384fd3583fd01ca3163a4b60518b7ee3a76d7960b00e2c8ed70fae31c4827b1a9b58424b39877c5267057c29c030000000000000000 +00000020f3e01e94cd49d9dc8a091f773b033e743eb3119ed2dfdd010000000000000000a2710476bd40dbb745d95a4cf7938feab7b2546db5c24f02ca8e56918fe18404d4c731634cb50518b8d83d9fd6960b005d1560dd9d6df4f28b43a12ea9f55412ac5986af7b1673040000000000000000 +00e0ff3f158bb8dc7e5e3c12c4f8873ff9ab79bd85189389056ade0400000000000000006592c29c065d39e0f3441c503c5814c4d1517579bbe6745445a67360c3f50ae892c4316311b50518192e4827d5960b00f3e01e94cd49d9dc8a091f773b033e743eb3119ed2dfdd010000000000000000 +00e0ff376951043915f322bf12ea9e22e948782100000bb20de7f2020000000000000000d75ab3e7f30fb8baa10a1e55fb9ac11ac744a644e6d435bd38394d189f53b7c316c23163ffb10518978b5141d4960b00158bb8dc7e5e3c12c4f8873ff9ab79bd85189389056ade040000000000000000 +00e0ff27e46354b5fc0a6d6c92dac93ce83286aac1ae4f77d5adea040000000000000000aa47a01b06673960d89950c170882bd7f1b9688754fc47446fe61fe6d5554133aebd3163c2b3051884471632d3960b006951043915f322bf12ea9e22e948782100000bb20de7f2020000000000000000 +00e00020756a072d0f38cb1a2ff8cf8f2521c88a4d305f72a672d8010000000000000000702160bb17d36b68fcf9afd864433dc2db3ec96a070c3cbdbfaf563b523f958b86bc3163aeb6051860ccee75d2960b00e46354b5fc0a6d6c92dac93ce83286aac1ae4f77d5adea040000000000000000 +0000c0208a62fe9ddc1bedd388d245e2bc85b4a4d8d0cd44a224340100000000000000003ace6f3284a63ff1405e0c65d9b5d6d5529296113f16cc3252d6f7d777bdd56b21bc3163e7b9051818f2c2ddd1960b00756a072d0f38cb1a2ff8cf8f2521c88a4d305f72a672d8010000000000000000 +000040206fc4ae7fdebdec71646c8cc7fdbc34371fa1cff3e6d6310500000000000000007690bb144406a82bb2c4c81c61838c545f9df088ab0c4fef1fcc0c48054e1a67efbb316371bb0518ac19a9aed0960b008a62fe9ddc1bedd388d245e2bc85b4a4d8d0cd44a22434010000000000000000 +00a05e3173b2fddaf7b52b387d10b9b07942b2e3c81b285260ba580500000000000000009d8d3250fa25f5d569d5826e0ff06514ef7a0959792f7e079121f2617018689f9cba3163dbbe0518deb9176dcf960b006fc4ae7fdebdec71646c8cc7fdbc34371fa1cff3e6d631050000000000000000 +00006020c3a21c66a47a55afe826b282177c0760c63bbadd6627730400000000000000007aaa0f233671faef784684e941d398ade648ed7f88fc4a1718b38b213ea26ca285ba31632ac0051805d8ea3dce960b0073b2fddaf7b52b387d10b9b07942b2e3c81b285260ba58050000000000000000 +00006020ced43cdf4a77e0317b32f42d96b8cc77f0b6b9fdd81829050000000000000000798b3fad78cc671c6a2d402c6c2916eefcfcdcc0ff89aa52f402398f25bb202b0eb931638fc3051876b70bfdcd960b00c3a21c66a47a55afe826b282177c0760c63bbadd662773040000000000000000 +0080342bc5976dc0b986a8de1122b311f162b861d2d03847ad79940200000000000000003203c507eab98bfc4c14be6827887834da0a1764f26df2f764f51b33343b721ef4b831631dc50518212f2822cc960b00ced43cdf4a77e0317b32f42d96b8cc77f0b6b9fdd81829050000000000000000 +000038337f1ed671f148b6e2d09cfa8b782ed281e2b7f7d1d64bca040000000000000000b601f5aa62733e73aa7e61cbbd960a061bc5ce532481f28a1cb29d2174cfd14fa2b731632ab90518575dfe36cb960b00c5976dc0b986a8de1122b311f162b861d2d03847ad7994020000000000000000 +00a0c82d6473e898066bb810b74d23e2cc7e1f3b1584c4908cb13c050000000000000000e11b41a150c886dac28cb20bb0b09c71157dcfeade07770e9338c86376694b8062ad3163a3bc05187848d7fdca960b007f1ed671f148b6e2d09cfa8b782ed281e2b7f7d1d64bca040000000000000000 +00000020846b4fad2b51228ef4eca81318c206d31144b9102b34ca03000000000000000069216d8aa6a355921445f846a3d0eae0da345c7141c96a4c1506faeb4b505bfb57ad31630dc005188fc5d2cfc9960b006473e898066bb810b74d23e2cc7e1f3b1584c4908cb13c050000000000000000 +000040202b2741ec02d7371fe065d7b45adbe1a631a5d1a000abd8010000000000000000ece8dea85c3bb4a8addc60235f311bf74a2066314f642989cf3aad0042dc9ff440ad31634ac20518364ca052c8960b00846b4fad2b51228ef4eca81318c206d31144b9102b34ca030000000000000000 +0000a0201cfaad9859544d66f0f236ad2ef350e6e14a20124d48010000000000000000005b8f49c169188ad816c520c33afc4699d57b6a919de75c7ff4e9003975fd1e6766ac3163e8c105188e0aaedbc7960b002b2741ec02d7371fe065d7b45adbe1a631a5d1a000abd8010000000000000000 +00000020071ea24f25723ad2db7afe8c57ffb8e2241361354a851f040000000000000000d66cbe6f7440d05c6ac60254ee0d71c54b1ec634411e91e6b63b130703b45570cea9316322c50518210a94a1c6960b001cfaad9859544d66f0f236ad2ef350e6e14a20124d4801000000000000000000 +00e0742be6e851a4d337e365f90f7e569bad74cb5cb9596034abdc0300000000000000002698c8a3e27f2f5df907aed9690fb59cb23ebf9df905fb2164659e620bb131c697a931637cc705182f803f49c5960b00071ea24f25723ad2db7afe8c57ffb8e2241361354a851f040000000000000000 +0060da25e00d0389e5eb867caab96ce1740b18033cd15d1074e213050000000000000000771a946034fe07119560f3f617e412705a83b82b1f593a6ac43a3ed635a81e04cba83163edc8051859a5eb24c4960b00e6e851a4d337e365f90f7e569bad74cb5cb9596034abdc030000000000000000 +00000020f367cbf843d8601f0bde9cc2fee11f8abb0b1040917f7b050000000000000000d5ac76c011b672fa914753a3a0746390dc154969e2b97c8dd2b6ac02edb1e9a668a7316362c90518d15d6c78c3960b00e00d0389e5eb867caab96ce1740b18033cd15d1074e213050000000000000000 +00c0f430faefcce8093613d764a56359e8ecda1ad178b03e27f1dc0100000000000000003c4c3baf7d29032c5183a5833c3ec43d7911312fa8375f882f0f194551693e315ba53163decb05182cd20c60c2960b00f367cbf843d8601f0bde9cc2fee11f8abb0b1040917f7b050000000000000000 +0000202098e2fa28f8634e3b72ce5555d7ee01ef4ea18617f1ef7d0000000000000000003035017772e2777ca8e299ebfc764810059296d8670c2f4e34180bd5d746f2a0a6a43163fac30518341a03dfc1960b00faefcce8093613d764a56359e8ecda1ad178b03e27f1dc010000000000000000 +0040182842a0a29fbfdab5f9708fa5df293912184a397f42c5006c030000000000000000221bee5741068fed823e17177b6e9efa455301d04403d8bfdbfbadcde025c7b2199d3163e1c60518566278cfc0960b0098e2fa28f8634e3b72ce5555d7ee01ef4ea18617f1ef7d000000000000000000 +00c0182f2f95a4aec2bbdf505797b32363f3d4bb2827ddc475cb8802000000000000000076c0a38228d3560d972c8d51cf9de9282ce4191f199d59325c08f01b9c84a994ad9c316362c9051831ee9b09bf960b0042a0a29fbfdab5f9708fa5df293912184a397f42c5006c030000000000000000 +000080208636c77ae6dc182ac18c0fb23006d7c2995194949e2aa202000000000000000034c5afbd9c9fdb84bfe9a180b597258557cd30057a1a8b313d63719bf1b5da7ffd9b31631bc005186601d156be960b002f95a4aec2bbdf505797b32363f3d4bb2827ddc475cb88020000000000000000 +0000402059fac0cb8655280728676e0da14ae7702b7609600afdda030000000000000000b718fed57c058afa36fca1c76e34f964f46184a4ee534a11ed68829e76697214839331632fc00518956c4d1bbd960b008636c77ae6dc182ac18c0fb23006d7c2995194949e2aa2020000000000000000 +00203627686e2c8df03c4ba81e6957e3f561dd65a34a0dcee61551000000000000000000267accf48ac27fa1bda14401ca14e89af26d180d777a1cd4f2848d8a50b66dfd3a9131636bbe0518003110d2bc960b0059fac0cb8655280728676e0da14ae7702b7609600afdda030000000000000000 +00000020f3cea79e5d7eb4247db22109553c98cd8b3a0263d08ce40000000000000000007c67e3f1ae8582f57a4237406a943f0f94d2a4effa6db55dee7c8fbc84ee81d7b58d3163e1bf05182664de6abb960b00686e2c8df03c4ba81e6957e3f561dd65a34a0dcee61551000000000000000000 +0000c0201461c9fa6cedc26cac29912ae7d887c7349ccd8f219c600400000000000000001b590999e5f5f88b034710781bf5868aed3f6d8465dcd7de415c648bf78ecea6538c3163a3bc0518d2106883ba960b00f3cea79e5d7eb4247db22109553c98cd8b3a0263d08ce4000000000000000000 +00008020019386dbb890b5535d78b3d8b6be89ef5a740e118595840300000000000000009fe5fe506122ae5e52ac240141f96fb0da875ede2393288c02a302d9941671aad78731637ebe051878fed23bb9960b001461c9fa6cedc26cac29912ae7d887c7349ccd8f219c60040000000000000000 +0000c02097253bc7fe6f01146774438bc325d2a96a2e4159b33c2b040000000000000000bb3e59c03921a3228ddfa4b91712a6bf3c0cb6a1cbaf0288a036ace8ea2e5dc0b9863163adc005186ea95fbbb8960b00019386dbb890b5535d78b3d8b6be89ef5a740e11859584030000000000000000 +0000c020c7f8d0d9938249fc0a7972d116b75278ef7df0e8e7e42b0000000000000000004e785f14aa3f13f42fc95c6c425a17d52aefecbe3c146260da1e2cc3f1e3d901d58531634eb3051843102b27b7960b0097253bc7fe6f01146774438bc325d2a96a2e4159b33c2b040000000000000000 +0000c020166a83e1756fac5eac5d502206cf154f42aa5b8802ba370100000000000000000b87c4c92e478809c637571690d185c98a1697513742523b9661518f8bd91a4f967a3163f9b405187c3aa282b6960b00c7f8d0d9938249fc0a7972d116b75278ef7df0e8e7e42b000000000000000000 +00a000203611ec971f68201b536548c405e358b7f2a2e93f7b925c00000000000000000012d095acb70c033abe070ebb6a22f2ff52114c269c76750f21b46ca0d521e1095f7931635eb40518c917768ab5960b00166a83e1756fac5eac5d502206cf154f42aa5b8802ba37010000000000000000 +0000c020f92872db4b1da7f6568c22199e21286ad05240c4c09f25000000000000000000065f04aa75fd2e0539ebb0c9a2a1e5339ab866877edb0111130c9aa5bf8ab2c49c76316349b70518890b4c66b4960b003611ec971f68201b536548c405e358b7f2a2e93f7b925c000000000000000000 +00006020580197327725d7986b8faa0ee86b4215e3ef5b96f215ad05000000000000000005496c54bab114e73397348ad8fc8277b330607ba2c7a2bde6716db3cc25de0838763163c2ba0518617cb73bb3960b00f92872db4b1da7f6568c22199e21286ad05240c4c09f25000000000000000000 +00006020f5e80d8ffc4bba1e25b5625bd2c99009304cac4c7339fe030000000000000000d1d7bb40a6bde710c55a2737abe81fe5ff84d45347db1910df8568530f1fb9ec2e76316300be0518893d1854b2960b00580197327725d7986b8faa0ee86b4215e3ef5b96f215ad050000000000000000 +0000e02080e37bb5a46a99fb266d802d47389d7065655dfd35d3dd040000000000000000f31f2837d66bd796466742569ee4e2a1151400afcae6ce8a3fb6e02edb3d1281fd753163b5bf0518557c1fb4b1960b00f5e80d8ffc4bba1e25b5625bd2c99009304cac4c7339fe030000000000000000 +0000e0206016291ad6740928fb7bc5f6751e4c85b6a95aa915d7c702000000000000000029be2f7aa2a03f883277ff0acc83eb4521d6382ddee3b40a76790d2cb3970e38ca74316336be051869dcf300b0960b0080e37bb5a46a99fb266d802d47389d7065655dfd35d3dd040000000000000000 +00e0b92bf0148f3c1d27f22994f751e6905d834cab48dfce24eca00500000000000000005b8e2cb72efcd7cf84a5de91a114a4cd6a47ec66ffb9236c2ba228ee05e6f49373713163e4be05181fecc50daf960b006016291ad6740928fb7bc5f6751e4c85b6a95aa915d7c7020000000000000000 +0000ff3f7c9350b5bab1f670be95ac4bec6865f1f1caa1e6bfbc0d02000000000000000059ec52862ba55d434b80ce8e5b678045fa745819a5911dda34e340b502bc9a8a8e6f31637ec1051881e1131cae960b00f0148f3c1d27f22994f751e6905d834cab48dfce24eca0050000000000000000 +0000952c361d619914a0f65e5303d3f005a2f29411898f7b2505ee00000000000000000051b9cf8bb43a019db4236172839b1ace25e0f7dc991186cf47c9e8a0013b7228ef6e31636ec405184f00d6d0ad960b007c9350b5bab1f670be95ac4bec6865f1f1caa1e6bfbc0d020000000000000000 +000040205bb10e4d02b8fe3445df70f670ad3e5f2f52996b558bf402000000000000000095a7b710b6f105fcc54fe8b16679cf6cc1df5774223c6342504d0123e8cbf1978a6e316350c705181fcb6561ac960b00361d619914a0f65e5303d3f005a2f29411898f7b2505ee000000000000000000 +00e068258c9112924e033ed6ea08f00e7b63b356576e0ccfc5b1ca00000000000000000058fafa6a78cf78a2903e0f6c9a19017bffc6832b6a7cf22c7042ac4ac5546c7e176e3163f7c8051822e0087dab960b005bb10e4d02b8fe3445df70f670ad3e5f2f52996b558bf4020000000000000000 +00600520de5af749e5cfc635df4946d4c999bdba27719b65c7092a030000000000000000ee80747688014f72f9e9cd55052d1cbe87caab37488c0f2c022f9a8c0931d29ad66c316395cb05185a9715adaa960b008c9112924e033ed6ea08f00e7b63b356576e0ccfc5b1ca000000000000000000 +00802d207e7cf269f1309ef4e4cd1d69fc9ec9c0bd813301f56b9f030000000000000000e8c47cde35ab07ceba6d1394d8205223e98c866851b9e13f5f1bb5b048e3dc72376c31639ecb0518421878fda9960b00de5af749e5cfc635df4946d4c999bdba27719b65c7092a030000000000000000 +0000622c69b2e2e21599b636eae0766b0a722ad2ddbc9f32b0fecd050000000000000000cdbe9fc5c289f7020b67961f8d3664f3511ed9be714c02db45ffcc6c4cfbb3ffe269316375cd0518218512fea8960b007e7cf269f1309ef4e4cd1d69fc9ec9c0bd813301f56b9f030000000000000000 +00e07b263599e841b563e1dd983b63d3b42aa4cea0b1b731b8f61f020000000000000000cdbf71d6d22a2357a42c8315ec93fd7a5bdcf079dd4007fcaac4a5ddb2804dd0c1683163aad005183b706c2aa7960b0069b2e2e21599b636eae0766b0a722ad2ddbc9f32b0fecd050000000000000000 +00008020a5639d7431c31ddad3e7a9f3e4dc682d8fbb2aa2e1b73d040000000000000000fd67647136c7662dbdc00b969b4572beb07fc688fbdbe99cf17a8b350cb66b9284683163b8cc0518574f8001a6960b003599e841b563e1dd983b63d3b42aa4cea0b1b731b8f61f020000000000000000 +00a09f235ad5a8dc5058a2592b177d028eb60b88df9708c96f01a102000000000000000036078f218f896409a643b22fc38c46b9268bac0454247082a60ce2a0ab52f1a6976331632acf051827e2fa14a5960b00a5639d7431c31ddad3e7a9f3e4dc682d8fbb2aa2e1b73d040000000000000000 +00000120132f6e0f0866e1167a5a64526b959ea1d76232b6f93d5004000000000000000097fc04adb46be309b6f6ce6dc5b255a8cd6ea0660c061b4cd07f6a8d48289dc3d662316397d00518247330faa4960b005ad5a8dc5058a2592b177d028eb60b88df9708c96f01a1020000000000000000 +0000ff3f1ef75924de3244dd73f854900a571e5a541df557b2f7af010000000000000000c7c23a4eedd04335dd5cac6d6c74b3e015f9cfbf29472be6d8a33466f345b9487061316388d00518c9e3b192a3960b00132f6e0f0866e1167a5a64526b959ea1d76232b6f93d50040000000000000000 +00a00020ded4c02a01672599f9f47b232f662d2800917ff67cb2720400000000000000008b7bf0565197a1d1646ff109bdc0c26382b0f89e0dc0ed41110e8790ef6aa784105f3163afc905189651a2aea2960b001ef75924de3244dd73f854900a571e5a541df557b2f7af010000000000000000 +000080207252b34be9ad005f240aa3c2e428544099de1e4429fd48010000000000000000eb9d6b5b3b31f1eb6280edb5584b25186d83e130de36cee848feb0dfb1c508353658316371ca0518004bb8cfa1960b00ded4c02a01672599f9f47b232f662d2800917ff67cb272040000000000000000 +0000ff3fa2aed8ec66b47793fa4cd140f0a9e6a3373dea7f94c0ba0400000000000000003627875755c3ae813297349a26b9030a56cfe3c9c7e105fa7263a93b596791015f563163f9cd051892378a2ca0960b007252b34be9ad005f240aa3c2e428544099de1e4429fd48010000000000000000 +00c04c2cc57d1f8c71d5c5b299d1e2be5f35fae385c8c5a632a0340100000000000000009e85325feea2c5fcf728e25709f3a26d8eeb6d0be2a1d885e1321f2281f5158a5656316332c6051804e56e7e9f960b00a2aed8ec66b47793fa4cd140f0a9e6a3373dea7f94c0ba040000000000000000 +0000002019bf3d399624da798eacb13e940c622a8e01ce6f61c05f0200000000000000006d077e3d9c75702ab9994894fa408acac5159cdd2a9866379092d0f83335f707e24e3163aac905183051b5819e960b00c57d1f8c71d5c5b299d1e2be5f35fae385c8c5a632a034010000000000000000 +00000020b46a14193cbcd7789932ac3c138f292adb6ac17f4dcef90400000000000000008f4a0a78b95144d7427a375295467509c8dc7c0d69432f4820550bc76ee89846d34e3163aac90518b884ad3d9d960b0019bf3d399624da798eacb13e940c622a8e01ce6f61c05f020000000000000000 +0060803361f55ba2b7b842c5b65f8dbd00f4c30233d10c18af3a980000000000000000008f5c8c9fe1db378360a53665181bf0720b072f9e91756f2f314e86fd15ccc2597d4c31633fcc051802117cd69c960b00b46a14193cbcd7789932ac3c138f292adb6ac17f4dcef9040000000000000000 +004036291024bead335049d5503489e9c5ad1b59adcda875ed11ce020000000000000000cd87df9b069d5baccd4150a4fceed57b42ef9c04c6eaec0be4c13f6e8225b390d44b31639ccd0518309271219b960b0061f55ba2b7b842c5b65f8dbd00f4c30233d10c18af3a98000000000000000000 +000029243a6eea26391ace1f1a1a08dfdd47e4516d1a02174ab7d8000000000000000000f8060d43860a46fdd9ae27e9d1d35aa7171f61cfc277518ae390a34b1eebaf3d614a3163d1d0051810d5b37c9a960b001024bead335049d5503489e9c5ad1b59adcda875ed11ce020000000000000000 +00004020e11359a47cc4fd182c122a97cb1bf52a927055e9f97d410500000000000000001aed69fb3722197161dddcf4879fc48f2f1939dab38f5d76bbc037aac72890d5244a31637cd2051849d3feb699960b003a6eea26391ace1f1a1a08dfdd47e4516d1a02174ab7d8000000000000000000 +0000002087c9f312e958160bc9252b36de26c344b23d2e6380a1db03000000000000000079fd78838e9da7c00f1144201faf878041a47c2b6f973d823de061752d046202e6483163cbd3051838c110a598960b00e11359a47cc4fd182c122a97cb1bf52a927055e9f97d41050000000000000000 +0020002059e6a5db580c6097854a487c98da7b659e5197562f18e20000000000000000009ef2b15c97b51c1bb869e8f93f6f9e005b319cfe9313fd9c7827f135bd4ebf0a6a4731635ed50518533b12f197960b0087c9f312e958160bc9252b36de26c344b23d2e6380a1db030000000000000000 +00a074225742b42e90982fc03a1021a2556813fbb1035805daf4170000000000000000009c8641681de62e3dfe1a79ca30a2d3b4d31eae8a95a0b3528f61a0ee2884a50019463163a3d605181ed19c5196960b0059e6a5db580c6097854a487c98da7b659e5197562f18e2000000000000000000 +00400020825ae05832eccf48c521fede6068fe72d9c6aa597576b601000000000000000018d4cc68e04ad4db293d68c3640f33ef89005751ea427a0ffddfafaec7d1ff299344316324d90518fa8594ae95960b005742b42e90982fc03a1021a2556813fbb1035805daf417000000000000000000 +00e0bd208b79116ee739747d01cc8d2941e2a9a7a185683c4b7fda03000000000000000033196be6fd64805fbecbb7d61b3dd9969bf3457cc0aecdecd1725c44ede61696df43316336d8051866e6c36894960b00825ae05832eccf48c521fede6068fe72d9c6aa597576b6010000000000000000 +00008020e5fe5adbc02e5cb5d02e7e4f35608cba6575bd82384d58050000000000000000e4762a95dcfdf76c8e9364c6652b88528c5beeb5fe222f8cbbf21c5f9d9eb5b9eb40316363d50518cc3234aa93960b008b79116ee739747d01cc8d2941e2a9a7a185683c4b7fda030000000000000000 +0000602032520e380b2bb2be0a2f68da0ba9ca92e91ee39829c1190100000000000000006ae370b1ae9ed15b774691eabfe8447e1029164edfb1a32a7550dcac58bc1434ba3c31638dd7051817d2de2e92960b00e5fe5adbc02e5cb5d02e7e4f35608cba6575bd82384d58050000000000000000 +00004020191a9872fd4dcd372850cfb413898f5d42a1b59611af5d010000000000000000b4b61d31150a0db7bfb605c6d60b256f538a8981744c3cfa754fcfbab175e8cacd3b3163a6d1051858b4ea7e91960b0032520e380b2bb2be0a2f68da0ba9ca92e91ee39829c119010000000000000000 +0000002005fe57e05cfa17ae50737f5a7e4e4514ad2f073f4dd9690000000000000000009637a28dbad47c5fe9c2051fbd08c66630e23f0770dc77ab10408a1e69ac938a97353163c6d305189dc3d6e690960b00191a9872fd4dcd372850cfb413898f5d42a1b59611af5d010000000000000000 +00e0232b0d3ae5feee1b17a8f855fbc237d977c5ad8679b0f9fffa040000000000000000c99fcb8dd9ebe2176ea63fc36d0da752e48f493592bb7145988bedbe325343cfa434316312cb05181fa967348f960b0005fe57e05cfa17ae50737f5a7e4e4514ad2f073f4dd969000000000000000000 +0000202054c1a8ca1e21c1d37f679d64494138b2ecbc17aa271e3a030000000000000000d40cb1ad8ea5273d7b1cd1200391ebc558fc8ce67eed5d82ebebbe3b1acf773e972c316323cd0518289393a08e960b000d3ae5feee1b17a8f855fbc237d977c5ad8679b0f9fffa040000000000000000 +00a09e31f36fe3a26f7cc7e63fd842e5f1042e2d57abfaed3773e3000000000000000000daa0d2fb3447ae3bd99f42977b551a86e335032150d7f482249a45f5196b1ea69b2b31633cca05181fec28948d960b0054c1a8ca1e21c1d37f679d64494138b2ecbc17aa271e3a030000000000000000 +0000002098dd086cccacd1efc2c6ff7024f070ac17811421bf6012000000000000000000d0b851f453255a60016fb502b7117f58f4d32310455c378845e1da872a6e85d65c27316348cc05183b2e998a8c960b00f36fe3a26f7cc7e63fd842e5f1042e2d57abfaed3773e3000000000000000000 +00e03b24f402bb7b0980db89b6b5bfea0901e8cc351d4f3003bf830300000000000000001e654708f1687ebf66601690eafe50618b3e3eb9186cdea143fd9c251ee53a7c5c263163bbce05187b4d3b388b960b0098dd086cccacd1efc2c6ff7024f070ac17811421bf6012000000000000000000 +00008020451e3e06ca29734a15f340774a922ff7b3854f9d0276a201000000000000000088f7379f6080d46fde5216d1aeabb7c5928aae6fdd7cde665596262d0f766a5ea02531636dce051849288f278a960b00f402bb7b0980db89b6b5bfea0901e8cc351d4f3003bf83030000000000000000 +0060462730c971c6e5507a30f30c28e44be65b487bd3ba2751df85010000000000000000b5575d8d2c6963e69e0ba70a111caecf0aa1dd35e7146267a99ed29014d3b1c513233163b9d005181f38f0ef89960b00451e3e06ca29734a15f340774a922ff7b3854f9d0276a2010000000000000000 +00207529091be57cc1e50490a4b1b299890ffe9e86bc155761852b0400000000000000001c2d6637d05ee0b062abecc73af3463d3c4b38f837a5c251a2ef388b1ea19ef83d223163dfd0051838452e1a88960b0030c971c6e5507a30f30c28e44be65b487bd3ba2751df85010000000000000000 +000000205791182e7118d290757ca91604fd52090996fba4d553db030000000000000000bd59b71ad1dcf892f61135347fc4049fe801d22ba490220b3ee55c5666e269cdff1f3163b0bb051866ce118d87960b00091be57cc1e50490a4b1b299890ffe9e86bc155761852b040000000000000000 +00004020fd7187c4bd75b47b4ab53ad981d31e7d1ed4c29e849a49040000000000000000b4e45cd1663159976fc80571870f3a47677e9e36f682656df113a516b196ca1eaf0f31638dbe05189074c30686960b005791182e7118d290757ca91604fd52090996fba4d553db030000000000000000 +00c0f5203963927ac4c2ab8f58447df22aa9b1bfa4ebcd1da3937f020000000000000000af7583d67121f8cb0d4138b83012ef5e0d91c5a7dde241413171db0a66fcfe683d0f3163cbc105181a7ce6e485960b00fd7187c4bd75b47b4ab53ad981d31e7d1ed4c29e849a49040000000000000000 +00209229aba9abb87a68f740757466104dc04a494e80f98d780b3502000000000000000047daf92a980b3175d83a35a5974ba9b8af3829bfed401241b35311b832aff01e0a0f3163b2c4051831e57d2584960b003963927ac4c2ab8f58447df22aa9b1bfa4ebcd1da3937f020000000000000000 +000080201307c9e6c3b7ce80e8eed091378c75b5faabb28037daa8040000000000000000c12ae089e4623935b88612942c3e5661d8e2485f616f13e6a9b0c642ce2721889d0e3163b5c605185e1293d083960b00aba9abb87a68f740757466104dc04a494e80f98d780b35020000000000000000 +0080d43e7b466ed8348f79c1fa583953b29240f7778e218b10cc04020000000000000000d17a064e4d1b3c4c92dc441027a2c702be41fc763da2235ce4bd64c312926f5b980d3163c5c7051888a0bb2182960b001307c9e6c3b7ce80e8eed091378c75b5faabb28037daa8040000000000000000 +00008020e1b86b38d91ab770e85a9116cc9d6574025788373598cc010000000000000000cf2453fe66209abd5e7886db6e17d14568b9f839cb108f8ff4ab42db8d8468e9f20b316331c90518992e208681960b007b466ed8348f79c1fa583953b29240f7778e218b10cc04020000000000000000 +00e0ff27b9e26b19200aec2d28879e7bd2ccd52748a685355c0b61020000000000000000c6600c0412ee6eec07617e33f48342a553e9b595674a2ea2890534033f1bb2968d0a316323c20518b37f327880960b00e1b86b38d91ab770e85a9116cc9d6574025788373598cc010000000000000000 +00a00a3190fda68375fc5b4eca266cd92170e08c2590c0f45bcf110500000000000000000bef1cb8696442fcd17a41334ed330976037cd903d4e80ff03f284554612a4688a033163cac405185cb508bd7f960b00b9e26b19200aec2d28879e7bd2ccd52748a685355c0b61020000000000000000 +0080d533ad1326f3235a2979344d96c0305ddb762f794dc6baf629000000000000000000e2a1fa5f494670876ed91d5a4ec290ad9d38adab33b00b1d33388992fc76f164f602316381c705188097364f7e960b0090fda68375fc5b4eca266cd92170e08c2590c0f45bcf11050000000000000000 +00404b2ba8e8ca6abcc3007568ea19d8b1cbc8d3ca0292bf2e681100000000000000000095c828102a75b88e8a870e2851ebb7e323bbfd070b760efb8814a9902d7917eb68023163f3c50518139c581a7d960b00ad1326f3235a2979344d96c0305ddb762f794dc6baf629000000000000000000 +0000402065406a5815b9f156f8398a1afadc48d588a548d86cd2e1010000000000000000e09d18a823fd28389259f330e94a973fc5e9a0c543343577a47654302c9e203d08ff306315c605184f7f45fd7c960b00a8e8ca6abcc3007568ea19d8b1cbc8d3ca0292bf2e6811000000000000000000 +00a03927a3be372d160ee98a1b6a03fbd4913f96a4f6d88b54567a030000000000000000087ae4e854284c0831ae024991a96519210fa09ef6c96d0801f5dfb057c51dcfc6fc30636fc80518290cb1e97b960b0065406a5815b9f156f8398a1afadc48d588a548d86cd2e1010000000000000000 +0000402027fe612a063a05c14c2090e2f40e16148b7a74b9668431050000000000000000c953a7503b59f80b7ff40cd8d08fc26e6a2e12cbdb5d8b454e6a6c29119f24b8fcfb306329ca0518263c29f47a960b00a3be372d160ee98a1b6a03fbd4913f96a4f6d88b54567a030000000000000000 +0000a020dae221388d8bab57c150044ac28daf298a4ebd1b74a40e040000000000000000f4efa13eb485a9403b3c5ec683a9d8bd5f16cdac680d4631dc85ba6a2e774a7bc5fa306300c905181f97011c79960b0027fe612a063a05c14c2090e2f40e16148b7a74b9668431050000000000000000 +00e08d2eeb0bf63c3da13ea7cbcd6d9461df58568a9803eea0b902010000000000000000825d50b9ec9db8e1c068cd5f511e765edb4a9c55fc0f6f18eb1c5eba13084023abf730638ac3051859eceda678960b00dae221388d8bab57c150044ac28daf298a4ebd1b74a40e040000000000000000 +0000c02097b430b723b91a0f0f6ff2101bc408e717c1cafe2b9c520500000000000000008eb3a65191c3276c0ed3bc1cd306755449542e62b87b3c0f1341e6e38f924135baf1306365c105188e8d9e2d77960b00eb0bf63c3da13ea7cbcd6d9461df58568a9803eea0b902010000000000000000 +00807126cf1a15a9d3ff35368c8e33fbc032d18e522d86a5b7cb79000000000000000000b3482664404d2cbefe87e5982fa2a106d6998200a79b6ea7c22f5334e64decc6f6ed30630dc4051853b2cf0176960b0097b430b723b91a0f0f6ff2101bc408e717c1cafe2b9c52050000000000000000 +0000ab28dc8c449b75a5ae965ab4732df0aca82b470cdda440f394020000000000000000ed408791bba222ca05dee06e88a830a9177800a8077cc9c55f89965420fd8e1e62ed30639dc3051861dcbd9775960b00cf1a15a9d3ff35368c8e33fbc032d18e522d86a5b7cb79000000000000000000 +000080200803c1318f61015ca85ca0b321b4220174d57fd3c59cb400000000000000000083fe5fa8785358d222a1f1b9eef1b7c019311691ed7361e62d1660c5adc16d92bdea3063fcc5051850b3042274960b00dc8c449b75a5ae965ab4732df0aca82b470cdda440f394020000000000000000 +00600f299a093dd8e22c10224ac17a248969de4ee9c79e6d19aa70010000000000000000469defa63b3a785a532e055625b34f09932d169278398f838173512661631586f7e93063c8c605184096523273960b000803c1318f61015ca85ca0b321b4220174d57fd3c59cb4000000000000000000 +0000af21a98f44744865d9c382ab24541e2207d3e18675115f6a9904000000000000000058ee38d6f485e8b4d98d21ce2a2c06e85bf32c02010c0232ef4f0694523163b026e830632dca051860d64a1672960b009a093dd8e22c10224ac17a248969de4ee9c79e6d19aa70010000000000000000 +00008020e7cc5641e324632c3eb4ddd201aefb2b51ae0dcd5a9a67040000000000000000a8a8ddc1ac6bc5728fae46425bed5c2adf2b687b9505793a96f730ad66cf947e08e8306322cc051822824a5771960b00a98f44744865d9c382ab24541e2207d3e18675115f6a99040000000000000000 +00c05227b81f0330d023759c861e21a19ef2a42c30762d432ac294030000000000000000988c1ad38d60937978b9c62ec7c67fbf86b8e4213f5c0a86917135cccf2b67a8f9e6306346ce05186259906770960b00e7cc5641e324632c3eb4ddd201aefb2b51ae0dcd5a9a67040000000000000000 +0000402022b88fb8249c59c28518732e6acb66a95e13f6482db805050000000000000000860132714921ad50e3d6324df99a00986f2d966a3c3291f2e015a343e820d0af0ce63063f0ce051872a8afcf6f960b00b81f0330d023759c861e21a19ef2a42c30762d432ac294030000000000000000 +00008020a7bcc40113f9b7f1abfa2d3e6165c2eca2173f423211bf02000000000000000051a7862f59b1811dfa343fa4e98248d9658b28553e47abcac2907c65b07daf2421e4306323cd05181bd091f96e960b0022b88fb8249c59c28518732e6acb66a95e13f6482db805050000000000000000 +000060208d0ac360e57738888f958c3eb9f1f6ed7075c23842bdb20400000000000000009c88b54d4b1d7bedbf63883b2c5047c73a38f425633862125051a81e605db8fe9be030635acb05186d1d89906d960b00a7bcc40113f9b7f1abfa2d3e6165c2eca2173f423211bf020000000000000000 +0000002026af32eb4a667ecd19ddab258c9d41be08535a204c0bb50300000000000000009dcb7b043a33fde7f9e654459c074e2384d2516031bc8e15cb7a09ca16d90b291add3063d7ca051860317be96c960b008d0ac360e57738888f958c3eb9f1f6ed7075c23842bdb2040000000000000000 +0020a322a28d6aeb01b7b0004803041d4016dfb856974d978b3b90010000000000000000978686cde66ff3d5f91154c608424c2b801c6d0c1aa2e684263030f4b7042cb46bda3063b7c40518c04ade2f6b960b0026af32eb4a667ecd19ddab258c9d41be08535a204c0bb5030000000000000000 \ No newline at end of file diff --git a/BitcoinCashKit/Assets/Checkpoints/TestNet-bip44.checkpoint b/BitcoinCashKit/Assets/Checkpoints/TestNet-bip44.checkpoint new file mode 100644 index 00000000..0e304a02 --- /dev/null +++ b/BitcoinCashKit/Assets/Checkpoints/TestNet-bip44.checkpoint @@ -0,0 +1,43 @@ +02000000420ad5d432dfa48f61fa5b0b6b65f4587291761d56b8d271cad9bb010000000031d3e2406e954acbf9134d2420ddb8f607cb29dc7cdbd9153af5751ad5c7f8ff444d1153ac02061cb3e8dba3370b03003c0205fa47283109a3272a9c305c28329bc6e7bc12e31598c015d60500000000 +02000000bed0b6cb5da8dce215d80720905bbb6cb19b8418f82df68d3bac3803000000002b7f607d44ef06ad1f96c8c042bb4c60694891a6bed5c3a6d708369aa0d3811c3a4d1153ac02061c48168705360b0300420ad5d432dfa48f61fa5b0b6b65f4587291761d56b8d271cad9bb0100000000 +02000000af6a359719e05a5715138aa42e6a67aca19c1ae26e6a7c66ce42cb0000000000cebc8e0f1d52ec0e55077decf69ae44dbc65820694d29a7227b17caa9ab5b5ee294d1153ac02061cc2a6875e350b0300bed0b6cb5da8dce215d80720905bbb6cb19b8418f82df68d3bac380300000000 +02000000d51bb879dda44622477ffcac907e44f3d09595fac165eb9f056cd903000000007dc5bec62bd9404560a021c5b77ff78178eec09c12da1d226d4ed9e276e25a23244d1153ac02061c3892904e340b0300af6a359719e05a5715138aa42e6a67aca19c1ae26e6a7c66ce42cb0000000000 +02000000a46f94687fb64c70e84661a57db04a4e09c32bc555b60e8a145c8f05000000005a4d29814979830a2a40b636c6723796b60b18a232f6c6296f9b6e0b0e2c793e174d1153ac02061ce78fa008330b0300d51bb879dda44622477ffcac907e44f3d09595fac165eb9f056cd90300000000 +02000000d4f4c2e43c564cbba9de9abe5b9cde022ac2ee30db331458fc2c9502000000005df7d4998b04d50745ea21eed93901a993e3550809a66a53b5479e9bda7febfa054d1153ac02061c4313b257320b0300a46f94687fb64c70e84661a57db04a4e09c32bc555b60e8a145c8f0500000000 +020000002ed46bc395a09a8b034c926950f8679c19338f198203598bd30fdf04000000001abc28e9e4f36ccae15bfd0bad028f145f290dd80bf58b91fbd674bd6357b332fb4c1153ac02061c4742d3ef310b0300d4f4c2e43c564cbba9de9abe5b9cde022ac2ee30db331458fc2c950200000000 +020000006cd566a53e2ada5163f46871c6103380512320b905f7363ceb4e9004000000009c371894786679561f6d891b6e725120191d190bd9d1bc59dfbfc1f94189c277f34c1153ac02061ca9af858c300b03002ed46bc395a09a8b034c926950f8679c19338f198203598bd30fdf0400000000 +0200000070361e684698c0858b7c2ec5346be8238b66cf7e08ea6bb2fd06a502000000000f0452bf84f8023fee8cda28a06f1edab760ee4fb57ec26b4f344f15c33a77d9ec4c1153ac02061c173f247d2f0b03006cd566a53e2ada5163f46871c6103380512320b905f7363ceb4e900400000000 +020000009605e44b7924bc2d3db9e7beff7ed0594d6ee79dfc95d7cc80a1520400000000e0008e3ca601062432b1c9e04930ff8b6c43b6ef38bc9ee3bf021a943ee22a6cde4c1153ac02061c5d11a3b02e0b030070361e684698c0858b7c2ec5346be8238b66cf7e08ea6bb2fd06a50200000000 +02000000a462900dc91079e7da04bae7f81f05d36199e548833c306caf31ff0300000000246914c192775e6318b5a578de640b68d43deea82a97db37a11ecac86d221009d84c1153ac02061ca1bb08bd2d0b03009605e44b7924bc2d3db9e7beff7ed0594d6ee79dfc95d7cc80a1520400000000 +02000000a266e7267075eda858897c09a1183586bec11526270ade4f4d299002000000008af392f52425c25f7abf8150ed2965f4bec3d3a3cc6c518905d7e8fb09dbf510d34c1153ac02061c4712c7a62c0b0300a462900dc91079e7da04bae7f81f05d36199e548833c306caf31ff0300000000 +02000000b2f4e9fff46b4a8d9cfc0ab633a801745c506cafed94a7669773d20000000000b5e18120fa7b2d6d74a7752f4c137651e0a0d9de133a9f07c0719f6e0a9d1949cd4c1153ac02061c574e76e12b0b0300a266e7267075eda858897c09a1183586bec11526270ade4f4d29900200000000 +02000000e63b556f7d28ed2b44e3ab175accc1878317c6e98bd8fbff70640e050000000081c1c5a8c313362b8171c0bc13263c30159b54baae97524aa722d21e67162f2fc74c1153ac02061c66935a282a0b0300b2f4e9fff46b4a8d9cfc0ab633a801745c506cafed94a7669773d20000000000 +02000000b377dc6ed586b0cb3d0b06c2af258cec7df9e826fb39b6e99f625a0200000000a548db3c14d3eaedf24fbc204b57922ec5274b45a08dbf3a973b15dfddc70af8bb4c1153ac02061c166bfae7290b0300e63b556f7d28ed2b44e3ab175accc1878317c6e98bd8fbff70640e0500000000 +020000000f183315940e93ff1476b3fe618cbf48d8f190bc826f3ab3da7a760100000000fccbf3dd196a761d21d0a68e5b41c9afc10604f6b537e93c8ca37238c9f3eae7b44c1153ac02061c7266300d280b0300b377dc6ed586b0cb3d0b06c2af258cec7df9e826fb39b6e99f625a0200000000 +0200000099dff232d4ce1de8057f8898bd4b740d3b8d0f77f2fd571c6e334b0500000000a99425697f55c5c114b1eed2452d51646923b01fd313dccd3ec4528001e88b6eaf4c1153ac02061cbf05bf99270b03000f183315940e93ff1476b3fe618cbf48d8f190bc826f3ab3da7a760100000000 +02000000fe3f131d633fd10057298eb603607f5ddcf045874afd9e2c75f497020000000089ca2b380865f57e72fe71dcfefbf68565aeef6914b60452f60ddbdf046de25faa4c1153ac02061cf2d85fe9260b030099dff232d4ce1de8057f8898bd4b740d3b8d0f77f2fd571c6e334b0500000000 +0200000013e039297871ccaaae40f4cecf8266bb8e9570d4f56864656815b80300000000dd9c94d54ed4e65289a867c129040240e4e2771ae7f8ce13069933a9c2b55ee6a44c1153ac02061cd3739bc9250b0300fe3f131d633fd10057298eb603607f5ddcf045874afd9e2c75f4970200000000 +02000000005975c7805234e00a2313e479ac3ca46d7dbac3eb0bc8433df15f01000000009d265c29b1c066d62ebb2147fb019e13a3a4d2786395397385e41c83925556b6a14c1153ac02061c1432e4f4240b030013e039297871ccaaae40f4cecf8266bb8e9570d4f56864656815b80300000000 +0200000068f841818be2d3709222b9254c825a79bbad91691dcf9070057bd20100000000c3d70a8ed7cb613e370d3429fd99507d9d197f2f3a167e207d5c47211362e2c09b4c1153ac02061c832ad33a230b0300005975c7805234e00a2313e479ac3ca46d7dbac3eb0bc8433df15f0100000000 +020000009557a1d63f67d6babfc253672339d98ea9a472f62672114488c8870500000000e8d58136fd1e6860f09b9297a88c3bd7dbeb1746cee8dfb71c362b9e6ce79138944c1153ac02061cc8158561220b030068f841818be2d3709222b9254c825a79bbad91691dcf9070057bd20100000000 +020000003a3c82afa1c0bd0bc05775b578e28f40c6ece502e844706682545b030000000091f371e37146aeaca5a1e0d2a5decbd61f917712ae2a2119fe81c3d3e7425ef1904c1153ac02061c0f00dd01210b03009557a1d63f67d6babfc253672339d98ea9a472f62672114488c8870500000000 +02000000e14ef72a8aea472614f3ae1306823cf0b325813f9f95987a47a7680400000000db5ea894cf5eab01dedf900c6b0387de31125fbe57df99c59d02637b71f42ecf8c4c1153ac02061cbaca34d2200b03003a3c82afa1c0bd0bc05775b578e28f40c6ece502e844706682545b0300000000 +0200000010ba8a1d57c99940327489d731248ef96850a0e25a6deec897ef7502000000006331673396115467ce6d1c3891c7926c68f70b091e092d24d058143a8d3d7273844c1153ac02061ca4a8d43a1f0b0300e14ef72a8aea472614f3ae1306823cf0b325813f9f95987a47a7680400000000 +02000000497def0e8859e314f7d0905fde763ad7f3888aad922d856674d57c05000000005ee1c0ad6dccf278cd96dc2cb3504f785686c1ea2ecca85f273c3b8f7a602c79694c1153ac02061c5cb6363c1e0b030010ba8a1d57c99940327489d731248ef96850a0e25a6deec897ef750200000000 +02000000b2665f20f90efeb3aacd2dc1b581fbb1bf7ee9f31590e3b51a6d0c00000000005f83b6a96f27609b6b98c11afbff350f83e533b1964bc70bd14027aab3127834314c1153ac02061cafb7a87d1d0b0300497def0e8859e314f7d0905fde763ad7f3888aad922d856674d57c0500000000 +02000000fcd266090fff4266d6e0759b5bacbbf971c07b61ec90d2d72903c60100000000afd22e7737e0eedf237d5e29da7fa8184a9883404a54aa16dc01f6c1d376124d544c1153ac02061cb75bcc9f1c0b0300b2665f20f90efeb3aacd2dc1b581fbb1bf7ee9f31590e3b51a6d0c0000000000 +02000000d017b6b0019acccb2102ff9a6cee8977c6989cb42cdabaccc1366403000000008d3f96674b1e20d8067027a71b4c136ba2959ce4bb444ee2d7b2bb8c9110d4e7404c1153ac02061ceba14e841b0b0300fcd266090fff4266d6e0759b5bacbbf971c07b61ec90d2d72903c60100000000 +020000004f6c33cae0a3b1aca6fbdb2a03b501dfc806ff072fbd2f65a7fdbd0100000000c098ca28eda4b933b287683ad38673544337837dfc352ef3d3ab72c6e05a75ee234c1153ac02061ca9763c611a0b0300d017b6b0019acccb2102ff9a6cee8977c6989cb42cdabaccc136640300000000 +02000000fa4d0441e31932a3ae75334beb88f57dfc269ee838b922d8b9e16d01000000003ce85bdf82f02fb7e851e0b663c1c25df820e05975ae3b5beaf17ab4a9e0e80eef4b1153ac02061ce296a3fa190b03004f6c33cae0a3b1aca6fbdb2a03b501dfc806ff072fbd2f65a7fdbd0100000000 +0200000042b12eeebba4e237177181ea6b224b0d99b41a1a0eb58d69601b1001000000004995bc1f26218cfcfe59fdb8542ef5aed8874c9e661eb4d69162e1bf2cd39c2e624b1153ac02061c5b366525180b0300fa4d0441e31932a3ae75334beb88f57dfc269ee838b922d8b9e16d0100000000 +02000000038bc3e0b917bb7904ae3cc885c7319fdc39b798a09667beabdac60400000000375d6d578405acba5581101377e0b40da9ad500d58d8e63b66f39345336b2a00564b1153ac02061cbbd9a280170b030042b12eeebba4e237177181ea6b224b0d99b41a1a0eb58d69601b100100000000 +0200000067e715e9adda369988f53551c853bdf6e1856d39a202c055326d010300000000bd47a543f8e6abfe2ccd008133fd77c4b09fd612a7e778bbf9f510b584cc61190e4b1153ac02061c4bcdd106160b0300038bc3e0b917bb7904ae3cc885c7319fdc39b798a09667beabdac60400000000 +02000000a932d3f845fc285e7f9752459ff1120d2ea40435a26c551e3e449502000000008d9b9bc7865c84b7fbc8797fb443b4308144eef8741c2484a222988f232be5d1444a1153ac02061c79ec5745150b030067e715e9adda369988f53551c853bdf6e1856d39a202c055326d010300000000 +02000000aadb6fe4be6d5de511d3b560b06ce13af8177105ddbfd01d7775600500000000183d45be68725b5dd1e53895bb7eb1019e8c25a5abb61b05450f1237d2fccb165e4a1153ac02061ca46351cd140b0300a932d3f845fc285e7f9752459ff1120d2ea40435a26c551e3e44950200000000 +02000000cb8d219b3cc17d24b350c8d69138f42ea997e79d0e6aacffa537ee0200000000b443d514354e340cf0c9ffec1935c565cef89a49fc7848b7a59c5f379f53f0774b4a1153ac02061ca5b217fb130b0300aadb6fe4be6d5de511d3b560b06ce13af8177105ddbfd01d7775600500000000 +02000000821e772f11b097d2f781b58900c7f7525d1c77bf12f3180b10b5d003000000008275b0f861bf8fdaf9c1cc1fe529fa11c0932413d127ca8268c899f737ca582d364a1153ac02061c2c5c0d18120b0300cb8d219b3cc17d24b350c8d69138f42ea997e79d0e6aacffa537ee0200000000 +020000006222b9c99f08b6947464ce617f845b03c52517adc2da9dbbeb6f2e0100000000dee8069db8c146081bcbb52eadaa9720f3041d56e7914557f9c53274f74399b1064a1153ac02061c137d19bc110b0300821e772f11b097d2f781b58900c7f7525d1c77bf12f3180b10b5d00300000000 +020000003c55fcde162e3f7a9ac2f4e401b5b3e14c89a2a347e0d0e1c9669f05000000002c2697645581d00006caeddb4cac857259a74ace02b8377729d2d22ce28cc989a4491153ac02061cf3c87e27100b03006222b9c99f08b6947464ce617f845b03c52517adc2da9dbbeb6f2e0100000000 +02000000bbce18a1c5b6c13acf8f032466cbd0ddcb477097ce59f14c2df9e20000000000a632f1256ba784578fc431c79cc76cecee33ece48c3e1c26aab5e1d5160e3ebe85491153ac02061c8ab84f1f0f0b03003c55fcde162e3f7a9ac2f4e401b5b3e14c89a2a347e0d0e1c9669f0500000000 +0200000038a87b508346358907e79245f9d2558d574dc98adb814940e3bbec04000000007613ebe4ba5fa0f1998de982425d0e3027f741c9a86164c33474fca8a0e5615177491153ac02061c0729ca190e0b0300bbce18a1c5b6c13acf8f032466cbd0ddcb477097ce59f14c2df9e20000000000 +0200000038274e05ac9f07b81123e904a69fe55006fd828eb939eff152a4de0100000000c281b9c080273f21a426584f735afbd1748effce10761d793c8ebbf7008725c0da551153ac02061c3696e1210d0b030038a87b508346358907e79245f9d2558d574dc98adb814940e3bbec0400000000 diff --git a/BitcoinCashKit/Assets/Checkpoints/TestNet-last.checkpoint b/BitcoinCashKit/Assets/Checkpoints/TestNet-last.checkpoint new file mode 100644 index 00000000..2e4f1397 --- /dev/null +++ b/BitcoinCashKit/Assets/Checkpoints/TestNet-last.checkpoint @@ -0,0 +1,147 @@ +0000402016ba74362eb3915d4115e06a2f665bbda8edfc61a02b091e9000000000000000a126274f1c9a80753457822e16b5e89261dea427fe400d5826ee7011b8de2f91daef32638112011a9ade256a463017009e6e7a879b9a1f711796c5b6b0bce2d32d62e623d27e18364800000000000000 +00e04d31ee152a0347a81cdc91b30c9650c8fd637559254d7eb80e2b3b000000000000005310169bb4dd26d2d4ca44f2f314369fb4ba0bac19454905eea0650a4a6566acfaee32638033011a62782f994530170016ba74362eb3915d4115e06a2f665bbda8edfc61a02b091e9000000000000000 +0000a020c84c1f6dc3c98bfc31037ae98295cfd347d1a5a14cd49e7203e4ec3a0000000099fb8075fb8693d94169789f02014cba2be18b601b6b66128d38b1e12c3db9ecf0ee32631752011a0992b10944301700ee152a0347a81cdc91b30c9650c8fd637559254d7eb80e2b3b00000000000000 +00000020b2c4abf28669c70672dee97816e8a65e192fa2106d3aa198a700000000000000c43259022e37327501bec5c5a96f67d49bdaf8f0b5a23507454625953119af3985ee3263ffff001d297224b543301700c84c1f6dc3c98bfc31037ae98295cfd347d1a5a14cd49e7203e4ec3a00000000 +000020200909020d25c08c8886e4371176d265399e6e0f0f7ff5c96c2e00000000000000589625a52ef221fa37ca833ae1983efd39ec171c4c045d6e345e5be66f6e305bcee93263a138011a5768c31e42301700b2c4abf28669c70672dee97816e8a65e192fa2106d3aa198a700000000000000 +00607a2ec4718b7ed6a56f8b3e379b23a597c8236a24ab1e9a616367afd60000000000004bd0f330dda7455fdb7709d51ca209e951f13045791d616a8c01fd276eab3f983ee832630748011a06d4eab1413017000909020d25c08c8886e4371176d265399e6e0f0f7ff5c96c2e00000000000000 +00000020dd877044e9851080e4e8447cd65e2cd00cfc06a3f4c5e563ea00000000000000ac84392d8403a812b647de1953af0bb3124750ffce524cbd90866ab0f21ade40e0e63263ffff001d9c48276940301700c4718b7ed6a56f8b3e379b23a597c8236a24ab1e9a616367afd6000000000000 +000040201bd956eda5ea70f9c73b42300a98c53fa62895d985c5bccd2634000000000000521f3c360317804c8c5f324341802733d82e5e7ede93d977fc9f09e79229385520e23263683b011a42dc0c823f301700dd877044e9851080e4e8447cd65e2cd00cfc06a3f4c5e563ea00000000000000 +0000802015fdc4fdc2c34d1381162a1821bdf7e3a6218788492ea86cc100000000000000b354d5be884e72582356c66338f83511aece2581f413f1645b29904d349d204e64e13263ffff001d86c45c903e3017001bd956eda5ea70f9c73b42300a98c53fa62895d985c5bccd2634000000000000 +00002020827fb7965cba45eebcece5fae62e2d0f6f59140d36c4df9f9f408128000000001f663e48d760d3144c366e73f14ccc939133d53627ee460ab10a00612f0d453dacdc3263fe35011a380673473d30170015fdc4fdc2c34d1381162a1821bdf7e3a6218788492ea86cc100000000000000 +0000002095fced40917f0fac38b9e028578b88e987ba597d37d7f7267b00000000000000e37a2c3ad72622b23fa5769d07c0ff67c4efb7258f2814be896cac03b9e3dfad5adc3263ffff001da326cc6a3c301700827fb7965cba45eebcece5fae62e2d0f6f59140d36c4df9f9f40812800000000 +0000c0202b013a482f337e76cb7684f6f818cd3454556111f587f6742201000000000000125a76e9f40c963dae66ed757ad24930538ddce113ed62c033923ccdfaa3eb8e71d73263d724011acfd5a54f3b30170095fced40917f0fac38b9e028578b88e987ba597d37d7f7267b00000000000000 +000000202a5e84d8bc9827e01fb56534820e5aa75ae27872cf4aa10188000000000000009ba187556c78cc0503e667947047437f60c92802b0953f628d31cb30999b79f282d632637133011a37d351363a3017002b013a482f337e76cb7684f6f818cd3454556111f587f6742201000000000000 +00000020df1a270705b67f63b937f268f4cf31ebcb9976a20c61eef704000000000000006e8acb9ad8d57af910a38734a8d71a74f2b9492cd1514252c553ac527599435127d532637f31011a29eed0e8393017002a5e84d8bc9827e01fb56534820e5aa75ae27872cf4aa1018800000000000000 +0020f4336e51edf0da765375b2fdc46f808283c4755c98449234c517bd0000000000000089e6f2d34f065ece91a02f78c3933216a95c731c50d8edd1ac9e0a3ed24ad813aed23263a235011a3235820b38301700df1a270705b67f63b937f268f4cf31ebcb9976a20c61eef70400000000000000 +00000020853c687c3bfbc134369bfb251ff6a624e3526feb48aa46e0b60000000000000006d564dc829cccd480c45ff93107b7f59e4b6849e3a139d746ac2c9cb22569d49cd03263ec59011aacebeeda373017006e51edf0da765375b2fdc46f808283c4755c98449234c517bd00000000000000 +000040203ac71e5d90b68ce11a150334b0f74d9b3e53a4ed067ee73d460000000000000065e224ea9ac542000c84d1f8f580e2b3e598dd581a8c6f9a30bc9c6180c9087584d032634368011a03a937e036301700853c687c3bfbc134369bfb251ff6a624e3526feb48aa46e0b600000000000000 +00e04f324efa45f974ccba06e51251c105be35ee7911dd6d84f6c8250c00000000000000b946ef8840e31000aa6063b3fb14fe1f080bb8313a300ffc5e14748365c80fe4ffce32632280011a43da191c353017003ac71e5d90b68ce11a150334b0f74d9b3e53a4ed067ee73d4600000000000000 +00c05d29107c9c290b6c7c42cc666a1b7d729521bdeaf24e0025a5db9900000000000000e98f774d65487c68c9eb540c1dd19b96e8904d99e3e5a1fdc6df6791d393b67df4cd3263765c011a5a74ef27343017004efa45f974ccba06e51251c105be35ee7911dd6d84f6c8250c00000000000000 +0000e0209df5171f8cadbd8a1e5b13a37e0974bce0a0d1497be97767b470031100000000d385889f27cc1539539248cb445d86d07423a2a3742b0ed13f6e89c6e869087ca2c932638a73011a982f455f33301700107c9c290b6c7c42cc666a1b7d729521bdeaf24e0025a5db9900000000000000 +00000020e053ed7a11fc2784c189af6be8396763c633bf13d24a387dd7230b100000000076dc5112bb4dd96f30ef846583e9bfe9518cfdeda812dbf8e29e5509b612c87f97c83263ffff001d172f865b323017009df5171f8cadbd8a1e5b13a37e0974bce0a0d1497be97767b470031100000000 +000000207cee704a0545176dfd99b6cad18b8fd49fbdc45e5db4958f4600000000000000b850df45336af077ef58d65c7dc6cb9ee1f5ac2862689ec501cbd28dbb014889e1c33263ffff001d9870bb7a31301700e053ed7a11fc2784c189af6be8396763c633bf13d24a387dd7230b1000000000 +0000c020667b94340bf7452d837f58c1537be6ee56b78c4836d9fa28426de32e0000000064ec75e0f06729147180320473d59d95a9bfd6a7d74e6db7bb76b7824e23985b12bf3263f741011a285ca5e1303017007cee704a0545176dfd99b6cad18b8fd49fbdc45e5db4958f4600000000000000 +000000200e2ffec4eb50c6102bd55d1898a226ce2c96208c5bb206dc08000000000000000b03486387b899fab986d46818e109e65b23b7af06c936ac2cedfb1cfe10bc2ca7be3263ffff001de1baf31e2f301700667b94340bf7452d837f58c1537be6ee56b78c4836d9fa28426de32e00000000 +00200020d0856d0294573cbdf95eb273c841aa9157930fa419c4c20e7b9b9f0a00000000b239114ea511b8fea73d80d43e394c976446d2f39912fdb952fcf780d2471b33d9b93263a12e011a7eaa1ece2e3017000e2ffec4eb50c6102bd55d1898a226ce2c96208c5bb206dc0800000000000000 +0000002092da6dd46e8faf18d5f3afc1dff2bf398417c91b3e1295ed1301000000000000d4f8fdec57419e8dc0d2ca799e367f8e4c1ecf35cac384ac9032a3a463144e4eb5b83263ffff001dbf68aaca2d301700d0856d0294573cbdf95eb273c841aa9157930fa419c4c20e7b9b9f0a00000000 +0000802070074ae60fd3fb224e381cb8da1376bf8728f1db3dc79b18c800000000000000c3cb452e7249dedba59322896ba4ca43e7490085a6aba8473269fbb559864acaf1b33263202a011a59792cda2c30170092da6dd46e8faf18d5f3afc1dff2bf398417c91b3e1295ed1301000000000000 +00002020e5549393eb998058a286543ca882b972fedb1f1470aea2290e00000000000000b40a9e05066305d5db8e49d0b7602e5b65bf89a4553a78f3c634cc98b02851a8b7b33263354e011a33873ade2b30170070074ae60fd3fb224e381cb8da1376bf8728f1db3dc79b18c800000000000000 +00400020d8b395ff6c3ba4eb0f25ac49f75a594e36e32dcf8eb97205b900000000000000f54b1cb466f73e62f899fb9bf1209ff0272869db4b80a55298c87d5400b0c8ffb1b33263a14d011ae2f2bfc82a301700e5549393eb998058a286543ca882b972fedb1f1470aea2290e00000000000000 +0000c020930d4f0a5278053c8796056de2a0afde464941f3e934632ea30fc92c000000006f3b16d16e5da655e7b3da25c34404ee464f555365dc65defeb1d79382004fef50b13263af55011a337b310129301700d8b395ff6c3ba4eb0f25ac49f75a594e36e32dcf8eb97205b900000000000000 +00000020a1ef917277bfddf476abf446540f238b4cad042c4a82b0f62901000000000000cecc2a8b26b98d5cf90692f11d3f64a542ccc986f455464f50baf060ac27c30574af3263ffff001d4780347628301700930d4f0a5278053c8796056de2a0afde464941f3e934632ea30fc92c00000000 +00200020a6a01580ef57af44e842157cc56b1e70f7d9344592963bc5a10a7a10000000007a47b80341bf8bfa381793e46ffc8f223ce3a37d5d3e41a178c28c0590a5eb23a0aa3263464e011a816b69c927301700a1ef917277bfddf476abf446540f238b4cad042c4a82b0f62901000000000000 +00000020fe4209652f62e3bca9ec5c9ce31f821e63bf66415c104768b400000000000000ef8f7f338dfd9aff69aad1898cba59797989f7d5dfc4a91c68a1ba69253ffefa52aa3263ffff001dda31bf5026301700a6a01580ef57af44e842157cc56b1e70f7d9344592963bc5a10a7a1000000000 +00000020f636cd53d2e72706598b45141c07fc1c8bfae0d6947cad3e2e01000000000000d6c86ec4ec1452c3aa8784c62c3485a6e6fa657c1173ebd57780d9597356eaf576a53263323a011a11975ca325301700fe4209652f62e3bca9ec5c9ce31f821e63bf66415c104768b400000000000000 +0000c02099dd45bd19e97c7761feaa185aba73cd47c56b038f7af377c700000000000000ca3820a3dfd3c5859a09c7d0ec9713b4bf93b7c5d6e39358e83defa961a7c15660a43263ba50011a37b9c74724301700f636cd53d2e72706598b45141c07fc1c8bfae0d6947cad3e2e01000000000000 +00600020db9f11488795fd6ae7a0c373eaa46d2ed2cd9f11bb943a444ccffd1200000000949d233c4755e660d42ba60b4a845254a69f3c7c72ed73fab9fa0b688a06bceb70a33263eb67011a2fbe03422330170099dd45bd19e97c7761feaa185aba73cd47c56b038f7af377c700000000000000 +000000209788b512d4c335e7d11da484e6c161dc201e0c67cc09705772b4451200000000b8122b3b5aaef841746561eb2684b38efa9e31583305f98d5b676cbd2869479b72a23263ffff001d4bbfb44422301700db9f11488795fd6ae7a0c373eaa46d2ed2cd9f11bb943a444ccffd1200000000 +00000020f7fb9bf52eac7874594875048aafc2f761f2011737a25222ff00000000000000d718abfc98a2396790b95e1da35f118015027a3b2a98eef6d0dea2e22f176b619b9d3263ffff001d83296d23213017009788b512d4c335e7d11da484e6c161dc201e0c67cc09705772b4451200000000 +0080002094312a8388b0b25ead7d944a2e6d3e71b00d5c82a39f67b4ac000000000000008f1ec1b415d29d0f40c893ca28ad16a4eb60e177ac59d792749adbe478ca4663e0983263323a011ae29f804c20301700f7fb9bf52eac7874594875048aafc2f761f2011737a25222ff00000000000000 +0000402017615cd2995568466e89c59112b2e95acaf52600c26a7288ef00000000000000b95807cff9a488299993dd46541f78a37c0e5c6e24f20925c8249b409dce5b1aa89832636542011a45ace4061f30170094312a8388b0b25ead7d944a2e6d3e71b00d5c82a39f67b4ac00000000000000 +00807d30faf4347f25a9ca8d35af7d72c055fcbd454a8aba5469eae0b327172600000000976419226eb412f0aa5e9594a862e782a478211a5cf338d8c177680494e6be35d69632632752011a64e954cc1e30170017615cd2995568466e89c59112b2e95acaf52600c26a7288ef00000000000000 +00000020a9023383d251157dd4d56b3cb0eccdaa786f4a76e8a9934f180100000000000089d6335ba8c38731345c3dbed454e42c9f1c84a9926b66f156bccd60f76d082b76953263ffff001d1b36dea21d301700faf4347f25a9ca8d35af7d72c055fcbd454a8aba5469eae0b327172600000000 +0000802035b7fb16a186c0311da4892d41ba1294b10e5423362ef3833146cd1900000000ce6b3be314de48785cc3b31d6dc7194a282101f83e29c104d70b434be0f1ac03a79032634446011a69aee1511c301700a9023383d251157dd4d56b3cb0eccdaa786f4a76e8a9934f1801000000000000 +00000020b1d6eca0d43004a577aa0ae724ae81b43d550e796a4783e10300000000000000fcf4e3ca798f4fe9e44c2a1fa1c6d9417617c2c0fed94b5f775a302120aa77c00c903263ffff001d005d44671b30170035b7fb16a186c0311da4892d41ba1294b10e5423362ef3833146cd1900000000 +000000205a3795e232c2ab042bb4d23c332706428c60c76485e64b2ea900000000000000a23bf3c57338178f0303c184487c26b83f617167b5b0085e2de5f30fe885d9da418b32636344011a707f36fd1a301700b1d6eca0d43004a577aa0ae724ae81b43d550e796a4783e10300000000000000 +0000c02046022c92e69c7201df1307f756a71e4458c0f10ad7d5c969410000000000000044b222198466e35eebac4ede8c50389335033b47e4020087b7b1abac8ac7020c3e8b32635444011a83550a39193017005a3795e232c2ab042bb4d23c332706428c60c76485e64b2ea900000000000000 +0060a32468420299f972d0f262e2b3cb493483df21b14ad1130685a71c01000000000000a152434e50457d6810be1c9f83d97f79ff0f21b9d8eb1cb05949e6ee246ca9a8e58832633344011a5aee8aad1830170046022c92e69c7201df1307f756a71e4458c0f10ad7d5c9694100000000000000 +0000802024205a463b601fcbe52af4f7904b26424a3c1a26cb2c8d98ce89313d0000000043103f746cd0953735b4d5e2ccef35eb9091687a9294d132ffeb4acb0ca3a8938b8632636443011a8e6744dd1730170068420299f972d0f262e2b3cb493483df21b14ad1130685a71c01000000000000 +00000020812a162ad747fb09eae2191a06a48b878778c501d072ba64ca00000000000000d4bae4e05ce682b847eddfad6d699b26ed2b81fcfcb69c34c39aee2a10b3777f26843263ffff001d332107871630170024205a463b601fcbe52af4f7904b26424a3c1a26cb2c8d98ce89313d00000000 +00800020f90487e90fb76ec42745e6460057d9f778ec006eb66e38473200000000000000ad3542dc35978d1c1f815358af39027e21edd3753bfa2f376c11953004461220707f32635333011a2c50bd5015301700812a162ad747fb09eae2191a06a48b878778c501d072ba64ca00000000000000 +00200020f0e98fdeea4ff13cf61633b24a3af3f8ec5c5fe69bf849eddb0f443a000000009c0df927287b876ee78517c2b5676decf669556a1ecd63b988362b3071193d2b6d7e32634353011a1eea95e914301700f90487e90fb76ec42745e6460057d9f778ec006eb66e38473200000000000000 +000000207d80fc253ba537fa4712e3e4975e0a0d29211661e7d53e6d42010000000000000f1986cd42016aaaf408baf4c50538c26f09cc84d2f96bbbb4aacd2324130da9177e3263ffff001d5d479ffe13301700f0e98fdeea4ff13cf61633b24a3af3f8ec5c5fe69bf849eddb0f443a00000000 +00a0602c9ad38e1f3413f9ab42d99d9c0dd649a33230c778f91a0cc57d000000000000006c5e25cb65eeec4a301138a0485f62ee58d4a541487e40ca65550ce0f398a9e2587932636546011a2db40375123017007d80fc253ba537fa4712e3e4975e0a0d29211661e7d53e6d4201000000000000 +0080fe27a32829c1d78cba997e260b7dbf51640dbefaab3858bdbb535ae58d13000000008a853842200ee95d9318272bc309bcf6839a056102583e79a42054d4944d30209e7832631963011a49b30d41113017009ad38e1f3413f9ab42d99d9c0dd649a33230c778f91a0cc57d00000000000000 +0000002060759d5116ee7b7e9a450972ccaadece66c3e5ce275d16d7b80000000000000048dcde75d7d28c4818c1010294c9ce934f9add4ad34ca3c1bbafe45a3587d06bfc773263ffff001d09872b1f10301700a32829c1d78cba997e260b7dbf51640dbefaab3858bdbb535ae58d1300000000 +00c0d63011766e62b0c51e7f6128d84911145fd727e9167ad41a8bc7d31eac29000000005b87bcbacb106645f3eddd97fe3b2571d4221d0c1563270affbec3fea52a517e3b7332633061011a17a2bee30f30170060759d5116ee7b7e9a450972ccaadece66c3e5ce275d16d7b800000000000000 +00000020607717a5d31694e4f9bb3da380be9afda0cbca25a58ad46eae000000000000000847abb2de6493840a6de5f08d0ac308388e359a650124c6a2d0c354b31ed87130733263ffff001da53cb9440e30170011766e62b0c51e7f6128d84911145fd727e9167ad41a8bc7d31eac2900000000 +00008020c3e3a759fc44fc747a0d67ac24760fc099bcefa7b408b6df42af2b0a00000000cd828541fffe7f128ed9501a142d495b44984c84936474044b55f8acd741f2f5666e3263a14d011a5864f55f0d301700607717a5d31694e4f9bb3da380be9afda0cbca25a58ad46eae00000000000000 +00000020d26ade8e19594629bbc490690d1a908be1c9a8b2a5153d6bd600000000000000fac18555286a37b2128063420929a224216b02280d7a2aae107d2f87a90c2a86586d3263ffff001d5060052c0c301700c3e3a759fc44fc747a0d67ac24760fc099bcefa7b408b6df42af2b0a00000000 +00008025f6b65571df4621d9fb2038cbf42bba62c0d6654b0ff8050baaca8b3300000000155d28f6c4fa0fcca8de73db15b717497fd92199829dfb143b80153585d84e1b89683263a344011a632caffc0b301700d26ade8e19594629bbc490690d1a908be1c9a8b2a5153d6bd600000000000000 +00000020b59c4dfb82b157542ed3b0c3c0d1d8464c5525a0d0b3531c3200000000000000fdbfe8cbd12f04a506947465506ac7ec1d7db3d32f468c8fde0479a98edbf9981a683263ffff001d0787b2500a301700f6b65571df4621d9fb2038cbf42bba62c0d6654b0ff8050baaca8b3300000000 +00000020b5afee354cab046d4f942dfc110bb7991b98c60b0e61919dda5c902800000000816bb0fa8455db6418ef488111f7e735c66c799eb3d938810422b29a55dd8cee446332632b3d011aefe5f70609301700b59c4dfb82b157542ed3b0c3c0d1d8464c5525a0d0b3531c3200000000000000 +00000020c65886739ee05bd9f606b163d07ff7924d70e7ea82d294056200000000000000e6abf7ce83a34e4ae9a8ccbe56a6dd881b0f6d421533dbd4ca3c25f91786eaebf1623263ffff001d2f3d751208301700b5afee354cab046d4f942dfc110bb7991b98c60b0e61919dda5c902800000000 +0000402053fa2194951b26b8c75045147f7a414307df36c4dc4f33575300000000000000e5c838df46aea9a8be22efaf931fa604a215e7f4e1e5015eecf391aff018915f275e32633a3b011a6b885d3307301700c65886739ee05bd9f606b163d07ff7924d70e7ea82d294056200000000000000 +00a00020b253c513ffe513ca85bbfc0036a5f9dd3d7ffbaed39c83111870c01e000000005fbcdfec4103e83e1b6e1946b343e867e907f5f3001ac92be0d99bcaa48e4472215e32631543011aa1ce95480630170053fa2194951b26b8c75045147f7a414307df36c4dc4f33575300000000000000 +00000020f134875004cb199d15185236dac5aca43807f5577dfbc778050000000000000007f3ec3c8fc970bf83768c86b3a2acee4a28bd41d4d1a82c7323d8e1c5631f12495c3263ffff001df9588e9505301700b253c513ffe513ca85bbfc0036a5f9dd3d7ffbaed39c83111870c01e00000000 +006000200808149631d247723d67deba6c6289d00bff3064da4b9c4814010000000000004cdc1f7cb42268fb5fc93a2294ad661c37d8b17b91dd5c47fe127b340ef291ad91573263d73d011a43393cef04301700f134875004cb199d15185236dac5aca43807f5577dfbc7780500000000000000 +00a0002092643bd14901cef0831f0adbef477fe10b2823e906e37449a20000000000000032ebd1ab6b9e5860958ee9379956e34f4a263ad37c54dca48cc02f65f6a4bb6c445732639a40011a147b5948033017000808149631d247723d67deba6c6289d00bff3064da4b9c481401000000000000 +004047290a66397488bd4049849a178c87ae1ab083484b8427bdfe7c6ea54b0200000000670cdeaf19b48a2ef967ed887a0b28d920004153debf247121830bfb1125a9db19553263eb50011a59965b8a0230170092643bd14901cef0831f0adbef477fe10b2823e906e37449a200000000000000 +0000002024fe0bae16e423016957d0fafea9683527c802d1ea1c528fbc00000000000000d708ffe18ccb055c005cdd20e03698d365dc1f23e2de9f683f9da5a44b945ba2c3533263ffff001da435e4d9013017000a66397488bd4049849a178c87ae1ab083484b8427bdfe7c6ea54b0200000000 +00e07d28c8fb6a0403c3a899cd5ffd54eb170435a77bda9a9fdfbfbf820000000000000099b731697c52d6aa8dfd0182a3e375707e198c1f1d78be64e7374f13cf84ade7fc4e32631f2d011a75af3d810030170024fe0bae16e423016957d0fafea9683527c802d1ea1c528fbc00000000000000 +00e0bd201cd0bef8cedc6afc15aa6b89be74ffbf9157fa3f57717d0e40f80e27000000000bcd2c238e12010a653c778fcedca8dcf9f7d9d902f95babe7a08ed85c03f9edcb4c32633e51011a76ecd3b8ff2f1700c8fb6a0403c3a899cd5ffd54eb170435a77bda9a9fdfbfbf8200000000000000 +000000205d97c96bee533a121f057984c0a3dc75ec634a4924005f3a6d00000000000000ce2e40e705e3227e1471107dab90f99e9b76fde4bf7d1ba6fb859d648baf4e3ac04c3263ffff001d15c50800fe2f17001cd0bef8cedc6afc15aa6b89be74ffbf9157fa3f57717d0e40f80e2700000000 +00000020a2b9b5dc763fa4911db29622d7e1d8274f51ee5734d8cfc3f7000000000000000eaef1895f040f3a86ab2be45c6c2b0a4f8feff9a3571b1dadcb237e4ceca654f5473263d23e011a4cd00447fd2f17005d97c96bee533a121f057984c0a3dc75ec634a4924005f3a6d00000000000000 +00e0492d94dc12852605d673ff8789bbaa027b8e8b1d753ae4d5451e88af702d00000000569e620ad5ccb15ad1a17bb47bc51c4ab1c9c928fac10cf757dc6f43d6f5faabec4632632158011a0f6da6a7fc2f1700a2b9b5dc763fa4911db29622d7e1d8274f51ee5734d8cfc3f700000000000000 +00000020ed17f63a76340cd67f814e84aa586143e510393b165b5d7c3300000000000000d1b8d2eb3976f73d429b968d5c786f378260c80f2d8838fbccfa8adbf55ca72c21463263ffff001d9e4be62ffb2f170094dc12852605d673ff8789bbaa027b8e8b1d753ae4d5451e88af702d00000000 +0080082d5343c833f9557cdaf4b9b48a1828cfcd6dcb73290f8cb044a6000000000000009d91d074a0721241ca2bde63bb5ddd488dd2cc5c36138813e4bd7f93b22c79ef64413263ac49011a56849d1bfa2f1700ed17f63a76340cd67f814e84aa586143e510393b165b5d7c3300000000000000 +00c0fd22d7f894c0e7473f9bede48ce17699eccfedc9e149cd5bd2928401000000000000298b222e6a51faca52a9e427c5d75f1583f119dca6c47b2a25f32a3e608edf6f92403263696f011a298213c7f92f17005343c833f9557cdaf4b9b48a1828cfcd6dcb73290f8cb044a600000000000000 +0060d62709f6294bd56b2a7635fe04ff9b43d91ac1723fe933231531a70000000000000010bc09c11fbf4c6989530ba2e7af3e3d1676dccecf5c0ecb8408f5d77c2ea0df6d4032630598011a0ce2f9ebf82f1700d7f894c0e7473f9bede48ce17699eccfedc9e149cd5bd2928401000000000000 +0000e0200238c2c9845ef7de8e1130493ac195a6470b6ac6976de88090000000000000005ea0fea85165b2a9275919812f842a9d3fbe46da0622a796e635ec08cfef37633540326397c9011a188a8536f72f170009f6294bd56b2a7635fe04ff9b43d91ac1723fe933231531a700000000000000 +0000402099a5388f19bf75e4c4ac86cd9cec5a64a7ba603672f42dbae4010000000000006b8726fcdd4d0e54c2a743df1937a200bc102b8e15da50f9335b2cb41e76c79830403263eed3011a0a6f1424f62f17000238c2c9845ef7de8e1130493ac195a6470b6ac6976de8809000000000000000 +0000402086dde5c13aa6ea233f7a344ce98ea25f34a4daf70939853d33010000000000003c5b89b36959049e32ad256bb0fc7bfd49c79766f950624e9bed9c13fb8778704c3e3263aff0011a67a35684f52f170099a5388f19bf75e4c4ac86cd9cec5a64a7ba603672f42dbae401000000000000 +0000c0203d2788e14f157a9b466821515f4f8b0bbff2306e923e01745d00000000000000c4b0f94dc0a23b9d4e3b39f1f942099d68e657472d90fec67d88210b5e137c862a3d3263772c021a16dd84cef42f170086dde5c13aa6ea233f7a344ce98ea25f34a4daf70939853d3301000000000000 +0060c92c63fa7f016a1b9b1571c27380a0e332795a05b2cdbf220078a70100000000000094fc02871bed0ccf3d758379616f638d452cfba9d603513d1cfcbda8d4494f2c203d32636551021a7ebe35fff32f17003d2788e14f157a9b466821515f4f8b0bbff2306e923e01745d00000000000000 +00c0f322977cebb560310f6fa34b18c2edcd1ddb65c2800ef26200f94c02000000000000c91d3d09c2928652f91da6374aa66ac4e1df6e4d40ad7b4377f442a5955d00e6163c3263a174021a0c154c3af22f170063fa7f016a1b9b1571c27380a0e332795a05b2cdbf220078a701000000000000 +0000502830e91c6c86e75fadb17db2e5854c632abddfbce15e226cb79700000000000000582db30980f229b820a8b99bc92c1a31433e583601dee1d0f4d0b5d3a6a151aaea3a3263efa0021a442634f4f12f1700977cebb560310f6fa34b18c2edcd1ddb65c2800ef26200f94c02000000000000 +0040022c9d6e6536f8088135ab0d599e539ec05a8d131e9918de5b7f240100000000000053181d9abaa4e6d487841bf09d9c283eea9c44e2133d04263e78ff74e2233c89f439326331f2021a23d81db0f02f170030e91c6c86e75fadb17db2e5854c632abddfbce15e226cb79700000000000000 +0000e020f2dbc70efdecdba98c2f4e77ce7ad18b02fe610f7fbbef667f030000000000009d080ce3fd8fe4d88850c9175e921535ee5dbed608e6f77c0ccb8c271ca421feec393263a048031a77db9641ef2f17009d6e6536f8088135ab0d599e539ec05a8d131e9918de5b7f2401000000000000 +0000802069c56308440c0d93d0da07adfd79327b4a4814b8fd13fd7c4e00000000000000c45f5aed1b91c8d350bb105c4adf1326ebef001a667b8e62354a40c4c8437923c73932631aad031a881e5917ee2f1700f2dbc70efdecdba98c2f4e77ce7ad18b02fe610f7fbbef667f03000000000000 +000060207aea17da01840187738b5f0e7895988160a2731d7e831c579e03000000000000d13556aedf9803b2a371accc339cdb4d4862a075cd614cd620d8034f50d8c008b9393263d31a041a452c7fcced2f170069c56308440c0d93d0da07adfd79327b4a4814b8fd13fd7c4e00000000000000 +0000e02002ba28cbd8bd05b46b2162b22d89c9711b6fb3e0aab9b0b1cf03000000000000b00c7e4f0e61a853f4497f2a2901d655f46cf1cc53fd7407827141567663a7609e393263cb91041a629fb298ec2f17007aea17da01840187738b5f0e7895988160a2731d7e831c579e03000000000000 +00206b2dd97b69d59fb5bfb9c6dc4ef6fb4fd0040b83e8efd1aaa834470100000000000056864af94df684baaf9ac262281d9b0f83a73b762743dfef64ca7ce4c20d0f5e73393263ce11051a336982d6eb2f170002ba28cbd8bd05b46b2162b22d89c9711b6fb3e0aab9b0b1cf03000000000000 +00002020e42372bc591966d633d21cbc3f5b384f6342b1e67d3c4b830200000000000000b536c5af22a35c778209ccbe274064ff50a3f0a8e9e64e2d75b978f3e9fb1d1e3739326336ac051a6583c85dea2f1700d97b69d59fb5bfb9c6dc4ef6fb4fd0040b83e8efd1aaa8344701000000000000 +004066262c5c210c67a84c19e54f851ecc92f2c64d11963d6e578fc00b040000000000006304af0cf4ee1f8c936bd3f45318ddc991b4974a3b4c23fbfd28a3be8c44dbfc27393263dd58061a40eec5a6e92f1700e42372bc591966d633d21cbc3f5b384f6342b1e67d3c4b830200000000000000 +00e09523988e7fc329838766b281bd7fb69c309ad297e4bb448b24987300000000000000b371cc1a1cc63287765fc57fa201acab4226eb0eef9f09db7597bba24e30e9f2163932632404071a761ccca4e82f17002c5c210c67a84c19e54f851ecc92f2c64d11963d6e578fc00b04000000000000 +00002020651f3cc28513829760bea3b2200ae241f9e40accce24c946ff00000000000000d7d8975fd5b11434b9da30bd260c38f6cb299c0325f8a768e9ba629ac1d65eecc638326340c9071a244afa9de72f1700988e7fc329838766b281bd7fb69c309ad297e4bb448b24987300000000000000 +00402520da691518357318693f616de1f0babf6b34371ec137af87792509000000000000963c6c97377dcb636ffc639f45149e4426202dcb859763b76749212759cfee368b383263b7b8081a51c2c843e62f1700651f3cc28513829760bea3b2200ae241f9e40accce24c946ff00000000000000 +00806529185865d490567b156352b7eb43f4f59180af2ab4b133cb2d9207000000000000c69775f99de92769b1f44621b6e26043bc189e2fc6c9c3f09a756c015f39942b803832634ea9091a5c849b9ee52f1700da691518357318693f616de1f0babf6b34371ec137af87792509000000000000 +00203a294eece9169a8f0db86adf420a3de212ee2244a1d32f4722c6fd08000000000000a832b6e470190bfae52a1be2b6df07dbfa257f0d25bb240e60dc579dad3c93a53c38326365d70a1a1f6a5b35e42f1700185865d490567b156352b7eb43f4f59180af2ab4b133cb2d9207000000000000 +00804722a1d568d262019e2243af4300fc1af8f2497ee0cebaec96359a01000000000000e8f276a2d092dfc7b40e770e505fa703eded4b7a4c1cbf1f1c1fafabc81a9ef63b383263dcf50b1a6c613843e32f17004eece9169a8f0db86adf420a3de212ee2244a1d32f4722c6fd08000000000000 +0000a02074e4c23787fee97132e65e248c964e7873815f2454cd81b66e08000000000000b405679be78b0efb6d1daf1bb58780e8fd511aed4c3663bb13f3ae9d7e7dc894e137326362590d1a799f5c36e22f1700a1d568d262019e2243af4300fc1af8f2497ee0cebaec96359a01000000000000 +00004a2a7e8b03824a4a69cfa2b90604ebadc84277f35cf4c33929081b08000000000000449e69839d5a562824e11fa9adeec7d49c6047f8c41955ab763949ba62376e47c3373263a9f70e1a6b607cbee12f170074e4c23787fee97132e65e248c964e7873815f2454cd81b66e08000000000000 +0000c02010a560d020ba10a5a9a36988e0285a23dae4e05b11b16962a011000000000000964dc09599930d6ccdd100654b248b7060b7aaf0096e3015a8d1dd3500ec617bbd373263afba101a2d150c31e02f17007e8b03824a4a69cfa2b90604ebadc84277f35cf4c33929081b08000000000000 +0000a0202394a89fda350d6bb6118ea6e5181065268657290a79bdcc5a0600000000000013bb61508f688ae1c35dad1c02773f931591e556502f61de13a922d5ad0a17a2a73732631d81121a073dcb26df2f170010a560d020ba10a5a9a36988e0285a23dae4e05b11b16962a011000000000000 +004049297a5593a4a8b842ad64224fd2d940476e4065113bcad94324db000000000000003ed6d0b9bedd85ef55af9dc8eb1f0b5dbe185f5c3297e54e48755c6b139578ff5b3732636bbd141a19decd16de2f17002394a89fda350d6bb6118ea6e5181065268657290a79bdcc5a06000000000000 +0000a020b4111bccf16d45beee60067632872c9c0d1c57c93d1cf0229114000000000000b2d7d7d4b0f69718864e63cd2607af5beec88f8ca7e03472ebe2ab5f43591db354373263883f171a2a417493dd2f17007a5593a4a8b842ad64224fd2d940476e4065113bcad94324db00000000000000 +0000602004f698ba3c69130aa7e7e3acca9e9c2b218a4b5afbe0f9c1d70200000000000060ed6e476ee4f48d26b4317390c551dc8c7ae4e244dabac65945e1b1e66d796c4d373263f5011a1a5a6e7e43dc2f1700b4111bccf16d45beee60067632872c9c0d1c57c93d1cf0229114000000000000 +000020209d773a41050db85505d53734677aba015d34c4be3da4a4bed0080000000000007ad4c0b468aa77d835057a06e90cda3bc667ebbbbfe76c0bacdea3a3022e30f63b37326382231d1a6b6f310adb2f170004f698ba3c69130aa7e7e3acca9e9c2b218a4b5afbe0f9c1d702000000000000 +0080762f8e96a57df55ad4899e5fc41896a1270c4fd9da26638549779d0d0000000000007cbd9eeeaed536e831478a570252aef97ce396bd40d80cd737a24551d44b69e031373263dfaa201a00726158da2f17009d773a41050db85505d53734677aba015d34c4be3da4a4bed008000000000000 +00006020f7684184ccc42bb102a6081da9276526ea5be042e22a20613807000000000000ca096a59949a32ce79dc57bacbd4733592d023c50a68bbe144a3d867dd66ceb92b3732633bab241a6196d0b0d92f17008e96a57df55ad4899e5fc41896a1270c4fd9da26638549779d0d000000000000 +00002020fb882c97836cd430bebcf78d7448ce47261d1dea29b2bb40cd1a0000000000003966d35137a3790b3f09b95487de9ac7536c148420dcc9b04506755724f619ca2b373263561f291a1c800c84d82f1700f7684184ccc42bb102a6081da9276526ea5be042e22a20613807000000000000 +00c0a92b045634400c9ed3f5020123e460e1ad3b11bb63b802b8b8066329000000000000976f396df4e401793cfb98fadb860bb7c4eb18ecf5db55c4d3f6ac1f1924235a27373263311f2e1a4c62a43ad72f1700fb882c97836cd430bebcf78d7448ce47261d1dea29b2bb40cd1a000000000000 +00401d34e65c3888a60d678f1d9af17b66c7b4820c45eddc0569aa630d25000000000000555b6d40f295a3eb658d1b9e6da87d4c04d68ef312342d52c3b8b4929245e05923373263acb4331a50b44fd1d62f1700045634400c9ed3f5020123e460e1ad3b11bb63b802b8b8066329000000000000 +0000c0206b69f9a6e713393a5b24c4e638b7d4b78f4202fd1f539501d51c000000000000dd2e127b1a522520f4d504b8ed488bfdfc2101f52f35d5135cd5112ea770df721c373263e50a3a1a42357b53d52f1700e65c3888a60d678f1d9af17b66c7b4820c45eddc0569aa630d25000000000000 +0000a020ef3bb1778c8c734befeec440da93f3aa44c61d2ddb544cc6fe400000000000001571ad277a1a050d8ef09b4b04b367992430ce0725df723371926071b9b2bfc01c373263fe1b411a702c543ed42f17006b69f9a6e713393a5b24c4e638b7d4b78f4202fd1f539501d51c000000000000 +0000a020563deb2aa7c3b18e8a5aaf6b141144b4842d7fc6a2c8bd28af3b0000000000002a26a01ca3afcb5b5de999bc9d0e1710fd7c5651fae5386119537608dfed45e219373263360e491a777ef9c9d32f1700ef3bb1778c8c734befeec440da93f3aa44c61d2ddb544cc6fe40000000000000 +0000c0208adb4bab588a717f22c48475e1adb97efc10e47cf2a6788dd31e0000000000007b5109dcc31ad987a6e72b9cca66a51771f44571cf5888ea1bea8a458975b31d17373263aed5511a3968aa50d22f1700563deb2aa7c3b18e8a5aaf6b141144b4842d7fc6a2c8bd28af3b000000000000 +00002020aacf7c3133e9a0aa822384996202c028e72b6d2cfc0c5ca87d250000000000004f3390dc3088ad6d712e6bcd2cc1c7b91550da09682e23262494598ba4e947920d373263e4d55b1a54cad9a2d12f17008adb4bab588a717f22c48475e1adb97efc10e47cf2a6788dd31e000000000000 +00e0ae275b24443c120d2d5de784d7a9f25601a6b2e8dccd99c93cb87b07000000000000477f2e07922173debee023529c3a14ca3a34e4b80731e252e09744ac4392848a0c37326399da661a386318a9d02f1700aacf7c3133e9a0aa822384996202c028e72b6d2cfc0c5ca87d25000000000000 +00008d2a934802996985b5c49feb330aa7613899d2cbc44237a0d4cb2b5b000000000000ff0ba4c0a69cd87c2b03ddfd15a0ff1ce28fc904d0974cc83e912c4d23412977003732630c5f731a4209d163cf2f17005b24443c120d2d5de784d7a9f25601a6b2e8dccd99c93cb87b07000000000000 +00008020d5102cc3e3c663123f63b999dd4f536b8208ea6b75d0beffa340000000000000dfc91c2d02e67eabdb83d0294f0f1fed129c99a8807fab02fb4738aeb4c8d61afc3632637d81001b197c5f6cce2f1700934802996985b5c49feb330aa7613899d2cbc44237a0d4cb2b5b000000000000 +0000c020452bdbfce21dad22f66cebcff045cad748c6ae037d90dafece50000000000000870abfba9b3574e554ebf18086b2cd7710ab0b580fe348181a54241d8db1de90fc3632635391001b8b679f9ccd2f1700d5102cc3e3c663123f63b999dd4f536b8208ea6b75d0beffa340000000000000 +00006020588bdfc423de07287e10540e5f07abbf702e0d5b29cc5a3668650000000000000958ab1c2b96207e945958ede80caebeade617c073e61ea18b0bb0595b0f0d6dfb3632631aa3001b42286a28cc2f1700452bdbfce21dad22f66cebcff045cad748c6ae037d90dafece50000000000000 +00400a2267d77ef04cae72a32d601e72f6e0d4dd0d69a87e05f666885b7b000000000000f81204f490a75df081d97eefef9e0a254e1ace1fc5f6c75a47dfc26af0c5a5b6fb36326309b7001b02f68dcbcb2f1700588bdfc423de07287e10540e5f07abbf702e0d5b29cc5a366865000000000000 +000080209c8da32d8a32d821b6df05658f0e9dc0e510d410d612a028df63000000000000f2b33a1179f276daf1f88a23f1aa2ed0e707c461f9f3839ef42533866a81e4cefa3632636ecd001b0aa969bcca2f170067d77ef04cae72a32d601e72f6e0d4dd0d69a87e05f666885b7b000000000000 +00605123fc0adecbcdac24d79d79c16f579a08949b7de45707a05f7098e3000000000000a5071cca7e1beb4b248a1878b5037ef623ca273260e35dbbdd1e58cbab044494f93632639ce6001b6536c9cac92f17009c8da32d8a32d821b6df05658f0e9dc0e510d410d612a028df63000000000000 +0000c020355e1f1093b2c7b77742e2db51712c183b8ca27331093a55d60b0000000000006f641c5c89f3f1d8460bfc745619caca6b3817314e4e833a91f6f56f8eb1b592f9363263d402011b8e6b4f3ac82f1700fc0adecbcdac24d79d79c16f579a08949b7de45707a05f7098e3000000000000 +00003921f0e1a857e26e787af7f9f92be62dc575c2c567c0da7c7b7400680000000000005ec6dbb228983b7f384ab082a8966e8f0fc2a130d5716549dc4d722fd2700a1af93632636d22011b4b30e88dc72f1700355e1f1093b2c7b77742e2db51712c183b8ca27331093a55d60b000000000000 +0000e020c609a2b51c459b841040a5e72a1417777535802dcf94ddc90e9b000000000000531b3291266370e3437274bac8218cbaeac7def2aee7b7a011eed0d410b28883f7363263e445011b09bac33cc62f1700f0e1a857e26e787af7f9f92be62dc575c2c567c0da7c7b740068000000000000 +0000e020e7f830a881f8a4a834140f27046c43a165b192ca293d0d9bdc7e000000000000faa858b5232ae81f5a62726caf6c96835d06377169f7a4c9da5fcfc294c1167bf6363263ca6d011b601b5b79c52f1700c609a2b51c459b841040a5e72a1417777535802dcf94ddc90e9b000000000000 +00c04120edc8a76f49f7e486fcfc8101fe8f1d331ace4ae87545efe4cf88000000000000ffa1b4e5166515228eda96e8aa5c7e91118d49765260c6b14a69a06fc28f5d2df63632638b9a011b4dbcd709c42f1700e7f830a881f8a4a834140f27046c43a165b192ca293d0d9bdc7e000000000000 +00603c237616b8acca05a0f5624a00888d53b0541352fe604d023bd7b6a100000000000090e3888edb29675d817858ec64b2446673d6ded4caf88a06dcbfb5fb58807ce4f5363263b0cc011b5cdd2793c32f1700edc8a76f49f7e486fcfc8101fe8f1d331ace4ae87545efe4cf88000000000000 +0000a020c7e7c0642c8d0097707c7a384235daef80afec048f0598475ca8000000000000ff3f3b4949f5ad7369ec6ec89881dbab25a102c46339dfaacd4eb5869b51f528f3363263dd04021b73486d07c22f17007616b8acca05a0f5624a00888d53b0541352fe604d023bd7b6a1000000000000 +00207529524c0e69d08175e0553ba78e329d367a751ffb6ba464dfdb467b0000000000002303b5c557b1a2ece774677de8d646444608a2528d119a18c6a8e7e23d779d12f13632631344021b62e9daf2c12f1700c7e7c0642c8d0097707c7a384235daef80afec048f0598475ca8000000000000 +000020207b7ce2d828faafede69af21d907fd2f8288176927c5fb0763dd40000000000000e8c13e6be39cd6d8829292a98a116d511d65310baa878f9609410a98aabee19f0363263e78a021b5b1c5edec02f1700524c0e69d08175e0553ba78e329d367a751ffb6ba464dfdb467b000000000000 +0080ed23de1122249dd5ba0ce8f4cb27d859dd5b83d3120012f3a530a6930000000000001bf7f826250df292f0951cd47c954fd5347c8e68a3abf7348e7a70ef8eb17b15ef36326375da021b469aaa13bf2f17007b7ce2d828faafede69af21d907fd2f8288176927c5fb0763dd4000000000000 +0040012c3e4c4dcf11b9b1829b88c17143806d632b3644ce0b2b3f007ac7000000000000dbfbc92019b2fae006a8be0610989e37288d6d40c0935357ad052540c6152bf5ee363263aa33031b72673325be2f1700de1122249dd5ba0ce8f4cb27d859dd5b83d3120012f3a530a693000000000000 +00600725af1e87a6c619b70a73300d0c7c1a1fe12aa099067c8ab567458a000000000000e871419b95d5030f5f949133555abdf37f832777ed52463b436b415fc2254b66ec363263f697031b3a8d1b0ebd2f17003e4c4dcf11b9b1829b88c17143806d632b3644ce0b2b3f007ac7000000000000 +0000602012fcd47a6ef474415cd954aae9d7f2ab2c3747bf4a11c6ffc6ce00000000000075f9f26595e4768cc1ef8c3206677a596b3ac7f4ce49de4a8577543160d3bfe8eb3632635308041b90b67931bc2f1700af1e87a6c619b70a73300d0c7c1a1fe12aa099067c8ab567458a000000000000 +000000200b22674a2196be7d22b7d2789f9cdd356f2e3a468a73f1eca6d6470e0000000007f6e7f5e446dd3778c3eef811447efb1a2b4d4a8a6a64626a95bb2814775effea3632639782041b7357a60fbb2f170012fcd47a6ef474415cd954aae9d7f2ab2c3747bf4a11c6ffc6ce000000000000 +0000002042f5828187a4932a162e908c03f1a75bc3e26e53b0f70c86b5000000000000002d360759ad4265fb6f4d22d3f3dc8e03eb2e911b87f5c2d79d47b45ef7434ab7d7363263ffff001d31a330ddba2f17000b22674a2196be7d22b7d2789f9cdd356f2e3a468a73f1eca6d6470e00000000 +0040012cf441993fd500888eb3bc40eabd1dcca5368a08b628c908d33f00000000000000c3ee936ebff90a84f8b38941c5c4c5c1c1bed65620bddecfc4abe6f335402c147ea73163202a011a3e6e1df1b92f170042f5828187a4932a162e908c03f1a75bc3e26e53b0f70c86b500000000000000 +0000602082ec22f4dbbfc3f445865c039b0ab1d4d7d13aff4b51db6bcd00000000000000c5b296f6f18c9e0bfdc83e9ca9ae69ea424e89d44f9b28450da5cb45241911ab2fa631639e3a011a57d42a0cb82f1700f441993fd500888eb3bc40eabd1dcca5368a08b628c908d33f00000000000000 +00e06923b28e20d44a1d3bbb3104af80b429a181300f31964c082d32f6bdd80d0000000002c094fe86bf86197bfb6db23f1a1d58675df8075d2163340a0bb2d787ed2383efa431633457011a230ea28bb72f170082ec22f4dbbfc3f445865c039b0ab1d4d7d13aff4b51db6bcd00000000000000 +000000206e06fb7a370867805fb281b25e9a7a442b8cefe420378584eb632919000000009e7e12e10596f65412c4cd332fc670e4ea5ee3bf8b51926fa4abd4e6eb2824905ba43163ffff001d60c92726b62f1700b28e20d44a1d3bbb3104af80b429a181300f31964c082d32f6bdd80d00000000 +00000020a7887bd4851f904fc2c6be02dace831712953a1f80053294650000000000000083e5f5a069c11218e17ee29d82f2bf8e5190edb90f8b1e3ac8cd2f9af7066c618d9f3163ffff001de32d070bb52f17006e06fb7a370867805fb281b25e9a7a442b8cefe420378584eb63291900000000 +00000020b30398715930088d465a0f7daa407847daf4abef67566bfdee0000000000000038d61b35c4456a22a2b4c8ca09ac8ca9579b8dc8315033b12049740d541c0919bd9a3163b32d011a908a3d4db42f1700a7887bd4851f904fc2c6be02dace831712953a1f800532946500000000000000 \ No newline at end of file diff --git a/BitcoinCashKit/BitcoinCashKit.xcodeproj/project.pbxproj b/BitcoinCashKit/BitcoinCashKit.xcodeproj/project.pbxproj deleted file mode 100644 index 26586e93..00000000 --- a/BitcoinCashKit/BitcoinCashKit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,741 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 32EC10538C0656CA1EFEB5A8 /* Pods_BitcoinCashKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90444CF4363A758C3E687DB6 /* Pods_BitcoinCashKitTests.framework */; }; - 3A5B14642269EF0800DF70E2 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA883AFCED343A6A93A4D /* GeneratedMocks.swift */; }; - 3A7A7D43226842CB0063D6AD /* BitcoinCashKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7A7D39226842CB0063D6AD /* BitcoinCashKit.framework */; }; - 3A7A7D4A226842CB0063D6AD /* BitcoinCashKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A7A7D3C226842CB0063D6AD /* BitcoinCashKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3A7A7D7C226843080063D6AD /* BitcoinCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7A7D7B226843080063D6AD /* BitcoinCore.framework */; }; - 58AAA0C46F354F901B5E7340 /* ForkValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF4B712339BE0A296D8B /* ForkValidatorTests.swift */; }; - 58AAA191811572F550B15C39 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA915B9A7B2CC60DFE579 /* Extensions.swift */; }; - 58AAA1DC0248B55348B2F28F /* CashAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA5CF51F86C46D478246 /* CashAddress.swift */; }; - 58AAA2047DCBDDF5B8E8C74E /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA52A0C026AFF6327EF38 /* TestData.swift */; }; - 58AAA44EAA5A88BD01DB5F99 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA05B23F61CC6B6057F2F /* Protocols.swift */; }; - 58AAA47795614383562E1602 /* BitcoinCashValidatorHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6A735B4CB392E2643A0 /* BitcoinCashValidatorHelperTests.swift */; }; - 58AAA4B27824731070116037 /* DAAValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE58DE2E10255CD84550 /* DAAValidator.swift */; }; - 58AAA52BC5EB99E129E1F686 /* BitcoinCashAddressSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA60C6D03771882923E33 /* BitcoinCashAddressSelector.swift */; }; - 58AAA56B03E2B4CD86EA7E6A /* EDAValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF54D4B5DE9187D77429 /* EDAValidatorTests.swift */; }; - 58AAA63D42A4BA25DE72F1BD /* BitcoinCoreCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE93C44463A5D9297539 /* BitcoinCoreCompatibility.swift */; }; - 58AAA6AB3292B8674FD56812 /* CashBech32AddressConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA729DEE6879404255A7C /* CashBech32AddressConverterTests.swift */; }; - 58AAA854EF8339A562A7F8BE /* CashAddrBech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA1FAAE1F663B3B657E91 /* CashAddrBech32.swift */; }; - 58AAAAD670B7A3FF24536C5D /* BitcoinCashBlockValidatorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFB6910240896F05DDCC /* BitcoinCashBlockValidatorHelper.swift */; }; - 58AAAB634B23381028A6C7CB /* MainNet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAAD5C6DDFC33925EE8C /* MainNet.swift */; }; - 58AAAD74B7FE72CAF43B79F6 /* ForkValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA7DC6022240F242A0314 /* ForkValidator.swift */; }; - 58AAAD9D0EB5FA1CBA66A950 /* BitcoinCashGrdbStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF413EE4D9619180856F /* BitcoinCashGrdbStorage.swift */; }; - 58AAADD7442B53496B179FF2 /* EDAValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6F67B9BCCEB9212EF94 /* EDAValidator.swift */; }; - 58AAADEDA8BDDCE00C8300D1 /* BitcoinCashKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA27CDC60669D4447320C /* BitcoinCashKit.swift */; }; - 58AAAEC09B3582BE2C6EAE3A /* TestNet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA600D45B8BDA5F96433F /* TestNet.swift */; }; - 58AAAF23A0B8737FA1CE3AED /* CashBech32AddressConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3F964711BDAEC84F01A /* CashBech32AddressConverter.swift */; }; - 58AAAF8A4D7F826B412A5FB9 /* DAAValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAADE9D67250202CFD7373 /* DAAValidatorTests.swift */; }; - 8D5016AF155D38EA06B5410A /* Pods_BitcoinCashKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE4ACF6EAB769A96F268B338 /* Pods_BitcoinCashKit.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 3A7A7D44226842CB0063D6AD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 3A7A7D30226842CB0063D6AD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3A7A7D38226842CB0063D6AD; - remoteInfo = BitcoinCashKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 0466F6031DB114C4DFF43268 /* Pods-BitcoinCashKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCashKit.release.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCashKit/Pods-BitcoinCashKit.release.xcconfig"; sourceTree = ""; }; - 3A7A7D39226842CB0063D6AD /* BitcoinCashKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BitcoinCashKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D3C226842CB0063D6AD /* BitcoinCashKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BitcoinCashKit.h; sourceTree = ""; }; - 3A7A7D3D226842CB0063D6AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7A7D42226842CB0063D6AD /* BitcoinCashKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitcoinCashKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D49226842CB0063D6AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7A7D7B226843080063D6AD /* BitcoinCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 58AAA05B23F61CC6B6057F2F /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; - 58AAA1FAAE1F663B3B657E91 /* CashAddrBech32.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CashAddrBech32.swift; sourceTree = ""; }; - 58AAA27CDC60669D4447320C /* BitcoinCashKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashKit.swift; sourceTree = ""; }; - 58AAA3F964711BDAEC84F01A /* CashBech32AddressConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CashBech32AddressConverter.swift; sourceTree = ""; }; - 58AAA52A0C026AFF6327EF38 /* TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = ""; }; - 58AAA600D45B8BDA5F96433F /* TestNet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNet.swift; sourceTree = ""; }; - 58AAA60C6D03771882923E33 /* BitcoinCashAddressSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashAddressSelector.swift; sourceTree = ""; }; - 58AAA6A735B4CB392E2643A0 /* BitcoinCashValidatorHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashValidatorHelperTests.swift; sourceTree = ""; }; - 58AAA6F67B9BCCEB9212EF94 /* EDAValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EDAValidator.swift; sourceTree = ""; }; - 58AAA729DEE6879404255A7C /* CashBech32AddressConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CashBech32AddressConverterTests.swift; sourceTree = ""; }; - 58AAA7DC6022240F242A0314 /* ForkValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForkValidator.swift; sourceTree = ""; }; - 58AAA883AFCED343A6A93A4D /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; - 58AAA915B9A7B2CC60DFE579 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; - 58AAAA5CF51F86C46D478246 /* CashAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CashAddress.swift; sourceTree = ""; }; - 58AAAAAD5C6DDFC33925EE8C /* MainNet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainNet.swift; sourceTree = ""; }; - 58AAADE9D67250202CFD7373 /* DAAValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAAValidatorTests.swift; sourceTree = ""; }; - 58AAAE58DE2E10255CD84550 /* DAAValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAAValidator.swift; sourceTree = ""; }; - 58AAAE93C44463A5D9297539 /* BitcoinCoreCompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCoreCompatibility.swift; sourceTree = ""; }; - 58AAAF413EE4D9619180856F /* BitcoinCashGrdbStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashGrdbStorage.swift; sourceTree = ""; }; - 58AAAF4B712339BE0A296D8B /* ForkValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForkValidatorTests.swift; sourceTree = ""; }; - 58AAAF54D4B5DE9187D77429 /* EDAValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EDAValidatorTests.swift; sourceTree = ""; }; - 58AAAFB6910240896F05DDCC /* BitcoinCashBlockValidatorHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashBlockValidatorHelper.swift; sourceTree = ""; }; - 75CCA88A27E1332974E5D7A3 /* Pods-BitcoinCashKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCashKitTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests.release.xcconfig"; sourceTree = ""; }; - 87EE956D42168423E986B1B0 /* Pods-BitcoinCashKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCashKitTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests.debug.xcconfig"; sourceTree = ""; }; - 90444CF4363A758C3E687DB6 /* Pods_BitcoinCashKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitcoinCashKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CE4ACF6EAB769A96F268B338 /* Pods_BitcoinCashKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitcoinCashKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - ECCC5F72BED2244491F2607F /* Pods-BitcoinCashKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCashKit.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCashKit/Pods-BitcoinCashKit.debug.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 3A7A7D36226842CB0063D6AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D7C226843080063D6AD /* BitcoinCore.framework in Frameworks */, - 8D5016AF155D38EA06B5410A /* Pods_BitcoinCashKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D3F226842CB0063D6AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D43226842CB0063D6AD /* BitcoinCashKit.framework in Frameworks */, - 32EC10538C0656CA1EFEB5A8 /* Pods_BitcoinCashKitTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3A7A7D2F226842CB0063D6AD = { - isa = PBXGroup; - children = ( - 3A7A7D3B226842CB0063D6AD /* BitcoinCashKit */, - 3A7A7D46226842CB0063D6AD /* BitcoinCashKitTests */, - 3A7A7D3A226842CB0063D6AD /* Products */, - 3A7A7D7A226843080063D6AD /* Frameworks */, - 7D371E0C229D49DD652623AC /* Pods */, - ); - sourceTree = ""; - }; - 3A7A7D3A226842CB0063D6AD /* Products */ = { - isa = PBXGroup; - children = ( - 3A7A7D39226842CB0063D6AD /* BitcoinCashKit.framework */, - 3A7A7D42226842CB0063D6AD /* BitcoinCashKitTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 3A7A7D3B226842CB0063D6AD /* BitcoinCashKit */ = { - isa = PBXGroup; - children = ( - 58AAAE4FC18678BDC8990B26 /* Blocks */, - 58AAA9B8B9C2CD7BE720AD34 /* Core */, - 58AAAE936FB46FD1053E8F21 /* InitialSync */, - 58AAA8763996C7D71EEDD2A7 /* Network */, - 58AAA71B1D2679E9B65B9B6B /* Storage */, - 3A7A7D3C226842CB0063D6AD /* BitcoinCashKit.h */, - 3A7A7D3D226842CB0063D6AD /* Info.plist */, - 58AAAFEA2E4DC8964564A738 /* Bech32 */, - ); - path = BitcoinCashKit; - sourceTree = ""; - }; - 3A7A7D46226842CB0063D6AD /* BitcoinCashKitTests */ = { - isa = PBXGroup; - children = ( - 58AAA5AC9CFF60A6BF7F0486 /* Blocks */, - 58AAA915B9A7B2CC60DFE579 /* Extensions.swift */, - 3A7A7D49226842CB0063D6AD /* Info.plist */, - 58AAA883AFCED343A6A93A4D /* GeneratedMocks.swift */, - 58AAA52A0C026AFF6327EF38 /* TestData.swift */, - 58AAAB41EE7B2188E931F7C9 /* Bech32 */, - ); - path = BitcoinCashKitTests; - sourceTree = ""; - }; - 3A7A7D7A226843080063D6AD /* Frameworks */ = { - isa = PBXGroup; - children = ( - 3A7A7D7B226843080063D6AD /* BitcoinCore.framework */, - CE4ACF6EAB769A96F268B338 /* Pods_BitcoinCashKit.framework */, - 90444CF4363A758C3E687DB6 /* Pods_BitcoinCashKitTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 58AAA5AC9CFF60A6BF7F0486 /* Blocks */ = { - isa = PBXGroup; - children = ( - 58AAAE09F6AC43DEF8015111 /* Validators */, - 58AAA6A735B4CB392E2643A0 /* BitcoinCashValidatorHelperTests.swift */, - ); - path = Blocks; - sourceTree = ""; - }; - 58AAA71B1D2679E9B65B9B6B /* Storage */ = { - isa = PBXGroup; - children = ( - 58AAAF413EE4D9619180856F /* BitcoinCashGrdbStorage.swift */, - ); - path = Storage; - sourceTree = ""; - }; - 58AAA8763996C7D71EEDD2A7 /* Network */ = { - isa = PBXGroup; - children = ( - 58AAA600D45B8BDA5F96433F /* TestNet.swift */, - 58AAAAAD5C6DDFC33925EE8C /* MainNet.swift */, - ); - path = Network; - sourceTree = ""; - }; - 58AAA9B8B9C2CD7BE720AD34 /* Core */ = { - isa = PBXGroup; - children = ( - 58AAA27CDC60669D4447320C /* BitcoinCashKit.swift */, - 58AAAE93C44463A5D9297539 /* BitcoinCoreCompatibility.swift */, - 58AAA05B23F61CC6B6057F2F /* Protocols.swift */, - ); - path = Core; - sourceTree = ""; - }; - 58AAA9D1E1510A0C67A9AC8D /* Validators */ = { - isa = PBXGroup; - children = ( - 58AAAE58DE2E10255CD84550 /* DAAValidator.swift */, - 58AAA6F67B9BCCEB9212EF94 /* EDAValidator.swift */, - 58AAA7DC6022240F242A0314 /* ForkValidator.swift */, - ); - path = Validators; - sourceTree = ""; - }; - 58AAAB41EE7B2188E931F7C9 /* Bech32 */ = { - isa = PBXGroup; - children = ( - 58AAA729DEE6879404255A7C /* CashBech32AddressConverterTests.swift */, - ); - path = Bech32; - sourceTree = ""; - }; - 58AAAE09F6AC43DEF8015111 /* Validators */ = { - isa = PBXGroup; - children = ( - 58AAAF54D4B5DE9187D77429 /* EDAValidatorTests.swift */, - 58AAADE9D67250202CFD7373 /* DAAValidatorTests.swift */, - 58AAAF4B712339BE0A296D8B /* ForkValidatorTests.swift */, - ); - path = Validators; - sourceTree = ""; - }; - 58AAAE4FC18678BDC8990B26 /* Blocks */ = { - isa = PBXGroup; - children = ( - 58AAA9D1E1510A0C67A9AC8D /* Validators */, - 58AAAFB6910240896F05DDCC /* BitcoinCashBlockValidatorHelper.swift */, - ); - path = Blocks; - sourceTree = ""; - }; - 58AAAE936FB46FD1053E8F21 /* InitialSync */ = { - isa = PBXGroup; - children = ( - 58AAA60C6D03771882923E33 /* BitcoinCashAddressSelector.swift */, - ); - path = InitialSync; - sourceTree = ""; - }; - 58AAAFEA2E4DC8964564A738 /* Bech32 */ = { - isa = PBXGroup; - children = ( - 58AAA3F964711BDAEC84F01A /* CashBech32AddressConverter.swift */, - 58AAA1FAAE1F663B3B657E91 /* CashAddrBech32.swift */, - 58AAAA5CF51F86C46D478246 /* CashAddress.swift */, - ); - path = Bech32; - sourceTree = ""; - }; - 7D371E0C229D49DD652623AC /* Pods */ = { - isa = PBXGroup; - children = ( - ECCC5F72BED2244491F2607F /* Pods-BitcoinCashKit.debug.xcconfig */, - 0466F6031DB114C4DFF43268 /* Pods-BitcoinCashKit.release.xcconfig */, - 87EE956D42168423E986B1B0 /* Pods-BitcoinCashKitTests.debug.xcconfig */, - 75CCA88A27E1332974E5D7A3 /* Pods-BitcoinCashKitTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 3A7A7D34226842CB0063D6AD /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D4A226842CB0063D6AD /* BitcoinCashKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 3A7A7D38226842CB0063D6AD /* BitcoinCashKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7A7D4D226842CB0063D6AD /* Build configuration list for PBXNativeTarget "BitcoinCashKit" */; - buildPhases = ( - D7FB58C104D5E2EA9D61AF01 /* [CP] Check Pods Manifest.lock */, - 3A7A7D34226842CB0063D6AD /* Headers */, - 3A7A7D35226842CB0063D6AD /* Sources */, - 3A7A7D36226842CB0063D6AD /* Frameworks */, - 3A7A7D37226842CB0063D6AD /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = BitcoinCashKit; - productName = BitcoinCashKit; - productReference = 3A7A7D39226842CB0063D6AD /* BitcoinCashKit.framework */; - productType = "com.apple.product-type.framework"; - }; - 3A7A7D41226842CB0063D6AD /* BitcoinCashKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7A7D50226842CB0063D6AD /* Build configuration list for PBXNativeTarget "BitcoinCashKitTests" */; - buildPhases = ( - 6F6C1117FBBEDEA789A19F5B /* [CP] Check Pods Manifest.lock */, - 3A5B14632269E76500DF70E2 /* Cuckoo */, - 3A7A7D3E226842CB0063D6AD /* Sources */, - 3A7A7D3F226842CB0063D6AD /* Frameworks */, - 3A7A7D40226842CB0063D6AD /* Resources */, - 30A8478C404EAE0AC899489B /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 3A7A7D45226842CB0063D6AD /* PBXTargetDependency */, - ); - name = BitcoinCashKitTests; - productName = BitcoinCashKitTests; - productReference = 3A7A7D42226842CB0063D6AD /* BitcoinCashKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 3A7A7D30226842CB0063D6AD /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1010; - LastUpgradeCheck = 1010; - ORGANIZATIONNAME = "Horizontal Systems"; - TargetAttributes = { - 3A7A7D38226842CB0063D6AD = { - CreatedOnToolsVersion = 10.1; - LastSwiftMigration = 1020; - }; - 3A7A7D41226842CB0063D6AD = { - CreatedOnToolsVersion = 10.1; - }; - }; - }; - buildConfigurationList = 3A7A7D33226842CB0063D6AD /* Build configuration list for PBXProject "BitcoinCashKit" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 3A7A7D2F226842CB0063D6AD; - productRefGroup = 3A7A7D3A226842CB0063D6AD /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 3A7A7D38226842CB0063D6AD /* BitcoinCashKit */, - 3A7A7D41226842CB0063D6AD /* BitcoinCashKitTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 3A7A7D37226842CB0063D6AD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D40226842CB0063D6AD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 30A8478C404EAE0AC899489B /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 3A5B14632269E76500DF70E2 /* Cuckoo */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/${PROJECT_NAME}/Core/Protocols.swift", - ); - name = Cuckoo; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Define output file. Change \"$PROJECT_DIR/${PROJECT_NAME}Tests\" to your test's root source folder, if it's not the default name.\nOUTPUT_FILE=\"$PROJECT_DIR/${PROJECT_NAME}Tests/GeneratedMocks.swift\"\necho \"Generated Mocks File = $OUTPUT_FILE\"\n\n# Define input directory. Change \"${PROJECT_DIR}/${PROJECT_NAME}\" to your project's root source folder, if it's not the default name.\nINPUT_DIR=\"${PROJECT_DIR}/${PROJECT_NAME}\"\necho \"Mocks Input Directory = $INPUT_DIR\"\n\n# Generate mock files, include as many input files as you'd like to create mocks for.\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"$PROJECT_NAME\" \\\n--output \"${OUTPUT_FILE}\"\n"; - }; - 6F6C1117FBBEDEA789A19F5B /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BitcoinCashKitTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - D7FB58C104D5E2EA9D61AF01 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BitcoinCashKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 3A7A7D35226842CB0063D6AD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 58AAADEDA8BDDCE00C8300D1 /* BitcoinCashKit.swift in Sources */, - 58AAAD9D0EB5FA1CBA66A950 /* BitcoinCashGrdbStorage.swift in Sources */, - 58AAA4B27824731070116037 /* DAAValidator.swift in Sources */, - 58AAADD7442B53496B179FF2 /* EDAValidator.swift in Sources */, - 58AAAAD670B7A3FF24536C5D /* BitcoinCashBlockValidatorHelper.swift in Sources */, - 58AAAEC09B3582BE2C6EAE3A /* TestNet.swift in Sources */, - 58AAAB634B23381028A6C7CB /* MainNet.swift in Sources */, - 58AAA52BC5EB99E129E1F686 /* BitcoinCashAddressSelector.swift in Sources */, - 58AAA44EAA5A88BD01DB5F99 /* Protocols.swift in Sources */, - 58AAA63D42A4BA25DE72F1BD /* BitcoinCoreCompatibility.swift in Sources */, - 58AAAF23A0B8737FA1CE3AED /* CashBech32AddressConverter.swift in Sources */, - 58AAA854EF8339A562A7F8BE /* CashAddrBech32.swift in Sources */, - 58AAA1DC0248B55348B2F28F /* CashAddress.swift in Sources */, - 58AAAD74B7FE72CAF43B79F6 /* ForkValidator.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D3E226842CB0063D6AD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A5B14642269EF0800DF70E2 /* GeneratedMocks.swift in Sources */, - 58AAA56B03E2B4CD86EA7E6A /* EDAValidatorTests.swift in Sources */, - 58AAAF8A4D7F826B412A5FB9 /* DAAValidatorTests.swift in Sources */, - 58AAA47795614383562E1602 /* BitcoinCashValidatorHelperTests.swift in Sources */, - 58AAA2047DCBDDF5B8E8C74E /* TestData.swift in Sources */, - 58AAA191811572F550B15C39 /* Extensions.swift in Sources */, - 58AAA6AB3292B8674FD56812 /* CashBech32AddressConverterTests.swift in Sources */, - 58AAA0C46F354F901B5E7340 /* ForkValidatorTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 3A7A7D45226842CB0063D6AD /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3A7A7D38226842CB0063D6AD /* BitcoinCashKit */; - targetProxy = 3A7A7D44226842CB0063D6AD /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 3A7A7D4B226842CB0063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 3A7A7D4C226842CB0063D6AD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 3A7A7D4E226842CB0063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = ECCC5F72BED2244491F2607F /* Pods-BitcoinCashKit.debug.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = HC4MCAXJ66; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = BitcoinCashKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinCashKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3A7A7D4F226842CB0063D6AD /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 0466F6031DB114C4DFF43268 /* Pods-BitcoinCashKit.release.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = HC4MCAXJ66; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = BitcoinCashKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinCashKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 3A7A7D51226842CB0063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 87EE956D42168423E986B1B0 /* Pods-BitcoinCashKitTests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HC4MCAXJ66; - INFOPLIST_FILE = BitcoinCashKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinCashKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3A7A7D52226842CB0063D6AD /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 75CCA88A27E1332974E5D7A3 /* Pods-BitcoinCashKitTests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HC4MCAXJ66; - INFOPLIST_FILE = BitcoinCashKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinCashKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 3A7A7D33226842CB0063D6AD /* Build configuration list for PBXProject "BitcoinCashKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D4B226842CB0063D6AD /* Debug */, - 3A7A7D4C226842CB0063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3A7A7D4D226842CB0063D6AD /* Build configuration list for PBXNativeTarget "BitcoinCashKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D4E226842CB0063D6AD /* Debug */, - 3A7A7D4F226842CB0063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3A7A7D50226842CB0063D6AD /* Build configuration list for PBXNativeTarget "BitcoinCashKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D51226842CB0063D6AD /* Debug */, - 3A7A7D52226842CB0063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 3A7A7D30226842CB0063D6AD /* Project object */; -} diff --git a/BitcoinCashKit/BitcoinCashKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKit.xcscheme b/BitcoinCashKit/BitcoinCashKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKit.xcscheme deleted file mode 100644 index 9722929a..00000000 --- a/BitcoinCashKit/BitcoinCashKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKit.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/BitcoinCashKit/BitcoinCashKit/BitcoinCashKit.h b/BitcoinCashKit/BitcoinCashKit/BitcoinCashKit.h deleted file mode 100644 index 06b7f7fc..00000000 --- a/BitcoinCashKit/BitcoinCashKit/BitcoinCashKit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// BitcoinCashKit.h -// BitcoinCashKit -// -// Created by Anton Stavnichiy on 4/18/19. -// Copyright © 2019 Horizontal Systems. All rights reserved. -// - -#import - -//! Project version number for BitcoinCashKit. -FOUNDATION_EXPORT double BitcoinCashKitVersionNumber; - -//! Project version string for BitcoinCashKit. -FOUNDATION_EXPORT const unsigned char BitcoinCashKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/BitcoinCashKit/BitcoinCashKit/Blocks/BitcoinCashBlockValidatorHelper.swift b/BitcoinCashKit/BitcoinCashKit/Blocks/BitcoinCashBlockValidatorHelper.swift deleted file mode 100644 index 81e5b0ab..00000000 --- a/BitcoinCashKit/BitcoinCashKit/Blocks/BitcoinCashBlockValidatorHelper.swift +++ /dev/null @@ -1,40 +0,0 @@ -import BitcoinCore - -class BitcoinCashBlockValidatorHelper: IBitcoinCashBlockValidatorHelper { - private let medianTimeSpan = 11 - private let bitcoinCashStorage: IBitcoinCashStorage - private let coreBlockValidatorHelper: IBlockValidatorHelperWrapper - - init(storage: IBitcoinCashStorage, coreBlockValidatorHelper: IBlockValidatorHelperWrapper) { - bitcoinCashStorage = storage - self.coreBlockValidatorHelper = coreBlockValidatorHelper - } - - func medianTimePast(block: Block) -> Int { - let startIndex = block.height - medianTimeSpan + 1 - var median = bitcoinCashStorage.timestamps(from: startIndex, to: block.height, ascending: true) - guard !median.isEmpty else { - return block.timestamp - } - - return median[median.count / 2] - } - - func suitableBlockIndex(for blocks: [Block]) -> Int? { // works just for 3 blocks - guard blocks.count == 3 else { - return nil - } - let suitableBlock = blocks.sorted(by: { $1.timestamp > $0.timestamp })[1] - - return blocks.firstIndex(where: { $0.height == suitableBlock.height }) - } - - func previous(for block: Block, count: Int) -> Block? { - return coreBlockValidatorHelper.previous(for: block, count: count) - } - - func previousWindow(for block: Block, count: Int) -> [Block]? { - return coreBlockValidatorHelper.previousWindow(for: block, count: count) - } - -} diff --git a/BitcoinCashKit/BitcoinCashKit/Core/BitcoinCashKit.swift b/BitcoinCashKit/BitcoinCashKit/Core/BitcoinCashKit.swift deleted file mode 100644 index 1b8af32b..00000000 --- a/BitcoinCashKit/BitcoinCashKit/Core/BitcoinCashKit.swift +++ /dev/null @@ -1,90 +0,0 @@ -import BitcoinCore -import HSHDWalletKit -import BigInt -import HSCryptoKit -import RxSwift - -public class BitcoinCashKit: AbstractKit { - private static let svChainForkHeight = 556767 // 2018 November 14 - private static let abcChainForkBlockHash = "0000000000000000004626ff6e3b936941d341c5932ece4357eeccac44e6d56c".reversedData! - - - private static let heightInterval = 144 // Blocks count in window for calculating difficulty ( BitcoinCash ) - private static let targetSpacing = 10 * 60 // Time to mining one block ( 10 min. same as Bitcoin ) - private static let maxTargetBits = 0x1d00ffff // Initially and max. target difficulty for blocks - - public static func clear() throws { - try DirectoryHelper.removeDirectory("BitcoinCashKit") - } - - public enum NetworkType { case mainNet, testNet } - - public weak var delegate: BitcoinCoreDelegate? { - didSet { - bitcoinCore.delegate = delegate - } - } - - private let storage: IBitcoinCashStorage - - public init(withWords words: [String], walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet, confirmationsThreshold: Int = 6, minLogLevel: Logger.Level = .verbose) throws { - let network: INetwork - let initialSyncApiUrl: String - - let validScheme: String - switch networkType { - case .mainNet: - network = MainNet() - initialSyncApiUrl = "https://blockdozer.com/api/" - validScheme = "bitcoincash" - case .testNet: - network = TestNet() - initialSyncApiUrl = "https://tbch.blockdozer.com/api/" - validScheme = "bchtest" - } - let initialSyncApi = InsightApi(url: initialSyncApiUrl) - - let databaseFilePath = try DirectoryHelper.directoryURL(for: "BitcoinCashKit").appendingPathComponent("\(walletId)-\(networkType)").path - let storage = BitcoinCashGrdbStorage(databaseFilePath: databaseFilePath) - self.storage = storage - - let paymentAddressParser = PaymentAddressParser(validScheme: validScheme, removeScheme: false) - let addressSelector = BitcoinCashAddressSelector() - - let bitcoinCore = try BitcoinCoreBuilder(minLogLevel: minLogLevel) - .set(network: network) - .set(initialSyncApi: initialSyncApi) - .set(words: words) - .set(paymentAddressParser: paymentAddressParser) - .set(addressSelector: addressSelector) - .set(walletId: walletId) - .set(confirmationsThreshold: confirmationsThreshold) - .set(peerSize: 10) - .set(syncMode: syncMode) - .set(storage: storage) - .build() - - super.init(bitcoinCore: bitcoinCore, network: network) - - // extending BitcoinCore - let bech32 = CashBech32AddressConverter(prefix: network.bech32PrefixPattern) - bitcoinCore.prepend(addressConverter: bech32) - - let coreBlockHelper = BlockValidatorHelper(storage: storage) - let blockHelper = BitcoinCashBlockValidatorHelper(storage: storage, coreBlockValidatorHelper: coreBlockHelper) - let difficultyEncoder = DifficultyEncoder() - - let daaValidator = DAAValidator(encoder: difficultyEncoder, blockHelper: blockHelper, targetSpacing: BitcoinCashKit.targetSpacing, heightInterval: BitcoinCashKit.heightInterval, firstCheckpointHeight: network.lastCheckpointBlock.height) - - switch networkType { - case .mainNet: - bitcoinCore.add(blockValidator: ForkValidator(concreteValidator: daaValidator, forkHeight: BitcoinCashKit.svChainForkHeight, expectedBlockHash: BitcoinCashKit.abcChainForkBlockHash)) - bitcoinCore.add(blockValidator: daaValidator) - bitcoinCore.add(blockValidator: LegacyDifficultyAdjustmentValidator(encoder: difficultyEncoder, blockValidatorHelper: coreBlockHelper, heightInterval: BitcoinCore.heightInterval, targetTimespan: BitcoinCore.targetSpacing * BitcoinCore.heightInterval, maxTargetBits: BitcoinCore.maxTargetBits)) - bitcoinCore.add(blockValidator: EDAValidator(encoder: difficultyEncoder, blockHelper: blockHelper, maxTargetBits: BitcoinCore.maxTargetBits, firstCheckpointHeight: network.bip44CheckpointBlock.height)) - case .testNet: () - // not use test validators - } - } - -} diff --git a/BitcoinCashKit/BitcoinCashKit/Info.plist b/BitcoinCashKit/BitcoinCashKit/Info.plist deleted file mode 100644 index e1fe4cfb..00000000 --- a/BitcoinCashKit/BitcoinCashKit/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/BitcoinCashKit/BitcoinCashKit/InitialSync/BitcoinCashAddressSelector.swift b/BitcoinCashKit/BitcoinCashKit/InitialSync/BitcoinCashAddressSelector.swift deleted file mode 100644 index e22758b6..00000000 --- a/BitcoinCashKit/BitcoinCashKit/InitialSync/BitcoinCashAddressSelector.swift +++ /dev/null @@ -1,10 +0,0 @@ -import BitcoinCore - -class BitcoinCashAddressSelector: IAddressSelector { - - func getAddressVariants(addressConverter: IAddressConverter, publicKey: PublicKey) -> [String] { - let legacyAddress = (try? addressConverter.convert(keyHash: publicKey.keyHash, type: .p2pkh))?.stringValue - return [legacyAddress].compactMap { $0 } - } - -} diff --git a/BitcoinCashKit/BitcoinCashKit/Network/MainNet.swift b/BitcoinCashKit/BitcoinCashKit/Network/MainNet.swift deleted file mode 100644 index ecfd3764..00000000 --- a/BitcoinCashKit/BitcoinCashKit/Network/MainNet.swift +++ /dev/null @@ -1,52 +0,0 @@ -import BitcoinCore - -class MainNet: INetwork { - - let name = "bitcoin-cash-main-net" - - let maxBlockSize: UInt32 = 32 * 1024 * 1024 - let pubKeyHash: UInt8 = 0x00 - let privateKey: UInt8 = 0x80 - let scriptHash: UInt8 = 0x05 - let bech32PrefixPattern: String = "bitcoincash" - let xPubKey: UInt32 = 0x0488b21e - let xPrivKey: UInt32 = 0x0488ade4 - let magic: UInt32 = 0xe3e1f3e8 - let port: UInt32 = 8333 - let coinType: UInt32 = 0 - let sigHash: SigHashType = .bitcoinCashAll - var syncableFromApi: Bool = true - - let dnsSeeds = [ - "seed.bitcoinabc.org", - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 2, - headerHash: "00000000000000003decdbb5f3811eab3148fbc29d3610528eb3b50d9ee5723f".reversedData!, - previousBlockHeaderHash: "00000000000000006bcf448b771c8f4db4e2ca653474e3b29504ec08422b3fba".reversedData!, - merkleRoot: "4ea18e999a57fc55fb390558dbb88a7b9c55c71c7de4cec160c045802ee587d2".reversedData!, - timestamp: 1397755646, - bits: 419470732, - nonce: 2160181286 - ), - height: 296352) - } - - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 0x2000e000, - headerHash: "00000000000000000040f26002e04126dc84700d6f82c0785efab2293080fe68".reversedData!, - previousBlockHeaderHash: "000000000000000002a1f5acfab47e5e1afcac9f50eb9b7c875e6c736d099763".reversedData!, - merkleRoot: "e6a8e517f708d294f426895c255cfd0a443d7f55a768b04398eadde0c516027c".reversedData!, - timestamp: 1559650598, - bits: 0x1803769a, - nonce: 0xed7bb8ff - ), - height: 585504) - } - -} diff --git a/BitcoinCashKit/BitcoinCashKit/Network/TestNet.swift b/BitcoinCashKit/BitcoinCashKit/Network/TestNet.swift deleted file mode 100644 index ddb78bdb..00000000 --- a/BitcoinCashKit/BitcoinCashKit/Network/TestNet.swift +++ /dev/null @@ -1,52 +0,0 @@ -import BitcoinCore - -class TestNet: INetwork { - - let name = "bitcoin-cash-test-net" - - let maxBlockSize: UInt32 = 32 * 1024 * 1024 - let pubKeyHash: UInt8 = 0x6f - let privateKey: UInt8 = 0xef - let scriptHash: UInt8 = 0xc4 - let bech32PrefixPattern: String = "bchtest" - let xPubKey: UInt32 = 0x043587cf - let xPrivKey: UInt32 = 0x04358394 - let magic: UInt32 = 0xf4e5f3f4 - let port: UInt32 = 18333 - let coinType: UInt32 = 1 - let sigHash: SigHashType = .bitcoinCashAll - var syncableFromApi: Bool = true - - let dnsSeeds = [ - "testnet-seed.bitcoinabc.org", - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 2, - headerHash: "000000000000bbde3a83bd29bc5cacd73f039f345318e7a4088914342c9d259a".reversedData!, - previousBlockHeaderHash: "0000000003dc49f7472f960eedb4fb2d1ccc8b0530ca6c75ed2bba9718b6f297".reversedData!, - merkleRoot: "a60fdbc889976c573450e9f78f1c330e374968a54f294e427180da1e9a07806b".reversedData!, - timestamp: 1393645018, - bits: 0x1c0180ab, - nonce: 634051227 - ), - height: 199584) - } - - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 0x20000000, - headerHash: "000000000000058417bfcbfaa5bd7c0449743d9a386331db58e4453bc77ae536".reversedData!, - previousBlockHeaderHash: "000000000000041abedc84c2ab85f72febbee655ed9d1dfdc9497126026e1bba".reversedData!, - merkleRoot: "cccf617e3ab704923dd45399649e7a5be11aa71ce344b7099b580c9d85445948".reversedData!, - timestamp: 1559627940, - bits: 0x1a065b0f, - nonce: 1911921100 - ), - height: 1307081) - } - -} diff --git a/BitcoinCashKit/BitcoinCashKit/Storage/BitcoinCashGrdbStorage.swift b/BitcoinCashKit/BitcoinCashKit/Storage/BitcoinCashGrdbStorage.swift deleted file mode 100644 index c1a10b1f..00000000 --- a/BitcoinCashKit/BitcoinCashKit/Storage/BitcoinCashGrdbStorage.swift +++ /dev/null @@ -1,26 +0,0 @@ -import GRDB -import BitcoinCore - -class BitcoinCashGrdbStorage: GrdbStorage { -} - -extension BitcoinCashGrdbStorage: IBitcoinCashStorage { - - func timestamps(from startHeight: Int, to endHeight: Int, ascending: Bool) -> [Int] { - return try! dbPool.read { db in - var timestamps = [Int]() - - let sql = "SELECT blocks.timestamp FROM blocks WHERE blocks.height >= \(startHeight) AND blocks.height <= \(endHeight) ORDER BY blocks.timestamp \(ascending ? "ASC" : "DESC")" - let rows = try Row.fetchCursor(db, sql: sql) - - while let row = try rows.next() { - if let timestamp = Int.fromDatabaseValue(row["timestamp"]) { - timestamps.append(timestamp) - } - } - - return timestamps - } - } - -} diff --git a/BitcoinCashKit/BitcoinCashKitTests/Extensions.swift b/BitcoinCashKit/BitcoinCashKitTests/Extensions.swift deleted file mode 100644 index 9e78e070..00000000 --- a/BitcoinCashKit/BitcoinCashKitTests/Extensions.swift +++ /dev/null @@ -1,9 +0,0 @@ -import BitcoinCore - -extension Block: Equatable { - - public static func ==(lhs: Block, rhs: Block) -> Bool { - return lhs.headerHash == rhs.headerHash - } - -} diff --git a/BitcoinCashKit/BitcoinCashKit/Bech32/CashAddrBech32.swift b/BitcoinCashKit/Classes/Bech32/CashAddrBech32.swift similarity index 99% rename from BitcoinCashKit/BitcoinCashKit/Bech32/CashAddrBech32.swift rename to BitcoinCashKit/Classes/Bech32/CashAddrBech32.swift index af4bd108..26b662e7 100644 --- a/BitcoinCashKit/BitcoinCashKit/Bech32/CashAddrBech32.swift +++ b/BitcoinCashKit/Classes/Bech32/CashAddrBech32.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit class CashAddrBech32 { private static let base32Alphabets = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" diff --git a/BitcoinCashKit/BitcoinCashKit/Bech32/CashAddress.swift b/BitcoinCashKit/Classes/Bech32/CashAddress.swift similarity index 76% rename from BitcoinCashKit/BitcoinCashKit/Bech32/CashAddress.swift rename to BitcoinCashKit/Classes/Bech32/CashAddress.swift index 3c445a7e..77565383 100644 --- a/BitcoinCashKit/BitcoinCashKit/Bech32/CashAddress.swift +++ b/BitcoinCashKit/Classes/Bech32/CashAddress.swift @@ -13,6 +13,13 @@ public class CashAddress: Address, Equatable { } } + public var lockingScript: Data { + switch type { + case .pubKeyHash: return OpCode.p2pkhStart + OpCode.push(keyHash) + OpCode.p2pkhFinish + case .scriptHash: return OpCode.p2shStart + OpCode.push(keyHash) + OpCode.p2shFinish + } + } + public init(type: AddressType, keyHash: Data, cashAddrBech32: String, version: UInt8) { self.type = type self.keyHash = keyHash diff --git a/BitcoinCashKit/BitcoinCashKit/Bech32/CashBech32AddressConverter.swift b/BitcoinCashKit/Classes/Bech32/CashBech32AddressConverter.swift similarity index 94% rename from BitcoinCashKit/BitcoinCashKit/Bech32/CashBech32AddressConverter.swift rename to BitcoinCashKit/Classes/Bech32/CashBech32AddressConverter.swift index 26784f53..acd9246a 100644 --- a/BitcoinCashKit/BitcoinCashKit/Bech32/CashBech32AddressConverter.swift +++ b/BitcoinCashKit/Classes/Bech32/CashBech32AddressConverter.swift @@ -57,4 +57,8 @@ public class CashBech32AddressConverter: IAddressConverter { return CashAddress(type: addressType, keyHash: keyHash, cashAddrBech32: bech32, version: versionByte) } + public func convert(publicKey: PublicKey, type: ScriptType) throws -> Address { + return try convert(keyHash: publicKey.keyHash, type: type) + } + } diff --git a/BitcoinCashKit/Classes/Blocks/BitcoinCashBlockValidatorHelper.swift b/BitcoinCashKit/Classes/Blocks/BitcoinCashBlockValidatorHelper.swift new file mode 100644 index 00000000..83df09aa --- /dev/null +++ b/BitcoinCashKit/Classes/Blocks/BitcoinCashBlockValidatorHelper.swift @@ -0,0 +1,27 @@ +import BitcoinCore + +class BitcoinCashBlockValidatorHelper: IBitcoinCashBlockValidatorHelper { + private let coreBlockValidatorHelper: IBlockValidatorHelperWrapper + + init(coreBlockValidatorHelper: IBlockValidatorHelperWrapper) { + self.coreBlockValidatorHelper = coreBlockValidatorHelper + } + + func suitableBlockIndex(for blocks: [Block]) -> Int? { // works just for 3 blocks + guard blocks.count == 3 else { + return nil + } + let suitableBlock = blocks.sorted(by: { $1.timestamp > $0.timestamp })[1] + + return blocks.firstIndex(where: { $0.height == suitableBlock.height }) + } + + func previous(for block: Block, count: Int) -> Block? { + coreBlockValidatorHelper.previous(for: block, count: count) + } + + func previousWindow(for block: Block, count: Int) -> [Block]? { + coreBlockValidatorHelper.previousWindow(for: block, count: count) + } + +} diff --git a/BitcoinCashKit/Classes/Blocks/Validators/ASERTValidator.swift b/BitcoinCashKit/Classes/Blocks/Validators/ASERTValidator.swift new file mode 100644 index 00000000..6831d0d8 --- /dev/null +++ b/BitcoinCashKit/Classes/Blocks/Validators/ASERTValidator.swift @@ -0,0 +1,73 @@ +import BitcoinCore +import BigInt + +class ASERTValidator: IBlockChainedValidator, IBitcoinCashBlockValidator { + private let anchorBlockHeight = 661647 + private let anchorParentBlockTime = 1605447844 // 2020 November 15, 14:13 GMT + private let anchorBlockBits = 0x1804dafe + private let anchorBlockTarget: BigInt + + private let idealBlockTime = 600 + private let halfLife = 172800 // 2 days (in seconds) on mainnet + private let radix: BigInt = 65536 // pow(2, 16) , 16 bits for decimal part of fixed-point integer arithmetic + private let maxBits = 0x1d00ffff // maximum target in bits representation + private let maxTarget: BigInt // maximum target as integer + + private let difficultyEncoder: IDifficultyEncoder + + init(encoder: IDifficultyEncoder) { + difficultyEncoder = encoder + maxTarget = difficultyEncoder.decodeCompact(bits: maxBits) + anchorBlockTarget = difficultyEncoder.decodeCompact(bits: anchorBlockBits) + } + + func nextTarget(timestamp: Int, height: Int) -> Int { + let timeDelta = timestamp - anchorParentBlockTime + let heightDelta = height - anchorBlockHeight + + var exponent = timeDelta - idealBlockTime * (heightDelta + 1) + exponent <<= 16 + exponent /= halfLife + + let numShifts = exponent >> 16 + + exponent -= numShifts << 16 + let bigIntExponent = BigInt(exponent) + + var factor = BigInt(195766423245049) * bigIntExponent + + BigInt(971821376) * bigIntExponent.power(2) + + BigInt(5127) * bigIntExponent.power(3) + + BigInt(2).power(47) + + factor >>= 48 + factor += radix + var nextTarget = anchorBlockTarget * factor + + if numShifts < 0 { + nextTarget >>= abs(numShifts) + } else { + nextTarget <<= numShifts + } + + nextTarget >>= 16 + if nextTarget == 0 { + return difficultyEncoder.encodeCompact(from: 1) + } + if nextTarget > maxTarget { + return maxBits + } + + return difficultyEncoder.encodeCompact(from: nextTarget) + } + + func validate(block: Block, previousBlock: Block) throws { + guard nextTarget(timestamp: previousBlock.timestamp, height: previousBlock.height) == block.bits else { + throw BitcoinCoreErrors.BlockValidation.notEqualBits + } + } + + func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { + previousBlock.height >= anchorBlockHeight + } + +} diff --git a/BitcoinCashKit/BitcoinCashKit/Blocks/Validators/DAAValidator.swift b/BitcoinCashKit/Classes/Blocks/Validators/DAAValidator.swift similarity index 78% rename from BitcoinCashKit/BitcoinCashKit/Blocks/Validators/DAAValidator.swift rename to BitcoinCashKit/Classes/Blocks/Validators/DAAValidator.swift index 751ddd89..3e54e7a3 100644 --- a/BitcoinCashKit/BitcoinCashKit/Blocks/Validators/DAAValidator.swift +++ b/BitcoinCashKit/Classes/Blocks/Validators/DAAValidator.swift @@ -1,30 +1,24 @@ import BitcoinCore import BigInt -class DAAValidator: IBlockValidator { +class DAAValidator: IBlockChainedValidator, IBitcoinCashBlockValidator { private let largestHash = BigInt(1) << 256 - private let consensusDaaForkHeight = 504030 // 2017 November 13, 14:06 GMT + private let consensusDaaForkHeight = 504031 // 2017 November 13, 14:06 GMT private let difficultyEncoder: IDifficultyEncoder private let blockHelper: IBitcoinCashBlockValidatorHelper private let targetSpacing: Int private let heightInterval: Int - private let firstCheckpointHeight: Int - init(encoder: IDifficultyEncoder, blockHelper: IBitcoinCashBlockValidatorHelper, targetSpacing: Int, heightInterval: Int, firstCheckpointHeight: Int) { + init(encoder: IDifficultyEncoder, blockHelper: IBitcoinCashBlockValidatorHelper, targetSpacing: Int, heightInterval: Int) { difficultyEncoder = encoder self.blockHelper = blockHelper self.targetSpacing = targetSpacing self.heightInterval = heightInterval - self.firstCheckpointHeight = firstCheckpointHeight } func validate(block: Block, previousBlock: Block) throws { - guard previousBlock.height >= firstCheckpointHeight + self.heightInterval + 4 else { - return // we must trust first 147 blocks from checkpoint, because can't calculate it's bits - } - var blocks = blockHelper.previousWindow(for: previousBlock, count: 146) ?? [Block]() // get all blocks without previousBlock needed for found suitable and range window guard !blocks.isEmpty else { @@ -65,7 +59,7 @@ class DAAValidator: IBlockValidator { } func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return previousBlock.height >= consensusDaaForkHeight // https://news.bitcoin.com/bitcoin-cash-network-completes-a-successful-hard-fork/ + previousBlock.height >= consensusDaaForkHeight // https://news.bitcoin.com/bitcoin-cash-network-completes-a-successful-hard-fork/ } } diff --git a/BitcoinCashKit/BitcoinCashKit/Blocks/Validators/EDAValidator.swift b/BitcoinCashKit/Classes/Blocks/Validators/EDAValidator.swift similarity index 70% rename from BitcoinCashKit/BitcoinCashKit/Blocks/Validators/EDAValidator.swift rename to BitcoinCashKit/Classes/Blocks/Validators/EDAValidator.swift index fe4aa0a9..be206ffe 100644 --- a/BitcoinCashKit/BitcoinCashKit/Blocks/Validators/EDAValidator.swift +++ b/BitcoinCashKit/Classes/Blocks/Validators/EDAValidator.swift @@ -1,25 +1,29 @@ import BitcoinCore import BigInt -class EDAValidator: IBlockValidator { +class EDAValidator { private let difficultyEncoder: IBitcoinCashDifficultyEncoder private let blockHelper: IBitcoinCashBlockValidatorHelper + private let blockMedianTimeHelper: IBitcoinCashBlockMedianTimeHelper private let maxTargetBits: Int - private let firstCheckpointHeight: Int - init(encoder: IBitcoinCashDifficultyEncoder, blockHelper: IBitcoinCashBlockValidatorHelper, maxTargetBits: Int, firstCheckpointHeight: Int) { + init(encoder: IBitcoinCashDifficultyEncoder, blockHelper: IBitcoinCashBlockValidatorHelper, blockMedianTimeHelper: IBitcoinCashBlockMedianTimeHelper, maxTargetBits: Int) { difficultyEncoder = encoder self.blockHelper = blockHelper + self.blockMedianTimeHelper = blockMedianTimeHelper self.maxTargetBits = maxTargetBits - self.firstCheckpointHeight = firstCheckpointHeight } - func validate(block: Block, previousBlock: Block) throws { - guard previousBlock.height >= firstCheckpointHeight + 6 else { - return // we must trust first 6 blocks from checkpoint, because can't calculate it's bits - } + private func medianTimePast(block: Block) -> Int { + blockMedianTimeHelper.medianTimePast(block: block) ?? block.height + } +} + +extension EDAValidator: IBlockChainedValidator { + + func validate(block: Block, previousBlock: Block) throws { if previousBlock.bits == maxTargetBits { if block.bits != maxTargetBits { throw BitcoinCoreErrors.BlockValidation.notEqualBits @@ -29,7 +33,7 @@ class EDAValidator: IBlockValidator { guard let cursorBlock = blockHelper.previous(for: previousBlock, count: 6) else { throw BitcoinCoreErrors.BlockValidation.noPreviousBlock } - let mpt6blocks = blockHelper.medianTimePast(block: previousBlock) - blockHelper.medianTimePast(block: cursorBlock) + let mpt6blocks = medianTimePast(block: previousBlock) - medianTimePast(block: cursorBlock) if(mpt6blocks >= 12 * 3600) { let decodedBits = difficultyEncoder.decodeCompact(bits: previousBlock.bits) let pow = decodedBits >> 2 @@ -47,7 +51,7 @@ class EDAValidator: IBlockValidator { } func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return true + true } } diff --git a/BitcoinCashKit/BitcoinCashKit/Blocks/Validators/ForkValidator.swift b/BitcoinCashKit/Classes/Blocks/Validators/ForkValidator.swift similarity index 83% rename from BitcoinCashKit/BitcoinCashKit/Blocks/Validators/ForkValidator.swift rename to BitcoinCashKit/Classes/Blocks/Validators/ForkValidator.swift index 93e1d414..6c49ffa6 100644 --- a/BitcoinCashKit/BitcoinCashKit/Blocks/Validators/ForkValidator.swift +++ b/BitcoinCashKit/Classes/Blocks/Validators/ForkValidator.swift @@ -1,6 +1,6 @@ import BitcoinCore -class ForkValidator: IBlockValidator { +class ForkValidator: IBlockChainedValidator { private let concreteValidator: IBitcoinCashBlockValidator private let forkHeight: Int private let expectedBlockHash: Data @@ -12,7 +12,7 @@ class ForkValidator: IBlockValidator { } func validate(block: Block, previousBlock: Block) throws { - if block.height == forkHeight, block.headerHash != expectedBlockHash { + if block.headerHash != expectedBlockHash { throw BitcoinCoreErrors.BlockValidation.wrongHeaderHash } @@ -20,7 +20,7 @@ class ForkValidator: IBlockValidator { } func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return true + block.height == forkHeight } } diff --git a/BitcoinCashKit/BitcoinCashKit/Core/BitcoinCoreCompatibility.swift b/BitcoinCashKit/Classes/Core/BitcoinCoreCompatibility.swift similarity index 67% rename from BitcoinCashKit/BitcoinCashKit/Core/BitcoinCoreCompatibility.swift rename to BitcoinCashKit/Classes/Core/BitcoinCoreCompatibility.swift index a518214b..f65011db 100644 --- a/BitcoinCashKit/BitcoinCashKit/Core/BitcoinCoreCompatibility.swift +++ b/BitcoinCashKit/Classes/Core/BitcoinCoreCompatibility.swift @@ -2,4 +2,4 @@ import BitcoinCore extension DifficultyEncoder: IBitcoinCashDifficultyEncoder {} extension BlockValidatorHelper: IBlockValidatorHelperWrapper {} -extension DAAValidator: IBitcoinCashBlockValidator {} +extension BlockMedianTimeHelper: IBitcoinCashBlockMedianTimeHelper {} diff --git a/BitcoinCashKit/Classes/Core/Kit.swift b/BitcoinCashKit/Classes/Core/Kit.swift new file mode 100644 index 00000000..385e74a9 --- /dev/null +++ b/BitcoinCashKit/Classes/Core/Kit.swift @@ -0,0 +1,130 @@ +import BitcoinCore +import HdWalletKit +import BigInt +import RxSwift +import HsToolKit + +public class Kit: AbstractKit { + private static let name = "BitcoinCashKit" + private static let svChainForkHeight = 556767 // 2018 November 14 + private static let bchnChainForkHeight = 661648 // 2020 November 15, 14:13 GMT + private static let abcChainForkBlockHash = "0000000000000000004626ff6e3b936941d341c5932ece4357eeccac44e6d56c".reversedData! + private static let bchnChainForkBlockHash = "0000000000000000029e471c41818d24b8b74c911071c4ef0b4a0509f9b5a8ce".reversedData! + + private static let legacyHeightInterval = 2016 // Default block count in difficulty change circle ( Bitcoin ) + private static let legacyTargetSpacing = 10 * 60 // Time to mining one block ( 10 min. Bitcoin ) + private static let legacyMaxTargetBits = 0x1d00ffff // Initially and max. target difficulty for blocks + + private static let heightInterval = 144 // Blocks count in window for calculating difficulty ( BitcoinCash ) + private static let targetSpacing = 10 * 60 // Time to mining one block ( 10 min. same as Bitcoin ) + private static let maxTargetBits = 0x1d00ffff // Initially and max. target difficulty for blocks + + public enum NetworkType { + case mainNet(coinType: CoinType) + case testNet + + var description: String { + switch self { + case .mainNet(let coinType): + switch coinType { + case .type0: return "mainNet" // back compatibility for database file name in old NetworkType + case .type145: return "mainNet-145" + } + case .testNet: + return "testNet" + } + } + } + + public weak var delegate: BitcoinCoreDelegate? { + didSet { + bitcoinCore.delegate = delegate + } + } + + public init(seed: Data, walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet(coinType: .type145), confirmationsThreshold: Int = 6, logger: Logger?) throws { + let network: INetwork + let initialSyncApiUrl: String + + let validScheme: String + switch networkType { + case .mainNet(let coinType): + network = MainNet(coinType: coinType) + initialSyncApiUrl = "https://api.haskoin.com/bch/blockchain" + validScheme = "bitcoincash" + case .testNet: + network = TestNet() + initialSyncApiUrl = "https://api.haskoin.com/bchtest/blockchain" + validScheme = "bchtest" + } + + let logger = logger ?? Logger(minLogLevel: .verbose) + + let initialSyncApi = BlockchainComApi(url: initialSyncApiUrl, hsUrl: "https://api.blocksdecoded.com/v1/blockchains/bitcoin-cash", logger: logger) + + let databaseFilePath = try DirectoryHelper.directoryURL(for: Kit.name).appendingPathComponent(Kit.databaseFileName(walletId: walletId, networkType: networkType, syncMode: syncMode)).path + let storage = GrdbStorage(databaseFilePath: databaseFilePath) + let paymentAddressParser = PaymentAddressParser(validScheme: validScheme, removeScheme: false) + + let difficultyEncoder = DifficultyEncoder() + + let blockValidatorSet = BlockValidatorSet() + blockValidatorSet.add(blockValidator: ProofOfWorkValidator(difficultyEncoder: difficultyEncoder)) + + let blockValidatorChain = BlockValidatorChain() + let coreBlockHelper = BlockValidatorHelper(storage: storage) + let blockHelper = BitcoinCashBlockValidatorHelper(coreBlockValidatorHelper: coreBlockHelper) + + let daaValidator = DAAValidator(encoder: difficultyEncoder, blockHelper: blockHelper, targetSpacing: Kit.targetSpacing, heightInterval: Kit.heightInterval) + let asertValidator = ASERTValidator(encoder: difficultyEncoder) + + switch networkType { + case .mainNet: + blockValidatorChain.add(blockValidator: ForkValidator(concreteValidator: asertValidator, forkHeight: Kit.bchnChainForkHeight, expectedBlockHash: Kit.bchnChainForkBlockHash)) + blockValidatorChain.add(blockValidator: asertValidator) + blockValidatorChain.add(blockValidator: ForkValidator(concreteValidator: daaValidator, forkHeight: Kit.svChainForkHeight, expectedBlockHash: Kit.abcChainForkBlockHash)) + blockValidatorChain.add(blockValidator: daaValidator) + blockValidatorChain.add(blockValidator: LegacyDifficultyAdjustmentValidator(encoder: difficultyEncoder, blockValidatorHelper: coreBlockHelper, heightInterval: Kit.legacyHeightInterval, targetTimespan: Kit.legacyTargetSpacing * Kit.legacyHeightInterval, maxTargetBits: Kit.legacyMaxTargetBits)) + blockValidatorChain.add(blockValidator: EDAValidator(encoder: difficultyEncoder, blockHelper: blockHelper, blockMedianTimeHelper: BlockMedianTimeHelper(storage: storage), maxTargetBits: Kit.legacyMaxTargetBits)) + case .testNet: () + // not use test validators + } + + blockValidatorSet.add(blockValidator: blockValidatorChain) + + let bitcoinCore = try BitcoinCoreBuilder(logger: logger) + .set(network: network) + .set(initialSyncApi: initialSyncApi) + .set(seed: seed) + .set(paymentAddressParser: paymentAddressParser) + .set(walletId: walletId) + .set(confirmationsThreshold: confirmationsThreshold) + .set(peerSize: 10) + .set(syncMode: syncMode) + .set(storage: storage) + .set(blockValidator: blockValidatorSet) + .build() + + super.init(bitcoinCore: bitcoinCore, network: network) + + // extending BitcoinCore + let bech32 = CashBech32AddressConverter(prefix: network.bech32PrefixPattern) + let base58 = Base58AddressConverter(addressVersion: network.pubKeyHash, addressScriptVersion: network.scriptHash) + bitcoinCore.prepend(addressConverter: bech32) + + bitcoinCore.add(restoreKeyConverter: Bip44RestoreKeyConverter(addressConverter: base58)) + } + +} + +extension Kit { + + public static func clear(exceptFor walletIdsToExclude: [String] = []) throws { + try DirectoryHelper.removeAll(inDirectory: Kit.name, except: walletIdsToExclude) + } + + private static func databaseFileName(walletId: String, networkType: NetworkType, syncMode: BitcoinCore.SyncMode) -> String { + "\(walletId)-\(networkType.description)-\(syncMode)" + } + +} diff --git a/BitcoinCashKit/BitcoinCashKit/Core/Protocols.swift b/BitcoinCashKit/Classes/Core/Protocols.swift similarity index 80% rename from BitcoinCashKit/BitcoinCashKit/Core/Protocols.swift rename to BitcoinCashKit/Classes/Core/Protocols.swift index 11a19d7f..9deb3597 100644 --- a/BitcoinCashKit/BitcoinCashKit/Core/Protocols.swift +++ b/BitcoinCashKit/Classes/Core/Protocols.swift @@ -20,20 +20,18 @@ protocol IBitcoinCashBlockValidator { // ############################### protocol IBitcoinCashBlockValidatorHelper { - func medianTimePast(block: Block) -> Int func suitableBlockIndex(for blocks: [Block]) -> Int? func previous(for block: Block, count: Int) -> Block? func previousWindow(for block: Block, count: Int) -> [Block]? } -protocol IBitcoinCashStorage { - func timestamps(from startHeight: Int, to endHeight: Int, ascending: Bool) -> [Int] - - func block(byHash: Data) -> Block? -} - protocol IBlockValidatorHelperWrapper { func previous(for block: Block, count: Int) -> Block? func previousWindow(for block: Block, count: Int) -> [Block]? } + +protocol IBitcoinCashBlockMedianTimeHelper { + var medianTimePast: Int? { get } + func medianTimePast(block: Block) -> Int? +} \ No newline at end of file diff --git a/BitcoinCashKit/Classes/Network/MainNet.swift b/BitcoinCashKit/Classes/Network/MainNet.swift new file mode 100644 index 00000000..1256be3c --- /dev/null +++ b/BitcoinCashKit/Classes/Network/MainNet.swift @@ -0,0 +1,39 @@ +import BitcoinCore + +public class MainNet: INetwork { + public let bundleName = "BitcoinCashKit" + + public let maxBlockSize: UInt32 = 32 * 1024 * 1024 + public let pubKeyHash: UInt8 = 0x00 + public let privateKey: UInt8 = 0x80 + public let scriptHash: UInt8 = 0x05 + public let bech32PrefixPattern: String = "bitcoincash" + public let xPubKey: UInt32 = 0x0488b21e + public let xPrivKey: UInt32 = 0x0488ade4 + public let magic: UInt32 = 0xe3e1f3e8 + public let port = 8333 + public let coinType: UInt32 + public let sigHash: SigHashType = .bitcoinCashAll + public var syncableFromApi: Bool = true + + public let dnsSeeds = [ + "x5.seed.bitcoinabc.org", // Bitcoin ABC seeder + "btccash-seeder.bitcoinunlimited.info", // BU backed seeder + "x5.seeder.jasonbcox.com", // Jason B. Cox + "seed.deadalnix.me", // Amaury SÉCHET + "seed.bchd.cash", // BCHD + "x5.seeder.fabien.cash" // Fabien + ] + + public let dustRelayTxFee = 3000 + + public init(coinType: CoinType = .type145) { + self.coinType = coinType.rawValue + } + +} + +public enum CoinType: UInt32 { + case type0 = 0 + case type145 = 145 +} diff --git a/BitcoinCashKit/Classes/Network/TestNet.swift b/BitcoinCashKit/Classes/Network/TestNet.swift new file mode 100644 index 00000000..0dd55f47 --- /dev/null +++ b/BitcoinCashKit/Classes/Network/TestNet.swift @@ -0,0 +1,25 @@ +import BitcoinCore + +class TestNet: INetwork { + let bundleName = "BitcoinCashKit" + + let maxBlockSize: UInt32 = 32 * 1024 * 1024 + let pubKeyHash: UInt8 = 0x6f + let privateKey: UInt8 = 0xef + let scriptHash: UInt8 = 0xc4 + let bech32PrefixPattern: String = "bchtest" + let xPubKey: UInt32 = 0x043587cf + let xPrivKey: UInt32 = 0x04358394 + let magic: UInt32 = 0xf4e5f3f4 + let port = 18333 + let coinType: UInt32 = 1 + let sigHash: SigHashType = .bitcoinCashAll + var syncableFromApi: Bool = true + + let dnsSeeds = [ + "testnet-seed.bitcoinabc.org", + "testnet-seed-abc.bitcoinforks.org" + ] + + let dustRelayTxFee = 1000 // https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/policy/policy.h#L78 +} diff --git a/BitcoinCore.swift.podspec b/BitcoinCore.swift.podspec index d54220b6..c0b1d2f9 100644 --- a/BitcoinCore.swift.podspec +++ b/BitcoinCore.swift.podspec @@ -1,28 +1,35 @@ -Pod::Spec.new do |spec| - spec.name = 'BitcoinCore.swift' - spec.module_name = "BitcoinCore" - spec.version = '0.6' - spec.summary = 'Core library Bitcoin derived wallets for Swift' - spec.description = <<-DESC - BitcoinCore implements Bitcoin core protocol in Swift. It is an implementation of the Bitcoin SPV protocol written (almost) entirely in swift. - ``` - DESC - spec.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' - spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' } - spec.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } - spec.social_media_url = 'http://horizontalsystems.io/' +Pod::Spec.new do |s| + s.name = 'BitcoinCore.swift' + s.module_name = 'BitcoinCore' + s.version = '0.18' + s.summary = 'Core library Bitcoin derived wallets for Swift.' - spec.requires_arc = true - spec.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "#{spec.version}" } - spec.source_files = 'BitcoinCore/BitcoinCore/**/*.{h,m,swift}' - spec.ios.deployment_target = '11.0' - spec.swift_version = '5' + s.description = <<-DESC +BitcoinCore implements Bitcoin core protocol in Swift. It is an implementation of the Bitcoin SPV protocol written (almost) entirely in swift. + DESC - spec.dependency 'HSCryptoKit', '~> 1.4' - spec.dependency 'HSHDWalletKit', '~> 1.1' - spec.dependency 'Alamofire', '~> 4.0' - spec.dependency 'ObjectMapper', '~> 3.0' - spec.dependency 'RxSwift', '~> 5.0' - spec.dependency 'BigInt', '~> 4.0' - spec.dependency 'GRDB.swift', '~> 4.0' + s.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } + s.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "bitcoin-core-#{s.version}" } + s.social_media_url = 'http://horizontalsystems.io/' + + s.ios.deployment_target = '13.0' + s.swift_version = '5' + + s.source_files = 'BitcoinCore/Classes/**/*' + + s.requires_arc = true + + s.dependency 'OpenSslKit.swift', '~> 1.0' + s.dependency 'Secp256k1Kit.swift', '~> 1.0' + s.dependency 'HdWalletKit.swift', '~> 1.5' + s.dependency 'HsToolKit.swift', '~> 1.3' + s.dependency 'UIExtensions.swift', '~> 1.1.1' + + s.dependency 'ObjectMapper', '~> 4.0' + s.dependency 'RxSwift', '~> 5.0' + s.dependency 'BigInt', '~> 5.0' + s.dependency 'GRDB.swift', '~> 5.0' + s.dependency 'SwiftNIO', '~> 2' end diff --git a/BitcoinCore/BitcoinCore.xcodeproj/project.pbxproj b/BitcoinCore/BitcoinCore.xcodeproj/project.pbxproj deleted file mode 100644 index f9ad9944..00000000 --- a/BitcoinCore/BitcoinCore.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1709 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 11B35178BC28AE1B995DE8E3 /* VarInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355C0072CA13ADBAC1351 /* VarInt.swift */; }; - 11B351E3E2B8E34D9ECAB6B6 /* SendTransactionTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350832C84DD224B3E8AF2 /* SendTransactionTask.swift */; }; - 11B35333093D726EC8ECA159 /* BlockSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352A955F667DC0C23CE1C /* BlockSyncerTests.swift */; }; - 11B3534C9529906826754D6E /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3501384C4274027587EB4 /* Protocols.swift */; }; - 11B3536E9A39907FCA035C33 /* GrdbStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3543D7114E1FE55B2DCC3 /* GrdbStorage.swift */; }; - 11B35377C00CCB8EEED57179 /* TransactionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B357C9E6B02801B0422881 /* TransactionInfo.swift */; }; - 11B3543B9CDA61125C2B7D78 /* ApiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3597DE3169FD250A982D5 /* ApiManager.swift */; }; - 11B35464997EBBD3B20BDEBB /* Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35352F3DA4C26A552C62B /* Input.swift */; }; - 11B35468A2ACF4B8534BB719 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3525D146F1119A0A9128A /* DataProvider.swift */; }; - 11B354C7D3039D767739BCB2 /* DifficultyEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B354EF0F404DF96E765CA1 /* DifficultyEncoder.swift */; }; - 11B354CD41E2CC368BC05CDB /* VarString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C47D552B7AC5EA8C463 /* VarString.swift */; }; - 11B3554339A5E16A9CE950E7 /* BlockSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356EF3A4749163293CCA3 /* BlockSyncer.swift */; }; - 11B35567A6F0430D0A95A389 /* PeerTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B352F4797ED2214EC26607 /* PeerTask.swift */; }; - 11B3561A75D7D7A6DE799675 /* OpCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B354DDB46C9DE37375424D /* OpCode.swift */; }; - 11B356509EA05BCD114603E0 /* Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350357DF0C843FDC0C2DE /* Block.swift */; }; - 11B3566FF1557B650F44D89E /* InitialSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F9A349EC7EF0DD5BA96 /* InitialSyncerTests.swift */; }; - 11B3567D267718668A0FF9A7 /* ServiceFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B356D308DCF4BB33D9F488 /* ServiceFlags.swift */; }; - 11B3569B6059A0F31D7C4866 /* PublicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E7E6BB3227429A41B13 /* PublicKey.swift */; }; - 11B3577DC09E75C39C5F7D2A /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3510658CE89D9DB6772E5 /* String.swift */; }; - 11B35785C5E654AB52A81E8A /* BloomFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350F93CE46013514830A3 /* BloomFilter.swift */; }; - 11B3579EE9017ED27A4F301E /* PeerAddressManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FE0F5B2A705A5C42439 /* PeerAddressManagerState.swift */; }; - 11B3580E3ED73322D657644F /* StateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B358481D7E7EB156912F52 /* StateManager.swift */; }; - 11B35823EEDAE4C578F2EA02 /* RequestTransactionsTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35413F7A859F19D27BA39 /* RequestTransactionsTask.swift */; }; - 11B358B84B9C6B09ADB8A0B0 /* HDWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F8792CA3A0945C18200 /* HDWallet.swift */; }; - 11B359142169EC6D44E21F59 /* ByteStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35AEA9C74CB8AFFFE9D8E /* ByteStream.swift */; }; - 11B3598874F8268BE87173B3 /* Output.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3559B75AD75C20287ECC7 /* Output.swift */; }; - 11B35A0F40D42FCAA4633D8A /* DifficultyEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35D0FBCC5A6A00640AA4E /* DifficultyEncoderTests.swift */; }; - 11B35ACFFB6D4F9EA1E7CDE7 /* TransactionInputExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3556BD9C3E52C992D1E92 /* TransactionInputExtractor.swift */; }; - 11B35AFB6F7733F41FE3EA49 /* GetHeadersMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FF2F94006C73B7773BC /* GetHeadersMessage.swift */; }; - 11B35BB1C166ED5CECF292C5 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3590614D2772B1436542A /* Extensions.swift */; }; - 11B35C8F2A29CA171165BF7A /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35F3FD9CCB938B62D07EA /* Observable.swift */; }; - 11B35CFD56E66080A63C060E /* BlockchainState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35525251BF8A38E021356 /* BlockchainState.swift */; }; - 11B35D23B451CFB803F69CA0 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B358F56F6298016B1A8AFD /* TestData.swift */; }; - 11B35DD0EE93320729D517CE /* GetMerkleBlocksTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35AEAAB5F1F7246063AEB /* GetMerkleBlocksTask.swift */; }; - 11B35DD55E37F22AE1797501 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35C67DB63EF992C380513 /* Transaction.swift */; }; - 11B35EE64EEEE37EB90C5D14 /* PeerGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B359E44D75610DF2841327 /* PeerGroup.swift */; }; - 11B35F02260D5A21CF0EA3B8 /* Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3529A1731702724B0342D /* Factory.swift */; }; - 2FA5D024DF2A429D6606350F /* KitStateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D859C070F9718CACF08F /* KitStateProvider.swift */; }; - 2FA5D13C7285E79B75FAE2BE /* PeerConnectionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF96100D81EACBBC87F6 /* PeerConnectionDelegateTests.swift */; }; - 2FA5D15733A45ACBD4C5B3A6 /* OutputsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DCAE853F5F4068A34CF2 /* OutputsCache.swift */; }; - 2FA5D166F7AD6D1E7A81CB4D /* Blockchain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D05597A1DC332C67BDF7 /* Blockchain.swift */; }; - 2FA5D19E18DC7528778D26F0 /* PeerAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DDED90E11DEC3647B164 /* PeerAddress.swift */; }; - 2FA5D1EE5C8EF298CD8A6733 /* TransactionSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D880471F191DBCDAE574 /* TransactionSerializer.swift */; }; - 2FA5D1F7E6DABFC12A0AD064 /* HDPrivateKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DADEB0FD09138FD308A1 /* HDPrivateKeyTests.swift */; }; - 2FA5D2239DF0C24C4CC6366B /* PeerHostManagerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D632A9EFB1F0CF2523DF /* PeerHostManagerDelegateTests.swift */; }; - 2FA5D26905936BB701998750 /* GetBlockHashesTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D65667DA8BFD91E91C8B /* GetBlockHashesTask.swift */; }; - 2FA5D2E6BA34E1A932C9C16B /* AddressManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5A9656117A6FD41E814 /* AddressManager.swift */; }; - 2FA5D371584B554068CCAD8E /* IPeerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D99586B15F4477D695F7 /* IPeerTests.swift */; }; - 2FA5D37E8B4A6D9B89410173 /* SendTransactionTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D2DAB8485F5F9E2E23B6 /* SendTransactionTaskTests.swift */; }; - 2FA5D39FBC802B5EF03AD53A /* TransactionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D11405173CD80A6B9904 /* TransactionBuilder.swift */; }; - 2FA5D3D47092D1903728F16A /* DirectoryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D131E769F7E4C62CC49F /* DirectoryHelper.swift */; }; - 2FA5D421B7EE9B499EEA6908 /* BloomFilterManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D4E64129FF62BED7ADE7 /* BloomFilterManagerTests.swift */; }; - 2FA5D43AE1D87F3892BF8C4C /* StateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DFBF60F56D8A2FC86ED2 /* StateManagerTests.swift */; }; - 2FA5D443E5A428D1A60CD031 /* KitStateProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D82EFF381CE6233790A9 /* KitStateProviderTests.swift */; }; - 2FA5D448B43D6BFB15208436 /* PeerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D3968E6E11AF76368CA5 /* PeerManager.swift */; }; - 2FA5D4912ABA5CEE7C00ECD0 /* PeerManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D27387B0CF46C2B0A22A /* PeerManagerTests.swift */; }; - 2FA5D4AB6E84748075FA6294 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DBBF82E71BB3E056B753 /* Array.swift */; }; - 2FA5D4F27E10A609FF81950E /* UnknownMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D3F63860A15B55DC196E /* UnknownMessage.swift */; }; - 2FA5D56E3AE0E2E9817FC370 /* TransactionInputSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DCD21585D41C520DF764 /* TransactionInputSerializer.swift */; }; - 2FA5D571931CA46B417C18BA /* PeerGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D602DBCFC5121F7C245F /* PeerGroupTests.swift */; }; - 2FA5D599E444F6847B6E9A4C /* Peer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5A744085AD28EFBB868 /* Peer.swift */; }; - 2FA5D5FB8B7F7B1E65A156BF /* GetMerkleBlocksTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D2907674257196C4F586 /* GetMerkleBlocksTaskTests.swift */; }; - 2FA5D5FE71A528D363227585 /* AddressManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D05FCBFFE93D46F8C292 /* AddressManagerTests.swift */; }; - 2FA5D6129186D79E7C7DD57F /* IPeerTaskDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DFB5B9A13478ACBB61EB /* IPeerTaskDelegateTests.swift */; }; - 2FA5D65869320A18AFB8CE98 /* BlockHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DB1F056B264F829BFDDD /* BlockHash.swift */; }; - 2FA5D67384D58067F71CD6B2 /* InputSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF9995FA7DD7556893F7 /* InputSigner.swift */; }; - 2FA5D6EC6F4A32E3489F89B0 /* TransactionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D307095385625960B0A0 /* TransactionMessage.swift */; }; - 2FA5D74EDAFB77BC86918E63 /* PeerDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DE9CAB1F15B04E2C54C9 /* PeerDiscovery.swift */; }; - 2FA5D806A7727E3BED0E54E7 /* DataObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D50922572F91F966DD4A /* DataObjects.swift */; }; - 2FA5D8284C7C94155AC50B7B /* GetBlockHashesTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DFC7FA0A5363BFFB8E28 /* GetBlockHashesTaskTests.swift */; }; - 2FA5D91DCF7F320369162C5A /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D6676E3CB6028E1B6018 /* ReachabilityManager.swift */; }; - 2FA5D9D3DA2DF9BD402CE5DD /* SentTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF037E951950A0F7FF03 /* SentTransaction.swift */; }; - 2FA5D9ED521660FA5EEF360E /* MerkleBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5C9BB0E185AF72B9174 /* MerkleBlock.swift */; }; - 2FA5DA409AB91D54163250D7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D9929AB239235F9B6895 /* Logger.swift */; }; - 2FA5DB74D510BA9F6D9525A4 /* BloomFilterManagerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D0A6A8C6C5A4E2AAFB74 /* BloomFilterManagerDelegateTests.swift */; }; - 2FA5DB7F5BD8E25DCAF9AC83 /* PeerAddressManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF9ADB516A45C797889D /* PeerAddressManagerTests.swift */; }; - 2FA5DBABD18D15E003686989 /* BlockSyncerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D2DDF60A9C5DA3776D8A /* BlockSyncerState.swift */; }; - 2FA5DBF35EDF66F6977BA032 /* ConnectionTimeoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DEC11B1DA4DA336DBB28 /* ConnectionTimeoutManager.swift */; }; - 2FA5DC1C403D74796F4131EC /* InputSignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DA9D7F78A26107F7209C /* InputSignerTests.swift */; }; - 2FA5DC27C4032E13E157A0D7 /* AddressConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D23A8EC7BB3569F51ABD /* AddressConverterTests.swift */; }; - 2FA5DC3D8DAD2FF85FB26C91 /* TransactionSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D316DD8A722A5300002B /* TransactionSyncerTests.swift */; }; - 2FA5DC6BEF26DD10A72A39EE /* BloomFilterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DE7E06652FCED7B288E7 /* BloomFilterManager.swift */; }; - 2FA5DC980A0E135863848CB0 /* TransactionProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF1470723462D2CF707B /* TransactionProcessor.swift */; }; - 2FA5DCA14A91CAB48F61F2A0 /* TransactionBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DDAE64200E386070668C /* TransactionBuilderTests.swift */; }; - 2FA5DD904B10D404DF9C6E04 /* RequestTransactionTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D4A92FB8C2376A9A0090 /* RequestTransactionTaskTests.swift */; }; - 2FA5DDCC995A7F27AFB3DE18 /* PeerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DCB87581859B2E083377 /* PeerDelegateTests.swift */; }; - 2FA5DDD47BF94625B499ECC2 /* TransactionOutputSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DEC34722F594F85688C0 /* TransactionOutputSerializer.swift */; }; - 2FA5DEB1D802341D26456814 /* TransactionProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D0B8032597D7392B0637 /* TransactionProcessorTests.swift */; }; - 2FA5DEDC4F93EAB928423720 /* IPeerGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF41CAF034B375E58E7A /* IPeerGroupTests.swift */; }; - 2FA5DF1A340A352EEDBF41E6 /* BlockchainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DEC678BF41F0D38095DD /* BlockchainTests.swift */; }; - 2FA5DF66AD595EAC2723B0B3 /* UnspentOutputSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DAE996A847B90E5D033A /* UnspentOutputSelectorTests.swift */; }; - 2FA5DFD4DDEA0B2F36600346 /* IPeerTaskRequesterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DAAAC2F759FD42948DDE /* IPeerTaskRequesterTests.swift */; }; - 3C7B91298318097F1615EFFF /* TransactionInputExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C7B9EBABAFF96958CFB2CB7 /* TransactionInputExtractorTests.swift */; }; - 3C7B913CB012F68959606805 /* BCoinApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C7B91E6E6DBA06A79ED8005 /* BCoinApi.swift */; }; - 3C7B9B6F34A3C30648B65661 /* TransactionPublicKeySetter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C7B983C4EC7F79E44560A7C /* TransactionPublicKeySetter.swift */; }; - 58AAA028B3706BCD8071E95D /* BaseTransactionInfoConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFE91DB09FA5E32DD881 /* BaseTransactionInfoConverter.swift */; }; - 58AAA0628AAE41265F75FE99 /* PaymentAddressParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA97A777A2F1E5734C89B /* PaymentAddressParser.swift */; }; - 58AAA0DF16FE6B7A7B1FCF32 /* BlockValidatorChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE9E2FE6584E581B71E1 /* BlockValidatorChain.swift */; }; - 58AAA120B3EEA225FFFBE2BC /* TransactionOutputAddressExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA674EE7C1523F42648DD /* TransactionOutputAddressExtractor.swift */; }; - 58AAA12B1809FDA36DCBC18D /* DoubleShaHasher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2422A1324C9260FB494 /* DoubleShaHasher.swift */; }; - 58AAA16365AECB1DF62883E5 /* BlockDiscoveryBatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA36C584F843DFB51B89C /* BlockDiscoveryBatch.swift */; }; - 58AAA163DAD085C355C8CA4A /* BlockHeaderParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA9B2685F25E6923DDA77 /* BlockHeaderParser.swift */; }; - 58AAA1702C9422A8EBA464D7 /* UnspentOutputSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA983B426801C46003567 /* UnspentOutputSelector.swift */; }; - 58AAA19EA96193409D8AA8EC /* ScriptConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC8BAD29CC18864884DB /* ScriptConverter.swift */; }; - 58AAA1C21F9E20749D8FA9B4 /* LegacyDifficultyAdjustmentValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA925AA61EB0BA46B7EAC /* LegacyDifficultyAdjustmentValidatorTests.swift */; }; - 58AAA1E4DEA7C5A4CD1DFFC0 /* LegacyTestNetDifficultyValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB31CA6F582F89A1BA14 /* LegacyTestNetDifficultyValidatorTests.swift */; }; - 58AAA1E87B62806CD5891D43 /* Chunk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3BE8FA350BD415CD5B3 /* Chunk.swift */; }; - 58AAA216024E9AE339C1A796 /* SyncTransactionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAADC9D2822A6C4D415A90 /* SyncTransactionItem.swift */; }; - 58AAA23384287398D79F14A6 /* TransactionPublicKeySetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA97B8114B3CE27A6D32C /* TransactionPublicKeySetterTests.swift */; }; - 58AAA29706D539111A919497 /* AbstractKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA087C2B107DA9ABFF5DD /* AbstractKit.swift */; }; - 58AAA2C85BFE1C624D1A1997 /* SigHashType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA63CA9758D7D97E1416 /* SigHashType.swift */; }; - 58AAA2EC7A7825B8E4408AA7 /* ProofOfWorkValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACDCDC4B51B6E2C9C07D /* ProofOfWorkValidatorTests.swift */; }; - 58AAA2F4990F1DAC17DE958D /* ScriptBuilderChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA4F887DF0F5E9527C761 /* ScriptBuilderChain.swift */; }; - 58AAA312C3BF2D105FE784EF /* TransactionInfoConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0FE0496F520DDA29BB1 /* TransactionInfoConverter.swift */; }; - 58AAA317128E10EE72E0D0B1 /* BlockHashFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD725CB287055DC7CCE5 /* BlockHashFetcherTests.swift */; }; - 58AAA321A8780859892D0C73 /* TransactionOutputExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE28D740FD1FCF9DC1B2 /* TransactionOutputExtractorTests.swift */; }; - 58AAA32402EFCE6218A9D082 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6603AEA2159F6126D10 /* Signal.swift */; }; - 58AAA3492A592C937473454F /* ScriptBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF2482982ED7215EEBF9 /* ScriptBuilder.swift */; }; - 58AAA3570F85F5A72E2377DB /* InventoryItemsHandlerChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB4C41058C7E06D10BFB /* InventoryItemsHandlerChain.swift */; }; - 58AAA36E48DB45B72112973B /* TransactionCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC66042A881E2F74AFD6 /* TransactionCreator.swift */; }; - 58AAA3A0474719B721A0E877 /* PaymentAddressParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3FBA5C8CE3D9C678F5D /* PaymentAddressParserTests.swift */; }; - 58AAA3B243D1EE084C7B0FC8 /* NetworkMessageParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5D8E61487D82ACFD9A6 /* NetworkMessageParser.swift */; }; - 58AAA3C55F225D1100102F78 /* BitcoinMainNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAFCA9E73AD5A7AB4CA6 /* BitcoinMainNetTests.swift */; }; - 58AAA445FA83E69172FFC1ED /* BlockHashFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFF6092A4436B5FF46FF /* BlockHashFetcher.swift */; }; - 58AAA51D2E6F57CEB8947032 /* BitcoinTestNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAEDC9D0D7BAE56C71DE0 /* BitcoinTestNetTests.swift */; }; - 58AAA572F80B63848F95B065 /* BlockHashFetcherHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC136573542B0D16C283 /* BlockHashFetcherHelper.swift */; }; - 58AAA6118006786B2225CAFB /* ProofOfWorkValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAB98CB96E73F39A7306 /* ProofOfWorkValidator.swift */; }; - 58AAA649DF284C668B495AB2 /* ScriptBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA422686CE355CACA11DF /* ScriptBuilderTests.swift */; }; - 58AAA6546322BDA56924A74D /* BlockHashFetcherHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA1C4F57CC56BB8CC772C /* BlockHashFetcherHelperTests.swift */; }; - 58AAA68750887DC647A77B79 /* SynchronizedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA4852F5313674AA9E2B3 /* SynchronizedArray.swift */; }; - 58AAA68C0D710ED3FCDE76DE /* ScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6BB66ECE0543BE377E3 /* ScriptTests.swift */; }; - 58AAA6A3D98BB1CF6360F639 /* TransactionSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA7C33E2529A70D0E529 /* TransactionSizeCalculator.swift */; }; - 58AAA6D586BB4E3E39C15E36 /* TransactionSizeCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAED5E10009C0720D949E /* TransactionSizeCalculatorTests.swift */; }; - 58AAA6DBA7D2439C4D5808B6 /* Base58AddressConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF92B98343622D7D4964 /* Base58AddressConverter.swift */; }; - 58AAA6EF974E88D9FBECB99D /* BitcoinCashMainNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3744E23993E6BA283AC /* BitcoinCashMainNetTests.swift */; }; - 58AAA744EF8A7193BB2EF860 /* NetworkMessageSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA201AEA32587BD5E2E12 /* NetworkMessageSerializer.swift */; }; - 58AAA74E89561DE6CCDC5EC3 /* BitcoinCoreErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF2AC5C1330DFE1FAAD3 /* BitcoinCoreErrors.swift */; }; - 58AAA789081293EB75B29A78 /* MempoolTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD73659B838E541862BA /* MempoolTransactions.swift */; }; - 58AAA7912B48C0C21F50DBF2 /* BitcoinAddressSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA55E49708A950F951E2D /* BitcoinAddressSelector.swift */; }; - 58AAA79D7763A69B80757392 /* BitsValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB994FBA1E42999E4968 /* BitsValidator.swift */; }; - 58AAA7B5F60F31613515F273 /* BlockDiscoveryBatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA9BB5301D7CB5292473B /* BlockDiscoveryBatchTest.swift */; }; - 58AAA8C5908EBFA5BCC2B5FC /* MerkleBlockValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC9FD7A9AD220E52F974 /* MerkleBlockValidator.swift */; }; - 58AAA8CCA46340450D2912C5 /* UnspentOutputProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA481A82D05C914AB0CCA /* UnspentOutputProvider.swift */; }; - 58AAA9479AB8BF53D2852502 /* TransactionCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA21C2FD6764CD385663E /* TransactionCreatorTests.swift */; }; - 58AAA958A19346CAFE9B38D8 /* MerkleBranch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA9C5C3A65FCEBB46F340 /* MerkleBranch.swift */; }; - 58AAA98383D6E9642327F1C0 /* SyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA684704A912E02B1138B /* SyncManager.swift */; }; - 58AAA9ABB0AAAF9A11B98261 /* TransactionOutputExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA62C7C37CA2D7FD668B5 /* TransactionOutputExtractor.swift */; }; - 58AAAA0E73B45A6262D8272F /* MerkleBranchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2A6D99FD23FFA252002 /* MerkleBranchTests.swift */; }; - 58AAAA1B8FD14D793868FA8D /* BlockValidatorHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0616D186C35DC0F3367 /* BlockValidatorHelperTests.swift */; }; - 58AAAA8C17BA61B9D9CFF1C7 /* LegacyTestNetDifficultyValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC35C314C36A49F3AE80 /* LegacyTestNetDifficultyValidator.swift */; }; - 58AAAAC9184F9395A71E318B /* UnspentOutputProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAECE6429C4CBD7FD2394 /* UnspentOutputProviderTests.swift */; }; - 58AAAAD07F22C65761A03E33 /* PeerTaskHandlerChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5081672879DFB3D483F /* PeerTaskHandlerChain.swift */; }; - 58AAAB219A1E5FFB2A1F37D1 /* AddressConverterChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6AD5B652DC76D5181BC /* AddressConverterChain.swift */; }; - 58AAAB436CF3BC02B5BE9222 /* BitcoinCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE010437D0A44D0B975B /* BitcoinCore.swift */; }; - 58AAAB857FAD14D8B361A248 /* TransactionSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAACEC07FF47326D89AD /* TransactionSender.swift */; }; - 58AAABF5ED93AD7DE1CE9F25 /* BitcoinRegTestNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA285EFA8FD5B4236D8CC /* BitcoinRegTestNetTests.swift */; }; - 58AAAC19A5DE1AFC539D10C7 /* InitialBlockDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA8F791902E5D0F3F1461 /* InitialBlockDownload.swift */; }; - 58AAAC5B6B652220A1A707EF /* BitcoinPaymentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0125762CC01D08AB167 /* BitcoinPaymentData.swift */; }; - 58AAAC7087175422D3BA89A2 /* SyncedReadyPeerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA7A391E4B2F8C4E99B97 /* SyncedReadyPeerManager.swift */; }; - 58AAAD1C062938F07E90EA9D /* Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB4FF63F2AA59C24E9C0 /* Address.swift */; }; - 58AAAD6BC19FF5BF360535F0 /* LegacyDifficultyAdjustmentValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA735418741ACF44ED4D6 /* LegacyDifficultyAdjustmentValidator.swift */; }; - 58AAAD86E3A2A31C2D2A705B /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA11CDED1A6F7663DEA37 /* Script.swift */; }; - 58AAAD9224BAB440C1B6E033 /* BitsValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC3F1788B8AFC44F6A4E /* BitsValidatorTests.swift */; }; - 58AAADBED25259CCD94B0B1E /* TransactionOutputAddressExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA7B8F141FBC32B735966 /* TransactionOutputAddressExtractorTests.swift */; }; - 58AAAE00FA79BB744B0EEA9C /* MerkleBlockValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5EE018C74544938121F /* MerkleBlockValidatorTests.swift */; }; - 58AAAE076B1930CB8B2C3C65 /* InitialSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE784BD9490F8775E78A /* InitialSyncer.swift */; }; - 58AAAE17248A670EE4E588A3 /* DataListSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB709C58E0E9DC3F074B /* DataListSerializer.swift */; }; - 58AAAE1B7C0F5659F2EC9229 /* ChunkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE1DA82FC3585855DED3 /* ChunkTests.swift */; }; - 58AAAE352FC2FE33B4FB8F53 /* ScriptConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAABDF62BBC64C9A3363A6 /* ScriptConverterTests.swift */; }; - 58AAAE3ED33146222CCB9503 /* BitcoinCoreBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0C32EB09BCB2D5D7396 /* BitcoinCoreBuilder.swift */; }; - 58AAAE80BE4074DB3B1F0B1C /* UnspentOutputSelectorSingleNoChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA81938DAE46B8074C9D /* UnspentOutputSelectorSingleNoChangeTests.swift */; }; - 58AAAE8B6F9F832A13753E78 /* UnspentOutputSelectorSingleNoChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA67094BBC66D3A385341 /* UnspentOutputSelectorSingleNoChange.swift */; }; - 58AAAF1F346D79776D04CEA6 /* UnspentOutputSelectorChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE5AF69AFF1730A54D24 /* UnspentOutputSelectorChain.swift */; }; - 58AAAF31EB5F07BD176A6F85 /* BloomFilterLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA83A08FA97C685C8E640 /* BloomFilterLoader.swift */; }; - 58AAAF671C4B3988EF6764B1 /* InsightApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA1173B95D1609546836E /* InsightApi.swift */; }; - 58AAAFDA3C6853E4024D8B99 /* BlockValidatorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3909BD23CBF0D217FDB /* BlockValidatorHelper.swift */; }; - 6440515C276D7ADF203CC3ED /* Pods_BitcoinCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 471C117B5DB1567486784B2E /* Pods_BitcoinCore.framework */; }; - 7BAB34250E0A3F6F5A976FEF /* Pods_BitcoinCoreTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE7E1640CACC3D2ED2DA233D /* Pods_BitcoinCoreTests.framework */; }; - D00DA94E213E86BC007F82D6 /* MemPoolMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DA94D213E86BC007F82D6 /* MemPoolMessage.swift */; }; - D026EFF021AFD1FB00B6640D /* ConnectionTimeoutManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D026EFEF21AFD1FB00B6640D /* ConnectionTimeoutManagerTests.swift */; }; - D093F45E21414CC000C5D8CD /* PeerAddressManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093F45D21414CC000C5D8CD /* PeerAddressManager.swift */; }; - D0F63BE421EF349D000CAB79 /* DataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F63BE321EF349D000CAB79 /* DataProviderTests.swift */; }; - D31711EA20F7192400AEF61B /* PeerConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711E920F7192300AEF61B /* PeerConnection.swift */; }; - D31711FF20F7193000AEF61B /* PingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711EB20F7192F00AEF61B /* PingMessage.swift */; }; - D317120020F7193000AEF61B /* AddressMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711EC20F7192F00AEF61B /* AddressMessage.swift */; }; - D317120120F7193000AEF61B /* NetworkAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711ED20F7192F00AEF61B /* NetworkAddress.swift */; }; - D317120320F7193000AEF61B /* NetworkMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711EF20F7192F00AEF61B /* NetworkMessage.swift */; }; - D317120520F7193000AEF61B /* VersionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711F120F7192F00AEF61B /* VersionMessage.swift */; }; - D317120620F7193000AEF61B /* GetBlocksMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711F220F7193000AEF61B /* GetBlocksMessage.swift */; }; - D317120720F7193000AEF61B /* RejectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711F320F7193000AEF61B /* RejectMessage.swift */; }; - D317120820F7193000AEF61B /* InventoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711F420F7193000AEF61B /* InventoryItem.swift */; }; - D317120A20F7193000AEF61B /* GetDataMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711F620F7193000AEF61B /* GetDataMessage.swift */; }; - D317120B20F7193000AEF61B /* InventoryMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711F720F7193000AEF61B /* InventoryMessage.swift */; }; - D317120E20F7193000AEF61B /* FilterLoadMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711FA20F7193000AEF61B /* FilterLoadMessage.swift */; }; - D317120F20F7193000AEF61B /* MerkleBlockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711FB20F7193000AEF61B /* MerkleBlockMessage.swift */; }; - D317121020F7193000AEF61B /* VerackMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711FC20F7193000AEF61B /* VerackMessage.swift */; }; - D317121120F7193000AEF61B /* PongMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31711FD20F7193000AEF61B /* PongMessage.swift */; }; - D317121820F719A900AEF61B /* MurmurHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = D317121720F719A900AEF61B /* MurmurHash.swift */; }; - D33FD6C920F3876800EC10DB /* Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33FD6C820F3876800EC10DB /* Serialization.swift */; }; - D33FD6D120F3885400EC10DB /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33FD6D020F3885400EC10DB /* Helpers.swift */; }; - D373EB83215A2A6C00124298 /* TransactionSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D373EB82215A2A6C00124298 /* TransactionSyncer.swift */; }; - D380FD6020FCC04400276993 /* BitcoinCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3FD450520F354EF00F472B9 /* BitcoinCore.framework */; }; - D380FD6820FCC22800276993 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D380FD6720FCC22800276993 /* GeneratedMocks.swift */; }; - D3FD450A20F354EF00F472B9 /* BitcoinCore.h in Headers */ = {isa = PBXBuildFile; fileRef = D3FD450820F354EF00F472B9 /* BitcoinCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - D343A1882177367E004248DA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D3FD44FC20F354EF00F472B9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = D3FD450420F354EF00F472B9; - remoteInfo = HSBitcoinKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 11B3501384C4274027587EB4 /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; - 11B350357DF0C843FDC0C2DE /* Block.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Block.swift; sourceTree = ""; }; - 11B350832C84DD224B3E8AF2 /* SendTransactionTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTransactionTask.swift; sourceTree = ""; }; - 11B350F93CE46013514830A3 /* BloomFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilter.swift; sourceTree = ""; }; - 11B3510658CE89D9DB6772E5 /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; - 11B3525D146F1119A0A9128A /* DataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; - 11B3529A1731702724B0342D /* Factory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Factory.swift; sourceTree = ""; }; - 11B352A955F667DC0C23CE1C /* BlockSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockSyncerTests.swift; sourceTree = ""; }; - 11B352F4797ED2214EC26607 /* PeerTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerTask.swift; sourceTree = ""; }; - 11B35352F3DA4C26A552C62B /* Input.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Input.swift; sourceTree = ""; }; - 11B35413F7A859F19D27BA39 /* RequestTransactionsTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTransactionsTask.swift; sourceTree = ""; }; - 11B3543D7114E1FE55B2DCC3 /* GrdbStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GrdbStorage.swift; sourceTree = ""; }; - 11B354DDB46C9DE37375424D /* OpCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpCode.swift; sourceTree = ""; }; - 11B354EF0F404DF96E765CA1 /* DifficultyEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DifficultyEncoder.swift; sourceTree = ""; }; - 11B35525251BF8A38E021356 /* BlockchainState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockchainState.swift; sourceTree = ""; }; - 11B3556BD9C3E52C992D1E92 /* TransactionInputExtractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInputExtractor.swift; sourceTree = ""; }; - 11B3559B75AD75C20287ECC7 /* Output.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Output.swift; sourceTree = ""; }; - 11B355C0072CA13ADBAC1351 /* VarInt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VarInt.swift; sourceTree = ""; }; - 11B356D308DCF4BB33D9F488 /* ServiceFlags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceFlags.swift; sourceTree = ""; }; - 11B356EF3A4749163293CCA3 /* BlockSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BlockSyncer.swift; path = ../Blocks/BlockSyncer.swift; sourceTree = ""; }; - 11B357C9E6B02801B0422881 /* TransactionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInfo.swift; sourceTree = ""; }; - 11B358481D7E7EB156912F52 /* StateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateManager.swift; sourceTree = ""; }; - 11B358F56F6298016B1A8AFD /* TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = ""; }; - 11B3590614D2772B1436542A /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; - 11B3597DE3169FD250A982D5 /* ApiManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiManager.swift; sourceTree = ""; }; - 11B359E44D75610DF2841327 /* PeerGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerGroup.swift; sourceTree = ""; }; - 11B35AEA9C74CB8AFFFE9D8E /* ByteStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ByteStream.swift; sourceTree = ""; }; - 11B35AEAAB5F1F7246063AEB /* GetMerkleBlocksTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMerkleBlocksTask.swift; sourceTree = ""; }; - 11B35C07292793FCE3096D03 /* BlockSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockSyncer.swift; sourceTree = ""; }; - 11B35C47D552B7AC5EA8C463 /* VarString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VarString.swift; sourceTree = ""; }; - 11B35C67DB63EF992C380513 /* Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = ""; }; - 11B35D0FBCC5A6A00640AA4E /* DifficultyEncoderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DifficultyEncoderTests.swift; sourceTree = ""; }; - 11B35E7E6BB3227429A41B13 /* PublicKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKey.swift; sourceTree = ""; }; - 11B35F3FD9CCB938B62D07EA /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; - 11B35F8792CA3A0945C18200 /* HDWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HDWallet.swift; sourceTree = ""; }; - 11B35F9A349EC7EF0DD5BA96 /* InitialSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialSyncerTests.swift; sourceTree = ""; }; - 11B35FE0F5B2A705A5C42439 /* PeerAddressManagerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAddressManagerState.swift; sourceTree = ""; }; - 11B35FF2F94006C73B7773BC /* GetHeadersMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetHeadersMessage.swift; sourceTree = ""; }; - 2FA5D05597A1DC332C67BDF7 /* Blockchain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blockchain.swift; sourceTree = ""; }; - 2FA5D05FCBFFE93D46F8C292 /* AddressManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressManagerTests.swift; sourceTree = ""; }; - 2FA5D0A6A8C6C5A4E2AAFB74 /* BloomFilterManagerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilterManagerDelegateTests.swift; sourceTree = ""; }; - 2FA5D0B8032597D7392B0637 /* TransactionProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionProcessorTests.swift; sourceTree = ""; }; - 2FA5D11405173CD80A6B9904 /* TransactionBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionBuilder.swift; sourceTree = ""; }; - 2FA5D131E769F7E4C62CC49F /* DirectoryHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryHelper.swift; sourceTree = ""; }; - 2FA5D23A8EC7BB3569F51ABD /* AddressConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressConverterTests.swift; sourceTree = ""; }; - 2FA5D27387B0CF46C2B0A22A /* PeerManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerManagerTests.swift; sourceTree = ""; }; - 2FA5D2907674257196C4F586 /* GetMerkleBlocksTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMerkleBlocksTaskTests.swift; sourceTree = ""; }; - 2FA5D2DAB8485F5F9E2E23B6 /* SendTransactionTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTransactionTaskTests.swift; sourceTree = ""; }; - 2FA5D2DDF60A9C5DA3776D8A /* BlockSyncerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockSyncerState.swift; sourceTree = ""; }; - 2FA5D307095385625960B0A0 /* TransactionMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionMessage.swift; sourceTree = ""; }; - 2FA5D316DD8A722A5300002B /* TransactionSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSyncerTests.swift; sourceTree = ""; }; - 2FA5D3968E6E11AF76368CA5 /* PeerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerManager.swift; sourceTree = ""; }; - 2FA5D3F63860A15B55DC196E /* UnknownMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnknownMessage.swift; sourceTree = ""; }; - 2FA5D4A92FB8C2376A9A0090 /* RequestTransactionTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTransactionTaskTests.swift; sourceTree = ""; }; - 2FA5D4E64129FF62BED7ADE7 /* BloomFilterManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilterManagerTests.swift; sourceTree = ""; }; - 2FA5D50922572F91F966DD4A /* DataObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataObjects.swift; sourceTree = ""; }; - 2FA5D5A744085AD28EFBB868 /* Peer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peer.swift; sourceTree = ""; }; - 2FA5D5A9656117A6FD41E814 /* AddressManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressManager.swift; sourceTree = ""; }; - 2FA5D5C9BB0E185AF72B9174 /* MerkleBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBlock.swift; sourceTree = ""; }; - 2FA5D602DBCFC5121F7C245F /* PeerGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerGroupTests.swift; sourceTree = ""; }; - 2FA5D632A9EFB1F0CF2523DF /* PeerHostManagerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerHostManagerDelegateTests.swift; sourceTree = ""; }; - 2FA5D65667DA8BFD91E91C8B /* GetBlockHashesTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBlockHashesTask.swift; sourceTree = ""; }; - 2FA5D6676E3CB6028E1B6018 /* ReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = ""; }; - 2FA5D82EFF381CE6233790A9 /* KitStateProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KitStateProviderTests.swift; sourceTree = ""; }; - 2FA5D859C070F9718CACF08F /* KitStateProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KitStateProvider.swift; sourceTree = ""; }; - 2FA5D880471F191DBCDAE574 /* TransactionSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSerializer.swift; sourceTree = ""; }; - 2FA5D9929AB239235F9B6895 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - 2FA5D99586B15F4477D695F7 /* IPeerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTests.swift; sourceTree = ""; }; - 2FA5DA9D7F78A26107F7209C /* InputSignerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSignerTests.swift; sourceTree = ""; }; - 2FA5DAAAC2F759FD42948DDE /* IPeerTaskRequesterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTaskRequesterTests.swift; sourceTree = ""; }; - 2FA5DADEB0FD09138FD308A1 /* HDPrivateKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HDPrivateKeyTests.swift; sourceTree = ""; }; - 2FA5DAE996A847B90E5D033A /* UnspentOutputSelectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorTests.swift; sourceTree = ""; }; - 2FA5DB1F056B264F829BFDDD /* BlockHash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHash.swift; sourceTree = ""; }; - 2FA5DBBF82E71BB3E056B753 /* Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; - 2FA5DCAE853F5F4068A34CF2 /* OutputsCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutputsCache.swift; sourceTree = ""; }; - 2FA5DCB87581859B2E083377 /* PeerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerDelegateTests.swift; sourceTree = ""; }; - 2FA5DCD21585D41C520DF764 /* TransactionInputSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInputSerializer.swift; sourceTree = ""; }; - 2FA5DDAE64200E386070668C /* TransactionBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionBuilderTests.swift; sourceTree = ""; }; - 2FA5DDED90E11DEC3647B164 /* PeerAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAddress.swift; sourceTree = ""; }; - 2FA5DE7E06652FCED7B288E7 /* BloomFilterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilterManager.swift; sourceTree = ""; }; - 2FA5DE9CAB1F15B04E2C54C9 /* PeerDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerDiscovery.swift; sourceTree = ""; }; - 2FA5DEC11B1DA4DA336DBB28 /* ConnectionTimeoutManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTimeoutManager.swift; sourceTree = ""; }; - 2FA5DEC34722F594F85688C0 /* TransactionOutputSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputSerializer.swift; sourceTree = ""; }; - 2FA5DEC678BF41F0D38095DD /* BlockchainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockchainTests.swift; sourceTree = ""; }; - 2FA5DF037E951950A0F7FF03 /* SentTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentTransaction.swift; sourceTree = ""; }; - 2FA5DF1470723462D2CF707B /* TransactionProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionProcessor.swift; sourceTree = ""; }; - 2FA5DF41CAF034B375E58E7A /* IPeerGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerGroupTests.swift; sourceTree = ""; }; - 2FA5DF96100D81EACBBC87F6 /* PeerConnectionDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionDelegateTests.swift; sourceTree = ""; }; - 2FA5DF9995FA7DD7556893F7 /* InputSigner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSigner.swift; sourceTree = ""; }; - 2FA5DF9ADB516A45C797889D /* PeerAddressManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAddressManagerTests.swift; sourceTree = ""; }; - 2FA5DFB5B9A13478ACBB61EB /* IPeerTaskDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTaskDelegateTests.swift; sourceTree = ""; }; - 2FA5DFBF60F56D8A2FC86ED2 /* StateManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateManagerTests.swift; sourceTree = ""; }; - 2FA5DFC7FA0A5363BFFB8E28 /* GetBlockHashesTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBlockHashesTaskTests.swift; sourceTree = ""; }; - 3C7B91E6E6DBA06A79ED8005 /* BCoinApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCoinApi.swift; sourceTree = ""; }; - 3C7B983C4EC7F79E44560A7C /* TransactionPublicKeySetter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionPublicKeySetter.swift; sourceTree = ""; }; - 3C7B9EBABAFF96958CFB2CB7 /* TransactionInputExtractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInputExtractorTests.swift; sourceTree = ""; }; - 4018E79E0DF6A0D6F606F9F9 /* Pods-BitcoinCore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCore.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCore/Pods-BitcoinCore.debug.xcconfig"; sourceTree = ""; }; - 471C117B5DB1567486784B2E /* Pods_BitcoinCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitcoinCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 58AAA0125762CC01D08AB167 /* BitcoinPaymentData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinPaymentData.swift; sourceTree = ""; }; - 58AAA0616D186C35DC0F3367 /* BlockValidatorHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockValidatorHelperTests.swift; sourceTree = ""; }; - 58AAA087C2B107DA9ABFF5DD /* AbstractKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbstractKit.swift; sourceTree = ""; }; - 58AAA0C32EB09BCB2D5D7396 /* BitcoinCoreBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCoreBuilder.swift; sourceTree = ""; }; - 58AAA0FE0496F520DDA29BB1 /* TransactionInfoConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInfoConverter.swift; sourceTree = ""; }; - 58AAA1173B95D1609546836E /* InsightApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsightApi.swift; sourceTree = ""; }; - 58AAA11CDED1A6F7663DEA37 /* Script.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Script.swift; sourceTree = ""; }; - 58AAA1C4F57CC56BB8CC772C /* BlockHashFetcherHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHashFetcherHelperTests.swift; sourceTree = ""; }; - 58AAA201AEA32587BD5E2E12 /* NetworkMessageSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMessageSerializer.swift; sourceTree = ""; }; - 58AAA21C2FD6764CD385663E /* TransactionCreatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionCreatorTests.swift; sourceTree = ""; }; - 58AAA2422A1324C9260FB494 /* DoubleShaHasher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleShaHasher.swift; sourceTree = ""; }; - 58AAA285EFA8FD5B4236D8CC /* BitcoinRegTestNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinRegTestNetTests.swift; sourceTree = ""; }; - 58AAA2A6D99FD23FFA252002 /* MerkleBranchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBranchTests.swift; sourceTree = ""; }; - 58AAA33E4E02571B077022F7 /* UnspentOutputsManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputsManagerTests.swift; sourceTree = ""; }; - 58AAA36C584F843DFB51B89C /* BlockDiscoveryBatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockDiscoveryBatch.swift; sourceTree = ""; }; - 58AAA3744E23993E6BA283AC /* BitcoinCashMainNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashMainNetTests.swift; sourceTree = ""; }; - 58AAA3909BD23CBF0D217FDB /* BlockValidatorHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockValidatorHelper.swift; sourceTree = ""; }; - 58AAA3BE8FA350BD415CD5B3 /* Chunk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Chunk.swift; sourceTree = ""; }; - 58AAA3FBA5C8CE3D9C678F5D /* PaymentAddressParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentAddressParserTests.swift; sourceTree = ""; }; - 58AAA422686CE355CACA11DF /* ScriptBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptBuilderTests.swift; sourceTree = ""; }; - 58AAA481A82D05C914AB0CCA /* UnspentOutputProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputProvider.swift; sourceTree = ""; }; - 58AAA4852F5313674AA9E2B3 /* SynchronizedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizedArray.swift; sourceTree = ""; }; - 58AAA4F887DF0F5E9527C761 /* ScriptBuilderChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptBuilderChain.swift; sourceTree = ""; }; - 58AAA5081672879DFB3D483F /* PeerTaskHandlerChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerTaskHandlerChain.swift; sourceTree = ""; }; - 58AAA55E49708A950F951E2D /* BitcoinAddressSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinAddressSelector.swift; sourceTree = ""; }; - 58AAA5D8E61487D82ACFD9A6 /* NetworkMessageParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMessageParser.swift; sourceTree = ""; }; - 58AAA5EE018C74544938121F /* MerkleBlockValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBlockValidatorTests.swift; sourceTree = ""; }; - 58AAA62C7C37CA2D7FD668B5 /* TransactionOutputExtractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputExtractor.swift; sourceTree = ""; }; - 58AAA6603AEA2159F6126D10 /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; - 58AAA67094BBC66D3A385341 /* UnspentOutputSelectorSingleNoChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorSingleNoChange.swift; sourceTree = ""; }; - 58AAA674EE7C1523F42648DD /* TransactionOutputAddressExtractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputAddressExtractor.swift; sourceTree = ""; }; - 58AAA684704A912E02B1138B /* SyncManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncManager.swift; sourceTree = ""; }; - 58AAA6AD5B652DC76D5181BC /* AddressConverterChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressConverterChain.swift; sourceTree = ""; }; - 58AAA6BB66ECE0543BE377E3 /* ScriptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptTests.swift; sourceTree = ""; }; - 58AAA735418741ACF44ED4D6 /* LegacyDifficultyAdjustmentValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyDifficultyAdjustmentValidator.swift; sourceTree = ""; }; - 58AAA7A391E4B2F8C4E99B97 /* SyncedReadyPeerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncedReadyPeerManager.swift; sourceTree = ""; }; - 58AAA7B8F141FBC32B735966 /* TransactionOutputAddressExtractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputAddressExtractorTests.swift; sourceTree = ""; }; - 58AAA83A08FA97C685C8E640 /* BloomFilterLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilterLoader.swift; sourceTree = ""; }; - 58AAA8F791902E5D0F3F1461 /* InitialBlockDownload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialBlockDownload.swift; sourceTree = ""; }; - 58AAA925AA61EB0BA46B7EAC /* LegacyDifficultyAdjustmentValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyDifficultyAdjustmentValidatorTests.swift; sourceTree = ""; }; - 58AAA97A777A2F1E5734C89B /* PaymentAddressParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentAddressParser.swift; sourceTree = ""; }; - 58AAA97B8114B3CE27A6D32C /* TransactionPublicKeySetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionPublicKeySetterTests.swift; sourceTree = ""; }; - 58AAA983B426801C46003567 /* UnspentOutputSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelector.swift; sourceTree = ""; }; - 58AAA9B2685F25E6923DDA77 /* BlockHeaderParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHeaderParser.swift; sourceTree = ""; }; - 58AAA9BB5301D7CB5292473B /* BlockDiscoveryBatchTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockDiscoveryBatchTest.swift; sourceTree = ""; }; - 58AAA9C5C3A65FCEBB46F340 /* MerkleBranch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBranch.swift; sourceTree = ""; }; - 58AAAA63CA9758D7D97E1416 /* SigHashType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SigHashType.swift; sourceTree = ""; }; - 58AAAA7C33E2529A70D0E529 /* TransactionSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSizeCalculator.swift; sourceTree = ""; }; - 58AAAA81938DAE46B8074C9D /* UnspentOutputSelectorSingleNoChangeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorSingleNoChangeTests.swift; sourceTree = ""; }; - 58AAAAACEC07FF47326D89AD /* TransactionSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSender.swift; sourceTree = ""; }; - 58AAAAB98CB96E73F39A7306 /* ProofOfWorkValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProofOfWorkValidator.swift; sourceTree = ""; }; - 58AAAAFCA9E73AD5A7AB4CA6 /* BitcoinMainNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinMainNetTests.swift; sourceTree = ""; }; - 58AAAB31CA6F582F89A1BA14 /* LegacyTestNetDifficultyValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyTestNetDifficultyValidatorTests.swift; sourceTree = ""; }; - 58AAAB4C41058C7E06D10BFB /* InventoryItemsHandlerChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InventoryItemsHandlerChain.swift; sourceTree = ""; }; - 58AAAB4FF63F2AA59C24E9C0 /* Address.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Address.swift; sourceTree = ""; }; - 58AAAB709C58E0E9DC3F074B /* DataListSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataListSerializer.swift; sourceTree = ""; }; - 58AAAB994FBA1E42999E4968 /* BitsValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitsValidator.swift; sourceTree = ""; }; - 58AAABDF62BBC64C9A3363A6 /* ScriptConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptConverterTests.swift; sourceTree = ""; }; - 58AAAC136573542B0D16C283 /* BlockHashFetcherHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHashFetcherHelper.swift; sourceTree = ""; }; - 58AAAC35C314C36A49F3AE80 /* LegacyTestNetDifficultyValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyTestNetDifficultyValidator.swift; sourceTree = ""; }; - 58AAAC3F1788B8AFC44F6A4E /* BitsValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitsValidatorTests.swift; sourceTree = ""; }; - 58AAAC66042A881E2F74AFD6 /* TransactionCreator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionCreator.swift; sourceTree = ""; }; - 58AAAC8BAD29CC18864884DB /* ScriptConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptConverter.swift; sourceTree = ""; }; - 58AAAC9FD7A9AD220E52F974 /* MerkleBlockValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBlockValidator.swift; sourceTree = ""; }; - 58AAACDCDC4B51B6E2C9C07D /* ProofOfWorkValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProofOfWorkValidatorTests.swift; sourceTree = ""; }; - 58AAAD725CB287055DC7CCE5 /* BlockHashFetcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHashFetcherTests.swift; sourceTree = ""; }; - 58AAAD73659B838E541862BA /* MempoolTransactions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MempoolTransactions.swift; sourceTree = ""; }; - 58AAADC9D2822A6C4D415A90 /* SyncTransactionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncTransactionItem.swift; sourceTree = ""; }; - 58AAAE010437D0A44D0B975B /* BitcoinCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCore.swift; sourceTree = ""; }; - 58AAAE1DA82FC3585855DED3 /* ChunkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChunkTests.swift; sourceTree = ""; }; - 58AAAE28D740FD1FCF9DC1B2 /* TransactionOutputExtractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputExtractorTests.swift; sourceTree = ""; }; - 58AAAE5AF69AFF1730A54D24 /* UnspentOutputSelectorChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorChain.swift; sourceTree = ""; }; - 58AAAE784BD9490F8775E78A /* InitialSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialSyncer.swift; sourceTree = ""; }; - 58AAAE9E2FE6584E581B71E1 /* BlockValidatorChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockValidatorChain.swift; sourceTree = ""; }; - 58AAAECE6429C4CBD7FD2394 /* UnspentOutputProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputProviderTests.swift; sourceTree = ""; }; - 58AAAED5E10009C0720D949E /* TransactionSizeCalculatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSizeCalculatorTests.swift; sourceTree = ""; }; - 58AAAEDC9D0D7BAE56C71DE0 /* BitcoinTestNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinTestNetTests.swift; sourceTree = ""; }; - 58AAAF2482982ED7215EEBF9 /* ScriptBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptBuilder.swift; sourceTree = ""; }; - 58AAAF2AC5C1330DFE1FAAD3 /* BitcoinCoreErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCoreErrors.swift; sourceTree = ""; }; - 58AAAF92B98343622D7D4964 /* Base58AddressConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Base58AddressConverter.swift; sourceTree = ""; }; - 58AAAFE91DB09FA5E32DD881 /* BaseTransactionInfoConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTransactionInfoConverter.swift; sourceTree = ""; }; - 58AAAFF6092A4436B5FF46FF /* BlockHashFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHashFetcher.swift; sourceTree = ""; }; - 657132791355B7D737C6AFF8 /* Pods-BitcoinCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCoreTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests.debug.xcconfig"; sourceTree = ""; }; - 733A12F673E7E69AC511C2EC /* Pods-BitcoinCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCore.release.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCore/Pods-BitcoinCore.release.xcconfig"; sourceTree = ""; }; - CB683BFE7F0F553B11E895DB /* Pods-BitcoinCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCoreTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests.release.xcconfig"; sourceTree = ""; }; - CE7E1640CACC3D2ED2DA233D /* Pods_BitcoinCoreTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitcoinCoreTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D00DA94D213E86BC007F82D6 /* MemPoolMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemPoolMessage.swift; sourceTree = ""; }; - D026EFEF21AFD1FB00B6640D /* ConnectionTimeoutManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionTimeoutManagerTests.swift; sourceTree = ""; }; - D093F45D21414CC000C5D8CD /* PeerAddressManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerAddressManager.swift; sourceTree = ""; }; - D0F63BE321EF349D000CAB79 /* DataProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProviderTests.swift; sourceTree = ""; }; - D31711E920F7192300AEF61B /* PeerConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnection.swift; sourceTree = ""; }; - D31711EB20F7192F00AEF61B /* PingMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PingMessage.swift; sourceTree = ""; }; - D31711EC20F7192F00AEF61B /* AddressMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressMessage.swift; sourceTree = ""; }; - D31711ED20F7192F00AEF61B /* NetworkAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkAddress.swift; sourceTree = ""; }; - D31711EF20F7192F00AEF61B /* NetworkMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkMessage.swift; sourceTree = ""; }; - D31711F120F7192F00AEF61B /* VersionMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionMessage.swift; sourceTree = ""; }; - D31711F220F7193000AEF61B /* GetBlocksMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBlocksMessage.swift; sourceTree = ""; }; - D31711F320F7193000AEF61B /* RejectMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RejectMessage.swift; sourceTree = ""; }; - D31711F420F7193000AEF61B /* InventoryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InventoryItem.swift; sourceTree = ""; }; - D31711F620F7193000AEF61B /* GetDataMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetDataMessage.swift; sourceTree = ""; }; - D31711F720F7193000AEF61B /* InventoryMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InventoryMessage.swift; sourceTree = ""; }; - D31711FA20F7193000AEF61B /* FilterLoadMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterLoadMessage.swift; sourceTree = ""; }; - D31711FB20F7193000AEF61B /* MerkleBlockMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBlockMessage.swift; sourceTree = ""; }; - D31711FC20F7193000AEF61B /* VerackMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerackMessage.swift; sourceTree = ""; }; - D31711FD20F7193000AEF61B /* PongMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PongMessage.swift; sourceTree = ""; }; - D317121720F719A900AEF61B /* MurmurHash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MurmurHash.swift; sourceTree = ""; }; - D33FD6B320F3782600EC10DB /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = Libraries/openssl/lib/libcrypto.a; sourceTree = ""; }; - D33FD6C820F3876800EC10DB /* Serialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Serialization.swift; sourceTree = ""; }; - D33FD6D020F3885400EC10DB /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; - D373EB82215A2A6C00124298 /* TransactionSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSyncer.swift; sourceTree = ""; }; - D380FD5B20FCC04400276993 /* BitcoinCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitcoinCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - D380FD5F20FCC04400276993 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; - D380FD6720FCC22800276993 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; - D3FD450520F354EF00F472B9 /* BitcoinCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BitcoinCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D3FD450820F354EF00F472B9 /* BitcoinCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BitcoinCore.h; sourceTree = ""; }; - D3FD450920F354EF00F472B9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - D380FD5820FCC04400276993 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D380FD6020FCC04400276993 /* BitcoinCore.framework in Frameworks */, - 7BAB34250E0A3F6F5A976FEF /* Pods_BitcoinCoreTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D3FD450120F354EF00F472B9 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6440515C276D7ADF203CC3ED /* Pods_BitcoinCore.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 11B3507A75FAF11E3C461160 /* BlockHeaders */ = { - isa = PBXGroup; - children = ( - 11B35D0FBCC5A6A00640AA4E /* DifficultyEncoderTests.swift */, - ); - path = BlockHeaders; - sourceTree = ""; - }; - 11B35156C3ADDCCD4A60CCD4 /* Core */ = { - isa = PBXGroup; - children = ( - 58AAA087C2B107DA9ABFF5DD /* AbstractKit.swift */, - 58AAAE010437D0A44D0B975B /* BitcoinCore.swift */, - 58AAA0C32EB09BCB2D5D7396 /* BitcoinCoreBuilder.swift */, - 58AAAF2AC5C1330DFE1FAAD3 /* BitcoinCoreErrors.swift */, - 58AAA83A08FA97C685C8E640 /* BloomFilterLoader.swift */, - 11B3525D146F1119A0A9128A /* DataProvider.swift */, - 11B3529A1731702724B0342D /* Factory.swift */, - 11B35F8792CA3A0945C18200 /* HDWallet.swift */, - 2FA5D859C070F9718CACF08F /* KitStateProvider.swift */, - 2FA5D9929AB239235F9B6895 /* Logger.swift */, - 11B3501384C4274027587EB4 /* Protocols.swift */, - 58AAA6603AEA2159F6126D10 /* Signal.swift */, - 58AAA0FE0496F520DDA29BB1 /* TransactionInfoConverter.swift */, - 58AAAFE91DB09FA5E32DD881 /* BaseTransactionInfoConverter.swift */, - ); - path = Core; - sourceTree = ""; - }; - 11B352462C0F682238607E64 /* Managers */ = { - isa = PBXGroup; - children = ( - 58AAA656C4EDCE966289B71F /* InitialSync */, - 2FA5D05FCBFFE93D46F8C292 /* AddressManagerTests.swift */, - 2FA5D4E64129FF62BED7ADE7 /* BloomFilterManagerTests.swift */, - 11B35F9A349EC7EF0DD5BA96 /* InitialSyncerTests.swift */, - 2FA5DFBF60F56D8A2FC86ED2 /* StateManagerTests.swift */, - 2FA5DAE996A847B90E5D033A /* UnspentOutputSelectorTests.swift */, - 58AAAECE6429C4CBD7FD2394 /* UnspentOutputProviderTests.swift */, - 58AAAA81938DAE46B8074C9D /* UnspentOutputSelectorSingleNoChangeTests.swift */, - ); - path = Managers; - sourceTree = ""; - }; - 11B353E22D0A82C3AC8ABDE9 /* Blocks */ = { - isa = PBXGroup; - children = ( - 58AAA417876C03B11809DB9A /* Validators */, - 2FA5D05597A1DC332C67BDF7 /* Blockchain.swift */, - 11B35C07292793FCE3096D03 /* BlockSyncer.swift */, - 2FA5D2DDF60A9C5DA3776D8A /* BlockSyncerState.swift */, - 58AAA3909BD23CBF0D217FDB /* BlockValidatorHelper.swift */, - 58AAA8F791902E5D0F3F1461 /* InitialBlockDownload.swift */, - 58AAAC9FD7A9AD220E52F974 /* MerkleBlockValidator.swift */, - ); - path = Blocks; - sourceTree = ""; - }; - 11B355C61361B7F77322E618 /* Extensions */ = { - isa = PBXGroup; - children = ( - 2FA5DBBF82E71BB3E056B753 /* Array.swift */, - 11B35F3FD9CCB938B62D07EA /* Observable.swift */, - 11B3510658CE89D9DB6772E5 /* String.swift */, - 58AAA4852F5313674AA9E2B3 /* SynchronizedArray.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - 11B35649863DB92E43F8D567 /* Helpers */ = { - isa = PBXGroup; - children = ( - 58AAA6AD5B652DC76D5181BC /* AddressConverterChain.swift */, - 58AAAF92B98343622D7D4964 /* Base58AddressConverter.swift */, - 2FA5D131E769F7E4C62CC49F /* DirectoryHelper.swift */, - 58AAA2422A1324C9260FB494 /* DoubleShaHasher.swift */, - D33FD6D020F3885400EC10DB /* Helpers.swift */, - 58AAA9C5C3A65FCEBB46F340 /* MerkleBranch.swift */, - D33FD6C820F3876800EC10DB /* Serialization.swift */, - 58AAA97A777A2F1E5734C89B /* PaymentAddressParser.swift */, - 58AAAE5AF69AFF1730A54D24 /* UnspentOutputSelectorChain.swift */, - ); - path = Helpers; - sourceTree = ""; - }; - 11B357B9FF089EC58748DA4C /* Network */ = { - isa = PBXGroup; - children = ( - D096D108219E93F300E8103F /* Helpers */, - 11B358F2406C382875DA91A2 /* Messages */, - D096D109219E95B800E8103F /* Peer */, - 58AAAFDF237EA32A0C25548D /* Parsers */, - 58AAAAACEC07FF47326D89AD /* TransactionSender.swift */, - ); - path = Network; - sourceTree = ""; - }; - 11B3582B0517A9873E63381B /* Transactions */ = { - isa = PBXGroup; - children = ( - 58AAAB58B5883EDAE754E34F /* Builder */, - 58AAAE8C826DAC0CB77E8521 /* Scripts */, - 58AAA21C2FD6764CD385663E /* TransactionCreatorTests.swift */, - 3C7B9EBABAFF96958CFB2CB7 /* TransactionInputExtractorTests.swift */, - 58AAAE28D740FD1FCF9DC1B2 /* TransactionOutputExtractorTests.swift */, - 58AAA7B8F141FBC32B735966 /* TransactionOutputAddressExtractorTests.swift */, - 2FA5D0B8032597D7392B0637 /* TransactionProcessorTests.swift */, - 58AAA97B8114B3CE27A6D32C /* TransactionPublicKeySetterTests.swift */, - 58AAAED5E10009C0720D949E /* TransactionSizeCalculatorTests.swift */, - 2FA5D316DD8A722A5300002B /* TransactionSyncerTests.swift */, - ); - path = Transactions; - sourceTree = ""; - }; - 11B358F2406C382875DA91A2 /* Messages */ = { - isa = PBXGroup; - children = ( - D31711EC20F7192F00AEF61B /* AddressMessage.swift */, - D31711FA20F7193000AEF61B /* FilterLoadMessage.swift */, - D31711F220F7193000AEF61B /* GetBlocksMessage.swift */, - D31711F620F7193000AEF61B /* GetDataMessage.swift */, - 11B35FF2F94006C73B7773BC /* GetHeadersMessage.swift */, - D31711F420F7193000AEF61B /* InventoryItem.swift */, - D31711F720F7193000AEF61B /* InventoryMessage.swift */, - D00DA94D213E86BC007F82D6 /* MemPoolMessage.swift */, - 2FA5D5C9BB0E185AF72B9174 /* MerkleBlock.swift */, - D31711FB20F7193000AEF61B /* MerkleBlockMessage.swift */, - D31711ED20F7192F00AEF61B /* NetworkAddress.swift */, - D31711EF20F7192F00AEF61B /* NetworkMessage.swift */, - D31711EB20F7192F00AEF61B /* PingMessage.swift */, - D31711FD20F7193000AEF61B /* PongMessage.swift */, - D31711F320F7193000AEF61B /* RejectMessage.swift */, - 2FA5D307095385625960B0A0 /* TransactionMessage.swift */, - 2FA5D3F63860A15B55DC196E /* UnknownMessage.swift */, - D31711FC20F7193000AEF61B /* VerackMessage.swift */, - D31711F120F7192F00AEF61B /* VersionMessage.swift */, - ); - path = Messages; - sourceTree = ""; - }; - 11B35ABE009449CF02027197 /* Extractors */ = { - isa = PBXGroup; - children = ( - 11B354DDB46C9DE37375424D /* OpCode.swift */, - ); - path = Extractors; - sourceTree = ""; - }; - 11B35B5B2F111C31B1D972EB /* Models */ = { - isa = PBXGroup; - children = ( - 58AAAB4FF63F2AA59C24E9C0 /* Address.swift */, - 58AAA0125762CC01D08AB167 /* BitcoinPaymentData.swift */, - 11B350357DF0C843FDC0C2DE /* Block.swift */, - 11B35525251BF8A38E021356 /* BlockchainState.swift */, - 2FA5DB1F056B264F829BFDDD /* BlockHash.swift */, - 2FA5D50922572F91F966DD4A /* DataObjects.swift */, - 11B35352F3DA4C26A552C62B /* Input.swift */, - 11B3559B75AD75C20287ECC7 /* Output.swift */, - 2FA5DDED90E11DEC3647B164 /* PeerAddress.swift */, - 11B35E7E6BB3227429A41B13 /* PublicKey.swift */, - 2FA5DF037E951950A0F7FF03 /* SentTransaction.swift */, - 58AAAA63CA9758D7D97E1416 /* SigHashType.swift */, - 11B35C67DB63EF992C380513 /* Transaction.swift */, - 11B357C9E6B02801B0422881 /* TransactionInfo.swift */, - ); - path = Models; - sourceTree = ""; - }; - 11B35BC0CDFD3512DD636A78 /* Transactions */ = { - isa = PBXGroup; - children = ( - 58AAA98E5C50F2723D500F9F /* Builder */, - 11B35ABE009449CF02027197 /* Extractors */, - 58AAA11F421202A1ADFF0CE2 /* Scripts */, - 11B356EF3A4749163293CCA3 /* BlockSyncer.swift */, - 2FA5DCAE853F5F4068A34CF2 /* OutputsCache.swift */, - 58AAAC66042A881E2F74AFD6 /* TransactionCreator.swift */, - 11B3556BD9C3E52C992D1E92 /* TransactionInputExtractor.swift */, - 58AAA674EE7C1523F42648DD /* TransactionOutputAddressExtractor.swift */, - 58AAA62C7C37CA2D7FD668B5 /* TransactionOutputExtractor.swift */, - 2FA5DF1470723462D2CF707B /* TransactionProcessor.swift */, - 3C7B983C4EC7F79E44560A7C /* TransactionPublicKeySetter.swift */, - 58AAAA7C33E2529A70D0E529 /* TransactionSizeCalculator.swift */, - D373EB82215A2A6C00124298 /* TransactionSyncer.swift */, - ); - path = Transactions; - sourceTree = ""; - }; - 11B35D2E54A01E9DCA96242C /* BlockHeaders */ = { - isa = PBXGroup; - children = ( - 58AAA9B2685F25E6923DDA77 /* BlockHeaderParser.swift */, - 11B354EF0F404DF96E765CA1 /* DifficultyEncoder.swift */, - ); - path = BlockHeaders; - sourceTree = ""; - }; - 11B35D7D1D8F72DFA208F497 /* PeerTask */ = { - isa = PBXGroup; - children = ( - 11B352F4797ED2214EC26607 /* PeerTask.swift */, - 2FA5D65667DA8BFD91E91C8B /* GetBlockHashesTask.swift */, - 11B35AEAAB5F1F7246063AEB /* GetMerkleBlocksTask.swift */, - 11B35413F7A859F19D27BA39 /* RequestTransactionsTask.swift */, - 11B350832C84DD224B3E8AF2 /* SendTransactionTask.swift */, - ); - path = PeerTask; - sourceTree = ""; - }; - 11B35DDF6BAA7B4337BA1725 /* Crypto */ = { - isa = PBXGroup; - children = ( - 11B350F93CE46013514830A3 /* BloomFilter.swift */, - D317121720F719A900AEF61B /* MurmurHash.swift */, - ); - path = Crypto; - sourceTree = ""; - }; - 11B35F875BE86EBC5D97DECC /* Blocks */ = { - isa = PBXGroup; - children = ( - 58AAAA35F88A07E4EAB6AD13 /* Validators */, - 2FA5DEC678BF41F0D38095DD /* BlockchainTests.swift */, - 11B352A955F667DC0C23CE1C /* BlockSyncerTests.swift */, - 2FA5D82EFF381CE6233790A9 /* KitStateProviderTests.swift */, - 58AAA5EE018C74544938121F /* MerkleBlockValidatorTests.swift */, - ); - path = Blocks; - sourceTree = ""; - }; - 11B35F900746A8190305C20E /* Managers */ = { - isa = PBXGroup; - children = ( - 58AAAEC3E09DB4796FF009DE /* InitialSync */, - 2FA5D5A9656117A6FD41E814 /* AddressManager.swift */, - 11B3597DE3169FD250A982D5 /* ApiManager.swift */, - 58AAA55E49708A950F951E2D /* BitcoinAddressSelector.swift */, - 2FA5DE7E06652FCED7B288E7 /* BloomFilterManager.swift */, - 2FA5D6676E3CB6028E1B6018 /* ReachabilityManager.swift */, - 11B358481D7E7EB156912F52 /* StateManager.swift */, - 58AAA684704A912E02B1138B /* SyncManager.swift */, - 58AAA983B426801C46003567 /* UnspentOutputSelector.swift */, - 58AAA481A82D05C914AB0CCA /* UnspentOutputProvider.swift */, - 58AAA7A391E4B2F8C4E99B97 /* SyncedReadyPeerManager.swift */, - 58AAA67094BBC66D3A385341 /* UnspentOutputSelectorSingleNoChange.swift */, - ); - path = Managers; - sourceTree = ""; - }; - 58AAA11F421202A1ADFF0CE2 /* Scripts */ = { - isa = PBXGroup; - children = ( - 58AAA3BE8FA350BD415CD5B3 /* Chunk.swift */, - 58AAA11CDED1A6F7663DEA37 /* Script.swift */, - 58AAAC8BAD29CC18864884DB /* ScriptConverter.swift */, - ); - path = Scripts; - sourceTree = ""; - }; - 58AAA417876C03B11809DB9A /* Validators */ = { - isa = PBXGroup; - children = ( - 58AAAB994FBA1E42999E4968 /* BitsValidator.swift */, - 58AAAE9E2FE6584E581B71E1 /* BlockValidatorChain.swift */, - 58AAA735418741ACF44ED4D6 /* LegacyDifficultyAdjustmentValidator.swift */, - 58AAAC35C314C36A49F3AE80 /* LegacyTestNetDifficultyValidator.swift */, - 58AAAAB98CB96E73F39A7306 /* ProofOfWorkValidator.swift */, - ); - path = Validators; - sourceTree = ""; - }; - 58AAA656C4EDCE966289B71F /* InitialSync */ = { - isa = PBXGroup; - children = ( - 58AAA1C4F57CC56BB8CC772C /* BlockHashFetcherHelperTests.swift */, - 58AAAD725CB287055DC7CCE5 /* BlockHashFetcherTests.swift */, - 58AAA9BB5301D7CB5292473B /* BlockDiscoveryBatchTest.swift */, - ); - path = InitialSync; - sourceTree = ""; - }; - 58AAA75CEC4FAF1FD10D1714 /* Storage */ = { - isa = PBXGroup; - children = ( - 11B3543D7114E1FE55B2DCC3 /* GrdbStorage.swift */, - ); - path = Storage; - sourceTree = ""; - }; - 58AAA80C18459F9822D75EC3 /* Network */ = { - isa = PBXGroup; - children = ( - D096D10A219E97E100E8103F /* Peer */, - 58AAA3744E23993E6BA283AC /* BitcoinCashMainNetTests.swift */, - 58AAAAFCA9E73AD5A7AB4CA6 /* BitcoinMainNetTests.swift */, - 58AAAEDC9D0D7BAE56C71DE0 /* BitcoinTestNetTests.swift */, - 58AAA285EFA8FD5B4236D8CC /* BitcoinRegTestNetTests.swift */, - ); - path = Network; - sourceTree = ""; - }; - 58AAA98E5C50F2723D500F9F /* Builder */ = { - isa = PBXGroup; - children = ( - 2FA5DF9995FA7DD7556893F7 /* InputSigner.swift */, - 58AAAF2482982ED7215EEBF9 /* ScriptBuilder.swift */, - 2FA5D11405173CD80A6B9904 /* TransactionBuilder.swift */, - 58AAA4F887DF0F5E9527C761 /* ScriptBuilderChain.swift */, - ); - path = Builder; - sourceTree = ""; - }; - 58AAAA35F88A07E4EAB6AD13 /* Validators */ = { - isa = PBXGroup; - children = ( - 58AAAC3F1788B8AFC44F6A4E /* BitsValidatorTests.swift */, - 58AAA925AA61EB0BA46B7EAC /* LegacyDifficultyAdjustmentValidatorTests.swift */, - 58AAAB31CA6F582F89A1BA14 /* LegacyTestNetDifficultyValidatorTests.swift */, - 58AAACDCDC4B51B6E2C9C07D /* ProofOfWorkValidatorTests.swift */, - ); - path = Validators; - sourceTree = ""; - }; - 58AAAB58B5883EDAE754E34F /* Builder */ = { - isa = PBXGroup; - children = ( - 2FA5DA9D7F78A26107F7209C /* InputSignerTests.swift */, - 2FA5DDAE64200E386070668C /* TransactionBuilderTests.swift */, - 58AAA422686CE355CACA11DF /* ScriptBuilderTests.swift */, - ); - path = Builder; - sourceTree = ""; - }; - 58AAAE7E1680DCF9D6EDCAB3 /* Handlers */ = { - isa = PBXGroup; - children = ( - 58AAAB4C41058C7E06D10BFB /* InventoryItemsHandlerChain.swift */, - 58AAA5081672879DFB3D483F /* PeerTaskHandlerChain.swift */, - ); - path = Handlers; - sourceTree = ""; - }; - 58AAAE8C826DAC0CB77E8521 /* Scripts */ = { - isa = PBXGroup; - children = ( - 58AAAE1DA82FC3585855DED3 /* ChunkTests.swift */, - 58AAA6BB66ECE0543BE377E3 /* ScriptTests.swift */, - 58AAABDF62BBC64C9A3363A6 /* ScriptConverterTests.swift */, - ); - path = Scripts; - sourceTree = ""; - }; - 58AAAEC3E09DB4796FF009DE /* InitialSync */ = { - isa = PBXGroup; - children = ( - 3C7B91E6E6DBA06A79ED8005 /* BCoinApi.swift */, - 58AAA36C584F843DFB51B89C /* BlockDiscoveryBatch.swift */, - 58AAAFF6092A4436B5FF46FF /* BlockHashFetcher.swift */, - 58AAAC136573542B0D16C283 /* BlockHashFetcherHelper.swift */, - 58AAAE784BD9490F8775E78A /* InitialSyncer.swift */, - 58AAADC9D2822A6C4D415A90 /* SyncTransactionItem.swift */, - 58AAA1173B95D1609546836E /* InsightApi.swift */, - ); - path = InitialSync; - sourceTree = ""; - }; - 58AAAFDF237EA32A0C25548D /* Parsers */ = { - isa = PBXGroup; - children = ( - 58AAA5D8E61487D82ACFD9A6 /* NetworkMessageParser.swift */, - 58AAA201AEA32587BD5E2E12 /* NetworkMessageSerializer.swift */, - ); - path = Parsers; - sourceTree = ""; - }; - D01B6FAA21230AE80085010F /* Helpers */ = { - isa = PBXGroup; - children = ( - 2FA5D23A8EC7BB3569F51ABD /* AddressConverterTests.swift */, - 58AAA0616D186C35DC0F3367 /* BlockValidatorHelperTests.swift */, - 58AAA2A6D99FD23FFA252002 /* MerkleBranchTests.swift */, - 58AAA3FBA5C8CE3D9C678F5D /* PaymentAddressParserTests.swift */, - ); - path = Helpers; - sourceTree = ""; - }; - D0232B37219C4740006803E1 /* PeerTests */ = { - isa = PBXGroup; - children = ( - 2FA5D99586B15F4477D695F7 /* IPeerTests.swift */, - 2FA5DF96100D81EACBBC87F6 /* PeerConnectionDelegateTests.swift */, - 2FA5DFB5B9A13478ACBB61EB /* IPeerTaskDelegateTests.swift */, - 2FA5DAAAC2F759FD42948DDE /* IPeerTaskRequesterTests.swift */, - ); - path = PeerTests; - sourceTree = ""; - }; - D05D5DEF219BF5AB006D0394 /* PeerTask */ = { - isa = PBXGroup; - children = ( - 2FA5DFC7FA0A5363BFFB8E28 /* GetBlockHashesTaskTests.swift */, - 2FA5D2907674257196C4F586 /* GetMerkleBlocksTaskTests.swift */, - 2FA5D4A92FB8C2376A9A0090 /* RequestTransactionTaskTests.swift */, - 2FA5D2DAB8485F5F9E2E23B6 /* SendTransactionTaskTests.swift */, - ); - path = PeerTask; - sourceTree = ""; - }; - D064ED8821622CA500310C99 /* PeerGroupTests */ = { - isa = PBXGroup; - children = ( - 2FA5D0A6A8C6C5A4E2AAFB74 /* BloomFilterManagerDelegateTests.swift */, - 2FA5DF41CAF034B375E58E7A /* IPeerGroupTests.swift */, - 2FA5DCB87581859B2E083377 /* PeerDelegateTests.swift */, - 2FA5D602DBCFC5121F7C245F /* PeerGroupTests.swift */, - 2FA5D632A9EFB1F0CF2523DF /* PeerHostManagerDelegateTests.swift */, - ); - path = PeerGroupTests; - sourceTree = ""; - }; - D096D108219E93F300E8103F /* Helpers */ = { - isa = PBXGroup; - children = ( - 11B35AEA9C74CB8AFFFE9D8E /* ByteStream.swift */, - 2FA5DE9CAB1F15B04E2C54C9 /* PeerDiscovery.swift */, - 11B356D308DCF4BB33D9F488 /* ServiceFlags.swift */, - 11B355C0072CA13ADBAC1351 /* VarInt.swift */, - 11B35C47D552B7AC5EA8C463 /* VarString.swift */, - ); - path = Helpers; - sourceTree = ""; - }; - D096D109219E95B800E8103F /* Peer */ = { - isa = PBXGroup; - children = ( - 11B35D7D1D8F72DFA208F497 /* PeerTask */, - 2FA5DEC11B1DA4DA336DBB28 /* ConnectionTimeoutManager.swift */, - 58AAAD73659B838E541862BA /* MempoolTransactions.swift */, - 2FA5D5A744085AD28EFBB868 /* Peer.swift */, - D093F45D21414CC000C5D8CD /* PeerAddressManager.swift */, - 11B35FE0F5B2A705A5C42439 /* PeerAddressManagerState.swift */, - D31711E920F7192300AEF61B /* PeerConnection.swift */, - 11B359E44D75610DF2841327 /* PeerGroup.swift */, - 2FA5D3968E6E11AF76368CA5 /* PeerManager.swift */, - ); - path = Peer; - sourceTree = ""; - }; - D096D10A219E97E100E8103F /* Peer */ = { - isa = PBXGroup; - children = ( - D0232B37219C4740006803E1 /* PeerTests */, - D064ED8821622CA500310C99 /* PeerGroupTests */, - D05D5DEF219BF5AB006D0394 /* PeerTask */, - D026EFEF21AFD1FB00B6640D /* ConnectionTimeoutManagerTests.swift */, - 2FA5DF9ADB516A45C797889D /* PeerAddressManagerTests.swift */, - 2FA5D27387B0CF46C2B0A22A /* PeerManagerTests.swift */, - ); - name = Peer; - path = "New Group"; - sourceTree = ""; - }; - D0CF87C9211D9E36003C950F /* Recovered References */ = { - isa = PBXGroup; - children = ( - 58AAA33E4E02571B077022F7 /* UnspentOutputsManagerTests.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; - D0F02417213CF40F002629EF /* Serializers */ = { - isa = PBXGroup; - children = ( - 58AAAB709C58E0E9DC3F074B /* DataListSerializer.swift */, - 2FA5DCD21585D41C520DF764 /* TransactionInputSerializer.swift */, - 2FA5DEC34722F594F85688C0 /* TransactionOutputSerializer.swift */, - 2FA5D880471F191DBCDAE574 /* TransactionSerializer.swift */, - ); - path = Serializers; - sourceTree = ""; - }; - D0F63BE221EF3469000CAB79 /* Core */ = { - isa = PBXGroup; - children = ( - D0F63BE321EF349D000CAB79 /* DataProviderTests.swift */, - ); - path = Core; - sourceTree = ""; - }; - D0FCA7092133D250008169E1 /* HDWallet */ = { - isa = PBXGroup; - children = ( - 2FA5DADEB0FD09138FD308A1 /* HDPrivateKeyTests.swift */, - ); - path = HDWallet; - sourceTree = ""; - }; - D33FD6B220F3782600EC10DB /* Frameworks */ = { - isa = PBXGroup; - children = ( - D33FD6B320F3782600EC10DB /* libcrypto.a */, - 471C117B5DB1567486784B2E /* Pods_BitcoinCore.framework */, - CE7E1640CACC3D2ED2DA233D /* Pods_BitcoinCoreTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - D380FD5C20FCC04400276993 /* BitcoinCoreTests */ = { - isa = PBXGroup; - children = ( - 11B35F875BE86EBC5D97DECC /* Blocks */, - 11B3507A75FAF11E3C461160 /* BlockHeaders */, - D0F63BE221EF3469000CAB79 /* Core */, - D0FCA7092133D250008169E1 /* HDWallet */, - D01B6FAA21230AE80085010F /* Helpers */, - 11B352462C0F682238607E64 /* Managers */, - 58AAA80C18459F9822D75EC3 /* Network */, - 11B3582B0517A9873E63381B /* Transactions */, - D380FD6720FCC22800276993 /* GeneratedMocks.swift */, - 11B3590614D2772B1436542A /* Extensions.swift */, - 11B358F56F6298016B1A8AFD /* TestData.swift */, - D380FD5F20FCC04400276993 /* Info.plist */, - ); - path = BitcoinCoreTests; - sourceTree = ""; - }; - D3FD44FB20F354EF00F472B9 = { - isa = PBXGroup; - children = ( - D3FD450720F354EF00F472B9 /* BitcoinCore */, - D380FD5C20FCC04400276993 /* BitcoinCoreTests */, - D3FD450620F354EF00F472B9 /* Products */, - D33FD6B220F3782600EC10DB /* Frameworks */, - D0CF87C9211D9E36003C950F /* Recovered References */, - FD898085DC2E2351526F6237 /* Pods */, - ); - sourceTree = ""; - }; - D3FD450620F354EF00F472B9 /* Products */ = { - isa = PBXGroup; - children = ( - D3FD450520F354EF00F472B9 /* BitcoinCore.framework */, - D380FD5B20FCC04400276993 /* BitcoinCoreTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - D3FD450720F354EF00F472B9 /* BitcoinCore */ = { - isa = PBXGroup; - children = ( - 11B353E22D0A82C3AC8ABDE9 /* Blocks */, - 11B35D2E54A01E9DCA96242C /* BlockHeaders */, - 11B35156C3ADDCCD4A60CCD4 /* Core */, - 11B35DDF6BAA7B4337BA1725 /* Crypto */, - 11B355C61361B7F77322E618 /* Extensions */, - 11B35649863DB92E43F8D567 /* Helpers */, - 58AAAE7E1680DCF9D6EDCAB3 /* Handlers */, - 11B35F900746A8190305C20E /* Managers */, - 11B35B5B2F111C31B1D972EB /* Models */, - 11B357B9FF089EC58748DA4C /* Network */, - D0F02417213CF40F002629EF /* Serializers */, - 58AAA75CEC4FAF1FD10D1714 /* Storage */, - 11B35BC0CDFD3512DD636A78 /* Transactions */, - D3FD450820F354EF00F472B9 /* BitcoinCore.h */, - D3FD450920F354EF00F472B9 /* Info.plist */, - ); - path = BitcoinCore; - sourceTree = ""; - }; - FD898085DC2E2351526F6237 /* Pods */ = { - isa = PBXGroup; - children = ( - 4018E79E0DF6A0D6F606F9F9 /* Pods-BitcoinCore.debug.xcconfig */, - 733A12F673E7E69AC511C2EC /* Pods-BitcoinCore.release.xcconfig */, - 657132791355B7D737C6AFF8 /* Pods-BitcoinCoreTests.debug.xcconfig */, - CB683BFE7F0F553B11E895DB /* Pods-BitcoinCoreTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - D3FD450220F354EF00F472B9 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - D3FD450A20F354EF00F472B9 /* BitcoinCore.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - D380FD5A20FCC04400276993 /* BitcoinCoreTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = D380FD6520FCC04400276993 /* Build configuration list for PBXNativeTarget "BitcoinCoreTests" */; - buildPhases = ( - D876CFE6F5BC8BE530FFB0C5 /* [CP] Check Pods Manifest.lock */, - D380FD6620FCC1C800276993 /* Cuckoo */, - D380FD5720FCC04400276993 /* Sources */, - D380FD5820FCC04400276993 /* Frameworks */, - D380FD5920FCC04400276993 /* Resources */, - 74DADF6F06F93353C84E2415 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - D343A1892177367E004248DA /* PBXTargetDependency */, - ); - name = BitcoinCoreTests; - productName = WalletKitTests; - productReference = D380FD5B20FCC04400276993 /* BitcoinCoreTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - D3FD450420F354EF00F472B9 /* BitcoinCore */ = { - isa = PBXNativeTarget; - buildConfigurationList = D3FD450D20F354EF00F472B9 /* Build configuration list for PBXNativeTarget "BitcoinCore" */; - buildPhases = ( - 33E5BF42C872A6BE25F853F5 /* [CP] Check Pods Manifest.lock */, - D3FD450020F354EF00F472B9 /* Sources */, - D3FD450120F354EF00F472B9 /* Frameworks */, - D3FD450220F354EF00F472B9 /* Headers */, - D3FD450320F354EF00F472B9 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = BitcoinCore; - productName = WalletKit; - productReference = D3FD450520F354EF00F472B9 /* BitcoinCore.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - D3FD44FC20F354EF00F472B9 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 0940; - ORGANIZATIONNAME = Grouvi; - TargetAttributes = { - D380FD5A20FCC04400276993 = { - CreatedOnToolsVersion = 9.4.1; - }; - D3FD450420F354EF00F472B9 = { - CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1020; - }; - }; - }; - buildConfigurationList = D3FD44FF20F354EF00F472B9 /* Build configuration list for PBXProject "BitcoinCore" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = D3FD44FB20F354EF00F472B9; - productRefGroup = D3FD450620F354EF00F472B9 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - D3FD450420F354EF00F472B9 /* BitcoinCore */, - D380FD5A20FCC04400276993 /* BitcoinCoreTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - D380FD5920FCC04400276993 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D3FD450320F354EF00F472B9 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 33E5BF42C872A6BE25F853F5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BitcoinCore-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 74DADF6F06F93353C84E2415 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - D380FD6620FCC1C800276993 /* Cuckoo */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/${PROJECT_NAME}/Core/Protocols.swift", - "$(SRCROOT)/${PROJECT_NAME}/Transactions/Scripts/Script.swift", - "$(SRCROOT)/${PROJECT_NAME}/Network/Peer/PeerTask/PeerTask.swift", - "$(SRCROOT)/${PROJECT_NAME}/Network/Peer/PeerAddressManagerState.swift", - "$(SRCROOT)/${PROJECT_NAME}/Blocks/BlockSyncerState.swift", - ); - name = Cuckoo; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Define output file. Change \"$PROJECT_DIR/${PROJECT_NAME}Tests\" to your test's root source folder, if it's not the default name.\nOUTPUT_FILE=\"$PROJECT_DIR/${PROJECT_NAME}Tests/GeneratedMocks.swift\"\necho \"Generated Mocks File = $OUTPUT_FILE\"\n\n# Define input directory. Change \"${PROJECT_DIR}/${PROJECT_NAME}\" to your project's root source folder, if it's not the default name.\nINPUT_DIR=\"${PROJECT_DIR}/${PROJECT_NAME}\"\necho \"Mocks Input Directory = $INPUT_DIR\"\n\n# Generate mock files, include as many input files as you'd like to create mocks for.\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"$PROJECT_NAME\" \\\n--output \"${OUTPUT_FILE}\"\n"; - }; - D876CFE6F5BC8BE530FFB0C5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BitcoinCoreTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - D380FD5720FCC04400276993 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D380FD6820FCC22800276993 /* GeneratedMocks.swift in Sources */, - 11B35D23B451CFB803F69CA0 /* TestData.swift in Sources */, - 11B35A0F40D42FCAA4633D8A /* DifficultyEncoderTests.swift in Sources */, - 2FA5DCA14A91CAB48F61F2A0 /* TransactionBuilderTests.swift in Sources */, - 58AAA649DF284C668B495AB2 /* ScriptBuilderTests.swift in Sources */, - 2FA5DC1C403D74796F4131EC /* InputSignerTests.swift in Sources */, - 2FA5DF66AD595EAC2723B0B3 /* UnspentOutputSelectorTests.swift in Sources */, - 58AAAAC9184F9395A71E318B /* UnspentOutputProviderTests.swift in Sources */, - 2FA5DC27C4032E13E157A0D7 /* AddressConverterTests.swift in Sources */, - 58AAA9479AB8BF53D2852502 /* TransactionCreatorTests.swift in Sources */, - 2FA5DEB1D802341D26456814 /* TransactionProcessorTests.swift in Sources */, - 2FA5DC3D8DAD2FF85FB26C91 /* TransactionSyncerTests.swift in Sources */, - 11B35BB1C166ED5CECF292C5 /* Extensions.swift in Sources */, - 58AAAE352FC2FE33B4FB8F53 /* ScriptConverterTests.swift in Sources */, - 58AAAE1B7C0F5659F2EC9229 /* ChunkTests.swift in Sources */, - 58AAA68C0D710ED3FCDE76DE /* ScriptTests.swift in Sources */, - 11B3566FF1557B650F44D89E /* InitialSyncerTests.swift in Sources */, - 58AAA6D586BB4E3E39C15E36 /* TransactionSizeCalculatorTests.swift in Sources */, - 2FA5D5FE71A528D363227585 /* AddressManagerTests.swift in Sources */, - 2FA5D1F7E6DABFC12A0AD064 /* HDPrivateKeyTests.swift in Sources */, - 58AAAD9224BAB440C1B6E033 /* BitsValidatorTests.swift in Sources */, - 58AAA2EC7A7825B8E4408AA7 /* ProofOfWorkValidatorTests.swift in Sources */, - 58AAA1C21F9E20749D8FA9B4 /* LegacyDifficultyAdjustmentValidatorTests.swift in Sources */, - 58AAAA1B8FD14D793868FA8D /* BlockValidatorHelperTests.swift in Sources */, - 58AAA1E4DEA7C5A4CD1DFFC0 /* LegacyTestNetDifficultyValidatorTests.swift in Sources */, - 58AAA6EF974E88D9FBECB99D /* BitcoinCashMainNetTests.swift in Sources */, - 58AAA3C55F225D1100102F78 /* BitcoinMainNetTests.swift in Sources */, - 58AAA51D2E6F57CEB8947032 /* BitcoinTestNetTests.swift in Sources */, - 58AAABF5ED93AD7DE1CE9F25 /* BitcoinRegTestNetTests.swift in Sources */, - 2FA5DB7F5BD8E25DCAF9AC83 /* PeerAddressManagerTests.swift in Sources */, - 11B35333093D726EC8ECA159 /* BlockSyncerTests.swift in Sources */, - 58AAAE00FA79BB744B0EEA9C /* MerkleBlockValidatorTests.swift in Sources */, - 2FA5DF1A340A352EEDBF41E6 /* BlockchainTests.swift in Sources */, - D026EFF021AFD1FB00B6640D /* ConnectionTimeoutManagerTests.swift in Sources */, - 2FA5D443E5A428D1A60CD031 /* KitStateProviderTests.swift in Sources */, - 2FA5D2239DF0C24C4CC6366B /* PeerHostManagerDelegateTests.swift in Sources */, - 2FA5DDCC995A7F27AFB3DE18 /* PeerDelegateTests.swift in Sources */, - 2FA5DB74D510BA9F6D9525A4 /* BloomFilterManagerDelegateTests.swift in Sources */, - 2FA5D571931CA46B417C18BA /* PeerGroupTests.swift in Sources */, - 2FA5DEDC4F93EAB928423720 /* IPeerGroupTests.swift in Sources */, - 2FA5D421B7EE9B499EEA6908 /* BloomFilterManagerTests.swift in Sources */, - 2FA5D4912ABA5CEE7C00ECD0 /* PeerManagerTests.swift in Sources */, - 2FA5D8284C7C94155AC50B7B /* GetBlockHashesTaskTests.swift in Sources */, - 2FA5D5FB8B7F7B1E65A156BF /* GetMerkleBlocksTaskTests.swift in Sources */, - 2FA5D37E8B4A6D9B89410173 /* SendTransactionTaskTests.swift in Sources */, - 2FA5DD904B10D404DF9C6E04 /* RequestTransactionTaskTests.swift in Sources */, - 2FA5D371584B554068CCAD8E /* IPeerTests.swift in Sources */, - 2FA5D13C7285E79B75FAE2BE /* PeerConnectionDelegateTests.swift in Sources */, - 2FA5D6129186D79E7C7DD57F /* IPeerTaskDelegateTests.swift in Sources */, - 2FA5DFD4DDEA0B2F36600346 /* IPeerTaskRequesterTests.swift in Sources */, - D0F63BE421EF349D000CAB79 /* DataProviderTests.swift in Sources */, - 58AAA3A0474719B721A0E877 /* PaymentAddressParserTests.swift in Sources */, - 58AAA321A8780859892D0C73 /* TransactionOutputExtractorTests.swift in Sources */, - 58AAADBED25259CCD94B0B1E /* TransactionOutputAddressExtractorTests.swift in Sources */, - 3C7B91298318097F1615EFFF /* TransactionInputExtractorTests.swift in Sources */, - 58AAA23384287398D79F14A6 /* TransactionPublicKeySetterTests.swift in Sources */, - 2FA5D43AE1D87F3892BF8C4C /* StateManagerTests.swift in Sources */, - 58AAA6546322BDA56924A74D /* BlockHashFetcherHelperTests.swift in Sources */, - 58AAA317128E10EE72E0D0B1 /* BlockHashFetcherTests.swift in Sources */, - 58AAA7B5F60F31613515F273 /* BlockDiscoveryBatchTest.swift in Sources */, - 58AAAA0E73B45A6262D8272F /* MerkleBranchTests.swift in Sources */, - 58AAAE80BE4074DB3B1F0B1C /* UnspentOutputSelectorSingleNoChangeTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D3FD450020F354EF00F472B9 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D317120120F7193000AEF61B /* NetworkAddress.swift in Sources */, - D317120B20F7193000AEF61B /* InventoryMessage.swift in Sources */, - D31711FF20F7193000AEF61B /* PingMessage.swift in Sources */, - D317120A20F7193000AEF61B /* GetDataMessage.swift in Sources */, - D317121120F7193000AEF61B /* PongMessage.swift in Sources */, - D093F45E21414CC000C5D8CD /* PeerAddressManager.swift in Sources */, - D33FD6C920F3876800EC10DB /* Serialization.swift in Sources */, - D373EB83215A2A6C00124298 /* TransactionSyncer.swift in Sources */, - D317120520F7193000AEF61B /* VersionMessage.swift in Sources */, - D33FD6D120F3885400EC10DB /* Helpers.swift in Sources */, - D317121820F719A900AEF61B /* MurmurHash.swift in Sources */, - D317120F20F7193000AEF61B /* MerkleBlockMessage.swift in Sources */, - D317120620F7193000AEF61B /* GetBlocksMessage.swift in Sources */, - D317120820F7193000AEF61B /* InventoryItem.swift in Sources */, - D317120020F7193000AEF61B /* AddressMessage.swift in Sources */, - 11B35C8F2A29CA171165BF7A /* Observable.swift in Sources */, - D317120E20F7193000AEF61B /* FilterLoadMessage.swift in Sources */, - D317121020F7193000AEF61B /* VerackMessage.swift in Sources */, - D317120720F7193000AEF61B /* RejectMessage.swift in Sources */, - D317120320F7193000AEF61B /* NetworkMessage.swift in Sources */, - D31711EA20F7192400AEF61B /* PeerConnection.swift in Sources */, - 11B35785C5E654AB52A81E8A /* BloomFilter.swift in Sources */, - 11B356509EA05BCD114603E0 /* Block.swift in Sources */, - 11B35AFB6F7733F41FE3EA49 /* GetHeadersMessage.swift in Sources */, - 11B359142169EC6D44E21F59 /* ByteStream.swift in Sources */, - 11B3567D267718668A0FF9A7 /* ServiceFlags.swift in Sources */, - 11B35178BC28AE1B995DE8E3 /* VarInt.swift in Sources */, - D00DA94E213E86BC007F82D6 /* MemPoolMessage.swift in Sources */, - 11B354CD41E2CC368BC05CDB /* VarString.swift in Sources */, - 11B35DD55E37F22AE1797501 /* Transaction.swift in Sources */, - 11B35464997EBBD3B20BDEBB /* Input.swift in Sources */, - 11B3598874F8268BE87173B3 /* Output.swift in Sources */, - 11B3577DC09E75C39C5F7D2A /* String.swift in Sources */, - 11B3569B6059A0F31D7C4866 /* PublicKey.swift in Sources */, - 11B35EE64EEEE37EB90C5D14 /* PeerGroup.swift in Sources */, - 11B3554339A5E16A9CE950E7 /* BlockSyncer.swift in Sources */, - 11B35ACFFB6D4F9EA1E7CDE7 /* TransactionInputExtractor.swift in Sources */, - 11B3561A75D7D7A6DE799675 /* OpCode.swift in Sources */, - 11B354C7D3039D767739BCB2 /* DifficultyEncoder.swift in Sources */, - 11B3543B9CDA61125C2B7D78 /* ApiManager.swift in Sources */, - 58AAA1702C9422A8EBA464D7 /* UnspentOutputSelector.swift in Sources */, - 2FA5D39FBC802B5EF03AD53A /* TransactionBuilder.swift in Sources */, - 58AAA3492A592C937473454F /* ScriptBuilder.swift in Sources */, - 2FA5D67384D58067F71CD6B2 /* InputSigner.swift in Sources */, - 11B35F02260D5A21CF0EA3B8 /* Factory.swift in Sources */, - 58AAA8CCA46340450D2912C5 /* UnspentOutputProvider.swift in Sources */, - 58AAA36E48DB45B72112973B /* TransactionCreator.swift in Sources */, - 2FA5DC980A0E135863848CB0 /* TransactionProcessor.swift in Sources */, - 58AAAD86E3A2A31C2D2A705B /* Script.swift in Sources */, - 58AAA19EA96193409D8AA8EC /* ScriptConverter.swift in Sources */, - 58AAA1E87B62806CD5891D43 /* Chunk.swift in Sources */, - 11B3580E3ED73322D657644F /* StateManager.swift in Sources */, - 58AAA6A3D98BB1CF6360F639 /* TransactionSizeCalculator.swift in Sources */, - 2FA5D2E6BA34E1A932C9C16B /* AddressManager.swift in Sources */, - 2FA5D1EE5C8EF298CD8A6733 /* TransactionSerializer.swift in Sources */, - 2FA5DDD47BF94625B499ECC2 /* TransactionOutputSerializer.swift in Sources */, - 2FA5D56E3AE0E2E9817FC370 /* TransactionInputSerializer.swift in Sources */, - 2FA5D6EC6F4A32E3489F89B0 /* TransactionMessage.swift in Sources */, - 2FA5D599E444F6847B6E9A4C /* Peer.swift in Sources */, - 2FA5D4F27E10A609FF81950E /* UnknownMessage.swift in Sources */, - 58AAAD1C062938F07E90EA9D /* Address.swift in Sources */, - 2FA5D9ED521660FA5EEF360E /* MerkleBlock.swift in Sources */, - 11B35377C00CCB8EEED57179 /* TransactionInfo.swift in Sources */, - 2FA5D19E18DC7528778D26F0 /* PeerAddress.swift in Sources */, - 11B35823EEDAE4C578F2EA02 /* RequestTransactionsTask.swift in Sources */, - 11B35567A6F0430D0A95A389 /* PeerTask.swift in Sources */, - 11B351E3E2B8E34D9ECAB6B6 /* SendTransactionTask.swift in Sources */, - 11B35DD0EE93320729D517CE /* GetMerkleBlocksTask.swift in Sources */, - 2FA5D74EDAFB77BC86918E63 /* PeerDiscovery.swift in Sources */, - 58AAA8C5908EBFA5BCC2B5FC /* MerkleBlockValidator.swift in Sources */, - 58AAAE17248A670EE4E588A3 /* DataListSerializer.swift in Sources */, - 2FA5D65869320A18AFB8CE98 /* BlockHash.swift in Sources */, - 2FA5D26905936BB701998750 /* GetBlockHashesTask.swift in Sources */, - 2FA5DC6BEF26DD10A72A39EE /* BloomFilterManager.swift in Sources */, - 2FA5D166F7AD6D1E7A81CB4D /* Blockchain.swift in Sources */, - 11B3534C9529906826754D6E /* Protocols.swift in Sources */, - 11B35468A2ACF4B8534BB719 /* DataProvider.swift in Sources */, - 11B358B84B9C6B09ADB8A0B0 /* HDWallet.swift in Sources */, - 58AAA2C85BFE1C624D1A1997 /* SigHashType.swift in Sources */, - 58AAA7912B48C0C21F50DBF2 /* BitcoinAddressSelector.swift in Sources */, - 2FA5DBF35EDF66F6977BA032 /* ConnectionTimeoutManager.swift in Sources */, - 2FA5D024DF2A429D6606350F /* KitStateProvider.swift in Sources */, - 2FA5D91DCF7F320369162C5A /* ReachabilityManager.swift in Sources */, - 2FA5D448B43D6BFB15208436 /* PeerManager.swift in Sources */, - 2FA5D9D3DA2DF9BD402CE5DD /* SentTransaction.swift in Sources */, - 2FA5DA409AB91D54163250D7 /* Logger.swift in Sources */, - 58AAA98383D6E9642327F1C0 /* SyncManager.swift in Sources */, - 58AAAC5B6B652220A1A707EF /* BitcoinPaymentData.swift in Sources */, - 58AAA0628AAE41265F75FE99 /* PaymentAddressParser.swift in Sources */, - 58AAA9ABB0AAAF9A11B98261 /* TransactionOutputExtractor.swift in Sources */, - 58AAA120B3EEA225FFFBE2BC /* TransactionOutputAddressExtractor.swift in Sources */, - 3C7B9B6F34A3C30648B65661 /* TransactionPublicKeySetter.swift in Sources */, - 2FA5D4AB6E84748075FA6294 /* Array.swift in Sources */, - 58AAA16365AECB1DF62883E5 /* BlockDiscoveryBatch.swift in Sources */, - 58AAAE076B1930CB8B2C3C65 /* InitialSyncer.swift in Sources */, - 58AAA445FA83E69172FFC1ED /* BlockHashFetcher.swift in Sources */, - 58AAA572F80B63848F95B065 /* BlockHashFetcherHelper.swift in Sources */, - 3C7B913CB012F68959606805 /* BCoinApi.swift in Sources */, - 58AAA32402EFCE6218A9D082 /* Signal.swift in Sources */, - 11B3536E9A39907FCA035C33 /* GrdbStorage.swift in Sources */, - 11B35CFD56E66080A63C060E /* BlockchainState.swift in Sources */, - 11B3579EE9017ED27A4F301E /* PeerAddressManagerState.swift in Sources */, - 2FA5DBABD18D15E003686989 /* BlockSyncerState.swift in Sources */, - 2FA5D806A7727E3BED0E54E7 /* DataObjects.swift in Sources */, - 58AAA3B243D1EE084C7B0FC8 /* NetworkMessageParser.swift in Sources */, - 58AAA68750887DC647A77B79 /* SynchronizedArray.swift in Sources */, - 58AAA744EF8A7193BB2EF860 /* NetworkMessageSerializer.swift in Sources */, - 58AAA958A19346CAFE9B38D8 /* MerkleBranch.swift in Sources */, - 58AAAF31EB5F07BD176A6F85 /* BloomFilterLoader.swift in Sources */, - 58AAAB436CF3BC02B5BE9222 /* BitcoinCore.swift in Sources */, - 58AAAE3ED33146222CCB9503 /* BitcoinCoreBuilder.swift in Sources */, - 58AAA3570F85F5A72E2377DB /* InventoryItemsHandlerChain.swift in Sources */, - 58AAA29706D539111A919497 /* AbstractKit.swift in Sources */, - 58AAA6DBA7D2439C4D5808B6 /* Base58AddressConverter.swift in Sources */, - 58AAAB857FAD14D8B361A248 /* TransactionSender.swift in Sources */, - 58AAA789081293EB75B29A78 /* MempoolTransactions.swift in Sources */, - 58AAAB219A1E5FFB2A1F37D1 /* AddressConverterChain.swift in Sources */, - 58AAAAD07F22C65761A03E33 /* PeerTaskHandlerChain.swift in Sources */, - 58AAAC19A5DE1AFC539D10C7 /* InitialBlockDownload.swift in Sources */, - 58AAA79D7763A69B80757392 /* BitsValidator.swift in Sources */, - 58AAA0DF16FE6B7A7B1FCF32 /* BlockValidatorChain.swift in Sources */, - 58AAAD6BC19FF5BF360535F0 /* LegacyDifficultyAdjustmentValidator.swift in Sources */, - 58AAAA8C17BA61B9D9CFF1C7 /* LegacyTestNetDifficultyValidator.swift in Sources */, - 58AAA6118006786B2225CAFB /* ProofOfWorkValidator.swift in Sources */, - 58AAAFDA3C6853E4024D8B99 /* BlockValidatorHelper.swift in Sources */, - 58AAA163DAD085C355C8CA4A /* BlockHeaderParser.swift in Sources */, - 58AAA12B1809FDA36DCBC18D /* DoubleShaHasher.swift in Sources */, - 58AAA74E89561DE6CCDC5EC3 /* BitcoinCoreErrors.swift in Sources */, - 58AAA2F4990F1DAC17DE958D /* ScriptBuilderChain.swift in Sources */, - 2FA5D15733A45ACBD4C5B3A6 /* OutputsCache.swift in Sources */, - 58AAA312C3BF2D105FE784EF /* TransactionInfoConverter.swift in Sources */, - 58AAA028B3706BCD8071E95D /* BaseTransactionInfoConverter.swift in Sources */, - 2FA5D3D47092D1903728F16A /* DirectoryHelper.swift in Sources */, - 58AAA216024E9AE339C1A796 /* SyncTransactionItem.swift in Sources */, - 58AAAC7087175422D3BA89A2 /* SyncedReadyPeerManager.swift in Sources */, - 58AAAF1F346D79776D04CEA6 /* UnspentOutputSelectorChain.swift in Sources */, - 58AAAE8B6F9F832A13753E78 /* UnspentOutputSelectorSingleNoChange.swift in Sources */, - 58AAAF671C4B3988EF6764B1 /* InsightApi.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - D343A1892177367E004248DA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D3FD450420F354EF00F472B9 /* BitcoinCore */; - targetProxy = D343A1882177367E004248DA /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - D380FD6320FCC04400276993 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 657132791355B7D737C6AFF8 /* Pods-BitcoinCoreTests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 72234W6E3D; - INFOPLIST_FILE = BitcoinCoreTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = im.grouvi.BitcoinCoreTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - D380FD6420FCC04400276993 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = CB683BFE7F0F553B11E895DB /* Pods-BitcoinCoreTests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 72234W6E3D; - INFOPLIST_FILE = BitcoinCoreTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = im.grouvi.BitcoinCoreTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - D3FD450B20F354EF00F472B9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - D3FD450C20F354EF00F472B9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 4.2; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - D3FD450E20F354EF00F472B9 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 4018E79E0DF6A0D6F606F9F9 /* Pods-BitcoinCore.debug.xcconfig */; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 72234W6E3D; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = BitcoinCore/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinCore; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - D3FD450F20F354EF00F472B9 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 733A12F673E7E69AC511C2EC /* Pods-BitcoinCore.release.xcconfig */; - buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 72234W6E3D; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = BitcoinCore/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinCore; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - D380FD6520FCC04400276993 /* Build configuration list for PBXNativeTarget "BitcoinCoreTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D380FD6320FCC04400276993 /* Debug */, - D380FD6420FCC04400276993 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D3FD44FF20F354EF00F472B9 /* Build configuration list for PBXProject "BitcoinCore" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D3FD450B20F354EF00F472B9 /* Debug */, - D3FD450C20F354EF00F472B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D3FD450D20F354EF00F472B9 /* Build configuration list for PBXNativeTarget "BitcoinCore" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D3FD450E20F354EF00F472B9 /* Debug */, - D3FD450F20F354EF00F472B9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = D3FD44FC20F354EF00F472B9 /* Project object */; -} diff --git a/BitcoinCore/BitcoinCore/BitcoinCore.h b/BitcoinCore/BitcoinCore/BitcoinCore.h deleted file mode 100644 index 29d5e400..00000000 --- a/BitcoinCore/BitcoinCore/BitcoinCore.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -//! Project version number for HSBitcoinKit. -FOUNDATION_EXPORT double HSBitcoinKitVersionNumber; - -//! Project version string for HSBitcoinKit. -FOUNDATION_EXPORT const unsigned char HSBitcoinKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import diff --git a/BitcoinCore/BitcoinCore/Blocks/Validators/BlockValidatorChain.swift b/BitcoinCore/BitcoinCore/Blocks/Validators/BlockValidatorChain.swift deleted file mode 100644 index fd334d84..00000000 --- a/BitcoinCore/BitcoinCore/Blocks/Validators/BlockValidatorChain.swift +++ /dev/null @@ -1,25 +0,0 @@ -class BlockValidatorChain: IBlockValidator { - private let proofOfWorkValidator: IBlockValidator - private var concreteValidators = [IBlockValidator]() - - init(proofOfWorkValidator: IBlockValidator) { - self.proofOfWorkValidator = proofOfWorkValidator - } - - func validate(block: Block, previousBlock: Block) throws { - try proofOfWorkValidator.validate(block: block, previousBlock: previousBlock) - - if let index = concreteValidators.firstIndex(where: { $0.isBlockValidatable(block: block, previousBlock: previousBlock) }) { - try concreteValidators[index].validate(block: block, previousBlock: previousBlock) - } - } - - func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return true - } - - func add(blockValidator: IBlockValidator) { - concreteValidators.append(blockValidator) - } - -} diff --git a/BitcoinCore/BitcoinCore/Blocks/Validators/ProofOfWorkValidator.swift b/BitcoinCore/BitcoinCore/Blocks/Validators/ProofOfWorkValidator.swift deleted file mode 100644 index b511f627..00000000 --- a/BitcoinCore/BitcoinCore/Blocks/Validators/ProofOfWorkValidator.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import BigInt - -class ProofOfWorkValidator: IBlockValidator { - private let difficultyEncoder: IDifficultyEncoder - - init(difficultyEncoder: IDifficultyEncoder) { - self.difficultyEncoder = difficultyEncoder - } - - func validate(block: Block, previousBlock: Block) throws { - - guard let headerHashBigInt = BigInt(block.headerHash.reversedHex, radix: 16), - headerHashBigInt < difficultyEncoder.decodeCompact(bits: block.bits) else { - throw BitcoinCoreErrors.BlockValidation.invalidProofOfWork - } - } - - func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return true - } - -} diff --git a/BitcoinCore/BitcoinCore/Core/AbstractKit.swift b/BitcoinCore/BitcoinCore/Core/AbstractKit.swift deleted file mode 100644 index b2521711..00000000 --- a/BitcoinCore/BitcoinCore/Core/AbstractKit.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation -import RxSwift - -open class AbstractKit { - public var bitcoinCore: BitcoinCore - public var network: INetwork - - public init(bitcoinCore: BitcoinCore, network: INetwork) { - self.bitcoinCore = bitcoinCore - self.network = network - } - - open func start() { - bitcoinCore.start() - } - - open func stop() { - bitcoinCore.stop() - } - - open var lastBlockInfo: BlockInfo? { - return bitcoinCore.lastBlockInfo - } - - open var balance: Int { - return bitcoinCore.balance - } - - open var syncState: BitcoinCore.KitState { - return bitcoinCore.syncState - } - - open func transactions(fromHash: String? = nil, limit: Int? = nil) -> Single<[TransactionInfo]> { - return bitcoinCore.transactions(fromHash: fromHash, limit: limit) - } - - open func send(to address: String, value: Int, feeRate: Int) throws { - try bitcoinCore.send(to: address, value: value, feeRate: feeRate) - } - - open func validate(address: String) throws { - try bitcoinCore.validate(address: address) - } - - open func parse(paymentAddress: String) -> BitcoinPaymentData { - return bitcoinCore.parse(paymentAddress: paymentAddress) - } - - open func fee(for value: Int, toAddress: String? = nil, senderPay: Bool, feeRate: Int) throws -> Int { - return try bitcoinCore.fee(for: value, toAddress: toAddress, senderPay: senderPay, feeRate: feeRate) - } - - open func receiveAddress(for type: ScriptType) -> String { - return bitcoinCore.receiveAddress(for: type) - } - - open var debugInfo: String { - return bitcoinCore.debugInfo - } - -} \ No newline at end of file diff --git a/BitcoinCore/BitcoinCore/Core/BaseTransactionInfoConverter.swift b/BitcoinCore/BitcoinCore/Core/BaseTransactionInfoConverter.swift deleted file mode 100644 index a27d2ea0..00000000 --- a/BitcoinCore/BitcoinCore/Core/BaseTransactionInfoConverter.swift +++ /dev/null @@ -1,56 +0,0 @@ -public protocol IBaseTransactionInfoConverter { - func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> T -} - -public class BaseTransactionInfoConverter: IBaseTransactionInfoConverter { - - public init() {} - - public func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> T { - var totalMineInput: Int = 0 - var totalMineOutput: Int = 0 - var fromAddresses = [TransactionAddressInfo]() - var toAddresses = [TransactionAddressInfo]() - - for inputWithPreviousOutput in transactionForInfo.inputsWithPreviousOutputs { - var mine = false - - if let previousOutput = inputWithPreviousOutput.previousOutput { - if previousOutput.publicKeyPath != nil { - totalMineInput += previousOutput.value - mine = true - } - } - - if let address = inputWithPreviousOutput.input.address { - fromAddresses.append(TransactionAddressInfo(address: address, mine: mine)) - } - } - - for output in transactionForInfo.outputs { - var mine = false - - if output.publicKeyPath != nil { - totalMineOutput += output.value - mine = true - } - - if let address = output.address { - toAddresses.append(TransactionAddressInfo(address: address, mine: mine)) - } - } - - let amount = totalMineOutput - totalMineInput - - return T( - transactionHash: transactionForInfo.transactionWithBlock.transaction.dataHash.reversedHex, - transactionIndex: transactionForInfo.transactionWithBlock.transaction.order, - from: fromAddresses, - to: toAddresses, - amount: amount, - blockHeight: transactionForInfo.transactionWithBlock.blockHeight, - timestamp: transactionForInfo.transactionWithBlock.transaction.timestamp - ) - } - -} diff --git a/BitcoinCore/BitcoinCore/Core/BitcoinCore.swift b/BitcoinCore/BitcoinCore/Core/BitcoinCore.swift deleted file mode 100644 index 8e655bee..00000000 --- a/BitcoinCore/BitcoinCore/Core/BitcoinCore.swift +++ /dev/null @@ -1,262 +0,0 @@ -import Foundation -import HSHDWalletKit -import BigInt -import HSCryptoKit -import RxSwift - -public class BitcoinCore { - public static let heightInterval = 2016 // Default block count in difficulty change circle ( Bitcoin ) - public static let targetSpacing = 10 * 60 // Time to mining one block ( 10 min. Bitcoin ) - public static let maxTargetBits = 0x1d00ffff // Initially and max. target difficulty for blocks - - - private let storage: IStorage - private let cache: OutputsCache - private var dataProvider: IDataProvider - private let addressManager: IAddressManager - private let addressConverter: AddressConverterChain - private let unspentOutputSelector: UnspentOutputSelectorChain - private let kitStateProvider: IKitStateProvider & ISyncStateListener - - private let scriptBuilder: ScriptBuilderChain - private let transactionBuilder: ITransactionBuilder - - private let transactionCreator: ITransactionCreator - private let paymentAddressParser: IPaymentAddressParser - - private let networkMessageSerializer: NetworkMessageSerializer - private let networkMessageParser: NetworkMessageParser - - private let syncManager: SyncManager - - // START: Extending - - public let peerGroup: IPeerGroup - public let initialBlockDownload: IInitialBlockDownload - public let syncedReadyPeerManager: ISyncedReadyPeerManager - public let transactionSyncer: ITransactionSyncer - - let bloomFilterLoader: BloomFilterLoader - let blockValidatorChain: BlockValidatorChain - let inventoryItemsHandlerChain = InventoryItemsHandlerChain() - let peerTaskHandlerChain = PeerTaskHandlerChain() - - public func add(blockValidator: IBlockValidator) { - blockValidatorChain.add(blockValidator: blockValidator) - } - - public func add(inventoryItemsHandler: IInventoryItemsHandler) { - inventoryItemsHandlerChain.add(handler: inventoryItemsHandler) - } - - public func add(peerTaskHandler: IPeerTaskHandler) { - peerTaskHandlerChain.add(handler: peerTaskHandler) - } - - @discardableResult public func add(messageParser: IMessageParser) -> Self { - networkMessageParser.add(parser: messageParser) - return self - } - - @discardableResult public func add(messageSerializer: IMessageSerializer) -> Self { - networkMessageSerializer.add(serializer: messageSerializer) - return self - } - - public func prepend(scriptBuilder: IScriptBuilder) { - self.scriptBuilder.prepend(scriptBuilder: scriptBuilder) - } - - public func prepend(addressConverter: IAddressConverter) { - self.addressConverter.prepend(addressConverter: addressConverter) - } - - public func prepend(unspentOutputSelector: IUnspentOutputSelector) { - self.unspentOutputSelector.prepend(unspentOutputSelector: unspentOutputSelector) - } - - // END: Extending - - public var delegateQueue = DispatchQueue(label: "bitcoin_delegate_queue") - public weak var delegate: BitcoinCoreDelegate? - - init(storage: IStorage, cache: OutputsCache, dataProvider: IDataProvider, - peerGroup: IPeerGroup, initialBlockDownload: IInitialBlockDownload, bloomFilterLoader: BloomFilterLoader, - syncedReadyPeerManager: ISyncedReadyPeerManager, transactionSyncer: ITransactionSyncer, - blockValidatorChain: BlockValidatorChain, addressManager: IAddressManager, addressConverter: AddressConverterChain, unspentOutputSelector: UnspentOutputSelectorChain, kitStateProvider: IKitStateProvider & ISyncStateListener, - scriptBuilder: ScriptBuilderChain, transactionBuilder: ITransactionBuilder, transactionCreator: ITransactionCreator, - paymentAddressParser: IPaymentAddressParser, networkMessageParser: NetworkMessageParser, networkMessageSerializer: NetworkMessageSerializer, - syncManager: SyncManager) { - self.storage = storage - self.cache = cache - self.dataProvider = dataProvider - self.peerGroup = peerGroup - self.initialBlockDownload = initialBlockDownload - self.bloomFilterLoader = bloomFilterLoader - self.syncedReadyPeerManager = syncedReadyPeerManager - self.transactionSyncer = transactionSyncer - self.blockValidatorChain = blockValidatorChain - self.addressManager = addressManager - self.addressConverter = addressConverter - self.unspentOutputSelector = unspentOutputSelector - self.kitStateProvider = kitStateProvider - self.scriptBuilder = scriptBuilder - self.transactionBuilder = transactionBuilder - self.transactionCreator = transactionCreator - self.paymentAddressParser = paymentAddressParser - - self.networkMessageParser = networkMessageParser - self.networkMessageSerializer = networkMessageSerializer - - self.syncManager = syncManager - } - -} - -extension BitcoinCore { - - public func start() { - syncManager.start() - } - - func stop() { - syncManager.stop() - } - -} - -extension BitcoinCore { - - public var lastBlockInfo: BlockInfo? { - return dataProvider.lastBlockInfo - } - - public var balance: Int { - return dataProvider.balance - } - - public var syncState: BitcoinCore.KitState { - return kitStateProvider.syncState - } - - public func transactions(fromHash: String? = nil, limit: Int? = nil) -> Single<[TransactionInfo]> { - return dataProvider.transactions(fromHash: fromHash, limit: limit) - } - - public func send(to address: String, value: Int, feeRate: Int) throws { - try transactionCreator.create(to: address, value: value, feeRate: feeRate, senderPay: true) - } - - public func validate(address: String) throws { - _ = try addressConverter.convert(address: address) - } - - public func parse(paymentAddress: String) -> BitcoinPaymentData { - return paymentAddressParser.parse(paymentAddress: paymentAddress) - } - - public func fee(for value: Int, toAddress: String? = nil, senderPay: Bool, feeRate: Int) throws -> Int { - return try transactionBuilder.fee(for: value, feeRate: feeRate, senderPay: senderPay, address: toAddress) - } - - public func receiveAddress(for type: ScriptType) -> String { - return (try? addressManager.receiveAddress(for: type)) ?? "" - } - - public var debugInfo: String { - return dataProvider.debugInfo - } - -} - -extension BitcoinCore: IDataProviderDelegate { - - func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) { - delegateQueue.async { [weak self] in - if let kit = self { - kit.delegate?.transactionsUpdated(inserted: inserted, updated: updated) - } - } - } - - func transactionsDeleted(hashes: [String]) { - delegateQueue.async { [weak self] in - self?.delegate?.transactionsDeleted(hashes: hashes) - } - } - - func balanceUpdated(balance: Int) { - delegateQueue.async { [weak self] in - if let kit = self { - kit.delegate?.balanceUpdated(balance: balance) - } - } - } - - func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) { - delegateQueue.async { [weak self] in - if let kit = self { - kit.delegate?.lastBlockInfoUpdated(lastBlockInfo: lastBlockInfo) - } - } - } - -} - -extension BitcoinCore: IKitStateProviderDelegate { - func handleKitStateUpdate(state: KitState) { - delegateQueue.async { [weak self] in - self?.delegate?.kitStateUpdated(state: state) - } - } -} - -public protocol BitcoinCoreDelegate: class { - func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) - func transactionsDeleted(hashes: [String]) - func balanceUpdated(balance: Int) - func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) - func kitStateUpdated(state: BitcoinCore.KitState) -} - -extension BitcoinCoreDelegate { - - public func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) {} - public func transactionsDeleted(hashes: [String]) {} - public func balanceUpdated(balance: Int) {} - public func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) {} - public func kitStateUpdated(state: BitcoinCore.KitState) {} - -} - -extension BitcoinCore { - - public enum KitState { - case synced - case syncing(progress: Double) - case notSynced - } - - public enum SyncMode: Equatable { - case full // Sync from bip44CheckpointBlock. Api restore disabled - case fromDate(date: TimeInterval) // Sync from given date. Api restore disable - case api // Sync from lastCheckpointBlock. Api restore enabled - case newWallet // Sync from lastCheckpointBlock. Api restore enabled - } - -} - -extension BitcoinCore.KitState { - - public static func == (lhs: BitcoinCore.KitState, rhs: BitcoinCore.KitState) -> Bool { - switch (lhs, rhs) { - case (.synced, .synced), (.notSynced, .notSynced): - return true - case (.syncing(progress: let leftProgress), .syncing(progress: let rightProgress)): - return leftProgress == rightProgress - default: - return false - } - } - -} \ No newline at end of file diff --git a/BitcoinCore/BitcoinCore/Core/HDWallet.swift b/BitcoinCore/BitcoinCore/Core/HDWallet.swift deleted file mode 100644 index a60dcf69..00000000 --- a/BitcoinCore/BitcoinCore/Core/HDWallet.swift +++ /dev/null @@ -1,13 +0,0 @@ -import HSHDWalletKit - -extension HDWallet: IHDWallet { - - func publicKey(account: Int, index: Int, external: Bool) throws -> PublicKey { - return PublicKey(withAccount: account, index: index, external: external, hdPublicKeyData: try publicKey(account: account, index: index, chain: external ? .external : .internal).raw) - } - - func privateKeyData(account: Int, index: Int, external: Bool) throws -> Data { - return try privateKey(account: account, index: index, chain: external ? .external : .internal).raw - } - -} diff --git a/BitcoinCore/BitcoinCore/Core/KitStateProvider.swift b/BitcoinCore/BitcoinCore/Core/KitStateProvider.swift deleted file mode 100644 index 44b2d939..00000000 --- a/BitcoinCore/BitcoinCore/Core/KitStateProvider.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -class KitStateProvider: IKitStateProvider { - - weak var delegate: IKitStateProviderDelegate? - - private var initialBestBlockHeight: Int32 = 0 - private var currentBestBlockHeight: Int32 = 0 - - private(set) var syncState: BitcoinCore.KitState = .notSynced { - didSet { - if !(oldValue == syncState) { - delegate?.handleKitStateUpdate(state: syncState) - } - } - } - -} - -extension KitStateProvider: ISyncStateListener { - - func syncStarted() { - syncState = .syncing(progress: 0) - } - - func syncStopped() { - syncState = .notSynced - } - - func syncFinished(all: Bool) { - syncState = all ? .synced : .syncing(progress: 1) - } - - func initialBestBlockHeightUpdated(height: Int32) { - initialBestBlockHeight = height - currentBestBlockHeight = height - } - - func currentBestBlockHeightUpdated(height: Int32, maxBlockHeight: Int32) { - if currentBestBlockHeight < height { - currentBestBlockHeight = height - } - - let blocksDownloaded = currentBestBlockHeight - initialBestBlockHeight - let allBlocksToDownload = maxBlockHeight - initialBestBlockHeight - - if allBlocksToDownload > 0 && allBlocksToDownload > blocksDownloaded { - syncState = .syncing(progress: Double(blocksDownloaded) / Double(allBlocksToDownload)) - } - } - -} diff --git a/BitcoinCore/BitcoinCore/Core/Logger.swift b/BitcoinCore/BitcoinCore/Core/Logger.swift deleted file mode 100644 index f4db6569..00000000 --- a/BitcoinCore/BitcoinCore/Core/Logger.swift +++ /dev/null @@ -1,158 +0,0 @@ -import Foundation - -public class Logger { - private static let logContextEmpty = String(repeating: "-", count: 15) - - public enum Level: Int { - case verbose = 0 - case debug = 1 - case info = 2 - case warning = 3 - case error = 4 - } - - private let colors: [Level: String] = [ - Level.verbose: "💜 VERBOSE ", // silver - Level.debug: "💚 DEBUG ", // green - Level.info: "💙 INFO ", // blue - Level.warning: "💛 WARNING ", // yellow - Level.error: "❤️ ERROR " // red - ] - - private lazy var dateFormatter: DateFormatter = { - var formatter = DateFormatter() - formatter.timeZone = TimeZone.autoupdatingCurrent - formatter.locale = Locale.current - formatter.dateFormat = "HH:mm:ss.SSS" - return formatter - }() - - private let network: INetwork? - private let minLogLevel: Level - - public init(network: INetwork? = nil, minLogLevel: Level) { - self.network = network - self.minLogLevel = minLogLevel - } - - private let includeFiles: [String] = [ - // "PeerConnection", - // "Peer", - ] - private let excludeFiles: [String] = [] - - /// log something generally unimportant (lowest priority) - public func verbose(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil, network: INetwork? = nil) { - log(level: .verbose, message: message(), file: file, function: function, line: line, context: context, network: network) - } - - /// log something which help during debugging (low priority) - public func debug(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil, network: INetwork? = nil) { - log(level: .debug, message: message(), file: file, function: function, line: line, context: context, network: network) - } - - /// log something which you are really interested but which is not an issue or error (normal priority) - public func info(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil, network: INetwork? = nil) { - log(level: .info, message: message(), file: file, function: function, line: line, context: context, network: network) - } - - /// log something which may cause big trouble soon (high priority) - public func warning(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil, network: INetwork? = nil) { - log(level: .warning, message: message(), file: file, function: function, line: line, context: context, network: network) - } - - /// log something which will keep you awake at night (highest priority) - public func error(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil, network: INetwork? = nil) { - log(level: .error, message: message(), file: file, function: function, line: line, context: context, network: network) - } - - /// custom logging to manually adjust values, should just be used by other frameworks - public func log(level: Logger.Level, message: @autoclosure () -> Any, - file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil, network: INetwork? = nil) { - - guard level.rawValue >= minLogLevel.rawValue else { - return - } - - let file = fileNameWithoutSuffix(file) - - guard includeFiles.isEmpty || includeFiles.contains(file) else { - return - } - - guard excludeFiles.isEmpty || !excludeFiles.contains(file) else { - return - } - - var str = "" - if let network = network { - str = "\(network.name) " - } else if let network = self.network { - str = "\(network.name) " - } - - str = str + "\(dateFormatter.string(from: Date())) \(colors[level]!)[\(threadName())]" - - if let context = context { - let contextString = " \(context)" - let formattedString = contextString + String(repeating: " ", count: max(0, 15 - contextString.count)) - str = str + formattedString - } else { - str = str + Logger.logContextEmpty - } - - str = str + " \(file).\(functionName(function)):\(line) - \(message())" - - print(str) - } - - private func functionName(_ function: String) -> String { - if let index = function.firstIndex(of: "(") { - return String(function.prefix(index.utf16Offset(in: function))) - } else { - return function - } - } - - // returns the current thread name - private func threadName() -> String { - if Thread.isMainThread { - return "" - } else { - let threadName = Thread.current.name - if let threadName = threadName, !threadName.isEmpty { - return threadName - } else { - return String(format: "%p", Thread.current) - } - } - } - - // returns the filename without suffix (= file ending) of a path - private func fileNameWithoutSuffix(_ file: String) -> String { - let fileName = fileNameOfFile(file) - - if !fileName.isEmpty { - let fileNameParts = fileName.components(separatedBy: ".") - if let firstPart = fileNameParts.first { - return firstPart - } - } - return "" - } - - // returns the filename of a path - private func fileNameOfFile(_ file: String) -> String { - let fileParts = file.components(separatedBy: "/") - if let lastPart = fileParts.last { - return lastPart - } - return "" - } - -} diff --git a/BitcoinCore/BitcoinCore/Core/TransactionInfoConverter.swift b/BitcoinCore/BitcoinCore/Core/TransactionInfoConverter.swift deleted file mode 100644 index b4e6fdf0..00000000 --- a/BitcoinCore/BitcoinCore/Core/TransactionInfoConverter.swift +++ /dev/null @@ -1,12 +0,0 @@ -open class TransactionInfoConverter: ITransactionInfoConverter { - private let baseTransactionInfoConverter: IBaseTransactionInfoConverter - - public init(baseTransactionInfoConverter: IBaseTransactionInfoConverter) { - self.baseTransactionInfoConverter = baseTransactionInfoConverter - } - - public func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> TransactionInfo { - return baseTransactionInfoConverter.transactionInfo(fromTransaction: transactionForInfo) - } - -} diff --git a/BitcoinCore/BitcoinCore/Info.plist b/BitcoinCore/BitcoinCore/Info.plist deleted file mode 100644 index 1007fd9d..00000000 --- a/BitcoinCore/BitcoinCore/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/BitcoinCore/BitcoinCore/Managers/ApiManager.swift b/BitcoinCore/BitcoinCore/Managers/ApiManager.swift deleted file mode 100644 index 17f0416a..00000000 --- a/BitcoinCore/BitcoinCore/Managers/ApiManager.swift +++ /dev/null @@ -1,128 +0,0 @@ -import Foundation -import RxSwift -import Alamofire -import ObjectMapper - -enum ApiError: Error { - case invalidRequest - case mappingError - case noConnection - case serverError(status: Int, data: Any?) -} - -class RequestRouter: URLRequestConvertible { - - private let request: URLRequest - private let encoding: ParameterEncoding - private let parameters: [String: Any]? - - init(request: URLRequest, encoding: ParameterEncoding, parameters: [String: Any]?) { - self.request = request - self.encoding = encoding - self.parameters = parameters - } - - func asURLRequest() throws -> URLRequest { - return try encoding.encode(request, with: parameters) - } - -} - -public class ApiManager { - private let apiUrl: String - - private let logger: Logger? - - - required public init(apiUrl: String, logger: Logger? = nil) { - self.apiUrl = apiUrl - - self.logger = logger - } - - public func request(withMethod method: HTTPMethod, path: String, parameters: [String: Any]? = nil, httpBody: Data? = nil) -> URLRequestConvertible { - let baseUrl = URL(string: apiUrl)! - var request = URLRequest(url: baseUrl.appendingPathComponent(path)) - request.httpMethod = method.rawValue - request.httpBody = httpBody - - request.setValue("application/json", forHTTPHeaderField: "Accept") - - logger?.debug("API OUT: \(method.rawValue) \(path) \(parameters.map { String(describing: $0) } ?? "")") - - return RequestRouter(request: request, encoding: method == .get ? URLEncoding.default : JSONEncoding.default, parameters: parameters) - } - - public func observable(forRequest request: URLRequestConvertible, mapper: @escaping (Any) -> T?) -> Observable { - return self.observable(forRequest: request) - .flatMap { dataResponse -> Observable in - switch dataResponse.result { - case .success(let result): - if let value = mapper(result) { - return Observable.just(value) - } else { - return Observable.error(ApiError.mappingError) - } - case .failure: - if let response = dataResponse.response { - let data = dataResponse.data.flatMap { try? JSONSerialization.jsonObject(with: $0, options: .allowFragments) } - return Observable.error(ApiError.serverError(status: response.statusCode, data: data)) - } else { - return Observable.error(ApiError.noConnection) - } - } - } - } - - public func observable(forRequest request: URLRequestConvertible) -> Observable<[T]> { - return observable(forRequest: request, mapper: { json in - if let jsonArray = json as? [[String: Any]] { - return jsonArray.compactMap { try? T(JSONObject: $0) } - } - return nil - }) - } - - public func observable(forRequest request: URLRequestConvertible) -> Observable { - return observable(forRequest: request, mapper: { json in - if let jsonObject = json as? [String: Any], let object = try? T(JSONObject: jsonObject) { - return object - } - return nil - }) - } - - private func observable(forRequest request: URLRequestConvertible) -> Observable> { - let observable = Observable>.create { observer in - self.logger?.debug("API OUT: \(request.urlRequest?.httpMethod ?? "") \(request.urlRequest?.url?.absoluteString ?? "")") - - let requestReference = Alamofire.request(request) - .validate() - .responseJSON(queue: DispatchQueue.global(qos: .background), completionHandler: { response in - observer.onNext(response) - observer.onCompleted() - }) - - return Disposables.create { - requestReference.cancel() - } - } - - return observable.do(onNext: { dataResponse in - switch dataResponse.result { - case .success(let result): - self.logger?.debug("API IN: SUCCESS: \(dataResponse.request?.url?.path ?? ""): response = \(result)") - () - case .failure: - let data = dataResponse.data.flatMap { - try? JSONSerialization.jsonObject(with: $0, options: .allowFragments) - } - - self.logger?.debug("API IN: ERROR: \(dataResponse.request?.url?.path ?? ""): status = \(dataResponse.response?.statusCode ?? 0), response: \(data.map { "\($0)" } ?? "nil")") - () - } - }) - - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/BitcoinAddressSelector.swift b/BitcoinCore/BitcoinCore/Managers/BitcoinAddressSelector.swift deleted file mode 100644 index 13ff5cea..00000000 --- a/BitcoinCore/BitcoinCore/Managers/BitcoinAddressSelector.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -public class BitcoinAddressSelector: IAddressSelector { - - public init() {} - - public func getAddressVariants(addressConverter: IAddressConverter, publicKey: PublicKey) -> [String] { - let wpkhShAddress = try? addressConverter.convert(keyHash: publicKey.scriptHashForP2WPKH, type: .p2sh).stringValue - return [wpkhShAddress, publicKey.keyHash.hex].compactMap { $0 } - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/BloomFilterManager.swift b/BitcoinCore/BitcoinCore/Managers/BloomFilterManager.swift deleted file mode 100644 index ebff3b7e..00000000 --- a/BitcoinCore/BitcoinCore/Managers/BloomFilterManager.swift +++ /dev/null @@ -1,77 +0,0 @@ -class BloomFilterManager { - class BloomFilterExpired: Error {} - - private let storage: IStorage - private let factory: IFactory - weak var delegate: IBloomFilterManagerDelegate? - - var bloomFilter: BloomFilter? - - init(storage: IStorage, factory: IFactory) { - self.storage = storage - self.factory = factory - } - - // This method is a workaround - private func byteArrayLittleEndian(int: Int) -> [UInt8] { - return [ - UInt8(int & 0x000000FF), - UInt8((int & 0x0000FF00) >> 8), - UInt8((int & 0x00FF0000) >> 16), - UInt8((int & 0xFF000000) >> 24) - ] - } - - private func needToSetToBloomFilter(output: OutputWithPublicKey, bestBlockHeight: Int) -> Bool { - // Need to set if output is unspent - guard let _ = output.spendingInput else { - return true - } - - if let spendingBlockHeight = output.spendingBlockHeight { - // If output is spent, we still need to set to bloom filter if it hasn't at least 100 confirmations - return bestBlockHeight - spendingBlockHeight < 100 - } - - // if output is spent by a mempool transaction, that is, spending input's transaction has not a block - return true - } -} - -extension BloomFilterManager: IBloomFilterManager { - - func regenerateBloomFilter() { - var elements = [Data]() - - let publicKeys = storage.publicKeys() - for publicKey in publicKeys { - elements.append(publicKey.keyHash) - elements.append(publicKey.raw) - elements.append(publicKey.scriptHashForP2WPKH) - } - - var outputs = storage.outputsWithPublicKeys().filter { output in - return output.output.scriptType == ScriptType.p2wpkh || output.output.scriptType == ScriptType.p2pk - } - - if let bestBlockHeight = storage.lastBlock?.height { - outputs = outputs.filter { - self.needToSetToBloomFilter(output: $0, bestBlockHeight: bestBlockHeight) - } - } - - for outputWithPublicKey in outputs { - let outpoint = outputWithPublicKey.output.transactionHash + byteArrayLittleEndian(int: outputWithPublicKey.output.index) - elements.append(outpoint) - } - - if !elements.isEmpty { - let bloomFilter = factory.bloomFilter(withElements: elements) - if self.bloomFilter?.filter != bloomFilter.filter { - self.bloomFilter = bloomFilter - delegate?.bloomFilterUpdated(bloomFilter: bloomFilter) - } - } - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/BCoinApi.swift b/BitcoinCore/BitcoinCore/Managers/InitialSync/BCoinApi.swift deleted file mode 100644 index eda69319..00000000 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/BCoinApi.swift +++ /dev/null @@ -1,26 +0,0 @@ -import RxSwift -import ObjectMapper - -public class BCoinApi { - private let apiManager: ApiManager - - public init(url: String, logger: Logger? = nil) { - apiManager = ApiManager(apiUrl: url, logger: logger) - } - -} - -extension BCoinApi: ISyncTransactionApi { - - public func getTransactions(addresses: [String]) -> Observable<[SyncTransactionItem]> { - let parameters: [String: Any] = [ - "addresses": addresses - ] - - let httpBody = try? JSONSerialization.data(withJSONObject: parameters) - - let request = apiManager.request(withMethod: .post, path: "/tx/address", httpBody: httpBody) - return apiManager.observable(forRequest: request) - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockDiscoveryBatch.swift b/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockDiscoveryBatch.swift deleted file mode 100644 index 791af261..00000000 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockDiscoveryBatch.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Foundation -import RxSwift -import ObjectMapper - -class BlockDiscoveryBatch { - private let wallet: IHDWallet - private let blockHashFetcher: IBlockHashFetcher - - private let maxHeight: Int - private let gapLimit: Int - - init(network: INetwork, wallet: IHDWallet, blockHashFetcher: IBlockHashFetcher, logger: Logger? = nil) { - self.wallet = wallet - self.blockHashFetcher = blockHashFetcher - - maxHeight = network.lastCheckpointBlock.height - gapLimit = wallet.gapLimit - } - - private func fetchRecursive(account: Int, external: Bool, publicKeys: [PublicKey] = [], blockHashes: [BlockHash] = [], prevCount: Int = 0, prevLastUsedIndex: Int = -1, startIndex: Int = 0) -> Observable<([PublicKey], [BlockHash])> { - let maxHeight = self.maxHeight - - let count = gapLimit - prevCount + prevLastUsedIndex + 1 - var newPublicKeys = [PublicKey]() - for i in 0.. Observable<([PublicKey], [BlockHash])> in - let resultBlockHashes = blockHashes + fetcherResult.responses.filter { $0.height <= maxHeight } - let resultPublicKeys = publicKeys + newPublicKeys - - let finishObservable = Observable.just((resultPublicKeys, resultBlockHashes)) - if fetcherResult.lastUsedIndex < 0 { - return finishObservable - } else { - return self?.fetchRecursive(account: account, external: external, publicKeys: resultPublicKeys, blockHashes: resultBlockHashes, prevCount: count, prevLastUsedIndex: fetcherResult.lastUsedIndex, startIndex: startIndex + count) ?? finishObservable - } - } - } - -} - -extension BlockDiscoveryBatch: IBlockDiscovery { - - func discoverBlockHashes(account: Int, external: Bool) -> Observable<([PublicKey], [BlockHash])> { - return fetchRecursive(account: account, external: external) - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockHashFetcher.swift b/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockHashFetcher.swift deleted file mode 100644 index 1839c963..00000000 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockHashFetcher.swift +++ /dev/null @@ -1,40 +0,0 @@ -import RxSwift - -class BlockHashFetcher { - private let addressSelector: IAddressSelector - private let addressConverter: IAddressConverter - private let apiManager: ISyncTransactionApi - private let helper: IBlockHashFetcherHelper - - init(addressSelector: IAddressSelector, apiManager: ISyncTransactionApi, addressConverter: IAddressConverter, helper: IBlockHashFetcherHelper) { - self.addressSelector = addressSelector - self.addressConverter = addressConverter - self.apiManager = apiManager - self.helper = helper - } - -} - -extension BlockHashFetcher: IBlockHashFetcher { - - func getBlockHashes(publicKeys: [PublicKey]) -> Observable<(responses: [BlockHash], lastUsedIndex: Int)> { - let addresses = publicKeys.map { - addressSelector.getAddressVariants(addressConverter: addressConverter, publicKey: $0) - } - - return apiManager.getTransactions(addresses: addresses.flatMap { $0 }).map { [weak self] transactionResponses -> (responses: [BlockHash], lastUsedIndex: Int) in - if transactionResponses.isEmpty { - return (responses: [], lastUsedIndex: -1) - } - - let lastUsedIndex = self?.helper.lastUsedIndex(addresses: addresses, outputs: transactionResponses.flatMap { $0.txOutputs }) - - let blockHashes: [BlockHash] = transactionResponses.compactMap { - BlockHash(headerHashReversedHex: $0.blockHash, height: $0.blockHeight, sequence: 0) - } - - return (responses: blockHashes, lastUsedIndex: lastUsedIndex ?? -1) - } - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/InitialSyncer.swift b/BitcoinCore/BitcoinCore/Managers/InitialSync/InitialSyncer.swift deleted file mode 100644 index 24ff6f22..00000000 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/InitialSyncer.swift +++ /dev/null @@ -1,108 +0,0 @@ -import HSHDWalletKit -import RxSwift - -class InitialSyncer { - weak var delegate: IInitialSyncerDelegate? - - private var disposeBag = DisposeBag() - - private let storage: IStorage - private let listener: ISyncStateListener - private var stateManager: IStateManager - private let blockDiscovery: IBlockDiscovery - private let addressManager: IAddressManager - - private let logger: Logger? - private let async: Bool - - private var restoring = false - - init(storage: IStorage, listener: ISyncStateListener, stateManager: IStateManager, blockDiscovery: IBlockDiscovery, addressManager: IAddressManager, async: Bool = true, logger: Logger? = nil) { - self.storage = storage - self.listener = listener - self.stateManager = stateManager - self.blockDiscovery = blockDiscovery - self.addressManager = addressManager - - self.logger = logger - self.async = async - } - - private func sync(forAccount account: Int) { - let externalObservable = blockDiscovery.discoverBlockHashes(account: account, external: true) - let internalObservable = blockDiscovery.discoverBlockHashes(account: account, external: false) - - var observable = Observable - .concat(externalObservable, internalObservable) - .toArray() - .map { array -> ([PublicKey], [BlockHash]) in - let (externalKeys, externalBlockHashes) = array[0] - let (internalKeys, internalBlockHashes) = array[1] - let sortedUniqueBlockHashes = Array(externalBlockHashes + internalBlockHashes).unique.sorted { a, b in a.height < b.height } - - return (externalKeys + internalKeys, sortedUniqueBlockHashes) - } - - if async { - observable = observable.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) - } - - observable.subscribe(onSuccess: { [weak self] keys, responses in - self?.handle(forAccount: account, keys: keys, blockHashes: responses) - }, onError: { [weak self] error in - self?.handle(error: error) - }) - .disposed(by: disposeBag) - } - - private func handle(forAccount account: Int, keys: [PublicKey], blockHashes: [BlockHash]) { - do { - logger?.debug("Account \(account) has \(keys.count) keys and \(blockHashes.count) blocks") - try addressManager.addKeys(keys: keys) - - // If gap shift is found - if blockHashes.isEmpty { - stateManager.restored = true - delegate?.syncingFinished() - } else { - storage.add(blockHashes: blockHashes) - sync(forAccount: account + 1) - } - - } catch { - handle(error: error) - } - } - - private func handle(error: Error) { - stop() - logger?.error(error) - listener.syncStopped() - } - -} - -extension InitialSyncer: IInitialSyncer { - - func sync() { - guard !stateManager.restored else { - delegate?.syncingFinished() - return - } - - guard !restoring else { - return - } - restoring = true - - listener.syncStarted() - sync(forAccount: 0) - } - - func stop() { - restoring = false - // Deinit old DisposeGag - disposeBag = DisposeBag() - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/InsightApi.swift b/BitcoinCore/BitcoinCore/Managers/InitialSync/InsightApi.swift deleted file mode 100644 index 4eee07fc..00000000 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/InsightApi.swift +++ /dev/null @@ -1,86 +0,0 @@ -import RxSwift -import ObjectMapper - -public class InsightApi { - private static let paginationLimit = 50 - private let apiManager: ApiManager - - public init(url: String, logger: Logger? = nil) { - apiManager = ApiManager(apiUrl: url, logger: logger) - } - -} - -extension InsightApi: ISyncTransactionApi { - - public func getTransactions(addresses: [String]) -> Observable<[SyncTransactionItem]> { - let joinedAddresses = addresses.joined(separator: ",") - - return getTransactionsRecursive(addresses: joinedAddresses) - } - - private func getTransactionsRecursive(addresses: String, from: Int = 0, transactions: [SyncTransactionItem] = []) -> Observable<([SyncTransactionItem])> { - return getTransactions(addresses: addresses, from: from).flatMap { [weak self] result -> Observable<([SyncTransactionItem])> in - let resultTransactions = transactions + result.transactionItems.map { $0 as SyncTransactionItem } - - let finishObservable = Observable.just(resultTransactions) - if result.totalItems <= result.to { - return finishObservable - } else { - return self?.getTransactionsRecursive(addresses: addresses, from: result.to, transactions: resultTransactions) ?? finishObservable - } - } - } - - private func getTransactions(addresses: String, from: Int = 0) -> Observable { - var params = [String: Any]() - params["from"] = from - params["to"] = from + InsightApi.paginationLimit - let request = apiManager.request(withMethod: .get, path: "/addrs/\(addresses)/txs", parameters: params) - let observable: Observable = apiManager.observable(forRequest: request) - return observable - } - - class InsightResponseItem: ImmutableMappable { - public let totalItems: Int - public let from: Int - public let to: Int - public let transactionItems: [InsightTransactionItem] - - public init(totalItems: Int, from: Int, to: Int, transactionItems: [InsightTransactionItem]) { - self.totalItems = totalItems - self.from = from - self.to = to - self.transactionItems = transactionItems - } - - required public init(map: Map) throws { - totalItems = try map.value("totalItems") - from = try map.value("from") - to = try map.value("to") - transactionItems = try map.value("items") - } - - } - - class InsightTransactionItem: SyncTransactionItem { - - required init(map: Map) throws { - let blockHash: String = try map.value("blockhash") - let blockHeight: Int = try map.value("blockheight") - let txOutputs: [InsightTransactionOutputItem] = try map.value("vout") - super.init(hash: blockHash, height: blockHeight, txOutputs: txOutputs.map { $0 as SyncTransactionOutputItem }) - } - - } - - class InsightTransactionOutputItem: SyncTransactionOutputItem { - required init(map: Map) throws { - let script: String = (try? map.value("scriptPubKey.hex")) ?? "" - let address: [String] = (try? map.value("scriptPubKey.addresses")) ?? [] - super.init(script: script, address: address.joined()) - } - - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/ReachabilityManager.swift b/BitcoinCore/BitcoinCore/Managers/ReachabilityManager.swift deleted file mode 100644 index 75c1c9fa..00000000 --- a/BitcoinCore/BitcoinCore/Managers/ReachabilityManager.swift +++ /dev/null @@ -1,38 +0,0 @@ -import RxSwift -import Alamofire - -class ReachabilityManager { - private let manager: NetworkReachabilityManager? - - private(set) var isReachable: Bool - let reachabilitySignal = Signal() - - init(configProvider: IApiConfigProvider? = nil) { - if let configProvider = configProvider { - manager = NetworkReachabilityManager(host: configProvider.reachabilityHost) - } else { - manager = NetworkReachabilityManager() - } - - isReachable = manager?.isReachable ?? false - - manager?.listener = { [weak self] _ in - self?.onUpdateStatus() - } - - manager?.startListening() - } - - private func onUpdateStatus() { - let newReachable = manager?.isReachable ?? false - - if isReachable != newReachable { - isReachable = newReachable - reachabilitySignal.notify() - } - } - -} - -extension ReachabilityManager: IReachabilityManager { -} diff --git a/BitcoinCore/BitcoinCore/Managers/SyncManager.swift b/BitcoinCore/BitcoinCore/Managers/SyncManager.swift deleted file mode 100644 index 73679d16..00000000 --- a/BitcoinCore/BitcoinCore/Managers/SyncManager.swift +++ /dev/null @@ -1,64 +0,0 @@ -import RxSwift - -class SyncManager { - private var disposeBag = DisposeBag() - - private let reachabilityManager: IReachabilityManager - private let initialSyncer: IInitialSyncer - private let peerGroup: IPeerGroup - - init(reachabilityManager: IReachabilityManager, initialSyncer: IInitialSyncer, peerGroup: IPeerGroup) { - self.reachabilityManager = reachabilityManager - self.initialSyncer = initialSyncer - self.peerGroup = peerGroup - } - - private func startSync() { - if reachabilityManager.isReachable { - initialSyncer.sync() - } - } - - private func stopSync() { - initialSyncer.stop() - peerGroup.stop() - } - - private func onReachabilityChanged() { - if reachabilityManager.isReachable { - startSync() - } else { - stopSync() - } - } - -} - -extension SyncManager: ISyncManager { - - func start() { - reachabilityManager.reachabilitySignal - .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) - .subscribe(onNext: { [weak self] in - self?.onReachabilityChanged() - }) - .disposed(by: disposeBag) - - initialSyncer.sync() - } - - func stop() { - disposeBag = DisposeBag() - stopSync() - } - -} - -extension SyncManager: IInitialSyncerDelegate { - - func syncingFinished() { - initialSyncer.stop() - peerGroup.start() - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/SyncedReadyPeerManager.swift b/BitcoinCore/BitcoinCore/Managers/SyncedReadyPeerManager.swift deleted file mode 100644 index 70e6967e..00000000 --- a/BitcoinCore/BitcoinCore/Managers/SyncedReadyPeerManager.swift +++ /dev/null @@ -1,104 +0,0 @@ -import RxSwift - -public class SyncedReadyPeerManager { - private let disposeBag = DisposeBag() - private let peerGroup: IPeerGroup - private let initialBlockDownload: IInitialBlockDownload - private var peerStates = [String: Bool]() - - private let peerSyncedAndReadySubject = PublishSubject() - - init(peerGroup: IPeerGroup, initialBlockDownload: IInitialBlockDownload) { - self.peerGroup = peerGroup - self.initialBlockDownload = initialBlockDownload - } - - private func set(state: Bool, to peer: IPeer) { - let oldState = peerStates[peer.host] ?? false - peerStates[peer.host] = state - - if oldState != state { - if state { - peerSyncedAndReadySubject.onNext(peer) - } else { - } - } - } - - func subscribeTo(observable: Observable) { - observable.subscribe( - onNext: { [weak self] in - switch $0 { - case .onPeerConnect(let peer): self?.onPeerConnect(peer: peer) - case .onPeerDisconnect(let peer, let error): self?.onPeerDisconnect(peer: peer, error: error) - case .onPeerReady(let peer): self?.onPeerReady(peer: peer) - case .onPeerBusy(let peer): self?.onPeerBusy(peer: peer) - default: () - } - } - ) - .disposed(by: disposeBag) - } - - func subscribeTo(observable: Observable) { - observable.subscribe( - onNext: { [weak self] in - switch $0 { - case .onPeerSynced(let peer): self?.onPeerSynced(peer: peer) - case .onPeerNotSynced(let peer): self?.onPeerNotSynced(peer: peer) - } - } - ) - .disposed(by: disposeBag) - } -} - -extension SyncedReadyPeerManager: ISyncedReadyPeerManager { - - public var peers: [IPeer] { - return initialBlockDownload.syncedPeers.filter { - self.peerGroup.isReady(peer: $0) - } - } - - public var observable: Observable { - return peerSyncedAndReadySubject.asObservable() - } - -} - -extension SyncedReadyPeerManager { - - private func onPeerConnect(peer: IPeer) { - set(state: false, to: peer) - } - - private func onPeerDisconnect(peer: IPeer, error: Error?) { - peerStates.removeValue(forKey: peer.host) - } - - private func onPeerReady(peer: IPeer) { - if initialBlockDownload.isSynced(peer: peer) { - set(state: true, to: peer) - } - } - - private func onPeerBusy(peer: IPeer) { - set(state: false, to: peer) - } - -} - -extension SyncedReadyPeerManager { - - private func onPeerSynced(peer: IPeer) { - if peerGroup.isReady(peer: peer) { - set(state: true, to: peer) - } - } - - private func onPeerNotSynced(peer: IPeer) { - set(state: false, to: peer) - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/UnspentOutputSelector.swift b/BitcoinCore/BitcoinCore/Managers/UnspentOutputSelector.swift deleted file mode 100644 index ff01d538..00000000 --- a/BitcoinCore/BitcoinCore/Managers/UnspentOutputSelector.swift +++ /dev/null @@ -1,94 +0,0 @@ -import Foundation - -public struct SelectedUnspentOutputInfo { - public let unspentOutputs: [UnspentOutput] - public let totalValue: Int // summary value on selected unspent unspentOutputs - public let fee: Int // fee for transaction with output(and maybe change output) and all selected inputs(unspent unspentOutputs) + maybe dust - public let addChangeOutput: Bool // need to add changeOutput. Fee was calculated with change output - - public init(unspentOutputs: [UnspentOutput], totalValue: Int, fee: Int, addChangeOutput: Bool) { - self.unspentOutputs = unspentOutputs - self.totalValue = totalValue - self.fee = fee - self.addChangeOutput = addChangeOutput - } -} - -public class UnspentOutputSelector { - - private let calculator: ITransactionSizeCalculator - private let provider: IUnspentOutputProvider - private let outputsLimit: Int? - - public init(calculator: ITransactionSizeCalculator, provider: IUnspentOutputProvider, outputsLimit: Int? = nil) { - self.calculator = calculator - self.provider = provider - self.outputsLimit = outputsLimit - } - -} - -extension UnspentOutputSelector: IUnspentOutputSelector { - - public func select(value: Int, feeRate: Int, outputScriptType: ScriptType = .p2pkh, changeType: ScriptType = .p2pkh, senderPay: Bool) throws -> SelectedUnspentOutputInfo { - let unspentOutputs = provider.allUnspentOutputs - - guard value > 0 else { - throw BitcoinCoreErrors.UnspentOutputSelection.wrongValue - } - guard !unspentOutputs.isEmpty else { - throw BitcoinCoreErrors.UnspentOutputSelection.emptyOutputs - } - let dust = (calculator.inputSize(type: changeType) + calculator.outputSize(type: changeType)) * feeRate // fee needed for make changeOutput, we use only p2pkh for change output - - let sortedOutputs = unspentOutputs.sorted(by: { lhs, rhs in lhs.output.value < rhs.output.value }) - - // select unspentOutputs with least value until we get needed value - var selectedOutputs = [UnspentOutput]() - var selectedOutputScriptTypes = [ScriptType]() - var totalValue = 0 - - var fee = 0 - var lastCalculatedFee = 0 - - for unspentOutput in sortedOutputs { - selectedOutputs.append(unspentOutput) - selectedOutputScriptTypes.append(unspentOutput.output.scriptType) - totalValue += unspentOutput.output.value - - if let outputsLimit = outputsLimit { - if (selectedOutputs.count > outputsLimit) { - guard let outputValueToExclude = selectedOutputs.first?.output.value else { - continue - } - selectedOutputs.remove(at: 0) - selectedOutputScriptTypes.remove(at: 0) - totalValue -= outputValueToExclude - } - } - lastCalculatedFee = calculator.transactionSize(inputs: selectedOutputScriptTypes, outputScriptTypes: [outputScriptType]) * feeRate - if senderPay { - fee = lastCalculatedFee - } - if totalValue >= lastCalculatedFee && totalValue >= value + fee { - break - } - } - - // if all unspentOutputs are selected and total value less than needed throw error - if totalValue < value + fee { - throw BitcoinCoreErrors.UnspentOutputSelection.notEnough(maxFee: fee) - } - - // if total selected unspentOutputs value more than value and fee for transaction with change output + change input -> add fee for change output and mark as need change address - var addChangeOutput = false - if totalValue > value + lastCalculatedFee + (senderPay ? dust : 0) { - lastCalculatedFee = calculator.transactionSize(inputs: selectedOutputScriptTypes, outputScriptTypes: [outputScriptType, changeType]) * feeRate - addChangeOutput = true - } else if senderPay { - lastCalculatedFee = totalValue - value - } - return SelectedUnspentOutputInfo(unspentOutputs: selectedOutputs, totalValue: totalValue, fee: lastCalculatedFee, addChangeOutput: addChangeOutput) - } - -} diff --git a/BitcoinCore/BitcoinCore/Managers/UnspentOutputSelectorSingleNoChange.swift b/BitcoinCore/BitcoinCore/Managers/UnspentOutputSelectorSingleNoChange.swift deleted file mode 100644 index a3e7a986..00000000 --- a/BitcoinCore/BitcoinCore/Managers/UnspentOutputSelectorSingleNoChange.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -public class UnspentOutputSelectorSingleNoChange { - - private let calculator: ITransactionSizeCalculator - private let provider: IUnspentOutputProvider - - public init(calculator: ITransactionSizeCalculator, provider: IUnspentOutputProvider) { - self.calculator = calculator - self.provider = provider - } - -} - -extension UnspentOutputSelectorSingleNoChange: IUnspentOutputSelector { - - public func select(value: Int, feeRate: Int, outputScriptType: ScriptType = .p2pkh, changeType: ScriptType = .p2pkh, senderPay: Bool) throws -> SelectedUnspentOutputInfo { - let unspentOutputs = provider.allUnspentOutputs - - guard value > 0 else { - throw BitcoinCoreErrors.UnspentOutputSelection.wrongValue - } - guard !unspentOutputs.isEmpty else { - throw BitcoinCoreErrors.UnspentOutputSelection.emptyOutputs - } - let dust = (calculator.inputSize(type: changeType) + calculator.outputSize(type: changeType)) * feeRate // fee needed for make changeOutput, we use only p2pkh for change output - - // try to find 1 unspent output with exactly matching value - for unspentOutput in unspentOutputs { - let output = unspentOutput.output - let fee = calculator.transactionSize(inputs: [output.scriptType], outputScriptTypes: [outputScriptType]) * feeRate - let totalFee = senderPay ? fee : 0 - if (value + totalFee <= output.value) && (value + totalFee + dust > output.value) { - return SelectedUnspentOutputInfo(unspentOutputs: [unspentOutput], totalValue: output.value, fee: senderPay ? (output.value - value) : fee, addChangeOutput: false) - } - } - - throw BitcoinCoreErrors.UnspentOutputSelection.notEnough(maxFee: 0) - } - -} diff --git a/BitcoinCore/BitcoinCore/Models/Output.swift b/BitcoinCore/BitcoinCore/Models/Output.swift deleted file mode 100644 index b0b7688a..00000000 --- a/BitcoinCore/BitcoinCore/Models/Output.swift +++ /dev/null @@ -1,106 +0,0 @@ -import Foundation -import GRDB - -public enum ScriptType: Int, DatabaseValueConvertible { - case unknown, p2pkh, p2pk, p2multi, p2sh, p2wsh, p2wpkh, p2wpkhSh - - var size: Int { - switch self { - case .p2pk: return 35 - case .p2pkh: return 25 - case .p2sh: return 23 - case .p2wsh: return 34 - case .p2wpkh: return 22 - case .p2wpkhSh: return 23 - default: return 0 - } - } - - var keyLength: UInt8 { - switch self { - case .p2pk: return 0x21 - case .p2pkh: return 0x14 - case .p2sh: return 0x14 - case .p2wsh: return 0x20 - case .p2wpkh: return 0x14 - case .p2wpkhSh: return 0x14 - default: return 0 - } - } - - var addressType: AddressType { - switch self { - case .p2sh, .p2wsh: return .scriptHash - default: return .pubKeyHash - } - } - - var witness: Bool { - return self == .p2wpkh || self == .p2wpkhSh || self == .p2wsh - } - -} - -public class Output: Record { - - public var value: Int - var lockingScript: Data - var index: Int - var transactionHash = Data() - var publicKeyPath: String? = nil - public var scriptType: ScriptType = .unknown - var keyHash: Data? = nil - var address: String? = nil - - init(withValue value: Int, index: Int, lockingScript script: Data, type: ScriptType = .unknown, address: String? = nil, keyHash: Data? = nil, publicKey: PublicKey? = nil) { - self.value = value - self.lockingScript = script - self.index = index - self.scriptType = type - self.address = address - self.keyHash = keyHash - self.publicKeyPath = publicKey?.path - - super.init() - } - - override open class var databaseTableName: String { - return "outputs" - } - - enum Columns: String, ColumnExpression, CaseIterable { - case value - case lockingScript - case index - case transactionHash - case publicKeyPath - case scriptType - case keyHash - case address - } - - required init(row: Row) { - value = row[Columns.value] - lockingScript = row[Columns.lockingScript] - index = row[Columns.index] - transactionHash = row[Columns.transactionHash] - publicKeyPath = row[Columns.publicKeyPath] - scriptType = row[Columns.scriptType] - keyHash = row[Columns.keyHash] - address = row[Columns.address] - - super.init(row: row) - } - - override open func encode(to container: inout PersistenceContainer) { - container[Columns.value] = value - container[Columns.lockingScript] = lockingScript - container[Columns.index] = index - container[Columns.transactionHash] = transactionHash - container[Columns.publicKeyPath] = publicKeyPath - container[Columns.scriptType] = scriptType - container[Columns.keyHash] = keyHash - container[Columns.address] = address - } - -} diff --git a/BitcoinCore/BitcoinCore/Models/TransactionInfo.swift b/BitcoinCore/BitcoinCore/Models/TransactionInfo.swift deleted file mode 100644 index e34cbb36..00000000 --- a/BitcoinCore/BitcoinCore/Models/TransactionInfo.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -open class TransactionInfo: ITransactionInfo { - public let transactionHash: String - public let transactionIndex: Int - public let from: [TransactionAddressInfo] - public let to: [TransactionAddressInfo] - public let amount: Int - public let blockHeight: Int? - public let timestamp: Int - - public required init(transactionHash: String, transactionIndex: Int, from: [TransactionAddressInfo], to: [TransactionAddressInfo], amount: Int, blockHeight: Int?, timestamp: Int) { - self.transactionHash = transactionHash - self.transactionIndex = transactionIndex - self.from = from - self.to = to - self.amount = amount - self.blockHeight = blockHeight - self.timestamp = timestamp - } - -} - -public struct TransactionAddressInfo { - public let address: String - public let mine: Bool -} - -public struct BlockInfo { - public let headerHash: String - public let height: Int - public let timestamp: Int? -} diff --git a/BitcoinCore/BitcoinCore/Network/Messages/TransactionMessage.swift b/BitcoinCore/BitcoinCore/Network/Messages/TransactionMessage.swift deleted file mode 100644 index d5dbdaa3..00000000 --- a/BitcoinCore/BitcoinCore/Network/Messages/TransactionMessage.swift +++ /dev/null @@ -1,9 +0,0 @@ -struct TransactionMessage: IMessage { - let transaction: FullTransaction - let size: Int - - var description: String { - return "\(transaction.header.dataHash.reversedHex)" - } - -} diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerAddressManagerState.swift b/BitcoinCore/BitcoinCore/Network/Peer/PeerAddressManagerState.swift deleted file mode 100644 index 9bbf300a..00000000 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerAddressManagerState.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -class PeerAddressManagerState { - private let queue = DispatchQueue(label: "PeerAddressManager.State", qos: .utility) - private(set) var usedIps: [String] = [] - - func add(usedIp: String) { - queue.sync { - self.usedIps.append(usedIp) - } - } - - func remove(usedIp: String) { - queue.sync { - self.usedIps.removeAll(where: { $0 == usedIp }) - } - } -} diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerConnection.swift b/BitcoinCore/BitcoinCore/Network/Peer/PeerConnection.swift deleted file mode 100644 index 52fd3b80..00000000 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerConnection.swift +++ /dev/null @@ -1,235 +0,0 @@ -import Foundation -import HSHDWalletKit - -class PeerConnection: NSObject { - enum PeerConnectionError: Error { - case connectionClosedWithUnknownError - case connectionClosedByPeer - } - - private let bufferSize = 4096 - private let interval = 1.0 - - let host: String - let port: UInt32 - private let networkMessageParser: INetworkMessageParser - private let networkMessageSerializer: INetworkMessageSerializer - - weak var delegate: PeerConnectionDelegate? - - private var readStream: Unmanaged? - private var writeStream: Unmanaged? - private var timer: Timer? - private weak var runLoop: RunLoop? - private weak var inputStream: InputStream? - private weak var outputStream: OutputStream? - - private var packets: Data = Data() - - private let logger: Logger? - - var connected: Bool = false - - var logName: String { - let index = abs(host.hash) % WordList.english.count - return "[\(WordList.english[index])]".uppercased() - } - - init(host: String, port: UInt32, networkMessageParser: INetworkMessageParser, networkMessageSerializer: INetworkMessageSerializer, logger: Logger? = nil) { - self.host = host - self.port = port - self.networkMessageParser = networkMessageParser - self.networkMessageSerializer = networkMessageSerializer - - self.timer = nil - self.logger = logger - } - - deinit { - disconnect() - } - - private func connectAsync() { - CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, port, &readStream, &writeStream) - inputStream = readStream!.takeUnretainedValue() - outputStream = writeStream!.takeUnretainedValue() - - weak var weakSelf = self - inputStream?.delegate = weakSelf - outputStream?.delegate = weakSelf - - inputStream?.schedule(in: .current, forMode: .common) - outputStream?.schedule(in: .current, forMode: .common) - - inputStream?.open() - outputStream?.open() - - let timer = Timer(timeInterval: interval, repeats: true, block: { [weak self] _ in self?.delegate?.connectionTimePeriodPassed() }) - self.timer = timer - - RunLoop.current.add(timer, forMode: .common) - RunLoop.current.run() - } - - private func readAvailableBytes(stream: InputStream) { - delegate?.connectionAlive() - - let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) - defer { - buffer.deallocate() - } - - while stream.hasBytesAvailable { - let numberOfBytesRead = stream.read(buffer, maxLength: bufferSize) - if numberOfBytesRead <= 0 { - if let _ = stream.streamError { - break - } - } else { - packets += Data(bytesNoCopy: buffer, count: numberOfBytesRead, deallocator: .none) - } - - while packets.count >= NetworkMessage.minimumLength { - guard let networkMessage = networkMessageParser.parse(data: packets) else { - break - } - - packets = Data(packets.dropFirst(NetworkMessage.minimumLength + Int(networkMessage.length))) - let message = networkMessage.message - - guard !(message is UnknownMessage) else { - break - } - - log("<- \(type(of: message)): \(message.description)") - delegate?.connection(didReceiveMessage: message) - } - } - } - - private func log(_ message: @autoclosure () -> Any, level: Logger.Level = .debug, file: String = #file, function: String = #function, line: Int = #line) { - logger?.log(level: level, message: message(), file: file, function: function, line: line, context: logName) - } -} - -extension PeerConnection: IPeerConnection { - - func connect() { - if runLoop == nil { - DispatchQueue.global(qos: .userInitiated).async { - self.runLoop = .current - self.connectAsync() - } - } else { - log("ALREADY CONNECTED") - } - } - - func disconnect(error: Error? = nil) { - guard readStream != nil && writeStream != nil else { - return - } - - if let runLoop = self.runLoop { - inputStream?.remove(from: runLoop, forMode: .common) - outputStream?.remove(from: runLoop, forMode: .common) - timer?.invalidate() - - CFRunLoopStop(runLoop.getCFRunLoop()) - } - - inputStream?.delegate = nil - outputStream?.delegate = nil - inputStream?.close() - outputStream?.close() - readStream?.release() - writeStream?.release() - - timer = nil - readStream = nil - writeStream = nil - inputStream = nil - outputStream = nil - connected = false - - delegate?.connectionDidDisconnect(withError: error) - - log("DISCONNECTED") - } - - func send(message: IMessage) { - log("-> \(type(of: message)): \(message.description)") - do { - let data = try networkMessageSerializer.serialize(message: message) - guard !data.isEmpty else { - return - } - _ = data.withUnsafeBytes { - outputStream?.write($0.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: data.count) - } - } catch { - log("Connection can't send message \(message) with error \(error)", level: .error) //todo catch error when try send message not registered in serializers - } - } - -} - -extension PeerConnection: StreamDelegate { - - func stream(_ stream: Stream, handle eventCode: Stream.Event) { - switch stream { - case let stream as InputStream: - switch eventCode { - case .openCompleted: - log("CONNECTION ESTABLISHED") - connected = true - break - case .hasBytesAvailable: - readAvailableBytes(stream: stream) - case .hasSpaceAvailable: - break - case .errorOccurred: - log("IN ERROR OCCURRED", level: .warning) - if connected { - // If connected, then error is related not to peer, but to network - disconnect() - } else { - disconnect(error: PeerConnectionError.connectionClosedWithUnknownError) - } - case .endEncountered: - log("IN CLOSED") - disconnect(error: PeerConnectionError.connectionClosedByPeer) - default: - break - } - case _ as OutputStream: - switch eventCode { - case .openCompleted: - break - case .hasBytesAvailable: - break - case .hasSpaceAvailable: - delegate?.connectionReadyForWrite() - case .errorOccurred: - log("OUT ERROR OCCURRED", level: .warning) - disconnect() - case .endEncountered: - log("OUT CLOSED") - disconnect() - default: - break - } - default: - break - } - } - -} - -protocol PeerConnectionDelegate: class { - func connectionAlive() - func connectionTimePeriodPassed() - func connectionReadyForWrite() - func connectionDidDisconnect(withError error: Error?) - func connection(didReceiveMessage message: IMessage) -} diff --git a/BitcoinCore/BitcoinCore/Network/TransactionSender.swift b/BitcoinCore/BitcoinCore/Network/TransactionSender.swift deleted file mode 100644 index 37f156b1..00000000 --- a/BitcoinCore/BitcoinCore/Network/TransactionSender.swift +++ /dev/null @@ -1,88 +0,0 @@ -import RxSwift - -class TransactionSender { - private let disposeBag = DisposeBag() - - var transactionSyncer: ITransactionSyncer - var peerManager: IPeerManager - var initialBlockDownload: IInitialBlockDownload - var syncedReadyPeerManager: ISyncedReadyPeerManager - var logger: Logger? - - init(transactionSyncer: ITransactionSyncer, peerManager: IPeerManager, initialBlockDownload: IInitialBlockDownload, syncedReadyPeerManager: ISyncedReadyPeerManager, logger: Logger? = nil) { - self.transactionSyncer = transactionSyncer - self.peerManager = peerManager - self.initialBlockDownload = initialBlockDownload - self.syncedReadyPeerManager = syncedReadyPeerManager - self.logger = logger - } - - private func peersToSendTo() throws -> [IPeer] { - guard peerManager.connected().count > 0 else { - throw BitcoinCoreErrors.TransactionSendError.noConnectedPeers - } - - guard initialBlockDownload.allPeersSynced else { - throw BitcoinCoreErrors.TransactionSendError.peersNotSynced - } - - let peers = syncedReadyPeerManager.peers - guard peers.count > 0 else { - throw BitcoinCoreErrors.TransactionSendError.peersNotSynced - } - - let peersToSendTo: [IPeer] - - if peers.count == 1 { - peersToSendTo = peers - } else { - peersToSendTo = Array(peers.prefix(peers.count / 2)) - } - - return peersToSendTo - } - - private func send(transactions: [FullTransaction], toPeers peers: [IPeer]) { - for peer in peers { - for transaction in transactions { - peer.add(task: SendTransactionTask(transaction: transaction)) - } - } - } - - private func onPeerSyncedAndReady(peer: IPeer) { - guard peerManager.connected().count == initialBlockDownload.syncedPeers.count else { - return - } - - let transactions = transactionSyncer.pendingTransactions() - - guard transactions.count > 0, let peers = try? peersToSendTo() else { - return - } - - send(transactions: transactionSyncer.pendingTransactions(), toPeers: peers) - } - -} - -extension TransactionSender: ITransactionSender { - - func verifyCanSend() throws { - _ = try peersToSendTo() - } - - func send(pendingTransaction: FullTransaction) throws { - let peers = try peersToSendTo() - - send(transactions: [pendingTransaction], toPeers: peers) - } - - func subscribeTo(observable: Observable) { - observable.subscribe(onNext: { [weak self] in - self?.onPeerSyncedAndReady(peer: $0) - }) - .disposed(by: disposeBag) - } - -} diff --git a/BitcoinCore/BitcoinCore/Storage/RealmFactory.swift b/BitcoinCore/BitcoinCore/Storage/RealmFactory.swift deleted file mode 100644 index e69de29b..00000000 diff --git a/BitcoinCore/BitcoinCore/Transactions/Builder/ScriptBuilder.swift b/BitcoinCore/BitcoinCore/Transactions/Builder/ScriptBuilder.swift deleted file mode 100644 index 2058992d..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/Builder/ScriptBuilder.swift +++ /dev/null @@ -1,12 +0,0 @@ -class ScriptBuilder: IScriptBuilder { - - open func lockingScript(for address: Address) throws -> Data { - switch address.scriptType { - case .p2pkh: return OpCode.p2pkhStart + OpCode.push(address.keyHash) + OpCode.p2pkhFinish - case .p2pk: return OpCode.push(address.keyHash) + OpCode.p2pkFinish - case .p2sh: return OpCode.p2shStart + OpCode.push(address.keyHash) + OpCode.p2shFinish - default: throw BitcoinCoreErrors.ScriptBuild.unknownType - } - } - -} diff --git a/BitcoinCore/BitcoinCore/Transactions/Builder/ScriptBuilderChain.swift b/BitcoinCore/BitcoinCore/Transactions/Builder/ScriptBuilderChain.swift deleted file mode 100644 index 31a8ba66..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/Builder/ScriptBuilderChain.swift +++ /dev/null @@ -1,23 +0,0 @@ -class ScriptBuilderChain: IScriptBuilder { - private var concreteBuilders = [IScriptBuilder]() - - func prepend(scriptBuilder: IScriptBuilder) { - concreteBuilders.insert(scriptBuilder, at: 0) - } - - func lockingScript(for address: Address) throws -> Data { - var errors = [Error]() - - for builder in concreteBuilders { - do { - let converted = try builder.lockingScript(for: address) - return converted - } catch { - errors.append(error) - } - } - - throw BitcoinCoreErrors.AddressConversionErrors(errors: errors) - } - -} diff --git a/BitcoinCore/BitcoinCore/Transactions/Builder/TransactionBuilder.swift b/BitcoinCore/BitcoinCore/Transactions/Builder/TransactionBuilder.swift deleted file mode 100644 index 0614ce5c..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/Builder/TransactionBuilder.swift +++ /dev/null @@ -1,131 +0,0 @@ -import HSCryptoKit - -class TransactionBuilder { - enum BuildError: Error { - case noChangeAddress - case feeMoreThanValue - } - - private let unspentOutputSelector: IUnspentOutputSelector - private let unspentOutputProvider: IUnspentOutputProvider - private let addressManager: IAddressManager - private let addressConverter: IAddressConverter - private let inputSigner: IInputSigner - private let factory: IFactory - - var scriptBuilder: IScriptBuilder - - init(unspentOutputSelector: IUnspentOutputSelector, unspentOutputProvider: IUnspentOutputProvider, addressManager: IAddressManager, addressConverter: IAddressConverter, inputSigner: IInputSigner, scriptBuilder: IScriptBuilder, factory: IFactory) { - self.unspentOutputSelector = unspentOutputSelector - self.unspentOutputProvider = unspentOutputProvider - self.addressManager = addressManager - self.addressConverter = addressConverter - self.inputSigner = inputSigner - self.scriptBuilder = scriptBuilder - self.factory = factory - } - - private func input(fromUnspentOutput unspentOutput: UnspentOutput) throws -> InputToSign { - if unspentOutput.output.scriptType == .p2wpkh { - // todo: refactoring version byte! - // witness key hashes stored with program byte and push code to determine - // version (current only 0), but for sign we need only public kee hash - unspentOutput.output.keyHash?.removeFirst(2) - } - - return factory.inputToSign(withPreviousOutput: unspentOutput, script: Data(), sequence: 0xFFFFFFFF) - } - - private func output(withIndex index: Int, address: Address, pubKey: PublicKey? = nil, value: Int) throws -> Output { - let script = try scriptBuilder.lockingScript(for: address) - let output = factory.output(withValue: value, index: index, lockingScript: script, type: address.scriptType, address: address.stringValue, keyHash: address.keyHash, publicKey: pubKey) - return output - } - -} - -extension TransactionBuilder: ITransactionBuilder { - - // :fee method returns the fee for the given amount - // If address given and it's valid, it returns the actual fee - // Otherwise, it returns the estimated fee - func fee(for value: Int, feeRate: Int, senderPay: Bool, address: String? = nil) throws -> Int { - if let string = address, let _ = try? addressConverter.convert(address: string) { - // Actual fee - let transaction = try buildTransaction(value: value, feeRate: feeRate, senderPay: senderPay, toAddress: string) - return TransactionSerializer.serialize(transaction: transaction, withoutWitness: true).count * feeRate - } else { - // Estimated fee - // Default to .p2pkh address - let selectedOutputsInfo = try unspentOutputSelector.select(value: value, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: senderPay) - return selectedOutputsInfo.fee - } - } - - func buildTransaction(value: Int, feeRate: Int, senderPay: Bool, toAddress: String) throws -> FullTransaction { - guard let changePubKey = try? addressManager.changePublicKey() else { - throw BuildError.noChangeAddress - } - - let changeScriptType = ScriptType.p2pkh - let address = try addressConverter.convert(address: toAddress) - let selectedOutputsInfo = try unspentOutputSelector.select(value: value, feeRate: feeRate, outputScriptType: address.scriptType, changeType: changeScriptType, senderPay: senderPay) - - if !senderPay { - guard selectedOutputsInfo.fee < value else { - throw BuildError.feeMoreThanValue - } - } - - var inputsToSign = [InputToSign]() - var outputs = [Output]() - - // Add inputs without unlocking scripts - for output in selectedOutputsInfo.unspentOutputs { - inputsToSign.append(try input(fromUnspentOutput: output)) - } - - // Calculate fee - let receivedValue = senderPay ? value : value - selectedOutputsInfo.fee - let sentValue = senderPay ? value + selectedOutputsInfo.fee : value - - // Add :to output - outputs.append(try output(withIndex: 0, address: address, value: receivedValue)) - - // Add :change output if needed - if selectedOutputsInfo.addChangeOutput { - let changeAddress = try addressConverter.convert(keyHash: changePubKey.keyHash, type: changeScriptType) - outputs.append(try output(withIndex: 1, address: changeAddress, value: selectedOutputsInfo.totalValue - sentValue)) - } - - // Build transaction - let transaction = factory.transaction(version: 1, lockTime: 0) - - // Sign inputs - for i in 0.. Bool { - for input in inputs { - if let outputIndices = myOutputs[input.previousOutputTxHash], outputIndices.contains(input.previousOutputIndex) { - return true - } - } - - return false - } - - func clear() { - myOutputs.removeAll() - } - -} - -extension OutputsCache { - - static func instance(storage: IStorage) -> OutputsCache { - let instance = OutputsCache() - let outputs = storage.outputsWithPublicKeys() - instance.add(fromOutputs: outputs.map { $0.output }) - - return instance - } - -} diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionCreator.swift b/BitcoinCore/BitcoinCore/Transactions/TransactionCreator.swift deleted file mode 100644 index 8aa8bca2..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionCreator.swift +++ /dev/null @@ -1,29 +0,0 @@ -class TransactionCreator { - enum CreationError: Error { - case transactionAlreadyExists - } - - private let transactionBuilder: ITransactionBuilder - private let transactionProcessor: ITransactionProcessor - private let transactionSender: ITransactionSender - - init(transactionBuilder: ITransactionBuilder, transactionProcessor: ITransactionProcessor, transactionSender: ITransactionSender) { - self.transactionBuilder = transactionBuilder - self.transactionProcessor = transactionProcessor - self.transactionSender = transactionSender - } - -} - -extension TransactionCreator: ITransactionCreator { - - func create(to address: String, value: Int, feeRate: Int, senderPay: Bool) throws { - try transactionSender.verifyCanSend() - - let transaction = try transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: senderPay, toAddress: address) - try transactionProcessor.processCreated(transaction: transaction) - - try transactionSender.send(pendingTransaction: transaction) - } - -} diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionProcessor.swift b/BitcoinCore/BitcoinCore/Transactions/TransactionProcessor.swift deleted file mode 100644 index a083e325..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionProcessor.swift +++ /dev/null @@ -1,123 +0,0 @@ -import RxSwift - -class TransactionProcessor { - private let storage: IStorage - private let outputExtractor: ITransactionExtractor - private let inputExtractor: ITransactionExtractor - private let outputAddressExtractor: ITransactionOutputAddressExtractor - private let outputsCache: IOutputsCache - private let addressManager: IAddressManager - - weak var listener: IBlockchainDataListener? - - private let dateGenerator: () -> Date - private let queue: DispatchQueue - - init(storage: IStorage, outputExtractor: ITransactionExtractor, inputExtractor: ITransactionExtractor, outputsCache: IOutputsCache, outputAddressExtractor: ITransactionOutputAddressExtractor, addressManager: IAddressManager, listener: IBlockchainDataListener? = nil, - dateGenerator: @escaping () -> Date = Date.init, queue: DispatchQueue = DispatchQueue(label: "Transactions", qos: .background - )) { - self.storage = storage - self.outputExtractor = outputExtractor - self.inputExtractor = inputExtractor - self.outputAddressExtractor = outputAddressExtractor - self.outputsCache = outputsCache - self.addressManager = addressManager - self.listener = listener - self.dateGenerator = dateGenerator - self.queue = queue - } - - private func hasUnspentOutputs(transaction: FullTransaction) -> Bool { - for output in transaction.outputs { - if output.publicKeyPath != nil, (output.scriptType == .p2wpkh || output.scriptType == .p2pk) { - return true - } - } - - return false - } - - private func process(transaction: FullTransaction) { - outputExtractor.extract(transaction: transaction) - if outputsCache.hasOutputs(forInputs: transaction.inputs) { - transaction.header.isMine = true - transaction.header.isOutgoing = true - } - - guard transaction.header.isMine else { - return - } - outputsCache.add(fromOutputs: transaction.outputs) - outputAddressExtractor.extractOutputAddresses(transaction: transaction) - inputExtractor.extract(transaction: transaction) - } - - private func relay(transaction: Transaction, withOrder order: Int, inBlock block: Block?) { - guard block != nil || transaction.blockHash == nil else { - return - } - transaction.blockHash = block?.headerHash - transaction.status = .relayed - transaction.timestamp = block?.timestamp ?? Int(dateGenerator().timeIntervalSince1970) - transaction.order = order - - if let block = block, !block.hasTransactions { - block.hasTransactions = true - storage.update(block: block) - } - } - -} - -extension TransactionProcessor: ITransactionProcessor { - - func processReceived(transactions: [FullTransaction], inBlock block: Block?, skipCheckBloomFilter: Bool) throws { - var needToUpdateBloomFilter = false - - var updated = [Transaction]() - var inserted = [Transaction]() - - try queue.sync { - for (index, transaction) in transactions.inTopologicalOrder().enumerated() { - if let existingTransaction = self.storage.transaction(byHash: transaction.header.dataHash) { - self.relay(transaction: existingTransaction, withOrder: index, inBlock: block) - try self.storage.update(transaction: existingTransaction) - updated.append(existingTransaction) - continue - } - - self.process(transaction: transaction) - - if transaction.header.isMine { - self.relay(transaction: transaction.header, withOrder: index, inBlock: block) - try self.storage.add(transaction: transaction) - - inserted.append(transaction.header) - - if !skipCheckBloomFilter { - needToUpdateBloomFilter = needToUpdateBloomFilter || self.addressManager.gapShifts() || self.hasUnspentOutputs(transaction: transaction) - } - } - } - } - - if !updated.isEmpty || !inserted.isEmpty { - listener?.onUpdate(updated: updated, inserted: inserted, inBlock: block) - } - - if needToUpdateBloomFilter { - throw BloomFilterManager.BloomFilterExpired() - } - } - - func processCreated(transaction: FullTransaction) throws { - guard storage.transaction(byHash: transaction.header.dataHash) == nil else { - throw TransactionCreator.CreationError.transactionAlreadyExists - } - - process(transaction: transaction) - try storage.add(transaction: transaction) - listener?.onUpdate(updated: [], inserted: [transaction.header], inBlock: nil) - } - -} diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionSizeCalculator.swift b/BitcoinCore/BitcoinCore/Transactions/TransactionSizeCalculator.swift deleted file mode 100644 index 128791e4..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionSizeCalculator.swift +++ /dev/null @@ -1,65 +0,0 @@ -public class TransactionSizeCalculator: ITransactionSizeCalculator { - static let legacyTx = 16 + 4 + 4 + 16 //40 Version + number of inputs + number of outputs + locktime - static let legacyWitnessData = 1 //1 Only 0x00 for legacy input - static let witnessData = 1 + signatureLength + pubKeyLength //108 Number of stack items for input + Size of stack item 0 + Stack item 0, signature + Size of stack item 1 + Stack item 1, pubkey - static let witnessTx = legacyTx + 1 + 1 //42 SegWit marker + SegWit flag - - static let signatureLength = 74 + 1 // signature length plus pushByte - static let pubKeyLength = 33 + 1 // pubKey length plus pushByte - static let p2wpkhShLength = 22 + 1 // 0014<20byte-scriptHash> plus pushByte - - public init() {} - - public func transactionSize(inputs: [ScriptType], outputScriptTypes: [ScriptType]) -> Int { // in real bytes upped to int - var segWit = false - var inputWeight = 0 - - for input in inputs { - if input.witness { - segWit = true - break - } - } - - inputs.forEach { input in - inputWeight += inputSize(type: input) * 4 // to vbytes - if input.witness { - inputWeight += witnessSize(type: input) - } - } - - let outputWeight = outputScriptTypes.reduce(0) { $0 + outputSize(type: $1) } * 4 // to vbytes - let txWeight = segWit ? TransactionSizeCalculator.witnessTx : TransactionSizeCalculator.legacyTx - - return toBytes(fee: txWeight + inputWeight + outputWeight) - } - - public func outputSize(type: ScriptType) -> Int { // in real bytes - let outputTxSize: Int = 8 + 1 + Int(type.size) // spentValue + scriptLength + script - return outputTxSize - } - - public func inputSize(type: ScriptType) -> Int { // in real bytes - let sigScriptLength: Int - switch type { - case .p2pkh: sigScriptLength = TransactionSizeCalculator.signatureLength + TransactionSizeCalculator.pubKeyLength - case .p2pk: sigScriptLength = TransactionSizeCalculator.signatureLength - case .p2wpkhSh: sigScriptLength = TransactionSizeCalculator.p2wpkhShLength - default: sigScriptLength = 0 - } - let inputTxSize: Int = 32 + 4 + 1 + sigScriptLength + 4 // PreviousOutputHex + InputIndex + sigLength + sigScript + sequence - return inputTxSize - } - - public func witnessSize(type: ScriptType) -> Int { // in vbytes - if type.witness { - return TransactionSizeCalculator.witnessData - } - return TransactionSizeCalculator.legacyWitnessData - } - - public func toBytes(fee: Int) -> Int { - return fee / 4 + (fee % 4 == 0 ? 0 : 1) - } - -} diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionSyncer.swift b/BitcoinCore/BitcoinCore/Transactions/TransactionSyncer.swift deleted file mode 100644 index 211aea5c..00000000 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionSyncer.swift +++ /dev/null @@ -1,79 +0,0 @@ -import Foundation - -public class TransactionSyncer { - private let storage: IStorage - private let transactionProcessor: ITransactionProcessor - private let addressManager: IAddressManager - private let bloomFilterManager: IBloomFilterManager - private let maxRetriesCount: Int - private let retriesPeriod: Double // seconds - private let totalRetriesPeriod: Double // seconds - - init(storage: IStorage, processor: ITransactionProcessor, addressManager: IAddressManager, bloomFilterManager: IBloomFilterManager, - maxRetriesCount: Int = 3, retriesPeriod: Double = 60, totalRetriesPeriod: Double = 60 * 60 * 24) { - self.storage = storage - self.transactionProcessor = processor - self.addressManager = addressManager - self.bloomFilterManager = bloomFilterManager - self.maxRetriesCount = maxRetriesCount - self.retriesPeriod = retriesPeriod - self.totalRetriesPeriod = totalRetriesPeriod - } - -} - -extension TransactionSyncer: ITransactionSyncer { - - public func pendingTransactions() -> [FullTransaction] { - return storage.newTransactions() - .filter { transaction in - if let sentTransaction = storage.sentTransaction(byHash: transaction.dataHash) { - return sentTransaction.retriesCount < self.maxRetriesCount && - sentTransaction.lastSendTime < CACurrentMediaTime() - self.retriesPeriod && - sentTransaction.firstSendTime > CACurrentMediaTime() - self.totalRetriesPeriod - } else { - return true - } - } - .map { FullTransaction(header: $0, inputs: self.storage.inputs(transactionHash: $0.dataHash), outputs: self.storage.outputs(transactionHash: $0.dataHash)) } - } - - public func handle(sentTransaction transaction: FullTransaction) { - guard let transaction = storage.newTransaction(byHash: transaction.header.dataHash) else { - return - } - - if let sentTransaction = storage.sentTransaction(byHash: transaction.dataHash) { - sentTransaction.lastSendTime = CACurrentMediaTime() - sentTransaction.retriesCount = sentTransaction.retriesCount + 1 - storage.update(sentTransaction: sentTransaction) - } else { - storage.add(sentTransaction: SentTransaction(dataHash: transaction.dataHash)) - } - } - - public func handle(transactions: [FullTransaction]) { - guard !transactions.isEmpty else { - return - } - - var needToUpdateBloomFilter = false - - do { - try self.transactionProcessor.processReceived(transactions: transactions, inBlock: nil, skipCheckBloomFilter: false) - } catch _ as BloomFilterManager.BloomFilterExpired { - needToUpdateBloomFilter = true - } catch { - } - - if needToUpdateBloomFilter { - try? addressManager.fillGap() - bloomFilterManager.regenerateBloomFilter() - } - } - - public func shouldRequestTransaction(hash: Data) -> Bool { - return !storage.relayedTransactionExists(byHash: hash) - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Managers/BloomFilterManagerTests.swift b/BitcoinCore/BitcoinCoreTests/Managers/BloomFilterManagerTests.swift deleted file mode 100644 index c04fad4f..00000000 --- a/BitcoinCore/BitcoinCoreTests/Managers/BloomFilterManagerTests.swift +++ /dev/null @@ -1,204 +0,0 @@ -import Quick -import Nimble -import XCTest -import Cuckoo -import HSHDWalletKit -@testable import BitcoinCore - -class BloomFilterManagerTests: QuickSpec { - override func spec() { - let mockStorage = MockIStorage() - let mockFactory = MockIFactory() - let mockBloomFilterManagerDelegate = MockIBloomFilterManagerDelegate() - - let bloomFilter = BloomFilter(elements: [Data(from: 9999999)]) - let lastBlock = TestData.checkpointBlock - - var manager: BloomFilterManager! - - beforeEach { - stub(mockBloomFilterManagerDelegate) { mock in - when(mock.bloomFilterUpdated(bloomFilter: any())).thenDoNothing() - } - stub(mockFactory) { mock in - when(mock).bloomFilter(withElements: any()).thenReturn(bloomFilter) - } - stub(mockStorage) { mock in - when(mock.lastBlock.get).thenReturn(lastBlock) - } - - manager = BloomFilterManager(storage: mockStorage, factory: mockFactory) - manager.delegate = mockBloomFilterManagerDelegate - } - - afterEach { - reset(mockStorage, mockFactory, mockBloomFilterManagerDelegate) - - manager = nil - } - - describe("#regenerateBloomFilter") { - context("when has publicKeys") { - let keys = [ - getPublicKey(withIndex: 0, chain: .external), - getPublicKey(withIndex: 0, chain: .internal), - getPublicKey(withIndex: 1, chain: .external), - ] - - beforeEach { - stub(mockStorage) { mock in - when(mock.publicKeys()).thenReturn(keys) - when(mock.outputsWithPublicKeys()).thenReturn([]) - } - } - - it("adds keys to bloom filter") { - manager.regenerateBloomFilter() - - var expectedElements: [Data] = [] - for key in keys { - expectedElements.append(key.keyHash) - expectedElements.append(key.raw) - expectedElements.append(key.scriptHashForP2WPKH) - } - - verify(mockFactory).bloomFilter(withElements: equal(to: expectedElements)) - verify(mockBloomFilterManagerDelegate).bloomFilterUpdated(bloomFilter: equal(to: bloomFilter, equalWhen: { $0.filter == $1.filter })) - } - } - - context("when has outputs with publicKeys") { - let output1 = TestData.p2wpkhTransaction.outputs[0] - output1.transactionHash = Data(repeating: 0, count: 32) - - beforeEach { - stub(mockStorage) { mock in - when(mock.publicKeys()).thenReturn([]) - } - } - - context("when output is not spent") { - beforeEach { - stub(mockStorage) { mock in - when(mock.outputsWithPublicKeys()).thenReturn([OutputWithPublicKey(output: output1, publicKey: TestData.pubKey(), spendingInput: nil, spendingBlockHeight: nil)]) - } - } - - it("adds outputs to bloom filter") { - manager.regenerateBloomFilter() - - let expectedElements = [output1.transactionHash + self.byteArrayLittleEndian(int: output1.index)] - - verify(mockFactory).bloomFilter(withElements: equal(to: expectedElements)) - verify(mockBloomFilterManagerDelegate).bloomFilterUpdated(bloomFilter: equal(to: bloomFilter, equalWhen: { $0.filter == $1.filter })) - } - } - - context("when output is spent") { - let input = TestData.p2pkhTransaction.inputs[0] - input.previousOutputTxHash = output1.transactionHash - input.previousOutputIndex = output1.index - - context("when spending transaction is in mempool") { - beforeEach { - stub(mockStorage) { mock in - when(mock.outputsWithPublicKeys()).thenReturn([OutputWithPublicKey(output: output1, publicKey: TestData.pubKey(), spendingInput: input, spendingBlockHeight: nil)]) - } - } - - it("adds output to bloom filter") { - manager.regenerateBloomFilter() - - let expectedElements = [output1.transactionHash + self.byteArrayLittleEndian(int: output1.index)] - - verify(mockFactory).bloomFilter(withElements: equal(to: expectedElements)) - verify(mockBloomFilterManagerDelegate).bloomFilterUpdated(bloomFilter: equal(to: bloomFilter, equalWhen: { $0.filter == $1.filter })) - } - } - - context("when spending transaction is in block") { - context("when block is not far enough in history") { - beforeEach { - stub(mockStorage) { mock in - when(mock.outputsWithPublicKeys()).thenReturn([OutputWithPublicKey(output: output1, publicKey: TestData.pubKey(), spendingInput: input, spendingBlockHeight: lastBlock.height - 98)]) - } - } - - it("adds output to bloom filter") { - manager.regenerateBloomFilter() - - let expectedElements = [output1.transactionHash + self.byteArrayLittleEndian(int: output1.index)] - - verify(mockFactory).bloomFilter(withElements: equal(to: expectedElements)) - verify(mockBloomFilterManagerDelegate).bloomFilterUpdated(bloomFilter: equal(to: bloomFilter, equalWhen: { $0.filter == $1.filter })) - } - } - - context("when block is far enough") { - beforeEach { - stub(mockStorage) { mock in - when(mock.outputsWithPublicKeys()).thenReturn([OutputWithPublicKey(output: output1, publicKey: TestData.pubKey(), spendingInput: input, spendingBlockHeight: lastBlock.height - 100)]) - } - } - - it("doesn't add output to bloom filter") { - manager.regenerateBloomFilter() - - verify(mockFactory, never()).bloomFilter(withElements: any()) - verify(mockBloomFilterManagerDelegate, never()).bloomFilterUpdated(bloomFilter: any()) - } - } - } - } - } - - context("when no elements") { - it("doesn't trigger events") { - stub(mockStorage) { mock in - when(mock.publicKeys()).thenReturn([]) - when(mock.outputsWithPublicKeys()).thenReturn([]) - } - - manager.regenerateBloomFilter() - verify(mockFactory, never()).bloomFilter(withElements: any()) - verify(mockBloomFilterManagerDelegate, never()).bloomFilterUpdated(bloomFilter: any()) - } - } - - context("when no new element") { - it("doesn't trigger events") { - stub(mockStorage) { mock in - when(mock.publicKeys()).thenReturn([self.getPublicKey(withIndex: 0, chain: .external)]) - when(mock.outputsWithPublicKeys()).thenReturn([]) - } - - manager.regenerateBloomFilter() - verify(mockBloomFilterManagerDelegate).bloomFilterUpdated(bloomFilter: any()) - reset(mockBloomFilterManagerDelegate) - stub(mockBloomFilterManagerDelegate) { mock in - when(mock.bloomFilterUpdated(bloomFilter: any())).thenDoNothing() - } - - manager.regenerateBloomFilter() - verify(mockBloomFilterManagerDelegate, never()).bloomFilterUpdated(bloomFilter: any()) - } - } - } - } - - private func getPublicKey(withIndex index: Int, chain: HDWallet.Chain) -> PublicKey { - let hdWallet = HDWallet(seed: Data(), coinType: UInt32(1), xPrivKey: UInt32(0x04358394), xPubKey: UInt32(0x043587cf)) - let hdPrivKeyData = try! hdWallet.privateKeyData(account: 0, index: index, external: chain == .external) - return PublicKey(withAccount: 0, index: index, external: chain == .external, hdPublicKeyData: hdPrivKeyData) - } - - private func byteArrayLittleEndian(int: Int) -> [UInt8] { - return [ - UInt8(int & 0x000000FF), - UInt8((int & 0x0000FF00) >> 8), - UInt8((int & 0x00FF0000) >> 16), - UInt8((int & 0xFF000000) >> 24) - ] - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Managers/InitialSync/BlockDiscoveryBatchTest.swift b/BitcoinCore/BitcoinCoreTests/Managers/InitialSync/BlockDiscoveryBatchTest.swift deleted file mode 100644 index 42ce367d..00000000 --- a/BitcoinCore/BitcoinCoreTests/Managers/InitialSync/BlockDiscoveryBatchTest.swift +++ /dev/null @@ -1,77 +0,0 @@ -import XCTest -import Cuckoo -import RxSwift -import RxBlocking - -@testable import BitcoinCore - - -class BlockDiscoveryBatchTest: XCTestCase { - - private var mockNetwork: MockINetwork! - private var mockWallet: MockIHDWallet! - private var mockBlockHashFetcher: MockIBlockHashFetcher! - - private var blockDiscovery: BlockDiscoveryBatch! - - private let publicKeys = [PublicKey(withAccount: 0, index: 0, external: true, hdPublicKeyData: Data()), - PublicKey(withAccount: 0, index: 1, external: true, hdPublicKeyData: Data()), - PublicKey(withAccount: 0, index: 2, external: true, hdPublicKeyData: Data()), - PublicKey(withAccount: 0, index: 3, external: true, hdPublicKeyData: Data()), - PublicKey(withAccount: 0, index: 4, external: true, hdPublicKeyData: Data()), - ] - - override func setUp() { - super.setUp() - - mockNetwork = MockINetwork() - mockWallet = MockIHDWallet() - mockBlockHashFetcher = MockIBlockHashFetcher() - - stub(mockWallet) {mock in - when(mock.gapLimit.get).thenReturn(3) - } - - stub(mockNetwork) {mock in - when(mock.lastCheckpointBlock.get).thenReturn(TestData.checkpointBlock) - } - - blockDiscovery = BlockDiscoveryBatch(network: mockNetwork, wallet: mockWallet, blockHashFetcher: mockBlockHashFetcher, logger: nil) - } - - override func tearDown() { - mockWallet = nil - mockNetwork = nil - mockBlockHashFetcher = nil - - blockDiscovery = nil - - super.tearDown() - } - - func testFetchFromApi() { - stub(mockWallet) { mock in - for i in 0..<5 { - when(mock.publicKey(account: 0, index: i, external: true)).thenReturn(publicKeys[i]) - } - } - - let lastUsedIndex = 1 - let blockHash = BlockHash(headerHashReversedHex: "1234", height: 1234, sequence: 0)! - - stub(mockBlockHashFetcher) { mock in - when(mock.getBlockHashes(publicKeys: equal(to: [publicKeys[0], publicKeys[1], publicKeys[2]]))).thenReturn(Observable.just(([blockHash], lastUsedIndex))) - when(mock.getBlockHashes(publicKeys: equal(to: [publicKeys[3], publicKeys[4]]))).thenReturn(Observable.just(([], -1))) - } - - let resultObservable = blockDiscovery.discoverBlockHashes(account: 0, external: true) - do{ - let result = try resultObservable.toBlocking().first() - XCTAssertEqual(publicKeys, result!.0) - XCTAssertEqual([blockHash], result!.1) - } catch { - XCTFail("Catch error!") - } - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputSelectorSingleNoChangeTests.swift b/BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputSelectorSingleNoChangeTests.swift deleted file mode 100644 index 928005f0..00000000 --- a/BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputSelectorSingleNoChangeTests.swift +++ /dev/null @@ -1,62 +0,0 @@ -import XCTest -import Quick -import Nimble -import Cuckoo -@testable import BitcoinCore - -class UnspentOutputSelectorSingleNoChangeTests: QuickSpec { - static let feeRate = 1 - - override func spec() { - let mockTransactionSizeCalculator = MockITransactionSizeCalculator() - let mockUnspentOutputProvider = MockIUnspentOutputProvider() - var selector: UnspentOutputSelectorSingleNoChange! - - let outputs = [TestData.unspentOutput(output: Output(withValue: 1000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), - TestData.unspentOutput(output: Output(withValue: 2000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), - TestData.unspentOutput(output: Output(withValue: 4000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), - TestData.unspentOutput(output: Output(withValue: 8000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), - TestData.unspentOutput(output: Output(withValue: 16000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())) - ] - - beforeEach { - stub(mockTransactionSizeCalculator) { mock in - when(mock.inputSize(type: any())).thenReturn(10) - when(mock.outputSize(type: any())).thenReturn(2) - when(mock.transactionSize(inputs: any(), outputScriptTypes: any())).thenReturn(100) - } - stub(mockUnspentOutputProvider) { mock in - when(mock.allUnspentOutputs.get).thenReturn(outputs) - } - selector = UnspentOutputSelectorSingleNoChange(calculator: mockTransactionSizeCalculator, provider: mockUnspentOutputProvider) - } - - afterEach { - reset(mockTransactionSizeCalculator, mockUnspentOutputProvider) - selector = nil - } - - context("select exactly value receiver pay") { - it("selects exactly output") { - let testBlock: ((Int, Int, Int, Bool, UnspentOutput) -> ()) = { value, feeRate, fee, senderPay, unspentOutput in - do { - let selectedOutputs = try selector.select(value: value, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: senderPay) - expect(selectedOutputs.unspentOutputs).to(equal([unspentOutput])) - expect(selectedOutputs.totalValue).to(equal(unspentOutput.output.value)) - expect(selectedOutputs.fee).to(equal(fee)) - expect(selectedOutputs.addChangeOutput).to(equal(false)) - } catch { - XCTFail("Unexpected error! \(error)") - } - } - testBlock(4000, UnspentOutputSelectorSingleNoChangeTests.feeRate, 100, false, outputs[2]) - testBlock(4000 - 10, UnspentOutputSelectorSingleNoChangeTests.feeRate, 100, false, outputs[2]) - - testBlock(3900, UnspentOutputSelectorSingleNoChangeTests.feeRate, 100, true, outputs[2]) - testBlock(4000 - 100 - 10, UnspentOutputSelectorSingleNoChangeTests.feeRate, 100 + 10, true, outputs[2]) - } - } - } - -} - diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/Builder/ScriptBuilderTests.swift b/BitcoinCore/BitcoinCoreTests/Transactions/Builder/ScriptBuilderTests.swift deleted file mode 100644 index 199dfe01..00000000 --- a/BitcoinCore/BitcoinCoreTests/Transactions/Builder/ScriptBuilderTests.swift +++ /dev/null @@ -1,45 +0,0 @@ -import XCTest -import Cuckoo -@testable import BitcoinCore - -class ScriptBuilderTests: XCTestCase { - - private var builder: ScriptBuilder! - - override func setUp() { - super.setUp() - - builder = ScriptBuilder() - } - - override func tearDown() { - builder = nil - - super.tearDown() - } - - func testP2PKH() { - let data = Data(hex: "76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac")! - let pubKey = Data(hex: "cbc20a7664f2f69e5355aa427045bc15e7c6c772")! - let address = LegacyAddress(type: .pubKeyHash, keyHash: pubKey, base58: "") - do { - let test = try builder.lockingScript(for: address) - XCTAssertEqual(test, data) - } catch { - XCTFail("\(error) Exception Thrown") - } - } - - func testP2SH() { - let data = Data(hex: "a9142a02dfd19c9108ad48878a01bfe53deaaf30cca487")! - let pubKey = Data(hex: "2a02dfd19c9108ad48878a01bfe53deaaf30cca4")! - let address = LegacyAddress(type: .scriptHash, keyHash: pubKey, base58: "") - do { - let test = try builder.lockingScript(for: address) - XCTAssertEqual(test, data) - } catch { - XCTFail("\(error) Exception Thrown") - } - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/Builder/TransactionBuilderTests.swift b/BitcoinCore/BitcoinCoreTests/Transactions/Builder/TransactionBuilderTests.swift deleted file mode 100644 index 99f2fbc0..00000000 --- a/BitcoinCore/BitcoinCoreTests/Transactions/Builder/TransactionBuilderTests.swift +++ /dev/null @@ -1,359 +0,0 @@ -import XCTest -import Cuckoo -@testable import BitcoinCore - -public class SegWitAddress: Address, Equatable { - public let type: AddressType - public let keyHash: Data - public let stringValue: String - public let version: UInt8 - - public var scriptType: ScriptType { - switch type { - case .pubKeyHash: return .p2wpkh - case .scriptHash: return .p2wsh - } - } - - public init(type: AddressType, keyHash: Data, bech32: String, version: UInt8) { - self.type = type - self.keyHash = keyHash - self.stringValue = bech32 - self.version = version - } - - static public func ==(lhs: SegWitAddress, rhs: T) -> Bool { - guard let rhs = rhs as? SegWitAddress else { - return false - } - return lhs.type == rhs.type && lhs.keyHash == rhs.keyHash && lhs.version == rhs.version - } -} - -class TransactionBuilderTests: XCTestCase { - - private var mockUnspentOutputSelector: MockIUnspentOutputSelector! - private var mockUnspentOutputProvider: MockIUnspentOutputProvider! - private var mockAddressManager: MockIAddressManager! - private var mockAddressConverter: MockIAddressConverter! - private var mockInputSigner: MockIInputSigner! - private var mockScriptBuilder: MockIScriptBuilder! - private var mockFactory: MockIFactory! - - private var transactionBuilder: TransactionBuilder! - - private var unspentOutputs: SelectedUnspentOutputInfo! - private var previousTransaction: FullTransaction! - private var transaction: Transaction! - private var toOutputPKH: Output! - private var toOutputWPKH: Output! - private var toOutputSH: Output! - private var changeOutput: Output! - private var inputToSign: InputToSign! - private var totalInputValue: Int! - private var value: Int! - private var feeRate: Int! - private var fee: Int! - private var changePubKey: PublicKey! - private var changePubKeyAddress: String! - private var toAddressPKH: String! - private var toAddressSH: String! - private var toAddressWPKH: String! - private var signature = Data(hex: "0000000000000000000111111111111222222222222")! - private var signatureScript = Data(hex: "150000000000000000000111111111111222222222222")! - - override func setUp() { - super.setUp() - - mockUnspentOutputSelector = MockIUnspentOutputSelector() - mockUnspentOutputProvider = MockIUnspentOutputProvider() - mockAddressManager = MockIAddressManager() - mockAddressConverter = MockIAddressConverter() - mockInputSigner = MockIInputSigner() - mockScriptBuilder = MockIScriptBuilder() - mockFactory = MockIFactory() - - transactionBuilder = TransactionBuilder(unspentOutputSelector: mockUnspentOutputSelector, unspentOutputProvider: mockUnspentOutputProvider, addressManager: mockAddressManager, addressConverter: mockAddressConverter, inputSigner: mockInputSigner, scriptBuilder: mockScriptBuilder, factory: mockFactory) - - changePubKey = TestData.pubKey() - changePubKeyAddress = "Rsfz3aRmCwTe2J8pSWSYRNYmweJ" - - toAddressPKH = "mzwSXvtPs7MFbW2ysNA4Gw3P2KjrcEWaE5" - toAddressSH = "2MyQWMrsLsqAMSUeusduAzN6pWuH2V27ykE" - toAddressWPKH = "bcrt1qsay3z5rn44v6du6c0u0eu352mm0sz3el0f0cs2" - - previousTransaction = TestData.p2pkhTransaction - - unspentOutputs = SelectedUnspentOutputInfo( - unspentOutputs: [UnspentOutput(output: previousTransaction.outputs[0], publicKey: TestData.pubKey(), transaction: previousTransaction.header, blockHeight: 1000)], - totalValue: previousTransaction.outputs[0].value, fee: 1008, addChangeOutput: true - ) - totalInputValue = unspentOutputs.unspentOutputs[0].output.value - value = 10782000 - feeRate = 6 - fee = 1008 - - transaction = Transaction(version: 1, timestamp: 0) - inputToSign = InputToSign( - input: Input(withPreviousOutputTxHash: previousTransaction.header.dataHash, previousOutputIndex: unspentOutputs.unspentOutputs[0].output.index, script: Data(), sequence: 0), - previousOutput: previousTransaction.outputs[0], previousOutputPublicKey: TestData.pubKey() - ) - toOutputPKH = Output(withValue: value - fee, index: 0, lockingScript: Data(), type: .p2pkh, address: toAddressPKH, keyHash: nil) - toOutputWPKH = Output(withValue: value - fee, index: 0, lockingScript: Data(), type: .p2wpkh, address: toAddressWPKH, keyHash: nil) - toOutputSH = Output(withValue: value - fee, index: 0, lockingScript: Data(), type: .p2sh, address: toAddressSH, keyHash: nil) - changeOutput = Output(withValue: totalInputValue - value, index: 1, lockingScript: Data(), type: .p2pkh, keyHash: changePubKey.keyHash) - - stub(mockUnspentOutputSelector) { mock in - when(mock.select(value: any(), feeRate: any(), outputScriptType: any(), changeType: any(), senderPay: any())).thenReturn(unspentOutputs) - } - - stub(mockUnspentOutputProvider) { mock in - when(mock.allUnspentOutputs.get).thenReturn(unspentOutputs.unspentOutputs) - } - - stub(mockAddressManager) { mock in - when(mock.changePublicKey()).thenReturn(changePubKey) - } - - stub(mockInputSigner) { mock in - when(mock.sigScriptData(transaction: any(), inputsToSign: any(), outputs: any(), index: any())).thenReturn([signature]) - } - - stub(mockAddressConverter) { mock in - when(mock.convert(address: toAddressPKH)).thenReturn(LegacyAddress(type: .pubKeyHash, keyHash: Data(hex: "d50bf226c9ff3bcf06f13d8ca129f24bedeef594")!, base58: "mzwSXvtPs7MFbW2ysNA4Gw3P2KjrcEWaE5")) - when(mock.convert(address: toAddressSH)).thenReturn(LegacyAddress(type: .scriptHash, keyHash: Data(hex: "43922a3f1dc4569f9eccce9a71549d5acabbc0ca")!, base58: toAddressSH)) - when(mock.convert(address: toAddressWPKH)).thenReturn(SegWitAddress(type: .pubKeyHash, keyHash: Data(hex: "43922a3f1dc4569f9eccce9a71549d5acabbc0ca")!, bech32: "bcrt1qsay3z5rn44v6du6c0u0eu352mm0sz3el0f0cs2", version: 0)) - when(mock.convert(address: changePubKeyAddress)).thenReturn(LegacyAddress(type: .pubKeyHash, keyHash: changePubKey.keyHash, base58: changePubKeyAddress)) - when(mock.convert(keyHash: equal(to: changePubKey.keyHash), type: equal(to: .p2pkh))).thenReturn(LegacyAddress(type: .pubKeyHash, keyHash: changePubKey.keyHash, base58: changePubKeyAddress)) - // when(mock.convert(address: any())).thenReturn(Address(type: .pubKeyHash, keyHash: Data(), base58: "")) - } - - stub(mockScriptBuilder) { mock in - when(mock.lockingScript(for: any())).thenReturn(Data()) - } - - stub(mockFactory) { mock in - when(mock.transaction(version: any(), lockTime: any())).thenReturn(transaction) - when(mock.inputToSign(withPreviousOutput: any(), script: any(), sequence: any())).thenReturn(inputToSign) - when(mock.output(withValue: any(), index: any(), lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: toAddressPKH), keyHash: any(), publicKey: any())).thenReturn(toOutputPKH) - when(mock.output(withValue: any(), index: any(), lockingScript: any(), type: equal(to: ScriptType.p2sh), address: equal(to: toAddressSH), keyHash: any(), publicKey: any())).thenReturn(toOutputSH) - when(mock.output(withValue: any(), index: any(), lockingScript: any(), type: equal(to: ScriptType.p2wpkh), address: equal(to: toAddressWPKH), keyHash: any(), publicKey: any())).thenReturn(toOutputWPKH) - when(mock.output(withValue: any(), index: any(), lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: changePubKeyAddress), keyHash: any(), publicKey: any())).thenReturn(changeOutput) - } - } - - override func tearDown() { - unspentOutputs = nil - mockUnspentOutputSelector = nil - mockUnspentOutputProvider = nil - mockAddressConverter = nil - mockInputSigner = nil - mockFactory = nil - transactionBuilder = nil - changePubKey = nil - toAddressPKH = nil - toAddressSH = nil - value = nil - feeRate = nil - fee = nil - - super.tearDown() - } - - func testFee_AddressGiven() { - let resultFee = try! transactionBuilder.fee(for: value, feeRate: feeRate, senderPay: false, address: toAddressPKH) - XCTAssertEqual(resultFee, 546) - } - - func testFee_AddressGiven_Error() { - stub(mockAddressConverter) { mock in - when(mock.convert(address: toAddressPKH)).thenThrow(BitcoinCoreErrors.AddressConversion.invalidAddressLength) - } - - do { - let _ = try transactionBuilder.fee(for: value, feeRate: feeRate, senderPay: false, address: toAddressPKH) - } catch let error as BitcoinCoreErrors.AddressConversion { - XCTAssertEqual(error, BitcoinCoreErrors.AddressConversion.invalidAddressLength) - } catch let error { - XCTFail(error.localizedDescription) - } - } - - func testFee_AddressNotGiven_Error() { - let resultFee = try! transactionBuilder.fee(for: value, feeRate: feeRate, senderPay: false) - XCTAssertEqual(resultFee, fee) - } - - func testBuildTransaction_P2PKH() { - let resultTx = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressPKH) - - XCTAssertNotEqual(resultTx.header.dataHash, Data()) - XCTAssertEqual(resultTx.header.status, .new) - XCTAssertEqual(resultTx.header.isMine, true) - XCTAssertEqual(resultTx.header.segWit, false) - XCTAssertEqual(resultTx.inputs.count, 1) - XCTAssertEqual(resultTx.inputs[0].signatureScript, signatureScript) - XCTAssertEqual(resultTx.inputs[0].witnessData.count, 0) - XCTAssertEqual(resultTx.inputs[0].previousOutputTxHash, unspentOutputs.unspentOutputs[0].output.transactionHash) - XCTAssertEqual(resultTx.inputs[0].previousOutputIndex, unspentOutputs.unspentOutputs[0].output.index) - XCTAssertEqual(resultTx.outputs.count, 2) - XCTAssertEqual(resultTx.outputs[0].address, toAddressPKH) - XCTAssertEqual(resultTx.outputs[0].value, value - fee) - XCTAssertEqual(resultTx.outputs[1].keyHash, changePubKey.keyHash) - XCTAssertEqual(resultTx.outputs[1].value, unspentOutputs.unspentOutputs[0].output.value - value) - - verify(mockFactory).output(withValue: value - fee, index: 0, lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: toAddressPKH), keyHash: any(), publicKey: any()) - verify(mockFactory).output(withValue: unspentOutputs.unspentOutputs[0].output.value - value, index: 1, lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: changePubKeyAddress), keyHash: any(), publicKey: any()) - } - -// todo: Extrect build witness transaction data to bitcoinKit chain -// func testBuildTransaction_P2WPKH() { -// let previousTransaction = TestData.p2wpkhTransaction -// -// unspentOutputs = SelectedUnspentOutputInfo( -// unspentOutputs: [UnspentOutput(output: previousTransaction.outputs[0], publicKey: TestData.pubKey(), transaction: previousTransaction.header, blockHeight: nil)], -// totalValue: previousTransaction.outputs[0].value, fee: 1008, addChangeOutput: true -// ) -// totalInputValue = unspentOutputs.unspentOutputs[0].output.value -// value = 10782000 -// feeRate = 6 -// fee = 1008 -// inputToSign = InputToSign( -// input: Input(withPreviousOutputTxReversedHex: previousTransaction.header.dataHashReversedHex, previousOutputIndex: unspentOutputs.unspentOutputs[0].output.index, script: Data(), sequence: 0), -// previousOutput: previousTransaction.outputs[0], previousOutputPublicKey: TestData.pubKey() -// ) -// -// stub(mockFactory) { mock in -// when(mock.inputToSign(withPreviousOutput: any(), script: any(), sequence: any())).thenReturn(inputToSign) -// } -// stub(mockUnspentOutputSelector) { mock in -// when(mock.select(value: any(), feeRate: any(), outputScriptType: any(), changeType: any(), senderPay: any(), unspentOutputs: any())).thenReturn(unspentOutputs) -// } -// -// stub(mockUnspentOutputProvider) { mock in -// when(mock.allUnspentOutputs.get).thenReturn(unspentOutputs.unspentOutputs) -// } -// -// let resultTx = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressWPKH) -// -// XCTAssertNotEqual(resultTx.header.dataHashReversedHex, "") -// XCTAssertEqual(resultTx.header.status, .new) -// XCTAssertEqual(resultTx.header.isMine, true) -// XCTAssertEqual(resultTx.header.segWit, true) -// XCTAssertEqual(resultTx.inputs.count, 1) -// XCTAssertEqual(resultTx.inputs[0].signatureScript.count, 0) -// XCTAssertEqual(resultTx.inputs[0].witnessData.count, 1) -// XCTAssertEqual(resultTx.inputs[0].witnessData[0], signature) -// XCTAssertEqual(resultTx.inputs[0].previousOutputTxReversedHex, unspentOutputs.unspentOutputs[0].output.transactionHash) -// XCTAssertEqual(resultTx.inputs[0].previousOutputIndex, unspentOutputs.unspentOutputs[0].output.index) -// XCTAssertEqual(resultTx.outputs.count, 2) -// XCTAssertEqual(resultTx.outputs[0].address, toAddressWPKH) -// XCTAssertEqual(resultTx.outputs[1].keyHash, changePubKey.keyHash) -// -// verify(mockFactory).output(withValue: value - fee, index: 0, lockingScript: any(), type: equal(to: ScriptType.p2wpkh), address: equal(to: toAddressWPKH), keyHash: any(), publicKey: any()) -// verify(mockFactory).output(withValue: unspentOutputs.unspentOutputs[0].output.value - value, index: 1, lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: changePubKeyAddress), keyHash: any(), publicKey: any()) -// -// } - - func testBuildTransaction_P2SH() { - let resultTx = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressSH) - - XCTAssertNotEqual(resultTx.header.dataHash, Data()) - XCTAssertEqual(resultTx.header.status, .new) - XCTAssertEqual(resultTx.header.isMine, true) - XCTAssertEqual(resultTx.inputs.count, 1) - XCTAssertEqual(resultTx.inputs[0].previousOutputTxHash, unspentOutputs.unspentOutputs[0].output.transactionHash) - XCTAssertEqual(resultTx.inputs[0].previousOutputIndex, unspentOutputs.unspentOutputs[0].output.index) - XCTAssertEqual(resultTx.outputs.count, 2) - XCTAssertEqual(resultTx.outputs[0].address, toAddressSH) - XCTAssertEqual(resultTx.outputs[0].value, value - fee) - XCTAssertEqual(resultTx.outputs[1].keyHash, changePubKey.keyHash) - - verify(mockFactory).output(withValue: value - fee, index: 0, lockingScript: any(), type: equal(to: ScriptType.p2sh), address: equal(to: toAddressSH), keyHash: any(), publicKey: any()) - verify(mockFactory).output(withValue: unspentOutputs.unspentOutputs[0].output.value - value, index: 1, lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: changePubKeyAddress), keyHash: any(), publicKey: any()) - } - - func testBuildTransactionSenderPay() { - _ = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: true, toAddress: toAddressPKH) - - verify(mockFactory).output(withValue: value, index: 0, lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: toAddressPKH), keyHash: any(), publicKey: any()) - verify(mockFactory).output(withValue: unspentOutputs.unspentOutputs[0].output.value - value - fee, index: 1, lockingScript: any(), type: equal(to: ScriptType.p2pkh), address: equal(to: changePubKeyAddress), keyHash: any(), publicKey: any()) - } - - func testBuildTransaction_WithoutChangeOutput() { - value = totalInputValue - unspentOutputs = SelectedUnspentOutputInfo(unspentOutputs: unspentOutputs.unspentOutputs, totalValue: unspentOutputs.totalValue, fee: unspentOutputs.fee, addChangeOutput: false) - stub(mockUnspentOutputSelector) { mock in - when(mock.select(value: any(), feeRate: any(), outputScriptType: any(), changeType: any(), senderPay: any())).thenReturn(unspentOutputs) - } - - let resultTx = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressPKH) - - XCTAssertEqual(resultTx.inputs.count, 1) - XCTAssertEqual(resultTx.inputs[0].previousOutputTxHash, unspentOutputs.unspentOutputs[0].output.transactionHash) - XCTAssertEqual(resultTx.inputs[0].previousOutputIndex, unspentOutputs.unspentOutputs[0].output.index) - XCTAssertEqual(resultTx.outputs.count, 1) - XCTAssertEqual(resultTx.outputs[0].address, toAddressPKH) - verify(mockFactory).output(withValue: Int(value - fee), index: 0, lockingScript: equal(to: Data()), type: equal(to: ScriptType.p2pkh), address: equal(to: toAddressPKH), keyHash: any(), publicKey: any()) - } - - func testBuildTransaction_ChangeNotAddedForDust() { - value = totalInputValue - TransactionSizeCalculator().outputSize(type: .p2pkh) * feeRate - unspentOutputs = SelectedUnspentOutputInfo(unspentOutputs: unspentOutputs.unspentOutputs, totalValue: unspentOutputs.totalValue, fee: unspentOutputs.fee, addChangeOutput: false) - stub(mockUnspentOutputSelector) { mock in - when(mock.select(value: any(), feeRate: any(), outputScriptType: any(), changeType: any(), senderPay: any())).thenReturn(unspentOutputs) - } - - let resultTx = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressPKH) - - XCTAssertEqual(resultTx.inputs.count, 1) - XCTAssertEqual(resultTx.inputs[0].previousOutputTxHash, unspentOutputs.unspentOutputs[0].output.transactionHash) - XCTAssertEqual(resultTx.inputs[0].previousOutputIndex, unspentOutputs.unspentOutputs[0].output.index) - XCTAssertEqual(resultTx.outputs.count, 1) - XCTAssertEqual(resultTx.outputs[0].address, toAddressPKH) - verify(mockFactory).output(withValue: Int(value - fee), index: 0, lockingScript: equal(to: Data()), type: equal(to: ScriptType.p2pkh), address: equal(to: toAddressPKH), keyHash: any(), publicKey: any()) - } - - func testBuildTransaction_InputsSigned() { - let sigData = [Data(hex: "000001")!, Data(hex: "000002")!] - let sigScript = Data(hex: "0300000103000002")! - - stub(mockInputSigner) { mock in - when(mock.sigScriptData(transaction: any(), inputsToSign: any(), outputs: any(), index: any())).thenReturn(sigData) - } - - let resultTx = try! transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressPKH) - XCTAssertEqual(resultTx.inputs[0].signatureScript, sigScript) - } - - func testBuildTransaction_feeMoreThanValue() { - unspentOutputs = SelectedUnspentOutputInfo( - unspentOutputs: [UnspentOutput(output: previousTransaction.outputs[0], publicKey: TestData.pubKey(), transaction: previousTransaction.header, blockHeight: nil)], - totalValue: previousTransaction.outputs[0].value, fee: value, addChangeOutput: true - ) - - do { - let _ = try transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressPKH) - } catch let error as TransactionBuilder.BuildError { - XCTAssertEqual(error, TransactionBuilder.BuildError.feeMoreThanValue) - } catch let error { - XCTFail(error.localizedDescription) - } - } - - func testBuildTransaction_noChangeAddress() { - stub(mockAddressManager) { mock in - when(mock.changePublicKey()).thenThrow(AddressManager.AddressManagerError.noUnusedPublicKey) - } - - do { - let _ = try transactionBuilder.buildTransaction(value: value, feeRate: feeRate, senderPay: false, toAddress: toAddressPKH) - XCTFail("No exception!") - } catch let error as TransactionBuilder.BuildError { - XCTAssertEqual(error, TransactionBuilder.BuildError.noChangeAddress) - } catch { - XCTFail("Unexpected exception!") - } - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionCreatorTests.swift b/BitcoinCore/BitcoinCoreTests/Transactions/TransactionCreatorTests.swift deleted file mode 100644 index 149afca4..00000000 --- a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionCreatorTests.swift +++ /dev/null @@ -1,71 +0,0 @@ -import XCTest -import Cuckoo -import Nimble -import Quick -@testable import BitcoinCore - -class TransactionCreatorTests: QuickSpec { - override func spec() { - let mockTransactionBuilder = MockITransactionBuilder() - let mockTransactionProcessor = MockITransactionProcessor() - let mockTransactionSender = MockITransactionSender() - - var transactionCreator: TransactionCreator! - let transaction = TestData.p2pkhTransaction - - afterEach { - reset(mockTransactionBuilder, mockTransactionProcessor, mockTransactionSender) - transactionCreator = nil - } - - describe("#create") { - beforeEach { - stub(mockTransactionBuilder) { mock in - when(mock.buildTransaction(value: any(), feeRate: any(), senderPay: any(), toAddress: any())).thenReturn(transaction) - } - stub(mockTransactionProcessor) { mock in - when(mock.processCreated(transaction: any())).thenDoNothing() - } - stub(mockTransactionSender) { mock in - when(mock.send(pendingTransaction: any())).thenDoNothing() - } - - transactionCreator = TransactionCreator(transactionBuilder: mockTransactionBuilder, transactionProcessor: mockTransactionProcessor, transactionSender: mockTransactionSender) - } - - context("when error") { - beforeEach { - stub(mockTransactionSender) { mock in - when(mock.verifyCanSend()).thenThrow(BitcoinCoreErrors.TransactionSendError.noConnectedPeers) - } - - try? transactionCreator.create(to: "", value: 0, feeRate: 0, senderPay: false) - } - - it("doesn't create transaction") { - verify(mockTransactionBuilder, never()).buildTransaction(value: any(), feeRate: any(), senderPay: any(), toAddress: any()) - verify(mockTransactionProcessor, never()).processCreated(transaction: any()) - } - } - - context("when success") { - beforeEach { - stub(mockTransactionSender) { mock in - when(mock.verifyCanSend()).thenDoNothing() - } - - try! transactionCreator.create(to: "", value: 0, feeRate: 0, senderPay: false) - } - - it("creates transaction") { - verify(mockTransactionBuilder).buildTransaction(value: 0, feeRate: 0, senderPay: false, toAddress: "") - verify(mockTransactionProcessor).processCreated(transaction: equal(to: transaction)) - } - - it("sends transaction") { - verify(mockTransactionSender).send(pendingTransaction: equal(to: transaction)) - } - } - } - } -} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionProcessorTests.swift b/BitcoinCore/BitcoinCoreTests/Transactions/TransactionProcessorTests.swift deleted file mode 100644 index 285e223b..00000000 --- a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionProcessorTests.swift +++ /dev/null @@ -1,484 +0,0 @@ -import XCTest -import Cuckoo -@testable import BitcoinCore - -class TransactionProcessorTests: XCTestCase { - private var mockStorage: MockIStorage! - private var mockOutputExtractor: MockITransactionExtractor! - private var mockOutputAddressExtractor: MockITransactionOutputAddressExtractor! - private var mockInputExtractor: MockITransactionExtractor! - private var mockOutputsCache: MockIOutputsCache! - private var mockAddressManager: MockIAddressManager! - private var mockBlockchainDataListener: MockIBlockchainDataListener! - - private var generatedDate: Date! - private var dateGenerator: (() -> Date)! - - private var transactionProcessor: TransactionProcessor! - - override func setUp() { - super.setUp() - - generatedDate = Date() - dateGenerator = { - return self.generatedDate - } - - mockStorage = MockIStorage() - mockOutputExtractor = MockITransactionExtractor() - mockOutputAddressExtractor = MockITransactionOutputAddressExtractor() - mockInputExtractor = MockITransactionExtractor() - mockOutputsCache = MockIOutputsCache() - mockAddressManager = MockIAddressManager() - mockBlockchainDataListener = MockIBlockchainDataListener() - - stub(mockStorage) { mock in - when(mock.transaction(byHash: any())).thenReturn(nil) - when(mock.add(transaction: any())).thenDoNothing() - when(mock.update(transaction: any())).thenDoNothing() - when(mock.update(block: any())).thenDoNothing() - } - stub(mockOutputsCache) { mock in - when(mock.add(fromOutputs: any())).thenDoNothing() - when(mock.hasOutputs(forInputs: any())).thenReturn(false) - } - stub(mockOutputExtractor) { mock in - when(mock.extract(transaction: any())).thenDoNothing() - } - stub(mockOutputAddressExtractor) { mock in - when(mock.extractOutputAddresses(transaction: any())).thenDoNothing() - } - stub(mockInputExtractor) { mock in - when(mock.extract(transaction: any())).thenDoNothing() - } - stub(mockAddressManager) { mock in - when(mock.gapShifts()).thenReturn(false) - } - stub(mockBlockchainDataListener) { mock in - when(mock.onUpdate(updated: any(), inserted: any(), inBlock: any())).thenDoNothing() - when(mock.onDelete(transactionHashes: any())).thenDoNothing() - when(mock.onInsert(block: any())).thenDoNothing() - } - - transactionProcessor = TransactionProcessor(storage: mockStorage, outputExtractor: mockOutputExtractor, inputExtractor: mockInputExtractor, outputsCache: mockOutputsCache, outputAddressExtractor: mockOutputAddressExtractor, addressManager: mockAddressManager, listener: mockBlockchainDataListener, dateGenerator: dateGenerator) - } - - override func tearDown() { - mockStorage = nil - mockOutputExtractor = nil - mockInputExtractor = nil - mockOutputsCache = nil - transactionProcessor = nil - mockBlockchainDataListener = nil - - generatedDate = nil - dateGenerator = nil - - super.tearDown() - } - - func testProcessCreated() { - let transaction = TestData.p2pkhTransaction - - try! transactionProcessor.processCreated(transaction: transaction) - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [Transaction]()), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) - verify(mockStorage).add(transaction: equal(to: transaction)) - verifyNoMoreInteractions(mockOutputAddressExtractor) - verifyNoMoreInteractions(mockInputExtractor) - } - - func testProcessCreated_isMine() { - let transaction = TestData.p2pkhTransaction - transaction.header.isMine = true - - try! transactionProcessor.processCreated(transaction: transaction) - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) - verify(mockStorage).add(transaction: equal(to: transaction)) - verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) - verify(mockInputExtractor).extract(transaction: equal(to: transaction)) - } - - func testProcessCreated_TransactionExists() { - let transaction = TestData.p2pkhTransaction - transaction.header.isMine = true - - stub(mockStorage) { mock in - when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) - } - - do { - try transactionProcessor.processCreated(transaction: transaction) - XCTFail("Expecting error") - } catch let error as TransactionCreator.CreationError { - XCTAssertEqual(error, TransactionCreator.CreationError.transactionAlreadyExists) - } catch { - XCTFail("Unexpected error") - } - - verify(mockOutputExtractor, never()).extract(transaction: any()) - verify(mockOutputsCache, never()).hasOutputs(forInputs: any()) - verify(mockBlockchainDataListener, never()).onUpdate(updated: any(), inserted: any(), inBlock: any()) - verify(mockStorage, never()).add(transaction: any()) - verify(mockOutputAddressExtractor, never()).extractOutputAddresses(transaction: any()) - verify(mockInputExtractor, never()).extract(transaction: any()) - } - - func testProcessReceived_TransactionExists() { - let transaction = TestData.p2pkhTransaction - transaction.header.status = .new - - stub(mockStorage) { mock in - when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) - } - - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - - verify(mockOutputExtractor, never()).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache, never()).hasOutputs(forInputs: any()) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transaction.header]), inserted: equal(to: []), inBlock: equal(to: nil)) - verify(mockStorage).update(transaction: equal(to: transaction.header)) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, nil) - XCTAssertEqual(transaction.header.order, 0) - } - - func testProcessReceived_SeveralMempoolTransactions() { - let transactions = self.transactions() - for transaction in transactions { - transaction.header.isMine = true - transaction.header.timestamp = 0 - transaction.header.order = 0 - } - transactions[1].header.status = .new - - stub(mockStorage) { mock in - when(mock.transaction(byHash: equal(to: transactions[1].header.dataHash))).thenReturn(transactions[1].header) - when(mock.transaction(byHash: equal(to: transactions[3].header.dataHash))).thenReturn(transactions[3].header) - } - - try! transactionProcessor.processReceived(transactions: [transactions[3], transactions[1], transactions[2], transactions[0]], inBlock: nil, skipCheckBloomFilter: false) - - verify(mockStorage).add(transaction: equal(to: transactions[0])) - verify(mockStorage).update(transaction: equal(to: transactions[1].header)) - verify(mockStorage).add(transaction: equal(to: transactions[2])) - verify(mockStorage).update(transaction: equal(to: transactions[3].header)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transactions[1].header, transactions[3].header]), inserted: equal(to: [transactions[0].header, transactions[2].header]), inBlock: equal(to: nil)) - - for (i, transaction) in transactions.enumerated() { - XCTAssertEqual(transaction.header.blockHash, nil) - XCTAssertEqual(transaction.header.status, .relayed) - XCTAssertEqual(transaction.header.order, i) - XCTAssertEqual(transaction.header.timestamp, Int(generatedDate.timeIntervalSince1970)) - } - } - - func testProcessReceivedMempool_After_Block_TransactionExists() { - let transaction = TestData.p2pkhTransaction - let block = TestData.firstBlock - transaction.header.status = .new - - stub(mockStorage) { mock in - when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) - } - - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: block, skipCheckBloomFilter: false) - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, block.headerHash) - XCTAssertEqual(transaction.header.timestamp, block.timestamp) - XCTAssertEqual(transaction.header.order, 0) - } - - func testProcessReceivedBlock_After_Block_TransactionExists() { - let transaction = TestData.p2pkhTransaction - let block = TestData.firstBlock - let nextBlock = TestData.secondBlock - transaction.header.status = .new - - stub(mockStorage) { mock in - when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) - } - - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: block, skipCheckBloomFilter: false) - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nextBlock, skipCheckBloomFilter: false) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, nextBlock.headerHash) - XCTAssertEqual(transaction.header.timestamp, nextBlock.timestamp) - XCTAssertEqual(transaction.header.order, 0) - } - - - func testProcessReceived_SeveralTransactionsInBlock() { - let transactions = self.transactions() - let block = TestData.firstBlock - - for transaction in transactions { - transaction.header.isMine = true - transaction.header.timestamp = 0 - transaction.header.order = 0 - } - transactions[1].header.status = .new - - stub(mockStorage) { mock in - when(mock.transaction(byHash: equal(to: transactions[1].header.dataHash))).thenReturn(transactions[1].header) - when(mock.transaction(byHash: equal(to: transactions[3].header.dataHash))).thenReturn(transactions[3].header) - } - - try! transactionProcessor.processReceived(transactions: [transactions[3], transactions[1], transactions[2], transactions[0]], inBlock: block, skipCheckBloomFilter: false) - - verify(mockStorage).add(transaction: equal(to: transactions[0])) - verify(mockStorage).update(transaction: equal(to: transactions[1].header)) - verify(mockStorage).add(transaction: equal(to: transactions[2])) - verify(mockStorage).update(transaction: equal(to: transactions[3].header)) - verify(mockStorage).update(block: equal(to: block)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transactions[1].header, transactions[3].header]), inserted: equal(to: [transactions[0].header, transactions[2].header]), inBlock: equal(to: block)) - - for (i, transaction) in transactions.enumerated() { - XCTAssertEqual(transaction.header.blockHash, block.headerHash) - XCTAssertEqual(transaction.header.status, .relayed) - XCTAssertEqual(transaction.header.order, i) - XCTAssertEqual(transaction.header.timestamp, block.header.timestamp) - } - } - - func testProcessReceived_TransactionNotExists_Mine() { - let transaction = TestData.p2pkhTransaction - transaction.header.isMine = true - - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) - verify(mockStorage).add(transaction: equal(to: transaction)) - verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) - verify(mockInputExtractor).extract(transaction: equal(to: transaction)) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, nil) - } - - func testProcessReceived_TransactionNotExists_NotMine() { - let transaction = TestData.p2pkhTransaction - transaction.header.isMine = false - - try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockStorage, never()).add(transaction: any()) - verifyNoMoreInteractions(mockBlockchainDataListener) - verifyNoMoreInteractions(mockOutputAddressExtractor) - verifyNoMoreInteractions(mockInputExtractor) - } - - func testProcessReceived_TransactionNotExists_Mine_GapShifts() { - let transaction = TestData.p2pkhTransaction - transaction.header.isMine = true - - stub(mockAddressManager) { mock in - when(mock.gapShifts()).thenReturn(true) - } - - do { - try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - XCTFail("Should throw exception") - } catch _ as BloomFilterManager.BloomFilterExpired { - } catch { - XCTFail("Unknown error thrown") - } - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) - verify(mockStorage).add(transaction: equal(to: transaction)) - verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) - verify(mockInputExtractor).extract(transaction: equal(to: transaction)) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, nil) - } - - func testProcessReceived_TransactionNotExists_Mine_HasUnspentOutputs() { - let transaction = TestData.p2wpkhTransaction - transaction.header.isMine = true - transaction.outputs[0].publicKeyPath = TestData.pubKey().path - - do { - try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - XCTFail("Should throw exception") - } catch _ as BloomFilterManager.BloomFilterExpired { - } catch { - XCTFail("Unknown error thrown") - } - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) - verify(mockStorage).add(transaction: equal(to: transaction)) - verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) - verify(mockInputExtractor).extract(transaction: equal(to: transaction)) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, nil) - } - - func testProcessReceived_TransactionNotExists_Mine_GapShifts_CheckBloomFilterFalse() { - let transaction = TestData.p2wpkhTransaction - transaction.header.isMine = true - transaction.outputs[0].publicKeyPath = TestData.pubKey().path - - do { - try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: true) - } catch { - XCTFail("Unknown error thrown") - } - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) - verify(mockStorage).add(transaction: equal(to: transaction)) - verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) - verify(mockInputExtractor).extract(transaction: equal(to: transaction)) - - XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) - XCTAssertEqual(transaction.header.blockHash, nil) - } - - func testProcessReceived_TransactionNotExists_NotMine_GapShifts() { - let transaction = TestData.p2pkhTransaction - transaction.header.isMine = false - - stub(mockAddressManager) { mock in - when(mock.gapShifts()).thenReturn(true) - } - - do { - try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) - } catch { - XCTFail("Shouldn't throw exception") - } - - verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) - verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) - verify(mockStorage, never()).add(transaction: any()) - verifyNoMoreInteractions(mockBlockchainDataListener) - verifyNoMoreInteractions(mockOutputAddressExtractor) - verifyNoMoreInteractions(mockInputExtractor) - } - - func testProcessReceived_TransactionNotInTopologicalOrder() { - let transactions = self.transactions() - var calledTransactions = [FullTransaction]() - - stub(mockOutputExtractor) { mock in - when(mock.extract(transaction: any())).then { transaction in - calledTransactions.append(transaction) - } - } - - for i in 0..<4 { - for j in 0..<4 { - for k in 0..<4 { - for l in 0..<4 { - if [0, 1, 2, 3].contains(where: { $0 != i && $0 != j && $0 != k && $0 != l }) { - continue - } - - calledTransactions = [] - - try! transactionProcessor.processReceived(transactions: [transactions[i], transactions[j], transactions[k], transactions[l]], inBlock: nil, skipCheckBloomFilter: false) - - verifyNoMoreInteractions(mockBlockchainDataListener) - - for (m, transaction) in calledTransactions.enumerated() { - XCTAssertEqual(transaction.header.dataHash, transactions[m].header.dataHash) - } - } - } - } - } - } - - - private func transactions() -> [FullTransaction] { - let transaction = FullTransaction( - header: Transaction(version: 0, lockTime: 0), - inputs: [ - Input( - withPreviousOutputTxHash: Data(from: 1), - previousOutputIndex: 0, - script: Data(from: 999999999999), - sequence: 0 - ) - ], - outputs: [ - Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()) - ] - ) - - let transaction2 = FullTransaction( - header: Transaction(version: 0, lockTime: 0), - inputs: [ - Input( - withPreviousOutputTxHash: transaction.header.dataHash, - previousOutputIndex: 0, - script: Data(from: 999999999999), - sequence: 0 - ) - ], - outputs: [ - Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()), - Output(withValue: 0, index: 1, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()) - ] - ) - - let transaction3 = FullTransaction( - header: Transaction(version: 0, lockTime: 0), - inputs: [ - Input( - withPreviousOutputTxHash: transaction2.header.dataHash, - previousOutputIndex: 0, - script: Data(from: 999999999999), - sequence: 0 - ) - ], - outputs: [ - Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()), - ] - ) - - let transaction4 = FullTransaction( - header: Transaction(version: 0, lockTime: 0), - inputs: [ - Input( - withPreviousOutputTxHash: transaction2.header.dataHash, - previousOutputIndex: 1, - script: Data(from: 999999999999), - sequence: 0 - ), - Input( - withPreviousOutputTxHash: transaction3.header.dataHash, - previousOutputIndex: 0, - script: Data(from: 999999999999), - sequence: 0 - ) - ], - outputs: [ - Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()) - ] - ) - - return [transaction, transaction2, transaction3, transaction4] - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionSizeCalculatorTests.swift b/BitcoinCore/BitcoinCoreTests/Transactions/TransactionSizeCalculatorTests.swift deleted file mode 100644 index 0da89ded..00000000 --- a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionSizeCalculatorTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -import XCTest -import Cuckoo -@testable import BitcoinCore - -class TransactionSizeCalculatorTests: XCTestCase { - var calculator: TransactionSizeCalculator! - - override func setUp() { - super.setUp() - - calculator = TransactionSizeCalculator() - } - - override func tearDown() { - calculator = nil - - super.tearDown() - } - - func testTransactionSize() { - XCTAssertEqual(calculator.transactionSize(inputs: [], outputScriptTypes: []), 10) // empty legacy tx - XCTAssertEqual(calculator.transactionSize(inputs: [.p2pkh], outputScriptTypes: [.p2pkh]), 194) // 1-in 1-out standart tx - XCTAssertEqual(calculator.transactionSize(inputs: [.p2pkh, .p2pk], outputScriptTypes: [.p2pkh]), 310) // 2-in 1-out legacy tx - XCTAssertEqual(calculator.transactionSize(inputs: [.p2pkh, .p2pk], outputScriptTypes: [.p2wpkh]), 307) // 2-in 1-out legacy tx with witness output - XCTAssertEqual(calculator.transactionSize(inputs: [.p2pkh, .p2pk], outputScriptTypes: [.p2pkh, .p2pk]), 354) // 2-in 2-out legacy tx - - XCTAssertEqual(calculator.transactionSize(inputs: [.p2wpkh], outputScriptTypes: [.p2pkh]), 113) // 1-in 1-out witness tx - XCTAssertEqual(calculator.transactionSize(inputs: [.p2wpkhSh], outputScriptTypes: [.p2pkh]), 136) // 1-in 1-out (sh) witness tx - XCTAssertEqual(calculator.transactionSize(inputs: [.p2wpkh, .p2pkh], outputScriptTypes: [.p2pkh]), 263) // 2-in 1-out witness tx - } - - func testInputSize() { - XCTAssertEqual(calculator.inputSize(type: .p2pkh), 150) - XCTAssertEqual(calculator.inputSize(type: .p2pk), 116) - XCTAssertEqual(calculator.inputSize(type: .p2wpkh), 41) - XCTAssertEqual(calculator.inputSize(type: .p2wpkhSh), 64) - } - - func testOutputSize() { - XCTAssertEqual(calculator.outputSize(type: .p2pkh), 34) - XCTAssertEqual(calculator.outputSize(type: .p2sh), 32) - XCTAssertEqual(calculator.outputSize(type: .p2pk), 44) - XCTAssertEqual(calculator.outputSize(type: .p2wpkh), 31) - XCTAssertEqual(calculator.outputSize(type: .p2wpkhSh), 32) - } - -} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionSyncerTests.swift b/BitcoinCore/BitcoinCoreTests/Transactions/TransactionSyncerTests.swift deleted file mode 100644 index eb28d4d8..00000000 --- a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionSyncerTests.swift +++ /dev/null @@ -1,247 +0,0 @@ -import XCTest -import Quick -import Nimble -import Cuckoo -@testable import BitcoinCore - -class TransactionSyncerTests: QuickSpec { - override func spec() { - let mockStorage = MockIStorage() - let mockTransactionProcessor = MockITransactionProcessor() - let mockAddressManager = MockIAddressManager() - let mockBloomFilterManager = MockIBloomFilterManager() - let maxRetriesCount = 3 - let retriesPeriod: Double = 60 - let totalRetriesPeriod: Double = 60 * 60 * 24 - - var syncer: TransactionSyncer! - - beforeEach { - stub(mockStorage) { mock in - when(mock.add(sentTransaction: any())).thenDoNothing() - when(mock.update(sentTransaction: any())).thenDoNothing() - } - stub(mockTransactionProcessor) { mock in - when(mock.processReceived(transactions: any(), inBlock: any(), skipCheckBloomFilter: any())).thenDoNothing() - } - stub(mockAddressManager) { mock in - when(mock.fillGap()).thenDoNothing() - } - stub(mockBloomFilterManager) { mock in - when(mock.regenerateBloomFilter()).thenDoNothing() - } - - syncer = TransactionSyncer( - storage: mockStorage, processor: mockTransactionProcessor, addressManager: mockAddressManager, bloomFilterManager: mockBloomFilterManager, - maxRetriesCount: maxRetriesCount, retriesPeriod: retriesPeriod, totalRetriesPeriod: totalRetriesPeriod) - } - - afterEach { - reset(mockStorage, mockTransactionProcessor, mockAddressManager, mockBloomFilterManager) - - syncer = nil - } - - describe("#pendingTransactions") { - let fullTransaction = TestData.p2pkTransaction - - context("when transaction is .new") { - beforeEach { - stub(mockStorage) { mock in - when(mock.newTransactions()).thenReturn([fullTransaction.header]) - } - } - - context("when it wasn't sent") { - it("returns transaction") { - stub(mockStorage) { mock in - when(mock.sentTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(nil) - when(mock.inputs(transactionHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.inputs) - when(mock.outputs(transactionHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.outputs) - } - let transactions = syncer.pendingTransactions() - - expect(transactions.count).to(equal(1)) - expect(transactions.first!.header.dataHash).to(equal(fullTransaction.header.dataHash)) - } - } - - context("when it was sent") { - let sentTransaction = SentTransaction(dataHash: fullTransaction.header.dataHash) - sentTransaction.lastSendTime = CACurrentMediaTime() - retriesPeriod - 1 - sentTransaction.retriesCount = 0 - beforeEach { - stub(mockStorage) { mock in - when(mock.sentTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(sentTransaction) - } - } - - context("when sent not too many times or too frequently") { - it("returns transaction") { - stub(mockStorage) { mock in - when(mock.inputs(transactionHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.inputs) - when(mock.outputs(transactionHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.outputs) - } - - let transactions = syncer.pendingTransactions() - expect(transactions.count).to(equal(1)) - expect(transactions.first!.header.dataHash).to(equal(fullTransaction.header.dataHash)) - } - } - - context("when sent too many times") { - it("doesn't return transaction") { - sentTransaction.retriesCount = maxRetriesCount - expect(syncer.pendingTransactions()).to(beEmpty()) - } - } - - context("when sent too often") { - it("doesn't return transaction") { - sentTransaction.lastSendTime = CACurrentMediaTime() - expect(syncer.pendingTransactions()).to(beEmpty()) - } - } - - context("when sent too often in totalRetriesPeriod period") { - it("doesn't return transaction") { - sentTransaction.firstSendTime = CACurrentMediaTime() - totalRetriesPeriod - 1 - expect(syncer.pendingTransactions()).to(beEmpty()) - } - } - } - } - - context("when transaction is not new") { - it("doesn't return transaction") { - stub(mockStorage) { mock in - when(mock.newTransactions()).thenReturn([]) - } - expect(syncer.pendingTransactions()).to(beEmpty()) - } - } - } - - describe("#handle(sentTransaction:)") { - let fullTransaction = TestData.p2pkhTransaction - - context("when SentTransaction does not exist") { - it("adds new SentTransaction object") { - stub(mockStorage) { mock in - when(mock.newTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.header) - when(mock.sentTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(nil) - } - - syncer.handle(sentTransaction: fullTransaction) - - let argumentCaptor = ArgumentCaptor() - verify(mockStorage).add(sentTransaction: argumentCaptor.capture()) - let sentTransaction = argumentCaptor.value! - - expect(sentTransaction.dataHash).to(equal(fullTransaction.header.dataHash)) - } - } - - context("when SentTransaction exists") { - var sentTransaction = SentTransaction(dataHash: fullTransaction.header.dataHash) - sentTransaction.firstSendTime = sentTransaction.firstSendTime - 100 - sentTransaction.lastSendTime = sentTransaction.lastSendTime - 100 - - it("updates existing SentTransaction object") { - stub(mockStorage) { mock in - when(mock.newTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.header) - when(mock.sentTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(sentTransaction) - } - - syncer.handle(sentTransaction: fullTransaction) - - let argumentCaptor = ArgumentCaptor() - verify(mockStorage).update(sentTransaction: argumentCaptor.capture()) - sentTransaction = argumentCaptor.value! - - expect(sentTransaction.dataHash).to(equal(fullTransaction.header.dataHash)) - } - } - - context("when Transaction doesn't exist") { - it("neither adds new nor updates existing") { - stub(mockStorage) { mock in - when(mock.newTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(nil) - } - - syncer.handle(sentTransaction: fullTransaction) - - verify(mockStorage, never()).add(sentTransaction: any()) - verify(mockStorage, never()).update(sentTransaction: any()) - } - } - } - - describe("#handle(transactions:)") { - context("when empty array is given") { - it("doesn't do anything") { - syncer.handle(transactions: []) - - verify(mockTransactionProcessor, never()).processReceived(transactions: any(), inBlock: any(), skipCheckBloomFilter: any()) - verify(mockAddressManager, never()).fillGap() - verify(mockBloomFilterManager, never()).regenerateBloomFilter() - } - } - - context("when not empty array is given") { - let transactions = [TestData.p2pkhTransaction] - - context("when need to update bloom filter") { - it("fills addresses gap and regenerates bloom filter") { - stub(mockTransactionProcessor) { mock in - when(mock.processReceived(transactions: equal(to: transactions), inBlock: any(), skipCheckBloomFilter: any())).thenThrow(BloomFilterManager.BloomFilterExpired()) - } - - syncer.handle(transactions: transactions) - verify(mockTransactionProcessor).processReceived(transactions: equal(to: transactions), inBlock: equal(to: nil), skipCheckBloomFilter: equal(to: false)) - verify(mockAddressManager).fillGap() - verify(mockBloomFilterManager).regenerateBloomFilter() - } - } - - context("when don't need to update bloom filter") { - it("doesn't run address fillGap and doesn't regenerate bloom filter") { - stub(mockTransactionProcessor) { mock in - when(mock.processReceived(transactions: equal(to: transactions), inBlock: any(), skipCheckBloomFilter: equal(to: false))).thenDoNothing() - } - - syncer.handle(transactions: transactions) - verify(mockTransactionProcessor).processReceived(transactions: equal(to: transactions), inBlock: equal(to: nil), skipCheckBloomFilter: equal(to: false)) - verify(mockAddressManager, never()).fillGap() - verify(mockBloomFilterManager, never()).regenerateBloomFilter() - } - } - - } - } - - describe("#shouldRequestTransaction") { - let fullTransaction = TestData.p2wpkhTransaction - - context("when relayed transaction exists") { - it("returns false") { - stub(mockStorage) { mock in - when(mock.relayedTransactionExists(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(true) - } - - XCTAssertEqual(syncer.shouldRequestTransaction(hash: fullTransaction.header.dataHash), false) - } - } - - context("when relayed transaction doesn't exist") { - it("returns true") { - stub(mockStorage) { mock in - when(mock.relayedTransactionExists(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(false) - } - - XCTAssertEqual(syncer.shouldRequestTransaction(hash: fullTransaction.header.dataHash), true) - } - } - } - } -} diff --git a/BitcoinCore/BitcoinCore/BlockHeaders/BlockHeaderParser.swift b/BitcoinCore/Classes/BlockHeaders/BlockHeaderParser.swift similarity index 100% rename from BitcoinCore/BitcoinCore/BlockHeaders/BlockHeaderParser.swift rename to BitcoinCore/Classes/BlockHeaders/BlockHeaderParser.swift diff --git a/BitcoinCore/BitcoinCore/BlockHeaders/DifficultyEncoder.swift b/BitcoinCore/Classes/BlockHeaders/DifficultyEncoder.swift similarity index 72% rename from BitcoinCore/BitcoinCore/BlockHeaders/DifficultyEncoder.swift rename to BitcoinCore/Classes/BlockHeaders/DifficultyEncoder.swift index c9a9690e..7810c33d 100644 --- a/BitcoinCore/BitcoinCore/BlockHeaders/DifficultyEncoder.swift +++ b/BitcoinCore/Classes/BlockHeaders/DifficultyEncoder.swift @@ -17,6 +17,29 @@ public class DifficultyEncoder: IDifficultyEncoder { */ public init() {} + public func compactFrom(hash: Data) -> Int { + var hashSize = hash.count - 1 + while hashSize >= 0, hash[hashSize] == 0 { + hashSize -= 1 + } + hashSize += 1 + hashSize = max(hashSize, 3) // if difficulty very stronger we must show bits as minimum 3 bytes (ex. 0x030000xx) + + var firstSignificant = 0 + + let isBigFirstSignificant = hash[hashSize - 1] > 0x7f // if first byte > 0x7f we need add 0x00 as first byte and increase hashSize + let ignoreByte = hashSize == hash.count && isBigFirstSignificant // if difficulty very simple and last byte > 0x7f we must make length = 33 and add 0x00) + + if isBigFirstSignificant { + hashSize += 1 + } + if !ignoreByte { + firstSignificant = Int(hash[hashSize - 1]) << 16 + } + + return hashSize << 24 + firstSignificant + Int(hash[hashSize - 2]) << 8 + Int(hash[hashSize - 3]) + } + public func decodeCompact(bits: Int) -> BigInt { let size = (bits >> 24) & 0xFF diff --git a/BitcoinCore/Classes/Blocks/BlockMedianTimeHelper.swift b/BitcoinCore/Classes/Blocks/BlockMedianTimeHelper.swift new file mode 100644 index 00000000..2bcd1d12 --- /dev/null +++ b/BitcoinCore/Classes/Blocks/BlockMedianTimeHelper.swift @@ -0,0 +1,28 @@ +public class BlockMedianTimeHelper { + private let medianTimeSpan = 11 + private let storage: IStorage + + public init(storage: IStorage) { + self.storage = storage + } + +} + +extension BlockMedianTimeHelper: IBlockMedianTimeHelper { + + public var medianTimePast: Int? { + storage.lastBlock.flatMap { medianTimePast(block: $0) } + } + + public func medianTimePast(block: Block) -> Int? { + let startIndex = block.height - medianTimeSpan + 1 + let median = storage.timestamps(from: startIndex, to: block.height) + + if block.height >= medianTimeSpan && median.count < medianTimeSpan { + return nil + } + + return median[median.count / 2] + } + +} diff --git a/BitcoinCore/BitcoinCore/Blocks/BlockSyncer.swift b/BitcoinCore/Classes/Blocks/BlockSyncer.swift similarity index 60% rename from BitcoinCore/BitcoinCore/Blocks/BlockSyncer.swift rename to BitcoinCore/Classes/Blocks/BlockSyncer.swift index 0238dfd9..87a935a2 100644 --- a/BitcoinCore/BitcoinCore/Blocks/BlockSyncer.swift +++ b/BitcoinCore/Classes/Blocks/BlockSyncer.swift @@ -1,34 +1,30 @@ -import HSCryptoKit +import HsToolKit class BlockSyncer { + weak var listener: IBlockSyncListener? private let storage: IStorage - private let listener: ISyncStateListener - private let checkpointBlock: Block + private let checkpoint: Checkpoint private let factory: IFactory - private let transactionProcessor: ITransactionProcessor + private let transactionProcessor: IBlockTransactionProcessor private let blockchain: IBlockchain - private let addressManager: IAddressManager - private let bloomFilterManager: IBloomFilterManager + private let publicKeyManager: IPublicKeyManager private let hashCheckpointThreshold: Int private var state: BlockSyncerState private let logger: Logger? - init(storage: IStorage, checkpointBlock: Block, factory: IFactory, listener: ISyncStateListener, transactionProcessor: ITransactionProcessor, - blockchain: IBlockchain, addressManager: IAddressManager, bloomFilterManager: IBloomFilterManager, - hashCheckpointThreshold: Int, logger: Logger?, state: BlockSyncerState + init(storage: IStorage, checkpoint: Checkpoint, factory: IFactory, transactionProcessor: IBlockTransactionProcessor, + blockchain: IBlockchain, publicKeyManager: IPublicKeyManager, hashCheckpointThreshold: Int, logger: Logger?, state: BlockSyncerState ) { self.storage = storage - self.checkpointBlock = checkpointBlock + self.checkpoint = checkpoint self.factory = factory self.transactionProcessor = transactionProcessor self.blockchain = blockchain - self.addressManager = addressManager - self.bloomFilterManager = bloomFilterManager + self.publicKeyManager = publicKeyManager self.hashCheckpointThreshold = hashCheckpointThreshold - self.listener = listener self.logger = logger self.state = state } @@ -50,15 +46,16 @@ class BlockSyncer { } private func clearPartialBlocks() throws { - let blockReversedHashes = storage.blockHashHeaderHashes(except: checkpointBlock.headerHash) + var excludedHashes = [checkpoint.block.headerHash] + checkpoint.additionalBlocks.forEach { excludedHashes.append($0.headerHash) } - let blocksToDelete = storage.blocks(byHexes: blockReversedHashes) + let blockHashes = storage.blockHashHeaderHashes(except: excludedHashes) + let blocksToDelete = storage.blocks(byHexes: blockHashes) try blockchain.deleteBlocks(blocks: blocksToDelete) } private func handlePartialBlocks() throws { - try addressManager.fillGap() - bloomFilterManager.regenerateBloomFilter() + try publicKeyManager.fillGap() state.iteration(hasPartialBlocks: false) } @@ -96,7 +93,7 @@ extension BlockSyncer: IBlockSyncer { } func getBlockHashes() -> [BlockHash] { - return storage.blockHashesSortedBySequenceAndHeight(limit: 500) + storage.blockHashesSortedBySequenceAndHeight(limit: 500) } func getBlockLocatorHashes(peerLastBlockHeight: Int32) -> [Data] { @@ -107,7 +104,7 @@ extension BlockSyncer: IBlockSyncer { } if blockLocatorHashes.isEmpty { - for block in storage.blocks(heightGreaterThan: checkpointBlock.height, sortedBy: Block.Columns.height, limit: 10) { + for block in storage.blocks(heightGreaterThan: checkpoint.block.height, sortedBy: Block.Columns.height, limit: 10) { blockLocatorHashes.append(block.headerHash) } } @@ -117,7 +114,7 @@ extension BlockSyncer: IBlockSyncer { blockLocatorHashes.append(peerLastBlock.headerHash) } } else { - blockLocatorHashes.append(checkpointBlock.headerHash) + blockLocatorHashes.append(checkpoint.block.headerHash) } return blockLocatorHashes @@ -157,32 +154,55 @@ extension BlockSyncer: IBlockSyncer { storage.deleteBlockHash(byHash: block.headerHash) } - listener.currentBestBlockHeightUpdated(height: Int32(block.height), maxBlockHeight: maxBlockHeight) + listener?.currentBestBlockHeightUpdated(height: Int32(block.height), maxBlockHeight: maxBlockHeight) } func shouldRequestBlock(withHash hash: Data) -> Bool { - return storage.block(byHash: hash) == nil + storage.block(byHash: hash) == nil } } extension BlockSyncer { - public static func instance(storage: IStorage, checkpointBlock: Block, factory: IFactory, listener: ISyncStateListener, transactionProcessor: ITransactionProcessor, - blockchain: IBlockchain, addressManager: IAddressManager, bloomFilterManager: IBloomFilterManager, + public static func instance(storage: IStorage, checkpoint: Checkpoint, factory: IFactory, + transactionProcessor: IBlockTransactionProcessor, blockchain: IBlockchain, publicKeyManager: IPublicKeyManager, hashCheckpointThreshold: Int = 100, logger: Logger? = nil, state: BlockSyncerState = BlockSyncerState()) -> BlockSyncer { - let syncer = BlockSyncer(storage: storage, checkpointBlock: checkpointBlock, factory: factory, listener: listener, transactionProcessor: transactionProcessor, - blockchain: blockchain, addressManager: addressManager, bloomFilterManager: bloomFilterManager, - hashCheckpointThreshold: hashCheckpointThreshold, logger: logger, state: state) + let syncer = BlockSyncer(storage: storage, checkpoint: checkpoint, factory: factory, transactionProcessor: transactionProcessor, + blockchain: blockchain, publicKeyManager: publicKeyManager, hashCheckpointThreshold: hashCheckpointThreshold, logger: logger, state: state) - if storage.blocksCount == 0 { - storage.save(block: checkpointBlock) + return syncer + } + + public static func resolveCheckpoint(network: INetwork, syncMode: BitcoinCore.SyncMode, storage: IStorage) -> Checkpoint { + let lastBlock = storage.lastBlock + let checkpoint: Checkpoint + + if syncMode == .full { + checkpoint = network.bip44Checkpoint + } else { + let lastCheckpoint = network.lastCheckpoint + + if let block = lastBlock, block.height < lastCheckpoint.block.height { + // When app is updated there may be case when the last block in DB is earlier than new checkpoint block. + // In this case we set the very first checkpoint block for bip44, + // since it surely will be earlier than the last block in DB + checkpoint = network.bip44Checkpoint + } else { + checkpoint = lastCheckpoint + } } - listener.initialBestBlockHeightUpdated(height: syncer.localDownloadedBestBlockHeight) + if lastBlock == nil { + storage.save(block: checkpoint.block) - return syncer + for block in checkpoint.additionalBlocks { + storage.save(block: block) + } + } + + return checkpoint } -} \ No newline at end of file +} diff --git a/BitcoinCore/BitcoinCore/Blocks/BlockSyncerState.swift b/BitcoinCore/Classes/Blocks/BlockSyncerState.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Blocks/BlockSyncerState.swift rename to BitcoinCore/Classes/Blocks/BlockSyncerState.swift diff --git a/BitcoinCore/BitcoinCore/Blocks/BlockValidatorHelper.swift b/BitcoinCore/Classes/Blocks/BlockValidatorHelper.swift similarity index 86% rename from BitcoinCore/BitcoinCore/Blocks/BlockValidatorHelper.swift rename to BitcoinCore/Classes/Blocks/BlockValidatorHelper.swift index 30e0ea81..cbdc1be0 100644 --- a/BitcoinCore/BitcoinCore/Blocks/BlockValidatorHelper.swift +++ b/BitcoinCore/Classes/Blocks/BlockValidatorHelper.swift @@ -7,7 +7,7 @@ open class BlockValidatorHelper: IBlockValidatorHelper { public func previous(for block: Block, count: Int) -> Block? { let previousHeight = block.height - count - guard let previousBlock = storage.block(byHeight: previousHeight) else { + guard let previousBlock = storage.blockByHeightStalePrioritized(height: previousHeight) else { return nil } return previousBlock diff --git a/BitcoinCore/BitcoinCore/Blocks/Blockchain.swift b/BitcoinCore/Classes/Blocks/Blockchain.swift similarity index 91% rename from BitcoinCore/BitcoinCore/Blocks/Blockchain.swift rename to BitcoinCore/Classes/Blocks/Blockchain.swift index 51903834..3173409f 100644 --- a/BitcoinCore/BitcoinCore/Blocks/Blockchain.swift +++ b/BitcoinCore/Classes/Blocks/Blockchain.swift @@ -1,11 +1,13 @@ +import UIExtensions + class Blockchain { private let storage: IStorage - private let blockValidator: IBlockValidator + private var blockValidator: IBlockValidator? private let factory: IFactory weak var listener: IBlockchainDataListener? private var previousBlock: Block? - init(storage: IStorage, blockValidator: IBlockValidator, factory: IFactory, listener: IBlockchainDataListener? = nil) { + init(storage: IStorage, blockValidator: IBlockValidator?, factory: IFactory, listener: IBlockchainDataListener? = nil) { self.storage = storage self.blockValidator = blockValidator self.factory = factory @@ -28,7 +30,7 @@ extension Blockchain: IBlockchain { // Validate and chain new blocks let block = factory.block(withHeader: merkleBlock.header, previousBlock: previousBlock) - try blockValidator.validate(block: block, previousBlock: previousBlock) + try blockValidator?.validate(block: block, previousBlock: previousBlock) block.stale = true try storage.add(block: block) diff --git a/BitcoinCore/BitcoinCore/Blocks/InitialBlockDownload.swift b/BitcoinCore/Classes/Blocks/InitialBlockDownload.swift similarity index 87% rename from BitcoinCore/BitcoinCore/Blocks/InitialBlockDownload.swift rename to BitcoinCore/Classes/Blocks/InitialBlockDownload.swift index 7b6252c8..00850f58 100644 --- a/BitcoinCore/BitcoinCore/Blocks/InitialBlockDownload.swift +++ b/BitcoinCore/Classes/Blocks/InitialBlockDownload.swift @@ -1,18 +1,20 @@ import RxSwift +import HsToolKit public enum InitialBlockDownloadEvent { + case onAllPeersSynced case onPeerSynced(peer: IPeer) case onPeerNotSynced(peer: IPeer) } public class InitialBlockDownload { + public weak var listener: IBlockSyncListener? private static let peerSwitchMinimumRatio = 1.5 private var disposeBag = DisposeBag() private var blockSyncer: IBlockSyncer private let peerManager: IPeerManager private let merkleBlockValidator: IMerkleBlockValidator - private let syncStateListener: ISyncStateListener private var minMerkleBlocksCount: Double = 0 private var minTransactionsCount: Double = 0 @@ -26,20 +28,19 @@ public class InitialBlockDownload { private var blockHashesSyncedStates = [String: Bool]() private var selectNewPeer = false - private var syncPeer: IPeer? private let peersQueue: DispatchQueue private let logger: Logger? public var syncedPeers = [IPeer]() + public var syncPeer: IPeer? - init(blockSyncer: IBlockSyncer, peerManager: IPeerManager, merkleBlockValidator: IMerkleBlockValidator, syncStateListener: ISyncStateListener, - peersQueue: DispatchQueue = DispatchQueue(label: "PeerGroup Local Queue", qos: .userInitiated), + init(blockSyncer: IBlockSyncer, peerManager: IPeerManager, merkleBlockValidator: IMerkleBlockValidator, + peersQueue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.initial-block-download", qos: .userInitiated), scheduler: SchedulerType = SerialDispatchQueueScheduler(qos: .background), logger: Logger? = nil) { self.blockSyncer = blockSyncer self.peerManager = peerManager self.merkleBlockValidator = merkleBlockValidator - self.syncStateListener = syncStateListener self.peersQueue = peersQueue self.logger = logger self.observable = subject.asObservable().observeOn(scheduler) @@ -47,11 +48,11 @@ public class InitialBlockDownload { } private func syncedState(_ peer: IPeer) -> Bool { - return syncedStates[peer.host] ?? false + syncedStates[peer.host] ?? false } private func blockHashesSyncedState(_ peer: IPeer) -> Bool { - return blockHashesSyncedStates[peer.host] ?? false + blockHashesSyncedStates[peer.host] ?? false } private func assignNextSyncPeer() { @@ -59,7 +60,10 @@ public class InitialBlockDownload { return } - let nonSyncedPeers = peerManager.sorted().filter { !syncedState($0) } + let nonSyncedPeers = peerManager.sorted.filter { !syncedState($0) } + if nonSyncedPeers.isEmpty { + subject.onNext(.onAllPeersSynced) + } if let peer = nonSyncedPeers.first(where: { $0.ready }) { logger?.debug("Setting sync peer to \(peer.logName)") @@ -125,7 +129,13 @@ public class InitialBlockDownload { syncedPeers.append(peer) subject.onNext(.onPeerSynced(peer: peer)) - syncStateListener.syncFinished(all: allPeersSynced) + + if blockSyncer.localDownloadedBestBlockHeight >= peer.announcedLastBlockHeight { + // Some peers fail to send InventoryMessage within expected time + // and become 'synced' in InitialBlockDownload without sending all of their blocks. + // In such case, we assume not all blocks are downloaded + listener?.blocksSyncFinished() + } } private func setPeerNotSynced(_ peer: IPeer) { @@ -154,8 +164,8 @@ public class InitialBlockDownload { .disposed(by: disposeBag) } - public var allPeersSynced: Bool { - return syncedPeers.count > 0 && syncedPeers.count >= peerManager.connected().count / 3 + public var hasSyncedPeer: Bool { + syncedPeers.count > 0 } } @@ -163,7 +173,7 @@ public class InitialBlockDownload { extension InitialBlockDownload: IInitialBlockDownload { public func isSynced(peer: IPeer) -> Bool { - return syncedState(peer) + syncedState(peer) } } @@ -206,13 +216,11 @@ extension InitialBlockDownload: IPeerTaskHandler { extension InitialBlockDownload { private func onStart() { - syncStateListener.syncStarted() resetRequiredDownloadSpeed() blockSyncer.prepareForDownload() } private func onStop() { - syncStateListener.syncStopped() } private func onPeerCreate(peer: IPeer) { @@ -222,6 +230,7 @@ extension InitialBlockDownload { private func onPeerConnect(peer: IPeer) { peersQueue.async { self.syncedStates[peer.host] = false + self.blockHashesSyncedStates[peer.host] = false if let syncPeer = self.syncPeer, syncPeer.connectionTime > peer.connectionTime * InitialBlockDownload.peerSwitchMinimumRatio { self.selectNewPeer = true } @@ -243,6 +252,7 @@ extension InitialBlockDownload { self.syncedPeers.remove(at: index) } self.syncedStates.removeValue(forKey: peer.host) + self.blockHashesSyncedStates.removeValue(forKey: peer.host) if peer.equalTo(self.syncPeer) { self.syncPeer = nil diff --git a/BitcoinCore/BitcoinCore/Blocks/MerkleBlockValidator.swift b/BitcoinCore/Classes/Blocks/MerkleBlockValidator.swift similarity index 99% rename from BitcoinCore/BitcoinCore/Blocks/MerkleBlockValidator.swift rename to BitcoinCore/Classes/Blocks/MerkleBlockValidator.swift index 5a25c26b..c99a492b 100644 --- a/BitcoinCore/BitcoinCore/Blocks/MerkleBlockValidator.swift +++ b/BitcoinCore/Classes/Blocks/MerkleBlockValidator.swift @@ -1,5 +1,4 @@ import Foundation -import HSCryptoKit class MerkleBlockValidator: IMerkleBlockValidator { diff --git a/BitcoinCore/BitcoinCore/Blocks/Validators/BitsValidator.swift b/BitcoinCore/Classes/Blocks/Validators/BitsValidator.swift similarity index 87% rename from BitcoinCore/BitcoinCore/Blocks/Validators/BitsValidator.swift rename to BitcoinCore/Classes/Blocks/Validators/BitsValidator.swift index 931d92c6..9e096c53 100644 --- a/BitcoinCore/BitcoinCore/Blocks/Validators/BitsValidator.swift +++ b/BitcoinCore/Classes/Blocks/Validators/BitsValidator.swift @@ -1,6 +1,6 @@ import Foundation -public class BitsValidator: IBlockValidator { +public class BitsValidator: IBlockChainedValidator { public init() {} diff --git a/BitcoinCore/Classes/Blocks/Validators/BlockValidatorChain.swift b/BitcoinCore/Classes/Blocks/Validators/BlockValidatorChain.swift new file mode 100644 index 00000000..e21061da --- /dev/null +++ b/BitcoinCore/Classes/Blocks/Validators/BlockValidatorChain.swift @@ -0,0 +1,17 @@ +public class BlockValidatorChain: IBlockValidator { + private var validators = [IBlockChainedValidator]() + + public init() { + } + + public func validate(block: Block, previousBlock: Block) throws { + if let index = validators.firstIndex(where: { $0.isBlockValidatable(block: block, previousBlock: previousBlock) }) { + try validators[index].validate(block: block, previousBlock: previousBlock) + } + } + + public func add(blockValidator: IBlockChainedValidator) { + validators.append(blockValidator) + } + +} diff --git a/BitcoinCore/Classes/Blocks/Validators/BlockValidatorSet.swift b/BitcoinCore/Classes/Blocks/Validators/BlockValidatorSet.swift new file mode 100644 index 00000000..8cb675c6 --- /dev/null +++ b/BitcoinCore/Classes/Blocks/Validators/BlockValidatorSet.swift @@ -0,0 +1,17 @@ +public class BlockValidatorSet: IBlockValidator { + private var validators = [IBlockValidator]() + + public init() { + } + + public func validate(block: Block, previousBlock: Block) throws { + for validator in validators { + try validator.validate(block: block, previousBlock: previousBlock) + } + } + + public func add(blockValidator: IBlockValidator) { + validators.append(blockValidator) + } + +} diff --git a/BitcoinCore/BitcoinCore/Blocks/Validators/LegacyDifficultyAdjustmentValidator.swift b/BitcoinCore/Classes/Blocks/Validators/LegacyDifficultyAdjustmentValidator.swift similarity index 93% rename from BitcoinCore/BitcoinCore/Blocks/Validators/LegacyDifficultyAdjustmentValidator.swift rename to BitcoinCore/Classes/Blocks/Validators/LegacyDifficultyAdjustmentValidator.swift index a8690b7b..e913d7e0 100644 --- a/BitcoinCore/BitcoinCore/Blocks/Validators/LegacyDifficultyAdjustmentValidator.swift +++ b/BitcoinCore/Classes/Blocks/Validators/LegacyDifficultyAdjustmentValidator.swift @@ -1,6 +1,6 @@ import BigInt -public class LegacyDifficultyAdjustmentValidator: IBlockValidator { +public class LegacyDifficultyAdjustmentValidator: IBlockChainedValidator { private let heightInterval: Int private let targetTimespan: Int private let maxTargetBits: Int @@ -44,7 +44,7 @@ public class LegacyDifficultyAdjustmentValidator: IBlockValidator { } public func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return block.height % heightInterval == 0 + block.height % heightInterval == 0 } } diff --git a/BitcoinCore/BitcoinCore/Blocks/Validators/LegacyTestNetDifficultyValidator.swift b/BitcoinCore/Classes/Blocks/Validators/LegacyTestNetDifficultyValidator.swift similarity index 95% rename from BitcoinCore/BitcoinCore/Blocks/Validators/LegacyTestNetDifficultyValidator.swift rename to BitcoinCore/Classes/Blocks/Validators/LegacyTestNetDifficultyValidator.swift index 8cbe157f..925f8f95 100644 --- a/BitcoinCore/BitcoinCore/Blocks/Validators/LegacyTestNetDifficultyValidator.swift +++ b/BitcoinCore/Classes/Blocks/Validators/LegacyTestNetDifficultyValidator.swift @@ -1,7 +1,7 @@ import Foundation import BigInt -public class LegacyTestNetDifficultyValidator: IBlockValidator { +public class LegacyTestNetDifficultyValidator: IBlockChainedValidator { private let diffDate = 1329264000 // February 16th 2012 private let heightInterval: Int diff --git a/BitcoinCore/Classes/Blocks/Validators/ProofOfWorkValidator.swift b/BitcoinCore/Classes/Blocks/Validators/ProofOfWorkValidator.swift new file mode 100644 index 00000000..aecbf043 --- /dev/null +++ b/BitcoinCore/Classes/Blocks/Validators/ProofOfWorkValidator.swift @@ -0,0 +1,17 @@ +import Foundation +import BigInt + +public class ProofOfWorkValidator: IBlockValidator { + private let difficultyEncoder: IDifficultyEncoder + + public init(difficultyEncoder: IDifficultyEncoder) { + self.difficultyEncoder = difficultyEncoder + } + + public func validate(block: Block, previousBlock: Block) throws { + guard difficultyEncoder.compactFrom(hash: block.headerHash) < block.bits else { + throw BitcoinCoreErrors.BlockValidation.invalidProofOfWork + } + } + +} diff --git a/BitcoinCore/Classes/Core/AbstractKit.swift b/BitcoinCore/Classes/Core/AbstractKit.swift new file mode 100644 index 00000000..07fb9b74 --- /dev/null +++ b/BitcoinCore/Classes/Core/AbstractKit.swift @@ -0,0 +1,113 @@ +import Foundation +import RxSwift + +open class AbstractKit { + public var bitcoinCore: BitcoinCore + public var network: INetwork + + public init(bitcoinCore: BitcoinCore, network: INetwork) { + self.bitcoinCore = bitcoinCore + self.network = network + } + + open func start() { + bitcoinCore.start() + } + + open func stop() { + bitcoinCore.stop() + } + + open var lastBlockInfo: BlockInfo? { + bitcoinCore.lastBlockInfo + } + + open var balance: BalanceInfo { + bitcoinCore.balance + } + + open var syncState: BitcoinCore.KitState { + bitcoinCore.syncState + } + + open func transactions(fromUid: String? = nil, type: TransactionFilterType?, limit: Int? = nil) -> Single<[TransactionInfo]> { + bitcoinCore.transactions(fromUid: fromUid, type: type, limit: limit) + } + + open func transaction(hash: String) -> TransactionInfo? { + bitcoinCore.transaction(hash: hash) + } + + open func send(to address: String, value: Int, feeRate: Int, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) throws -> FullTransaction { + try bitcoinCore.send(to: address, value: value, feeRate: feeRate, sortType: sortType, pluginData: pluginData) + } + + public func send(to hash: Data, scriptType: ScriptType, value: Int, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction { + try bitcoinCore.send(to: hash, scriptType: scriptType, value: value, feeRate: feeRate, sortType: sortType) + } + + public func redeem(from unspentOutput: UnspentOutput, to address: String, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction { + try bitcoinCore.redeem(from: unspentOutput, to: address, feeRate: feeRate, sortType: sortType) + } + + open func createRawTransaction(to address: String, value: Int, feeRate: Int, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) throws -> Data { + try bitcoinCore.createRawTransaction(to: address, value: value, feeRate: feeRate, sortType: sortType, pluginData: pluginData) + } + + open func validate(address: String, pluginData: [UInt8: IPluginData] = [:]) throws { + try bitcoinCore.validate(address: address, pluginData: pluginData) + } + + open func parse(paymentAddress: String) -> BitcoinPaymentData { + bitcoinCore.parse(paymentAddress: paymentAddress) + } + + open func fee(for value: Int, toAddress: String? = nil, feeRate: Int, pluginData: [UInt8: IPluginData] = [:]) throws -> Int { + try bitcoinCore.fee(for: value, toAddress: toAddress, feeRate: feeRate, pluginData: pluginData) + } + + open func maxSpendableValue(toAddress: String? = nil, feeRate: Int, pluginData: [UInt8: IPluginData] = [:]) throws -> Int { + try bitcoinCore.maxSpendableValue(toAddress: toAddress, feeRate: feeRate, pluginData: pluginData) + } + + open func maxSpendLimit(pluginData: [UInt8: IPluginData]) throws -> Int? { + try bitcoinCore.maxSpendLimit(pluginData: pluginData) + } + + open func minSpendableValue(toAddress: String? = nil) -> Int { + bitcoinCore.minSpendableValue(toAddress: toAddress) + } + + open func receiveAddress() -> String { + bitcoinCore.receiveAddress() + } + + open func changePublicKey() throws -> PublicKey { + try bitcoinCore.changePublicKey() + } + + open func receivePublicKey() throws -> PublicKey { + try bitcoinCore.receivePublicKey() + } + + public func publicKey(byPath path: String) throws -> PublicKey { + try bitcoinCore.publicKey(byPath: path) + } + + open func watch(transaction: BitcoinCore.TransactionFilter, delegate: IWatchedTransactionDelegate) { + bitcoinCore.watch(transaction: transaction, delegate: delegate) + } + + open var debugInfo: String { + bitcoinCore.debugInfo(network: network) + } + + open var statusInfo: [(String, Any)] { + bitcoinCore.statusInfo + } + + public func rawTransaction(transactionHash: String) -> String? { + bitcoinCore.rawTransaction(transactionHash: transactionHash) + } + +} diff --git a/BitcoinCore/Classes/Core/BaseTransactionInfoConverter.swift b/BitcoinCore/Classes/Core/BaseTransactionInfoConverter.swift new file mode 100644 index 00000000..a21f0bb9 --- /dev/null +++ b/BitcoinCore/Classes/Core/BaseTransactionInfoConverter.swift @@ -0,0 +1,85 @@ +import UIExtensions + +public protocol IBaseTransactionInfoConverter { + func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> T +} + +public class BaseTransactionInfoConverter: IBaseTransactionInfoConverter { + private let pluginManager: IPluginManager + + public init(pluginManager: IPluginManager) { + self.pluginManager = pluginManager + } + + public func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> T { + if let invalidTransactionInfo: T = transactionInfo(fromInvalidTransaction: transactionForInfo) { + return invalidTransactionInfo + } + + var inputsInfo = [TransactionInputInfo]() + var outputsInfo = [TransactionOutputInfo]() + let transaction = transactionForInfo.transactionWithBlock.transaction + let transactionTimestamp = transaction.timestamp + + for inputWithPreviousOutput in transactionForInfo.inputsWithPreviousOutputs { + var mine = false + var value: Int? = nil + + if let previousOutput = inputWithPreviousOutput.previousOutput { + value = previousOutput.value + + if previousOutput.publicKeyPath != nil { + mine = true + } + } + + inputsInfo.append(TransactionInputInfo(mine: mine, address: inputWithPreviousOutput.input.address, value: value)) + } + + for output in transactionForInfo.outputs { + let outputInfo = TransactionOutputInfo(mine: output.publicKeyPath != nil, changeOutput: output.changeOutput, value: output.value, address: output.address) + + if let pluginId = output.pluginId, let pluginDataString = output.pluginData { + outputInfo.pluginId = pluginId + outputInfo.pluginDataString = pluginDataString + outputInfo.pluginData = pluginManager.parsePluginData(fromPlugin: pluginId, pluginDataString: pluginDataString, transactionTimestamp: transactionTimestamp) + } + + outputsInfo.append(outputInfo) + } + + return T( + uid: transaction.uid, + transactionHash: transaction.dataHash.reversedHex, + transactionIndex: transaction.order, + inputs: inputsInfo, + outputs: outputsInfo, + amount: transactionForInfo.metaData.amount, + type: transactionForInfo.metaData.type, + fee: transactionForInfo.metaData.fee, + blockHeight: transactionForInfo.transactionWithBlock.blockHeight, + timestamp: transactionTimestamp, + status: transaction.status, + conflictingHash: transaction.conflictingTxHash?.reversedHex + ) + } + + private func transactionInfo(fromInvalidTransaction transactionForInfo: FullTransactionForInfo) -> T? { + guard let invalidTransaction = transactionForInfo.transactionWithBlock.transaction as? InvalidTransaction else { + return nil + } + + guard let transactionInfo: T = try? JSONDecoder.init().decode(T.self, from: invalidTransaction.transactionInfoJson) else { + return nil + } + + for addressInfo in transactionInfo.outputs { + if let pluginId = addressInfo.pluginId, let pluginDataString = addressInfo.pluginDataString { + addressInfo.pluginData = pluginManager.parsePluginData(fromPlugin: pluginId, pluginDataString: pluginDataString, transactionTimestamp: invalidTransaction.timestamp) + } + } + + return transactionInfo + } + +} diff --git a/BitcoinCore/Classes/Core/Bip.swift b/BitcoinCore/Classes/Core/Bip.swift new file mode 100644 index 00000000..2f7d3cba --- /dev/null +++ b/BitcoinCore/Classes/Core/Bip.swift @@ -0,0 +1,32 @@ +import HdWalletKit + +public enum Bip: CustomStringConvertible { + case bip44 + case bip49 + case bip84 + + public var scriptType: ScriptType { + switch self { + case .bip44: return .p2pkh + case .bip49: return .p2wpkhSh + case .bip84: return .p2wpkh + } + } + + var purpose: Purpose { + switch self { + case .bip44: return Purpose.bip44 + case .bip49: return Purpose.bip49 + case .bip84: return Purpose.bip84 + } + } + + public var description: String { + switch self { + case .bip44: return "bip44" + case .bip49: return "bip49" + case .bip84: return "bip84" + } + } + +} diff --git a/BitcoinCore/Classes/Core/BitcoinCore.swift b/BitcoinCore/Classes/Core/BitcoinCore.swift new file mode 100644 index 00000000..9d4f4497 --- /dev/null +++ b/BitcoinCore/Classes/Core/BitcoinCore.swift @@ -0,0 +1,379 @@ +import Foundation +import HdWalletKit +import HsToolKit +import BigInt +import RxSwift + +public class BitcoinCore { + private let storage: IStorage + private var dataProvider: IDataProvider + private let publicKeyManager: IPublicKeyManager + private let watchedTransactionManager: IWatchedTransactionManager + private let addressConverter: AddressConverterChain + private let restoreKeyConverterChain: RestoreKeyConverterChain + private let unspentOutputSelector: UnspentOutputSelectorChain + + private let transactionCreator: ITransactionCreator + private let transactionFeeCalculator: ITransactionFeeCalculator + private let dustCalculator: IDustCalculator + private let paymentAddressParser: IPaymentAddressParser + + private let networkMessageSerializer: NetworkMessageSerializer + private let networkMessageParser: NetworkMessageParser + + private let syncManager: SyncManager + private let pluginManager: IPluginManager + + private let bip: Bip + + private let peerManager: IPeerManager + + // START: Extending + + public let peerGroup: IPeerGroup + public let initialBlockDownload: IInitialBlockDownload + public let transactionSyncer: ITransactionSyncer + + let bloomFilterLoader: BloomFilterLoader + let inventoryItemsHandlerChain = InventoryItemsHandlerChain() + let peerTaskHandlerChain = PeerTaskHandlerChain() + + public func add(inventoryItemsHandler: IInventoryItemsHandler) { + inventoryItemsHandlerChain.add(handler: inventoryItemsHandler) + } + + public func add(peerTaskHandler: IPeerTaskHandler) { + peerTaskHandlerChain.add(handler: peerTaskHandler) + } + + public func add(restoreKeyConverter: IRestoreKeyConverter) { + restoreKeyConverterChain.add(converter: restoreKeyConverter) + } + + @discardableResult public func add(messageParser: IMessageParser) -> Self { + networkMessageParser.add(parser: messageParser) + return self + } + + @discardableResult public func add(messageSerializer: IMessageSerializer) -> Self { + networkMessageSerializer.add(serializer: messageSerializer) + return self + } + + public func add(plugin: IPlugin) { + pluginManager.add(plugin: plugin) + } + + func publicKey(byPath path: String) throws -> PublicKey { + try publicKeyManager.publicKey(byPath: path) + } + + public func prepend(addressConverter: IAddressConverter) { + self.addressConverter.prepend(addressConverter: addressConverter) + } + + public func prepend(unspentOutputSelector: IUnspentOutputSelector) { + self.unspentOutputSelector.prepend(unspentOutputSelector: unspentOutputSelector) + } + + // END: Extending + + public var delegateQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.bitcoin-core-delegate-queue") + public weak var delegate: BitcoinCoreDelegate? + + init(storage: IStorage, dataProvider: IDataProvider, + peerGroup: IPeerGroup, initialBlockDownload: IInitialBlockDownload, bloomFilterLoader: BloomFilterLoader, transactionSyncer: ITransactionSyncer, + publicKeyManager: IPublicKeyManager, addressConverter: AddressConverterChain, restoreKeyConverterChain: RestoreKeyConverterChain, + unspentOutputSelector: UnspentOutputSelectorChain, + transactionCreator: ITransactionCreator, transactionFeeCalculator: ITransactionFeeCalculator, dustCalculator: IDustCalculator, + paymentAddressParser: IPaymentAddressParser, networkMessageParser: NetworkMessageParser, networkMessageSerializer: NetworkMessageSerializer, + syncManager: SyncManager, pluginManager: IPluginManager, watchedTransactionManager: IWatchedTransactionManager, bip: Bip, + peerManager: IPeerManager) { + self.storage = storage + self.dataProvider = dataProvider + self.peerGroup = peerGroup + self.initialBlockDownload = initialBlockDownload + self.bloomFilterLoader = bloomFilterLoader + self.transactionSyncer = transactionSyncer + self.publicKeyManager = publicKeyManager + self.addressConverter = addressConverter + self.restoreKeyConverterChain = restoreKeyConverterChain + self.unspentOutputSelector = unspentOutputSelector + self.transactionCreator = transactionCreator + self.transactionFeeCalculator = transactionFeeCalculator + self.dustCalculator = dustCalculator + self.paymentAddressParser = paymentAddressParser + + self.networkMessageParser = networkMessageParser + self.networkMessageSerializer = networkMessageSerializer + + self.syncManager = syncManager + self.pluginManager = pluginManager + self.watchedTransactionManager = watchedTransactionManager + self.bip = bip + + self.peerManager = peerManager + } + +} + +extension BitcoinCore { + + public func start() { + syncManager.start() + } + + func stop() { + syncManager.stop() + } + +} + +extension BitcoinCore { + + public var lastBlockInfo: BlockInfo? { + dataProvider.lastBlockInfo + } + + public var balance: BalanceInfo { + dataProvider.balance + } + + public var syncState: BitcoinCore.KitState { + syncManager.syncState + } + + public func transactions(fromUid: String? = nil, type: TransactionFilterType?, limit: Int? = nil) -> Single<[TransactionInfo]> { + dataProvider.transactions(fromUid: fromUid, type: type, limit: limit) + } + + public func transaction(hash: String) -> TransactionInfo? { + dataProvider.transaction(hash: hash) + } + + public func send(to address: String, value: Int, feeRate: Int, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) throws -> FullTransaction { + try transactionCreator.create(to: address, value: value, feeRate: feeRate, senderPay: true, sortType: sortType, pluginData: pluginData) + } + + public func send(to hash: Data, scriptType: ScriptType, value: Int, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction { + let toAddress = try addressConverter.convert(keyHash: hash, type: scriptType) + return try transactionCreator.create(to: toAddress.stringValue, value: value, feeRate: feeRate, senderPay: true, sortType: sortType, pluginData: [:]) + } + + func redeem(from unspentOutput: UnspentOutput, to address: String, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction { + try transactionCreator.create(from: unspentOutput, to: address, feeRate: feeRate, sortType: sortType) + } + + public func createRawTransaction(to address: String, value: Int, feeRate: Int, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) throws -> Data { + try transactionCreator.createRawTransaction(to: address, value: value, feeRate: feeRate, senderPay: true, sortType: sortType, pluginData: pluginData) + } + + public func validate(address: String, pluginData: [UInt8: IPluginData] = [:]) throws { + try pluginManager.validate(address: try addressConverter.convert(address: address), pluginData: pluginData) + } + + public func parse(paymentAddress: String) -> BitcoinPaymentData { + paymentAddressParser.parse(paymentAddress: paymentAddress) + } + + public func fee(for value: Int, toAddress: String? = nil, feeRate: Int, pluginData: [UInt8: IPluginData] = [:]) throws -> Int { + try transactionFeeCalculator.fee(for: value, feeRate: feeRate, senderPay: true, toAddress: toAddress, pluginData: pluginData) + } + + public func maxSpendableValue(toAddress: String? = nil, feeRate: Int, pluginData: [UInt8: IPluginData] = [:]) throws -> Int { + let sendAllFee = try transactionFeeCalculator.fee(for: balance.spendable, feeRate: feeRate, senderPay: false, toAddress: toAddress, pluginData: pluginData) + return max(0, balance.spendable - sendAllFee) + } + + public func minSpendableValue(toAddress: String? = nil) -> Int { + var scriptType = ScriptType.p2pkh + if let addressStr = toAddress, let address = try? addressConverter.convert(address: addressStr) { + scriptType = address.scriptType + } + + return dustCalculator.dust(type: scriptType) + } + + public func maxSpendLimit(pluginData: [UInt8: IPluginData]) throws -> Int? { + try pluginManager.maxSpendLimit(pluginData: pluginData) + } + + public func receiveAddress() -> String { + guard let publicKey = try? publicKeyManager.receivePublicKey(), + let address = try? addressConverter.convert(publicKey: publicKey, type: bip.scriptType) else { + return "" + } + + return address.stringValue + } + + public func changePublicKey() throws -> PublicKey { + try publicKeyManager.changePublicKey() + } + + public func receivePublicKey() throws -> PublicKey { + try publicKeyManager.receivePublicKey() + } + + func watch(transaction: BitcoinCore.TransactionFilter, delegate: IWatchedTransactionDelegate) { + watchedTransactionManager.add(transactionFilter: transaction, delegatedTo: delegate) + } + + public func debugInfo(network: INetwork) -> String { + dataProvider.debugInfo(network: network, scriptType: bip.scriptType, addressConverter: addressConverter) + } + + public var statusInfo: [(String, Any)] { + var status = [(String, Any)]() + status.append(("state", syncManager.syncState.toString())) + status.append(("synced until", ((lastBlockInfo?.timestamp.map { Double($0) })?.map { Date(timeIntervalSince1970: $0) }) ?? "n/a")) + status.append(("syncing peer", initialBlockDownload.syncPeer?.host ?? "n/a")) + status.append(("derivation", bip.description)) + + status.append(contentsOf: + peerManager.connected.enumerated().map { (index, peer) in + var peerStatus = [(String, Any)]() + peerStatus.append(("status", initialBlockDownload.isSynced(peer: peer) ? "synced" : "not synced")) + peerStatus.append(("host", peer.host)) + peerStatus.append(("best block", peer.announcedLastBlockHeight)) + + let tasks = peer.tasks + if tasks.isEmpty { + peerStatus.append(("tasks", "no tasks")) + } else { + peerStatus.append(("tasks", tasks.map { task in + (String(describing: task), task.state) + })) + } + + return ("peer \(index + 1)", peerStatus) + } + ) + + return status + } + + func rawTransaction(transactionHash: String) -> String? { + dataProvider.rawTransaction(transactionHash: transactionHash) + } + +} + +extension BitcoinCore: IDataProviderDelegate { + + func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) { + delegateQueue.async { [weak self] in + if let kit = self { + kit.delegate?.transactionsUpdated(inserted: inserted, updated: updated) + } + } + } + + func transactionsDeleted(hashes: [String]) { + delegateQueue.async { [weak self] in + self?.delegate?.transactionsDeleted(hashes: hashes) + } + } + + func balanceUpdated(balance: BalanceInfo) { + delegateQueue.async { [weak self] in + if let kit = self { + kit.delegate?.balanceUpdated(balance: balance) + } + } + } + + func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) { + delegateQueue.async { [weak self] in + if let kit = self { + kit.delegate?.lastBlockInfoUpdated(lastBlockInfo: lastBlockInfo) + } + } + } + +} + +extension BitcoinCore: ISyncManagerDelegate { + func kitStateUpdated(state: KitState) { + delegateQueue.async { [weak self] in + self?.delegate?.kitStateUpdated(state: state) + } + } +} + +public protocol BitcoinCoreDelegate: class { + func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) + func transactionsDeleted(hashes: [String]) + func balanceUpdated(balance: BalanceInfo) + func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) + func kitStateUpdated(state: BitcoinCore.KitState) +} + +extension BitcoinCoreDelegate { + + public func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) {} + public func transactionsDeleted(hashes: [String]) {} + public func balanceUpdated(balance: BalanceInfo) {} + public func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) {} + public func kitStateUpdated(state: BitcoinCore.KitState) {} + +} + +extension BitcoinCore { + + public enum KitState { + case synced + case apiSyncing(transactions: Int) + case syncing(progress: Double) + case notSynced(error: Error) + + func toString() -> String { + switch self { + case .synced: return "Synced" + case .apiSyncing(let transactions): return "ApiSyncing-\(transactions)" + case .syncing(let progress): return "Syncing-\(Int(progress * 100))" + case .notSynced(let error): return "NotSynced-\(String(reflecting: error))" + } + } + } + + public enum SyncMode: Equatable { + case full // Sync from bip44Checkpoint. Api restore disabled + case fromDate(date: TimeInterval) // Sync from given date. Api restore disable + case api // Sync from lastCheckpoint. Api restore enabled + case newWallet // Sync from lastCheckpoint. Api restore enabled + } + + public enum TransactionFilter { + case p2shOutput(scriptHash: Data) + case outpoint(transactionHash: Data, outputIndex: Int) + } + +} + +extension BitcoinCore.KitState: Equatable { + + public static func == (lhs: BitcoinCore.KitState, rhs: BitcoinCore.KitState) -> Bool { + switch (lhs, rhs) { + case (.synced, .synced): + return true + case (.apiSyncing(transactions: let leftCount), .apiSyncing(transactions: let rightCount)): + return leftCount == rightCount + case (.syncing(progress: let leftProgress), .syncing(progress: let rightProgress)): + return leftProgress == rightProgress + case (.notSynced(let lhsError), .notSynced(let rhsError)): + return "\(lhsError)" == "\(rhsError)" + default: + return false + } + } + +} + +extension BitcoinCore { + + public enum StateError: Error { + case notStarted + } + +} diff --git a/BitcoinCore/BitcoinCore/Core/BitcoinCoreBuilder.swift b/BitcoinCore/Classes/Core/BitcoinCoreBuilder.swift similarity index 53% rename from BitcoinCore/BitcoinCore/Core/BitcoinCoreBuilder.swift rename to BitcoinCore/Classes/Core/BitcoinCoreBuilder.swift index 363f875d..1e99ce50 100644 --- a/BitcoinCore/BitcoinCore/Core/BitcoinCoreBuilder.swift +++ b/BitcoinCore/Classes/Core/BitcoinCoreBuilder.swift @@ -1,21 +1,25 @@ import Foundation -import HSHDWalletKit +import HdWalletKit +import HsToolKit public class BitcoinCoreBuilder { - public enum BuildError: Error { case noSeedData, noWalletId, noNetwork, noPaymentAddressParser, noAddressSelector, noStorage, noInitialSyncApi } + public enum BuildError: Error { case peerSizeLessThanRequired, noSeedData, noWalletId, noNetwork, noPaymentAddressParser, noAddressSelector, noStorage, noInitialSyncApi } + + // chains + public let addressConverter = AddressConverterChain() // required parameters private var seed: Data? - private var words: [String]? + private var bip: Bip = .bip44 private var network: INetwork? private var paymentAddressParser: IPaymentAddressParser? - private var addressSelector: IAddressSelector? - private var addressKeyHashConverter: IAddressKeyHashConverter? private var walletId: String? private var initialSyncApi: ISyncTransactionApi? + private var plugins = [IPlugin]() private var logger: Logger private var blockHeaderHasher: IHasher? + private var blockValidator: IBlockValidator? private var transactionInfoConverter: ITransactionInfoConverter? // parameters with default values @@ -31,8 +35,8 @@ public class BitcoinCoreBuilder { return self } - public func set(words: [String]) -> BitcoinCoreBuilder { - self.words = words + public func set(bip: Bip) -> BitcoinCoreBuilder { + self.bip = bip return self } @@ -46,16 +50,6 @@ public class BitcoinCoreBuilder { return self } - public func set(addressSelector: IAddressSelector) -> BitcoinCoreBuilder { - self.addressSelector = addressSelector - return self - } - - public func set(addressKeyHashConverter: IAddressKeyHashConverter) -> BitcoinCoreBuilder { - self.addressKeyHashConverter = addressKeyHashConverter - return self - } - public func set(walletId: String) -> BitcoinCoreBuilder { self.walletId = walletId return self @@ -71,7 +65,11 @@ public class BitcoinCoreBuilder { return self } - public func set(peerSize: Int) -> BitcoinCoreBuilder { + public func set(peerSize: Int) throws -> BitcoinCoreBuilder { + guard peerSize >= TransactionSender.minConnectedPeersCount else { + throw BuildError.peerSizeLessThanRequired + } + self.peerCount = peerSize return self } @@ -86,6 +84,11 @@ public class BitcoinCoreBuilder { return self } + public func set(blockValidator: IBlockValidator) -> BitcoinCoreBuilder { + self.blockValidator = blockValidator + return self + } + public func set(transactionInfoConverter: ITransactionInfoConverter) -> BitcoinCoreBuilder { self.transactionInfoConverter = transactionInfoConverter return self @@ -96,31 +99,28 @@ public class BitcoinCoreBuilder { return self } - public init(minLogLevel: Logger.Level = .verbose) { - self.logger = Logger(network: network, minLogLevel: minLogLevel) + public func add(plugin: IPlugin) -> BitcoinCoreBuilder { + plugins.append(plugin) + return self + } + + public init(logger: Logger) { + self.logger = logger } public func build() throws -> BitcoinCore { let seed: Data if let selfSeed = self.seed { seed = selfSeed - } else if let words = self.words { - seed = Mnemonic.seed(mnemonic: words) } else { throw BuildError.noSeedData } -// guard let walletId = self.walletId else { -// throw BuildError.noWalletId -// } guard let network = self.network else { throw BuildError.noNetwork } guard let paymentAddressParser = self.paymentAddressParser else { throw BuildError.noPaymentAddressParser } - guard let addressSelector = self.addressSelector else { - throw BuildError.noAddressSelector - } guard let storage = self.storage else { throw BuildError.noStorage } @@ -128,20 +128,21 @@ public class BitcoinCoreBuilder { throw BuildError.noInitialSyncApi } - let addressConverter = AddressConverterChain() + let scriptConverter = ScriptConverter() + let restoreKeyConverterChain = RestoreKeyConverterChain() + let pluginManager = PluginManager(scriptConverter: scriptConverter, logger: logger) + + plugins.forEach { pluginManager.add(plugin: $0) } + restoreKeyConverterChain.add(converter: pluginManager) -// let dbName = "bitcoinkit-${network.javaClass}-$walletId" -// let database = KitDatabase.getInstance(context, dbName) -// let realmFactory = RealmFactory(dbName) -// let storage = Storage(database, realmFactory) -// - let unspentOutputProvider = UnspentOutputProvider(storage: storage, confirmationsThreshold: confirmationsThreshold) - let transactionInfoConverter = self.transactionInfoConverter ?? TransactionInfoConverter(baseTransactionInfoConverter: BaseTransactionInfoConverter()) - let dataProvider = DataProvider(storage: storage, unspentOutputProvider: unspentOutputProvider, transactionInfoConverter: transactionInfoConverter) + let unspentOutputProvider = UnspentOutputProvider(storage: storage, pluginManager: pluginManager, confirmationsThreshold: confirmationsThreshold) + var transactionInfoConverter = self.transactionInfoConverter ?? TransactionInfoConverter() + transactionInfoConverter.baseTransactionInfoConverter = BaseTransactionInfoConverter(pluginManager: pluginManager) + let dataProvider = DataProvider(storage: storage, balanceProvider: unspentOutputProvider, transactionInfoConverter: transactionInfoConverter) let reachabilityManager = ReachabilityManager() - let hdWallet = HDWallet(seed: seed, coinType: network.coinType, xPrivKey: network.xPrivKey, xPubKey: network.xPubKey, gapLimit: 20) + let hdWallet = HDWallet(seed: seed, coinType: network.coinType, xPrivKey: network.xPrivKey, xPubKey: network.xPubKey, gapLimit: 20, purpose: bip.purpose) let networkMessageParser = NetworkMessageParser(magic: network.magic) let networkMessageSerializer = NetworkMessageSerializer(magic: network.magic) @@ -152,94 +153,115 @@ public class BitcoinCoreBuilder { let factory = Factory(network: network, networkMessageParser: networkMessageParser, networkMessageSerializer: networkMessageSerializer) - let addressManager = AddressManager.instance(storage: storage, hdWallet: hdWallet, addressConverter: addressConverter, addressKeyHashConverter: addressKeyHashConverter) + let publicKeyManager = PublicKeyManager.instance(storage: storage, hdWallet: hdWallet, restoreKeyConverter: restoreKeyConverterChain) + let pendingOutpointsProvider = PendingOutpointsProvider(storage: storage) - let myOutputsCache = OutputsCache.instance(storage: storage) - let scriptConverter = ScriptConverter() + let transactionMetadataExtractor = TransactionMetadataExtractor(storage: storage) + let irregularOutputFinder = IrregularOutputFinder(storage: storage) let transactionInputExtractor = TransactionInputExtractor(storage: storage, scriptConverter: scriptConverter, addressConverter: addressConverter, logger: logger) let transactionKeySetter = TransactionPublicKeySetter(storage: storage) - let transactionOutputExtractor = TransactionOutputExtractor(transactionKeySetter: transactionKeySetter, logger: logger) + let transactionOutputExtractor = TransactionOutputExtractor(transactionKeySetter: transactionKeySetter, pluginManager: pluginManager, logger: logger) let transactionAddressExtractor = TransactionOutputAddressExtractor(storage: storage, addressConverter: addressConverter) - let transactionProcessor = TransactionProcessor(storage: storage, - outputExtractor: transactionOutputExtractor, inputExtractor: transactionInputExtractor, - outputsCache: myOutputsCache, outputAddressExtractor: transactionAddressExtractor, - addressManager: addressManager, listener: dataProvider) - - let kitStateProvider = KitStateProvider() + let transactionExtractor = TransactionExtractor(outputExtractor: transactionOutputExtractor, inputExtractor: transactionInputExtractor, metaDataExtractor: transactionMetadataExtractor, outputAddressExtractor: transactionAddressExtractor) + let transactionInvalidator = TransactionInvalidator(storage: storage, transactionInfoConverter: transactionInfoConverter, listener: dataProvider) + let transactionConflictResolver = TransactionConflictsResolver(storage: storage) + let transactionsProcessorQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.transaction-processor", qos: .background) + let blockTransactionProcessor = BlockTransactionProcessor(storage: storage, extractor: transactionExtractor, publicKeyManager: publicKeyManager, irregularOutputFinder: irregularOutputFinder, conflictsResolver: transactionConflictResolver, invalidator: transactionInvalidator, listener: dataProvider, queue: transactionsProcessorQueue) + let pendingTransactionProcessor = PendingTransactionProcessor(storage: storage, extractor: transactionExtractor, publicKeyManager: publicKeyManager, irregularOutputFinder: irregularOutputFinder, conflictsResolver: transactionConflictResolver, listener: dataProvider, queue: transactionsProcessorQueue) let peerDiscovery = PeerDiscovery() let peerAddressManager = PeerAddressManager(storage: storage, dnsSeeds: network.dnsSeeds, peerDiscovery: peerDiscovery, logger: logger) peerDiscovery.peerAddressManager = peerAddressManager - let bloomFilterManager = BloomFilterManager(storage: storage, factory: factory) + let bloomFilterManager = BloomFilterManager(factory: factory) let peerManager = PeerManager() let unspentOutputSelector = UnspentOutputSelectorChain() - let transactionSyncer = TransactionSyncer(storage: storage, processor: transactionProcessor, addressManager: addressManager, bloomFilterManager: bloomFilterManager) - let mempoolTransactions = MempoolTransactions(transactionSyncer: transactionSyncer) + let transactionSyncer = TransactionSyncer(storage: storage, processor: pendingTransactionProcessor, invalidator: transactionInvalidator, publicKeyManager: publicKeyManager) + + let checkpoint = BlockSyncer.resolveCheckpoint(network: network, syncMode: syncMode, storage: storage) - let blockHashFetcher = BlockHashFetcher(addressSelector: addressSelector, apiManager: initialSyncApi, addressConverter: addressConverter, helper: BlockHashFetcherHelper()) - let blockDiscovery = BlockDiscoveryBatch(network: network, wallet: hdWallet, blockHashFetcher: blockHashFetcher, logger: logger) + let blockHashFetcher = BlockHashFetcher(restoreKeyConverter: restoreKeyConverterChain, apiManager: initialSyncApi, helper: BlockHashFetcherHelper()) + let blockDiscovery = BlockDiscoveryBatch(checkpoint: checkpoint, wallet: hdWallet, blockHashFetcher: blockHashFetcher, logger: logger) - let stateManager = StateManager(storage: storage, restoreFromApi: network.syncableFromApi && syncMode == BitcoinCore.SyncMode.api) + let stateManager = ApiSyncStateManager(storage: storage, restoreFromApi: network.syncableFromApi && syncMode == BitcoinCore.SyncMode.api) - let initialSyncer = InitialSyncer(storage: storage, listener: kitStateProvider, stateManager: stateManager, blockDiscovery: blockDiscovery, addressManager: addressManager, logger: logger) + let initialSyncer = InitialSyncer(storage: storage, blockDiscovery: blockDiscovery, publicKeyManager: publicKeyManager, logger: logger) let bloomFilterLoader = BloomFilterLoader(bloomFilterManager: bloomFilterManager, peerManager: peerManager) + let watchedTransactionManager = WatchedTransactionManager() - let blockValidatorChain = BlockValidatorChain(proofOfWorkValidator: ProofOfWorkValidator(difficultyEncoder: DifficultyEncoder())) - let blockchain = Blockchain(storage: storage, blockValidator: blockValidatorChain, factory: factory, listener: dataProvider) - let checkpointBlock = syncMode == .full ? network.bip44CheckpointBlock : network.lastCheckpointBlock - let blockSyncer = BlockSyncer.instance(storage: storage, checkpointBlock: checkpointBlock, factory: factory, listener: kitStateProvider, transactionProcessor: transactionProcessor, blockchain: blockchain, addressManager: addressManager, bloomFilterManager: bloomFilterManager, logger: logger) - let initialBlockDownload = InitialBlockDownload(blockSyncer: blockSyncer, peerManager: peerManager, merkleBlockValidator: merkleBlockValidator, syncStateListener: kitStateProvider, logger: logger) + let blockchain = Blockchain(storage: storage, blockValidator: blockValidator, factory: factory, listener: dataProvider) + let blockSyncer = BlockSyncer.instance(storage: storage, checkpoint: checkpoint, factory: factory, transactionProcessor: blockTransactionProcessor, blockchain: blockchain, publicKeyManager: publicKeyManager, logger: logger) + let initialBlockDownload = InitialBlockDownload(blockSyncer: blockSyncer, peerManager: peerManager, merkleBlockValidator: merkleBlockValidator, logger: logger) let peerGroup = PeerGroup(factory: factory, reachabilityManager: reachabilityManager, peerAddressManager: peerAddressManager, peerCount: peerCount, localDownloadedBestBlockHeight: blockSyncer.localDownloadedBestBlockHeight, peerManager: peerManager, logger: logger) - let syncedReadyPeerManager = SyncedReadyPeerManager(peerGroup: peerGroup, initialBlockDownload: initialBlockDownload) - let inputSigner = InputSigner(hdWallet: hdWallet, network: network) - let scriptBuilder = ScriptBuilderChain() - let transactionBuilder = TransactionBuilder(unspentOutputSelector: unspentOutputSelector, unspentOutputProvider: unspentOutputProvider, addressManager: addressManager, addressConverter: addressConverter, inputSigner: inputSigner, scriptBuilder: scriptBuilder, factory: factory) - let transactionSender = TransactionSender(transactionSyncer: transactionSyncer, peerManager: peerManager, initialBlockDownload: initialBlockDownload, syncedReadyPeerManager: syncedReadyPeerManager, logger: logger) - let transactionCreator = TransactionCreator(transactionBuilder: transactionBuilder, transactionProcessor: transactionProcessor, transactionSender: transactionSender) + let transactionDataSorterFactory = TransactionDataSorterFactory() - let syncManager = SyncManager(reachabilityManager: reachabilityManager, initialSyncer: initialSyncer, peerGroup: peerGroup) + let inputSigner = InputSigner(hdWallet: hdWallet, network: network) + let transactionSizeCalculator = TransactionSizeCalculator() + let dustCalculator = DustCalculator(dustRelayTxFee: network.dustRelayTxFee, sizeCalculator: transactionSizeCalculator) + let recipientSetter = RecipientSetter(addressConverter: addressConverter, pluginManager: pluginManager) + let outputSetter = OutputSetter(outputSorterFactory: transactionDataSorterFactory, factory: factory) + let inputSetter = InputSetter(unspentOutputSelector: unspentOutputSelector, transactionSizeCalculator: transactionSizeCalculator, addressConverter: addressConverter, publicKeyManager: publicKeyManager, factory: factory, pluginManager: pluginManager, dustCalculator: dustCalculator, changeScriptType: bip.scriptType, inputSorterFactory: transactionDataSorterFactory) + let lockTimeSetter = LockTimeSetter(storage: storage) + let transactionSigner = TransactionSigner(inputSigner: inputSigner) + let transactionBuilder = TransactionBuilder(recipientSetter: recipientSetter, inputSetter: inputSetter, lockTimeSetter: lockTimeSetter, outputSetter: outputSetter, signer: transactionSigner) + let transactionFeeCalculator = TransactionFeeCalculator(recipientSetter: recipientSetter, inputSetter: inputSetter, addressConverter: addressConverter, publicKeyManager: publicKeyManager, changeScriptType: bip.scriptType) + let transactionSendTimer = TransactionSendTimer(interval: 60) + let transactionSender = TransactionSender(transactionSyncer: transactionSyncer, initialBlockDownload: initialBlockDownload, peerManager: peerManager, storage: storage, timer: transactionSendTimer, logger: logger) + let transactionCreator = TransactionCreator(transactionBuilder: transactionBuilder, transactionProcessor: pendingTransactionProcessor, transactionSender: transactionSender, bloomFilterManager: bloomFilterManager) + let mempoolTransactions = MempoolTransactions(transactionSyncer: transactionSyncer, transactionSender: transactionSender) + + let syncManager = SyncManager(reachabilityManager: reachabilityManager, initialSyncer: initialSyncer, peerGroup: peerGroup, apiSyncStateManager: stateManager, bestBlockHeight: blockSyncer.localDownloadedBestBlockHeight) let bitcoinCore = BitcoinCore(storage: storage, - cache: myOutputsCache, dataProvider: dataProvider, peerGroup: peerGroup, initialBlockDownload: initialBlockDownload, bloomFilterLoader: bloomFilterLoader, - syncedReadyPeerManager: syncedReadyPeerManager, transactionSyncer: transactionSyncer, - blockValidatorChain: blockValidatorChain, - addressManager: addressManager, + publicKeyManager: publicKeyManager, addressConverter: addressConverter, + restoreKeyConverterChain: restoreKeyConverterChain, unspentOutputSelector: unspentOutputSelector, - kitStateProvider: kitStateProvider, - scriptBuilder: scriptBuilder, - transactionBuilder: transactionBuilder, transactionCreator: transactionCreator, + transactionFeeCalculator: transactionFeeCalculator, + dustCalculator: dustCalculator, paymentAddressParser: paymentAddressParser, networkMessageParser: networkMessageParser, networkMessageSerializer: networkMessageSerializer, - syncManager: syncManager) + syncManager: syncManager, + pluginManager: pluginManager, + watchedTransactionManager: watchedTransactionManager, + bip: bip, + peerManager: peerManager) initialSyncer.delegate = syncManager + blockSyncer.listener = syncManager + initialBlockDownload.listener = syncManager + blockHashFetcher.listener = syncManager + bloomFilterManager.delegate = bloomFilterLoader dataProvider.delegate = bitcoinCore - kitStateProvider.delegate = bitcoinCore + syncManager.delegate = bitcoinCore + blockTransactionProcessor.transactionListener = watchedTransactionManager + pendingTransactionProcessor.transactionListener = watchedTransactionManager + transactionSendTimer.delegate = transactionSender + + bloomFilterManager.add(provider: watchedTransactionManager) + bloomFilterManager.add(provider: publicKeyManager) + bloomFilterManager.add(provider: pendingOutpointsProvider) + bloomFilterManager.add(provider: irregularOutputFinder) peerGroup.peerTaskHandler = bitcoinCore.peerTaskHandlerChain peerGroup.inventoryItemsHandler = bitcoinCore.inventoryItemsHandlerChain - bitcoinCore.prepend(scriptBuilder: ScriptBuilder()) bitcoinCore.prepend(addressConverter: Base58AddressConverter(addressVersion: network.pubKeyHash, addressScriptVersion: network.scriptHash)) - - let transactionSizeCalculator = TransactionSizeCalculator() - bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelector(calculator: transactionSizeCalculator, provider: unspentOutputProvider)) - bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelectorSingleNoChange(calculator: transactionSizeCalculator, provider: unspentOutputProvider)) + bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelector(calculator: transactionSizeCalculator, provider: unspentOutputProvider, dustCalculator: dustCalculator)) + bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelectorSingleNoChange(calculator: transactionSizeCalculator, provider: unspentOutputProvider, dustCalculator: dustCalculator)) // this part can be moved to another place let blockHeaderParser = BlockHeaderParser(hasher: blockHeaderHasher ?? doubleShaHasher) @@ -267,17 +289,14 @@ public class BitcoinCoreBuilder { bloomFilterLoader.subscribeTo(observable: peerGroup.observable) initialBlockDownload.subscribeTo(observable: peerGroup.observable) - syncedReadyPeerManager.subscribeTo(observable: peerGroup.observable) mempoolTransactions.subscribeTo(observable: peerGroup.observable) - bitcoinCore.add(peerTaskHandler: initialBlockDownload) bitcoinCore.add(inventoryItemsHandler: initialBlockDownload) - syncedReadyPeerManager.subscribeTo(observable: initialBlockDownload.observable) - transactionSender.subscribeTo(observable: syncedReadyPeerManager.observable) - + transactionSender.subscribeTo(observable: initialBlockDownload.observable) + bitcoinCore.add(peerTaskHandler: transactionSender) bitcoinCore.add(peerTaskHandler: mempoolTransactions) bitcoinCore.add(inventoryItemsHandler: mempoolTransactions) diff --git a/BitcoinCore/BitcoinCore/Core/BitcoinCoreErrors.swift b/BitcoinCore/Classes/Core/BitcoinCoreErrors.swift similarity index 90% rename from BitcoinCore/BitcoinCore/Core/BitcoinCoreErrors.swift rename to BitcoinCore/Classes/Core/BitcoinCoreErrors.swift index 62a70e84..769da201 100644 --- a/BitcoinCore/BitcoinCore/Core/BitcoinCoreErrors.swift +++ b/BitcoinCore/Classes/Core/BitcoinCoreErrors.swift @@ -50,14 +50,15 @@ public class BitcoinCoreErrors { let errors: [Error] } - public enum UnspentOutputSelection: Error { - case wrongValue + public enum SendValueErrors: Error { + case dust case emptyOutputs - case notEnough(maxFee: Int) + case singleNoChangeOutputNotFound + case notEnough } public enum Unexpected: Error { - case unkown + case unknown } diff --git a/BitcoinCore/BitcoinCore/Core/BloomFilterLoader.swift b/BitcoinCore/Classes/Core/BloomFilterLoader.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Core/BloomFilterLoader.swift rename to BitcoinCore/Classes/Core/BloomFilterLoader.swift index 0f657426..3073ff59 100644 --- a/BitcoinCore/BitcoinCore/Core/BloomFilterLoader.swift +++ b/BitcoinCore/Classes/Core/BloomFilterLoader.swift @@ -17,7 +17,7 @@ class BloomFilterLoader: IBloomFilterManagerDelegate { } func bloomFilterUpdated(bloomFilter: BloomFilter) { - for peer in peerManager.connected() { + for peer in peerManager.connected { peer.filterLoad(bloomFilter: bloomFilter) } } diff --git a/BitcoinCore/BitcoinCore/Core/DataProvider.swift b/BitcoinCore/Classes/Core/DataProvider.swift similarity index 59% rename from BitcoinCore/BitcoinCore/Core/DataProvider.swift rename to BitcoinCore/Classes/Core/DataProvider.swift index 20aaf588..164f031f 100644 --- a/BitcoinCore/BitcoinCore/Core/DataProvider.swift +++ b/BitcoinCore/Classes/Core/DataProvider.swift @@ -1,19 +1,19 @@ import Foundation -import HSHDWalletKit +import HdWalletKit import RxSwift import BigInt -import HSCryptoKit +import UIExtensions class DataProvider { private let disposeBag = DisposeBag() private let storage: IStorage - private let unspentOutputProvider: IUnspentOutputProvider + private let balanceProvider: IBalanceProvider private let transactionInfoConverter: ITransactionInfoConverter private let balanceUpdateSubject = PublishSubject() - public var balance: Int = 0 { + public var balance: BalanceInfo { didSet { if !(oldValue == balance) { delegate?.balanceUpdated(balance: balance) @@ -24,20 +24,20 @@ class DataProvider { weak var delegate: IDataProviderDelegate? - init(storage: IStorage, unspentOutputProvider: IUnspentOutputProvider, transactionInfoConverter: ITransactionInfoConverter, throttleTimeMilliseconds: Int = 500) { + init(storage: IStorage, balanceProvider: IBalanceProvider, transactionInfoConverter: ITransactionInfoConverter, throttleTimeMilliseconds: Int = 500) { self.storage = storage - self.unspentOutputProvider = unspentOutputProvider + self.balanceProvider = balanceProvider self.transactionInfoConverter = transactionInfoConverter - self.balance = unspentOutputProvider.allUnspentOutputs.map { $0.output.value }.reduce(0, +) + self.balance = balanceProvider.balanceInfo self.lastBlockInfo = storage.lastBlock.map { blockInfo(fromBlock: $0) } balanceUpdateSubject.throttle(DispatchTimeInterval.milliseconds(throttleTimeMilliseconds), scheduler: ConcurrentDispatchQueueScheduler(qos: .background)).subscribe(onNext: { [weak self] in - self?.balance = unspentOutputProvider.allUnspentOutputs.map { $0.output.value }.reduce(0, +) + self?.balance = balanceProvider.balanceInfo }).disposed(by: disposeBag) } private func blockInfo(fromBlock block: Block) -> BlockInfo { - return BlockInfo( + BlockInfo( headerHash: block.headerHash.reversedHex, height: block.height, timestamp: block.timestamp @@ -49,12 +49,6 @@ class DataProvider { extension DataProvider: IBlockchainDataListener { func onUpdate(updated: [Transaction], inserted: [Transaction], inBlock block: Block?) { - var blocks = [Block]() - - if let block = block { - blocks.append(block) - } - delegate?.transactionsUpdated( inserted: storage.fullInfo(forTransactions: inserted.map { TransactionWithBlock(transaction: $0, blockHeight: block?.height) }).map { transactionInfoConverter.transactionInfo(fromTransaction: $0) }, updated: storage.fullInfo(forTransactions: updated.map { TransactionWithBlock(transaction: $0, blockHeight: block?.height) }).map { transactionInfoConverter.transactionInfo(fromTransaction: $0) } @@ -83,33 +77,54 @@ extension DataProvider: IBlockchainDataListener { extension DataProvider: IDataProvider { - func transactions(fromHash: String?, limit: Int?) -> Single<[TransactionInfo]> { - return Single.create { observer in - var fromTimestamp: Int? = nil - var fromOrder: Int? = nil + func transactions(fromUid: String?, type: TransactionFilterType?, limit: Int?) -> Single<[TransactionInfo]> { + Single.create { observer in + var resolvedTimestamp: Int? = nil + var resolvedOrder: Int? = nil - if let fromHash = fromHash, let fromHashData = Data(hex: fromHash), let fromTransaction = self.storage.transaction(byHash: Data(fromHashData.reversed())) { - fromTimestamp = fromTransaction.timestamp - fromOrder = fromTransaction.order + if let fromUid = fromUid, let transaction = self.storage.validOrInvalidTransaction(byUid: fromUid) { + resolvedTimestamp = transaction.timestamp + resolvedOrder = transaction.order } - let transactions = self.storage.fullTransactionsInfo(fromTimestamp: fromTimestamp, fromOrder: fromOrder, limit: limit) + let transactions = self.storage.validOrInvalidTransactionsFullInfo(fromTimestamp: resolvedTimestamp, fromOrder: resolvedOrder, type: type, limit: limit) observer(.success(transactions.map() { self.transactionInfoConverter.transactionInfo(fromTransaction: $0) })) return Disposables.create() } } - var debugInfo: String { + func transaction(hash: String) -> TransactionInfo? { + guard let hash = hash.reversedData else { + return nil + } + + guard let transactionFullInfo = storage.transactionFullInfo(byHash: hash) else { + return nil + } + + return transactionInfoConverter.transactionInfo(fromTransaction: transactionFullInfo) + } + + func debugInfo(network: INetwork, scriptType: ScriptType, addressConverter: IAddressConverter) -> String { var lines = [String]() let pubKeys = storage.publicKeys().sorted(by: { $0.index < $1.index }) for pubKey in pubKeys { - lines.append("acc: \(pubKey.account) - inx: \(pubKey.index) - ext: \(pubKey.external) : \((try! Base58AddressConverter(addressVersion: 0x00, addressScriptVersion: 0x05).convert(keyHash: pubKey.keyHash, type: .p2pkh)).stringValue)") + lines.append("acc: \(pubKey.account) - inx: \(pubKey.index) - ext: \(pubKey.external) : \((try! addressConverter.convert(publicKey: pubKey, type: scriptType)).stringValue)") } lines.append("PUBLIC KEYS COUNT: \(pubKeys.count)") return lines.joined(separator: "\n") } + func rawTransaction(transactionHash: String) -> String? { + guard let hash = transactionHash.reversedData else { + return nil + } + + return storage.transactionFullInfo(byHash: hash)?.rawTransaction ?? + storage.invalidTransaction(byHash: hash)?.rawTransaction + } + } diff --git a/BitcoinCore/BitcoinCore/Core/Factory.swift b/BitcoinCore/Classes/Core/Factory.swift similarity index 53% rename from BitcoinCore/BitcoinCore/Core/Factory.swift rename to BitcoinCore/Classes/Core/Factory.swift index 69d974e4..235fcbd3 100644 --- a/BitcoinCore/BitcoinCore/Core/Factory.swift +++ b/BitcoinCore/Classes/Core/Factory.swift @@ -1,3 +1,6 @@ +import HsToolKit +import NIO + class Factory: IFactory { private let network: INetwork private let networkMessageParser: INetworkMessageParser @@ -10,15 +13,15 @@ class Factory: IFactory { } func block(withHeader header: BlockHeader, previousBlock: Block) -> Block { - return Block(withHeader: header, previousBlock: previousBlock) + Block(withHeader: header, previousBlock: previousBlock) } func block(withHeader header: BlockHeader, height: Int) -> Block { - return Block(withHeader: header, height: height) + Block(withHeader: header, height: height) } func transaction(version: Int, lockTime: Int) -> Transaction { - return Transaction(version: version, lockTime: lockTime) + Transaction(version: version, lockTime: lockTime) } func inputToSign(withPreviousOutput previousOutput: UnspentOutput, script: Data, sequence: Int) -> InputToSign { @@ -30,20 +33,26 @@ class Factory: IFactory { return InputToSign(input: input, previousOutput: previousOutput.output, previousOutputPublicKey: previousOutput.publicKey) } - func output(withValue value: Int, index: Int, lockingScript script: Data = Data(), type: ScriptType, address: String?, keyHash: Data?, publicKey: PublicKey?) -> Output { - return Output(withValue: value, index: index, lockingScript: script, type: type, address: address, keyHash: keyHash, publicKey: publicKey) + func output(withIndex index: Int, address: Address, value: Int, publicKey: PublicKey?) -> Output { + Output(withValue: value, index: index, lockingScript: address.lockingScript, type: address.scriptType, address: address.stringValue, keyHash: address.keyHash, publicKey: publicKey) + } + + func nullDataOutput(data: Data) -> Output { + Output(withValue: 0, index: 0, lockingScript: data, type: .nullData) } - func peer(withHost host: String, logger: Logger? = nil) -> IPeer { - return Peer(host: host, network: network, connection: PeerConnection(host: host, port: network.port, networkMessageParser: networkMessageParser, networkMessageSerializer: networkMessageSerializer, logger: logger), connectionTimeoutManager: ConnectionTimeoutManager(), logger: logger) + func peer(withHost host: String, eventLoopGroup: MultiThreadedEventLoopGroup, logger: Logger? = nil) -> IPeer { + let connection = PeerConnection(host: host, port: network.port, networkMessageParser: networkMessageParser, networkMessageSerializer: networkMessageSerializer, eventLoopGroup: eventLoopGroup, logger: logger) + + return Peer(host: host, network: network, connection: connection, connectionTimeoutManager: ConnectionTimeoutManager(), logger: logger) } func blockHash(withHeaderHash headerHash: Data, height: Int, order: Int = 0) -> BlockHash { - return BlockHash(headerHash: headerHash, height: height, order: order) + BlockHash(headerHash: headerHash, height: height, order: order) } func bloomFilter(withElements elements: [Data]) -> BloomFilter { - return BloomFilter(elements: elements) + BloomFilter(elements: elements) } } diff --git a/BitcoinCore/Classes/Core/HDWallet.swift b/BitcoinCore/Classes/Core/HDWallet.swift new file mode 100644 index 00000000..1b353248 --- /dev/null +++ b/BitcoinCore/Classes/Core/HDWallet.swift @@ -0,0 +1,34 @@ +import HdWalletKit + +extension HDWallet: IHDWallet { + + enum HDWalletError: Error { + case publicKeysDerivationFailed + } + + func publicKey(account: Int, index: Int, external: Bool) throws -> PublicKey { + PublicKey(withAccount: account, index: index, external: external, hdPublicKeyData: try publicKey(account: account, index: index, chain: external ? .external : .internal).raw) + } + + func publicKeys(account: Int, indices: Range, external: Bool) throws -> [PublicKey] { + let hdPublicKeys: [HDPublicKey] = try publicKeys(account: account, indices: indices, chain: external ? .external : .internal) + + guard hdPublicKeys.count == indices.count else { + throw HDWalletError.publicKeysDerivationFailed + } + + return indices.map { index in + let key = hdPublicKeys[Int(index - indices.lowerBound)] + return PublicKey(withAccount: account, index: Int(index), external: external, hdPublicKeyData: key.raw) + } + } + +} + +extension HDWallet: IPrivateHDWallet { + + func privateKeyData(account: Int, index: Int, external: Bool) throws -> Data { + try privateKey(account: account, index: index, chain: external ? .external : .internal).raw + } + +} diff --git a/BitcoinCore/BitcoinCore/Core/NetworkMessageConfiguration.swift b/BitcoinCore/Classes/Core/NetworkMessageConfiguration.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Core/NetworkMessageConfiguration.swift rename to BitcoinCore/Classes/Core/NetworkMessageConfiguration.swift diff --git a/BitcoinCore/BitcoinCore/Core/Protocols.swift b/BitcoinCore/Classes/Core/Protocols.swift similarity index 54% rename from BitcoinCore/BitcoinCore/Core/Protocols.swift rename to BitcoinCore/Classes/Core/Protocols.swift index 4a844264..679e1ce4 100644 --- a/BitcoinCore/BitcoinCore/Core/Protocols.swift +++ b/BitcoinCore/Classes/Core/Protocols.swift @@ -1,11 +1,12 @@ import BigInt import RxSwift -import Alamofire -import HSCryptoKit +import HsToolKit +import NIO enum BlockValidatorType { case header, bits, legacy, testNet, EDA, DAA, DGW } public protocol IDifficultyEncoder { + func compactFrom(hash: Data) -> Int func decodeCompact(bits: Int) -> BigInt func encodeCompact(from bigInt: BigInt) -> Int } @@ -17,16 +18,19 @@ public protocol IBlockValidatorHelper { public protocol IBlockValidator: class { func validate(block: Block, previousBlock: Block) throws - func isBlockValidatable(block: Block, previousBlock: Block) -> Bool } -protocol IBlockValidatorFactory { - func validator(for validatorType: BlockValidatorType) -> IBlockValidator +public protocol IBlockChainedValidator: IBlockValidator { + func isBlockValidatable(block: Block, previousBlock: Block) -> Bool } protocol IHDWallet { var gapLimit: Int { get } func publicKey(account: Int, index: Int, external: Bool) throws -> PublicKey + func publicKeys(account: Int, indices: Range, external: Bool) throws -> [PublicKey] +} + +protocol IPrivateHDWallet { func privateKeyData(account: Int, index: Int, external: Bool) throws -> Data } @@ -35,11 +39,6 @@ protocol IApiConfigProvider { var apiUrl: String { get } } -protocol IReachabilityManager { - var isReachable: Bool { get } - var reachabilitySignal: Signal { get } -} - protocol IPeerAddressManager: class { var delegate: IPeerAddressManagerDelegate? { get set } var ip: String? { get } @@ -50,28 +49,33 @@ protocol IPeerAddressManager: class { func markConnected(peer: IPeer) } -protocol IStateManager { +protocol IApiSyncStateManager: AnyObject { var restored: Bool { get set } } protocol IBlockDiscovery { - func discoverBlockHashes(account: Int, external: Bool) -> Observable<([PublicKey], [BlockHash])> + func discoverBlockHashes(account: Int) -> Single<([PublicKey], [BlockHash])> } -public protocol IStorage { +public protocol IOutputStorage { + func previousOutput(ofInput: Input) -> Output? + func outputsWithPublicKeys() -> [OutputWithPublicKey] +} + +public protocol IStorage: IOutputStorage { var initialRestored: Bool? { get } func set(initialRestored: Bool) func leastScoreFastestPeerAddress(excludingIps: [String]) -> PeerAddress? + func peerAddressExist(address: String) -> Bool func save(peerAddresses: [PeerAddress]) - func increasePeerAddressScore(ip: String) func deletePeerAddress(byIp ip: String) func set(connectionTime: Double, toPeerAddress: String) var blockchainBlockHashes: [BlockHash] { get } var lastBlockchainBlockHash: BlockHash? { get } - func blockHashHeaderHashes(except: Data) -> [String] + func blockHashHeaderHashes(except: [Data]) -> [Data] var blockHashHeaderHashes: [Data] { get } var lastBlockHash: BlockHash? { get } func blockHashesSortedBySequenceAndHeight(limit: Int) -> [BlockHash] @@ -88,37 +92,48 @@ public protocol IStorage { func save(block: Block) func blocks(heightGreaterThan: Int, sortedBy: Block.Columns, limit: Int) -> [Block] func blocks(from startHeight: Int, to endHeight: Int, ascending: Bool) -> [Block] - func blocks(byHexes: [String]) -> [Block] + func blocks(byHexes: [Data]) -> [Block] func blocks(heightGreaterThanOrEqualTo: Int, stale: Bool) -> [Block] func blocks(stale: Bool) -> [Block] + func blockByHeightStalePrioritized(height: Int) -> Block? func block(byHeight: Int) -> Block? func block(byHash: Data) -> Block? func block(stale: Bool, sortedHeight: String) -> Block? func add(block: Block) throws func delete(blocks: [Block]) throws func unstaleAllBlocks() throws - + func timestamps(from startHeight: Int, to endHeight: Int) -> [Int] func transactionExists(byHash: Data) -> Bool + func fullTransaction(byHash hash: Data) -> FullTransaction? func transaction(byHash: Data) -> Transaction? + func invalidTransaction(byHash: Data) -> InvalidTransaction? + func validOrInvalidTransaction(byUid: String) -> Transaction? + func incomingPendingTransactionHashes() -> [Data] + func incomingPendingTransactionsExist() -> Bool + func inputs(byHashes hashes: [Data]) -> [Input] func transactions(ofBlock: Block) -> [Transaction] - func newTransactions() -> [Transaction] + func newTransactions() -> [FullTransaction] func newTransaction(byHash: Data) -> Transaction? func relayedTransactionExists(byHash: Data) -> Bool func add(transaction: FullTransaction) throws + func update(transaction: FullTransaction) throws func update(transaction: Transaction) throws func fullInfo(forTransactions: [TransactionWithBlock]) -> [FullTransactionForInfo] - func fullTransactionsInfo(fromTimestamp: Int?, fromOrder: Int?, limit: Int?) -> [FullTransactionForInfo] - func fullTransactionInfo(byHash hash: Data) -> FullTransactionForInfo? + func validOrInvalidTransactionsFullInfo(fromTimestamp: Int?, fromOrder: Int?, type: TransactionFilterType?, limit: Int?) -> [FullTransactionForInfo] + func transactionFullInfo(byHash hash: Data) -> FullTransactionForInfo? + func moveTransactionsTo(invalidTransactions: [InvalidTransaction]) throws + func move(invalidTransaction: InvalidTransaction, toTransactions: FullTransaction) throws - func outputsWithPublicKeys() -> [OutputWithPublicKey] func unspentOutputs() -> [UnspentOutput] func inputs(transactionHash: Data) -> [Input] func outputs(transactionHash: Data) -> [Output] - func previousOutput(ofInput: Input) -> Output? + func inputsUsingOutputs(withTransactionHash: Data) -> [Input] + func inputsUsing(previousOutputTxHash: Data, previousOutputIndex: Int) -> [Input] func sentTransaction(byHash: Data) -> SentTransaction? func update(sentTransaction: SentTransaction) + func delete(sentTransaction: SentTransaction) func add(sentTransaction: SentTransaction) func publicKeys() -> [PublicKey] @@ -126,25 +141,28 @@ public protocol IStorage { func publicKey(byRawOrKeyHash: Data) -> PublicKey? func add(publicKeys: [PublicKey]) func publicKeysWithUsedState() -> [PublicKeyWithUsedState] + func publicKey(byPath: String) -> PublicKey? } -public protocol IAddressSelector { - func getAddressVariants(addressConverter: IAddressConverter, publicKey: PublicKey) -> [String] +public protocol IRestoreKeyConverter { + func keysForApiRestore(publicKey: PublicKey) -> [String] + func bloomFilterElements(publicKey: PublicKey) -> [Data] } -public protocol IAddressManager { +public protocol IPublicKeyManager { func changePublicKey() throws -> PublicKey - func receiveAddress(for type: ScriptType) throws -> String + func receivePublicKey() throws -> PublicKey func fillGap() throws - func addKeys(keys: [PublicKey]) throws + func addKeys(keys: [PublicKey]) func gapShifts() -> Bool + func publicKey(byPath: String) throws -> PublicKey } public protocol IBloomFilterManagerDelegate: class { func bloomFilterUpdated(bloomFilter: BloomFilter) } -public protocol IBloomFilterManager { +public protocol IBloomFilterManager: AnyObject { var delegate: IBloomFilterManagerDelegate? { get set } var bloomFilter: BloomFilter? { get } func regenerateBloomFilter() @@ -156,17 +174,19 @@ public protocol IPeerGroup: class { func start() func stop() + func reconnectPeers() func isReady(peer: IPeer) -> Bool } protocol IPeerManager: class { + var totalPeersCount: Int { get } + var connected: [IPeer] { get } + var sorted: [IPeer] { get } + var readyPeers: [IPeer] { get } func add(peer: IPeer) func peerDisconnected(peer: IPeer) func disconnectAll() - func totalPeersCount() -> Int - func connected() -> [IPeer] - func sorted() -> [IPeer] } public protocol IPeer: class { @@ -178,6 +198,7 @@ public protocol IPeer: class { var ready: Bool { get } var connected: Bool { get } var connectionTime: Double { get } + var tasks: [PeerTask] { get } func connect() func disconnect(error: Error?) func add(task: PeerTask) @@ -210,7 +231,7 @@ public protocol IPeerTaskDelegate: class { protocol IPeerConnection: class { var delegate: PeerConnectionDelegate? { get set } var host: String { get } - var port: UInt32 { get } + var port: Int { get } var logName: String { get } func connect() func disconnect(error: Error?) @@ -222,11 +243,12 @@ protocol IConnectionTimeoutManager: class { func timePeriodPassed(peer: IPeer) } -protocol ISyncStateListener: class { - func syncStarted() - func syncStopped() - func syncFinished(all: Bool) - func initialBestBlockHeightUpdated(height: Int32) +protocol IApiSyncListener: class { + func transactionsFound(count: Int) +} + +public protocol IBlockSyncListener: class { + func blocksSyncFinished() func currentBestBlockHeightUpdated(height: Int32, maxBlockHeight: Int32) } @@ -236,22 +258,23 @@ protocol IPeerAddressManagerDelegate: class { protocol IPeerDiscovery { var peerAddressManager: IPeerAddressManager? { get set } - func lookup(dnsSeed: String) + func lookup(dnsSeeds: [String]) } protocol IFactory { func block(withHeader header: BlockHeader, previousBlock: Block) -> Block func block(withHeader header: BlockHeader, height: Int) -> Block func blockHash(withHeaderHash headerHash: Data, height: Int, order: Int) -> BlockHash - func peer(withHost host: String, logger: Logger?) -> IPeer + func peer(withHost host: String, eventLoopGroup: MultiThreadedEventLoopGroup, logger: Logger?) -> IPeer func transaction(version: Int, lockTime: Int) -> Transaction func inputToSign(withPreviousOutput: UnspentOutput, script: Data, sequence: Int) -> InputToSign - func output(withValue value: Int, index: Int, lockingScript script: Data, type: ScriptType, address: String?, keyHash: Data?, publicKey: PublicKey?) -> Output + func output(withIndex index: Int, address: Address, value: Int, publicKey: PublicKey?) -> Output + func nullDataOutput(data: Data) -> Output func bloomFilter(withElements: [Data]) -> BloomFilter } public protocol ISyncTransactionApi { - func getTransactions(addresses: [String]) -> Observable<[SyncTransactionItem]> + func getTransactions(addresses: [String]) -> Single<[SyncTransactionItem]> } protocol ISyncManager { @@ -262,7 +285,7 @@ protocol ISyncManager { protocol IInitialSyncer { var delegate: IInitialSyncerDelegate? { get set } func sync() - func stop() + func terminate() } public protocol IHasher { @@ -270,7 +293,7 @@ public protocol IHasher { } protocol IBlockHashFetcher { - func getBlockHashes(publicKeys: [PublicKey]) -> Observable<(responses: [BlockHash], lastUsedIndex: Int)> + func getBlockHashes(externalKeys: [PublicKey], internalKeys: [PublicKey]) -> Single } protocol IBlockHashFetcherHelper { @@ -278,20 +301,18 @@ protocol IBlockHashFetcherHelper { } protocol IInitialSyncerDelegate: class { - func syncingFinished() + func onSyncSuccess() + func onSyncFailed(error: Error) } protocol IPaymentAddressParser { func parse(paymentAddress: String) -> BitcoinPaymentData } -public protocol IAddressKeyHashConverter { - func convert(keyHash: Data, type: ScriptType) -> Data -} - public protocol IAddressConverter { func convert(address: String) throws -> Address func convert(keyHash: Data, type: ScriptType) throws -> Address + func convert(publicKey: PublicKey, type: ScriptType) throws -> Address } public protocol IScriptConverter { @@ -304,15 +325,31 @@ protocol IScriptExtractor: class { } protocol IOutputsCache: class { - func add(fromOutputs outputs: [Output]) - func hasOutputs(forInputs inputs: [Input]) -> Bool + func add(outputs: [Output]) + func valueSpent(by input: Input) -> Int? func clear() } -public protocol ITransactionProcessor: class { +protocol ITransactionInvalidator { + func invalidate(transaction: Transaction) +} + +protocol ITransactionConflictsResolver { + func transactionsConflicting(withInblockTransaction transaction: FullTransaction) -> [Transaction] + func transactionsConflicting(withPendingTransaction transaction: FullTransaction) -> [Transaction] + func incomingPendingTransactionsConflicting(with transaction: FullTransaction) -> [Transaction] +} + +public protocol IBlockTransactionProcessor: class { + var listener: IBlockchainDataListener? { get set } + + func processReceived(transactions: [FullTransaction], inBlock block: Block, skipCheckBloomFilter: Bool) throws +} + +public protocol IPendingTransactionProcessor: class { var listener: IBlockchainDataListener? { get set } - func processReceived(transactions: [FullTransaction], inBlock block: Block?, skipCheckBloomFilter: Bool) throws + func processReceived(transactions: [FullTransaction], skipCheckBloomFilter: Bool) throws func processCreated(transaction: FullTransaction) throws } @@ -320,32 +357,34 @@ protocol ITransactionExtractor { func extract(transaction: FullTransaction) } -protocol ITransactionOutputAddressExtractor { - func extractOutputAddresses(transaction: FullTransaction) -} - protocol ITransactionLinker { func handle(transaction: FullTransaction) } protocol ITransactionPublicKeySetter { - func set(output: Output) -> Bool + func set(output: Output) } public protocol ITransactionSyncer: class { - func pendingTransactions() -> [FullTransaction] - func handle(transactions: [FullTransaction]) - func handle(sentTransaction transaction: FullTransaction) + func newTransactions() -> [FullTransaction] + func handleRelayed(transactions: [FullTransaction]) + func handleInvalid(fullTransaction: FullTransaction) func shouldRequestTransaction(hash: Data) -> Bool } public protocol ITransactionCreator { - func create(to address: String, value: Int, feeRate: Int, senderPay: Bool) throws + func create(to address: String, value: Int, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData]) throws -> FullTransaction + func create(from: UnspentOutput, to address: String, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction + func createRawTransaction(to address: String, value: Int, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData]) throws -> Data } protocol ITransactionBuilder { - func fee(for value: Int, feeRate: Int, senderPay: Bool, address: String?) throws -> Int - func buildTransaction(value: Int, feeRate: Int, senderPay: Bool, toAddress: String) throws -> FullTransaction + func buildTransaction(toAddress: String, value: Int, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData]) throws -> FullTransaction + func buildTransaction(from: UnspentOutput, toAddress: String, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction +} + +protocol ITransactionFeeCalculator { + func fee(for value: Int, feeRate: Int, senderPay: Bool, toAddress: String?, pluginData: [UInt8: IPluginData]) throws -> Int } protocol IBlockchain { @@ -368,23 +407,29 @@ protocol IInputSigner { func sigScriptData(transaction: Transaction, inputsToSign: [InputToSign], outputs: [Output], index: Int) throws -> [Data] } -public protocol IScriptBuilder { - func lockingScript(for address: Address) throws -> Data -} - public protocol ITransactionSizeCalculator { - func transactionSize(inputs: [ScriptType], outputScriptTypes: [ScriptType]) -> Int + func transactionSize(previousOutputs: [Output], outputScriptTypes: [ScriptType]) -> Int + func transactionSize(previousOutputs: [Output], outputScriptTypes: [ScriptType], pluginDataOutputSize: Int) -> Int func outputSize(type: ScriptType) -> Int func inputSize(type: ScriptType) -> Int + func witnessSize(type: ScriptType) -> Int func toBytes(fee: Int) -> Int } +public protocol IDustCalculator { + func dust(type: ScriptType) -> Int +} + public protocol IUnspentOutputSelector { - func select(value: Int, feeRate: Int, outputScriptType: ScriptType, changeType: ScriptType, senderPay: Bool) throws -> SelectedUnspentOutputInfo + func select(value: Int, feeRate: Int, outputScriptType: ScriptType, changeType: ScriptType, senderPay: Bool, pluginDataOutputSize: Int) throws -> SelectedUnspentOutputInfo } public protocol IUnspentOutputProvider { - var allUnspentOutputs: [UnspentOutput] { get } + var spendableUtxo: [UnspentOutput] { get } +} + +public protocol IBalanceProvider { + var balanceInfo: BalanceInfo { get } } public protocol IBlockSyncer: class { @@ -402,20 +447,16 @@ public protocol IBlockSyncer: class { func shouldRequestBlock(withHash hash: Data) -> Bool } -protocol IKitStateProvider: class { - var syncState: BitcoinCore.KitState { get } - var delegate: IKitStateProviderDelegate? { get set } -} - -protocol IKitStateProviderDelegate: class { - func handleKitStateUpdate(state: BitcoinCore.KitState) +protocol ISyncManagerDelegate: class { + func kitStateUpdated(state: BitcoinCore.KitState) } public protocol ITransactionInfo: class { - init(transactionHash: String, transactionIndex: Int, from: [TransactionAddressInfo], to: [TransactionAddressInfo], amount: Int, blockHeight: Int?, timestamp: Int) + init(uid: String, transactionHash: String, transactionIndex: Int, inputs: [TransactionInputInfo], outputs: [TransactionOutputInfo], amount: Int, type: TransactionType, fee: Int?, blockHeight: Int?, timestamp: Int, status: TransactionStatus, conflictingHash: String?) } public protocol ITransactionInfoConverter { + var baseTransactionInfoConverter: IBaseTransactionInfoConverter! { get set } func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> TransactionInfo } @@ -423,22 +464,25 @@ protocol IDataProvider { var delegate: IDataProviderDelegate? { get set } var lastBlockInfo: BlockInfo? { get } - var balance: Int { get } - var debugInfo: String { get } - func transactions(fromHash: String?, limit: Int?) -> Single<[TransactionInfo]> + var balance: BalanceInfo { get } + func debugInfo(network: INetwork, scriptType: ScriptType, addressConverter: IAddressConverter) -> String + func transactions(fromUid: String?, type: TransactionFilterType?, limit: Int?) -> Single<[TransactionInfo]> + func transaction(hash: String) -> TransactionInfo? + + func rawTransaction(transactionHash: String) -> String? } protocol IDataProviderDelegate: class { func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) func transactionsDeleted(hashes: [String]) - func balanceUpdated(balance: Int) + func balanceUpdated(balance: BalanceInfo) func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) } public protocol INetwork: class { var maxBlockSize: UInt32 { get } var protocolVersion: Int32 { get } - var name: String { get } + var bundleName: String { get } var pubKeyHash: UInt8 { get } var privateKey: UInt8 { get } var scriptHash: UInt8 { get } @@ -446,10 +490,11 @@ public protocol INetwork: class { var xPubKey: UInt32 { get } var xPrivKey: UInt32 { get } var magic: UInt32 { get } - var port: UInt32 { get } + var port: Int { get } var dnsSeeds: [String] { get } - var bip44CheckpointBlock: Block { get } - var lastCheckpointBlock: Block { get } + var dustRelayTxFee: Int { get } + var bip44Checkpoint: Checkpoint { get } + var lastCheckpoint: Checkpoint { get } var coinType: UInt32 { get } var sigHash: SigHashType { get } var syncableFromApi: Bool { get } @@ -464,15 +509,6 @@ public protocol IMerkleBranch: class { func calculateMerkleRoot(txCount: Int, hashes: [Data], flags: [UInt8]) throws -> (merkleRoot: Data, matchedHashes: [Data]) } -public extension INetwork { - var protocolVersion: Int32 { return 70015 } - - var maxBlockSize: UInt32 { return 1_000_000 } - var serviceFullNode: UInt64 { return 1 } - var bloomFilter: Int32 { return 70000 } - -} - public protocol IMessage { var description: String { get } } @@ -500,7 +536,8 @@ public protocol IMessageSerializer { } public protocol IInitialBlockDownload { - var allPeersSynced: Bool { get } + var syncPeer: IPeer? { get } + var hasSyncedPeer: Bool { get } var observable: Observable { get } var syncedPeers: [IPeer] { get } func isSynced(peer: IPeer) -> Bool @@ -508,7 +545,7 @@ public protocol IInitialBlockDownload { public protocol ISyncedReadyPeerManager { var peers: [IPeer] { get } - var observable: Observable { get } + var observable: Observable { get } } public protocol IInventoryItemsHandler: class { @@ -521,9 +558,106 @@ public protocol IPeerTaskHandler: class { protocol ITransactionSender { func verifyCanSend() throws - func send(pendingTransaction: FullTransaction) throws + func send(pendingTransaction: FullTransaction) + func transactionsRelayed(transactions: [FullTransaction]) +} + +protocol ITransactionSendTimerDelegate: class { + func timePassed() +} + +protocol ITransactionSendTimer { + var delegate: ITransactionSendTimerDelegate? { get set } + func startIfNotRunning() + func stop() } protocol IMerkleBlockHandler: AnyObject { func handle(merkleBlock: MerkleBlock) throws } + +protocol ITransactionListener: class { + func onReceive(transaction: FullTransaction) +} + +public protocol IWatchedTransactionDelegate { + func transactionReceived(transaction: FullTransaction, outputIndex: Int) + func transactionReceived(transaction: FullTransaction, inputIndex: Int) +} + +protocol IWatchedTransactionManager { + func add(transactionFilter: BitcoinCore.TransactionFilter, delegatedTo: IWatchedTransactionDelegate) +} + +protocol IBloomFilterProvider: AnyObject { + var bloomFilterManager: IBloomFilterManager? { set get } + func filterElements() -> [Data] +} + +protocol IIrregularOutputFinder { + func hasIrregularOutput(outputs: [Output]) -> Bool +} + +public protocol IPlugin { + var id: UInt8 { get } + var maxSpendLimit: Int? { get } + func validate(address: Address) throws + func processOutputs(mutableTransaction: MutableTransaction, pluginData: IPluginData, skipChecks: Bool) throws + func processTransactionWithNullData(transaction: FullTransaction, nullDataChunks: inout IndexingIterator<[Chunk]>) throws + func isSpendable(unspentOutput: UnspentOutput) throws -> Bool + func inputSequenceNumber(output: Output) throws -> Int + func parsePluginData(from: String, transactionTimestamp: Int) throws -> IPluginOutputData + func keysForApiRestore(publicKey: PublicKey) throws -> [String] +} + +public protocol IPluginManager { + func validate(address: Address, pluginData: [UInt8: IPluginData]) throws + func maxSpendLimit(pluginData: [UInt8: IPluginData]) throws -> Int? + func add(plugin: IPlugin) + func processOutputs(mutableTransaction: MutableTransaction, pluginData: [UInt8: IPluginData], skipChecks: Bool) throws + func processInputs(mutableTransaction: MutableTransaction) throws + func processTransactionWithNullData(transaction: FullTransaction, nullDataOutput: Output) throws + func isSpendable(unspentOutput: UnspentOutput) -> Bool + func parsePluginData(fromPlugin: UInt8, pluginDataString: String, transactionTimestamp: Int) -> IPluginOutputData? +} + +public protocol IBlockMedianTimeHelper { + var medianTimePast: Int? { get } + func medianTimePast(block: Block) -> Int? +} + +protocol IRecipientSetter { + func setRecipient(to mutableTransaction: MutableTransaction, toAddress: String, value: Int, pluginData: [UInt8: IPluginData], skipChecks: Bool) throws +} + +protocol IOutputSetter { + func setOutputs(to mutableTransaction: MutableTransaction, sortType: TransactionDataSortType) +} + +protocol IInputSetter { + func setInputs(to mutableTransaction: MutableTransaction, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType) throws + func setInputs(to mutableTransaction: MutableTransaction, fromUnspentOutput unspentOutput: UnspentOutput, feeRate: Int) throws +} + +protocol ILockTimeSetter { + func setLockTime(to mutableTransaction: MutableTransaction) +} + +protocol ITransactionSigner { + func sign(mutableTransaction: MutableTransaction) throws +} + +public protocol IPluginData { +} + +public protocol IPluginOutputData { +} + +protocol ITransactionDataSorterFactory { + func sorter(for type: TransactionDataSortType) -> ITransactionDataSorter +} + +protocol ITransactionDataSorter { + func sort(outputs: [Output]) -> [Output] + func sort(unspentOutputs: [UnspentOutput]) -> [UnspentOutput] +} diff --git a/BitcoinCore/Classes/Core/ReadOnlyWallet.swift b/BitcoinCore/Classes/Core/ReadOnlyWallet.swift new file mode 100644 index 00000000..4064a928 --- /dev/null +++ b/BitcoinCore/Classes/Core/ReadOnlyWallet.swift @@ -0,0 +1,43 @@ +import HdWalletKit + +class ReadOnlyWallet { + + enum ReadOnlyWalletError: Error { + case noKeyForGivenAccount + case publicKeysDerivationFailed + } + + private let keys: [Int: String] // [accountId: extendedPublicKey] + var gapLimit: Int + + init(keys: [Int: String], gapLimit: Int) { + self.keys = keys + self.gapLimit = gapLimit + } + +} + +extension ReadOnlyWallet: IHDWallet { + + func publicKey(account: Int, index: Int, external: Bool) throws -> PublicKey { + try publicKeys(account: account, indices: UInt32(index).., external: Bool) throws -> [PublicKey] { + guard let key = keys[account] else { + throw ReadOnlyWalletError.noKeyForGivenAccount + } + + let hdPublicKeys: [HDPublicKey] = try ReadOnlyHDWallet.publicKeys(extendedPublicKey: key, indices: indices, chain: external ? .external : .internal) + + guard hdPublicKeys.count == indices.count else { + throw ReadOnlyWalletError.publicKeysDerivationFailed + } + + return indices.map { index in + let key = hdPublicKeys[Int(index - indices.lowerBound)] + return PublicKey(withAccount: account, index: Int(index), external: external, hdPublicKeyData: key.raw) + } + } + +} diff --git a/BitcoinCore/Classes/Core/RestoreKeyConverter.swift b/BitcoinCore/Classes/Core/RestoreKeyConverter.swift new file mode 100644 index 00000000..d746bedd --- /dev/null +++ b/BitcoinCore/Classes/Core/RestoreKeyConverter.swift @@ -0,0 +1,113 @@ +class RestoreKeyConverterChain : IRestoreKeyConverter { + + var converters = [IRestoreKeyConverter]() + + func add(converter: IRestoreKeyConverter) { + converters.append(converter) + } + + func keysForApiRestore(publicKey: PublicKey) -> [String] { + var keys = [String]() + for converter in converters { + keys.append(contentsOf: converter.keysForApiRestore(publicKey: publicKey)) + } + + return keys.unique + } + + func bloomFilterElements(publicKey: PublicKey) -> [Data] { + var keys = [Data]() + for converter in converters { + keys.append(contentsOf: converter.bloomFilterElements(publicKey: publicKey)) + } + + return keys.unique + } + +} + +public class Bip44RestoreKeyConverter { + + let addressConverter: IAddressConverter + + public init(addressConverter: IAddressConverter) { + self.addressConverter = addressConverter + } + +} + +extension Bip44RestoreKeyConverter : IRestoreKeyConverter { + + public func keysForApiRestore(publicKey: PublicKey) -> [String] { + let legacyAddress = try? addressConverter.convert(publicKey: publicKey, type: .p2pkh).stringValue + + return [legacyAddress].compactMap { $0 } + } + + public func bloomFilterElements(publicKey: PublicKey) -> [Data] { + [publicKey.keyHash, publicKey.raw] + } + +} + +public class Bip49RestoreKeyConverter { + + let addressConverter: IAddressConverter + + public init(addressConverter: IAddressConverter) { + self.addressConverter = addressConverter + } + +} + +extension Bip49RestoreKeyConverter : IRestoreKeyConverter { + + public func keysForApiRestore(publicKey: PublicKey) -> [String] { + let wpkhShAddress = try? addressConverter.convert(publicKey: publicKey, type: .p2wpkhSh).stringValue + + return [wpkhShAddress].compactMap { $0 } + } + + public func bloomFilterElements(publicKey: PublicKey) -> [Data] { + [publicKey.scriptHashForP2WPKH] + } + +} + +public class Bip84RestoreKeyConverter { + + let addressConverter: IAddressConverter + + public init(addressConverter: IAddressConverter) { + self.addressConverter = addressConverter + } + +} + +extension Bip84RestoreKeyConverter : IRestoreKeyConverter { + + public func keysForApiRestore(publicKey: PublicKey) -> [String] { + let segwitAddress = try? addressConverter.convert(publicKey: publicKey, type: .p2wpkh).stringValue + + return [segwitAddress].compactMap { $0 } + } + + public func bloomFilterElements(publicKey: PublicKey) -> [Data] { + [publicKey.keyHash] + } + +} + +public class KeyHashRestoreKeyConverter : IRestoreKeyConverter { + + public init() {} + + public func keysForApiRestore(publicKey: PublicKey) -> [String] { + [publicKey.keyHash.hex] + } + + public func bloomFilterElements(publicKey: PublicKey) -> [Data] { + [publicKey.keyHash] + } + +} diff --git a/BitcoinCore/BitcoinCore/Core/Signal.swift b/BitcoinCore/Classes/Core/Signal.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Core/Signal.swift rename to BitcoinCore/Classes/Core/Signal.swift diff --git a/BitcoinCore/Classes/Core/TransactionDataSorterFactory.swift b/BitcoinCore/Classes/Core/TransactionDataSorterFactory.swift new file mode 100644 index 00000000..0992223c --- /dev/null +++ b/BitcoinCore/Classes/Core/TransactionDataSorterFactory.swift @@ -0,0 +1,13 @@ +import Foundation + +class TransactionDataSorterFactory: ITransactionDataSorterFactory { + + func sorter(for type: TransactionDataSortType) -> ITransactionDataSorter { + switch type { + case .none: return StraightSorter() + case .shuffle: return ShuffleSorter() + case .bip69: return Bip69Sorter() + } + } + +} diff --git a/BitcoinCore/Classes/Core/TransactionInfoConverter.swift b/BitcoinCore/Classes/Core/TransactionInfoConverter.swift new file mode 100644 index 00000000..bdbeb072 --- /dev/null +++ b/BitcoinCore/Classes/Core/TransactionInfoConverter.swift @@ -0,0 +1,10 @@ +open class TransactionInfoConverter: ITransactionInfoConverter { + public var baseTransactionInfoConverter: IBaseTransactionInfoConverter! + + public init() {} + + public func transactionInfo(fromTransaction transactionForInfo: FullTransactionForInfo) -> TransactionInfo { + baseTransactionInfoConverter.transactionInfo(fromTransaction: transactionForInfo) + } + +} diff --git a/BitcoinCore/BitcoinCore/Crypto/BloomFilter.swift b/BitcoinCore/Classes/Crypto/BloomFilter.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Crypto/BloomFilter.swift rename to BitcoinCore/Classes/Crypto/BloomFilter.swift diff --git a/BitcoinCore/BitcoinCore/Crypto/MurmurHash.swift b/BitcoinCore/Classes/Crypto/MurmurHash.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Crypto/MurmurHash.swift rename to BitcoinCore/Classes/Crypto/MurmurHash.swift diff --git a/BitcoinCore/BitcoinCore/Extensions/Array.swift b/BitcoinCore/Classes/Extensions/Array.swift similarity index 91% rename from BitcoinCore/BitcoinCore/Extensions/Array.swift rename to BitcoinCore/Classes/Extensions/Array.swift index cca34b94..b8e66fd3 100644 --- a/BitcoinCore/BitcoinCore/Extensions/Array.swift +++ b/BitcoinCore/Classes/Extensions/Array.swift @@ -50,27 +50,27 @@ extension Array where Element : Hashable { } extension Array: SQLExpressible where Element == Data { - + public var sqlExpression: SQLExpression { - return databaseValue + databaseValue.sqlExpression } - + } -extension Array: DatabaseValueConvertible where Element == Data { - +extension Array: DatabaseValueConvertible, StatementBinding where Element == Data { + public var databaseValue: DatabaseValue { - return DataListSerializer.serialize(dataList: self).databaseValue + DataListSerializer.serialize(dataList: self).databaseValue } - + public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Array? { if case let DatabaseValue.Storage.blob(value) = dbValue.storage { return DataListSerializer.deserialize(data: value) } - + return nil } - + } extension Array { diff --git a/BitcoinCore/BitcoinCore/Extensions/Observable.swift b/BitcoinCore/Classes/Extensions/Observable.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Extensions/Observable.swift rename to BitcoinCore/Classes/Extensions/Observable.swift diff --git a/BitcoinCore/BitcoinCore/Extensions/String.swift b/BitcoinCore/Classes/Extensions/String.swift similarity index 87% rename from BitcoinCore/BitcoinCore/Extensions/String.swift rename to BitcoinCore/Classes/Extensions/String.swift index 6eea20cb..0dd69749 100644 --- a/BitcoinCore/BitcoinCore/Extensions/String.swift +++ b/BitcoinCore/Classes/Extensions/String.swift @@ -1,4 +1,5 @@ import Foundation +import UIExtensions extension String { diff --git a/BitcoinCore/BitcoinCore/Extensions/SynchronizedArray.swift b/BitcoinCore/Classes/Extensions/SynchronizedArray.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Extensions/SynchronizedArray.swift rename to BitcoinCore/Classes/Extensions/SynchronizedArray.swift diff --git a/BitcoinCore/Classes/Extensions/URLRequestConvertible.swift b/BitcoinCore/Classes/Extensions/URLRequestConvertible.swift new file mode 100644 index 00000000..955edcdb --- /dev/null +++ b/BitcoinCore/Classes/Extensions/URLRequestConvertible.swift @@ -0,0 +1,9 @@ +import Alamofire + +extension URLRequestConvertible { + + var description: String { + "\(urlRequest?.httpMethod ?? "") \(urlRequest?.url?.absoluteString ?? "")" + } + +} diff --git a/BitcoinCore/BitcoinCore/Handlers/InventoryItemsHandlerChain.swift b/BitcoinCore/Classes/Handlers/InventoryItemsHandlerChain.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Handlers/InventoryItemsHandlerChain.swift rename to BitcoinCore/Classes/Handlers/InventoryItemsHandlerChain.swift diff --git a/BitcoinCore/BitcoinCore/Handlers/PeerTaskHandlerChain.swift b/BitcoinCore/Classes/Handlers/PeerTaskHandlerChain.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Handlers/PeerTaskHandlerChain.swift rename to BitcoinCore/Classes/Handlers/PeerTaskHandlerChain.swift diff --git a/BitcoinCore/BitcoinCore/Helpers/AddressConverterChain.swift b/BitcoinCore/Classes/Helpers/AddressConverterChain.swift similarity index 58% rename from BitcoinCore/BitcoinCore/Helpers/AddressConverterChain.swift rename to BitcoinCore/Classes/Helpers/AddressConverterChain.swift index b30412f1..59b9bb63 100644 --- a/BitcoinCore/BitcoinCore/Helpers/AddressConverterChain.swift +++ b/BitcoinCore/Classes/Helpers/AddressConverterChain.swift @@ -1,11 +1,11 @@ -class AddressConverterChain: IAddressConverter { +public class AddressConverterChain: IAddressConverter { private var concreteConverters = [IAddressConverter]() func prepend(addressConverter: IAddressConverter) { concreteConverters.insert(addressConverter, at: 0) } - func convert(address: String) throws -> Address { + public func convert(address: String) throws -> Address { var errors = [Error]() for converter in concreteConverters { @@ -20,7 +20,7 @@ class AddressConverterChain: IAddressConverter { throw BitcoinCoreErrors.AddressConversionErrors(errors: errors) } - func convert(keyHash: Data, type: ScriptType) throws -> Address { + public func convert(keyHash: Data, type: ScriptType) throws -> Address { var errors = [Error]() for converter in concreteConverters { @@ -35,4 +35,19 @@ class AddressConverterChain: IAddressConverter { throw BitcoinCoreErrors.AddressConversionErrors(errors: errors) } + public func convert(publicKey: PublicKey, type: ScriptType) throws -> Address { + var errors = [Error]() + + for converter in concreteConverters { + do { + let converted = try converter.convert(publicKey: publicKey, type: type) + return converted + } catch { + errors.append(error) + } + } + + throw BitcoinCoreErrors.AddressConversionErrors(errors: errors) + } + } diff --git a/BitcoinCore/BitcoinCore/Helpers/Base58AddressConverter.swift b/BitcoinCore/Classes/Helpers/Base58AddressConverter.swift similarity index 75% rename from BitcoinCore/BitcoinCore/Helpers/Base58AddressConverter.swift rename to BitcoinCore/Classes/Helpers/Base58AddressConverter.swift index e991e0a7..6b234cda 100644 --- a/BitcoinCore/BitcoinCore/Helpers/Base58AddressConverter.swift +++ b/BitcoinCore/Classes/Helpers/Base58AddressConverter.swift @@ -1,16 +1,16 @@ -import HSCryptoKit +import OpenSslKit -class Base58AddressConverter: IAddressConverter { +public class Base58AddressConverter: IAddressConverter { private static let checkSumLength = 4 private let addressVersion: UInt8 private let addressScriptVersion: UInt8 - init(addressVersion: UInt8, addressScriptVersion: UInt8) { + public init(addressVersion: UInt8, addressScriptVersion: UInt8) { self.addressVersion = addressVersion self.addressScriptVersion = addressScriptVersion } - func convert(address: String) throws -> Address { + public func convert(address: String) throws -> Address { // check length of address to avoid wrong converting guard address.count >= 26 && address.count <= 35 else { throw BitcoinCoreErrors.AddressConversion.invalidAddressLength @@ -22,7 +22,7 @@ class Base58AddressConverter: IAddressConverter { throw BitcoinCoreErrors.AddressConversion.invalidAddressLength } let givenChecksum = hex.suffix(Base58AddressConverter.checkSumLength) - let doubleSHA256 = CryptoKit.sha256sha256(hex.prefix(hex.count - Base58AddressConverter.checkSumLength)) + let doubleSHA256 = Kit.sha256sha256(hex.prefix(hex.count - Base58AddressConverter.checkSumLength)) let actualChecksum = doubleSHA256.prefix(Base58AddressConverter.checkSumLength) guard givenChecksum == actualChecksum else { throw BitcoinCoreErrors.AddressConversion.invalidChecksum @@ -39,7 +39,7 @@ class Base58AddressConverter: IAddressConverter { return LegacyAddress(type: type, keyHash: keyHash, base58: address) } - func convert(keyHash: Data, type: ScriptType) throws -> Address { + public func convert(keyHash: Data, type: ScriptType) throws -> Address { let version: UInt8 let addressType: AddressType @@ -54,11 +54,16 @@ class Base58AddressConverter: IAddressConverter { } var withVersion = (Data([version])) + keyHash - let doubleSHA256 = CryptoKit.sha256sha256(withVersion) + let doubleSHA256 = Kit.sha256sha256(withVersion) let checksum = doubleSHA256.prefix(4) withVersion += checksum let base58 = Base58.encode(withVersion) return LegacyAddress(type: addressType, keyHash: keyHash, base58: base58) } + public func convert(publicKey: PublicKey, type: ScriptType) throws -> Address { + let keyHash = type == .p2wpkhSh ? publicKey.scriptHashForP2WPKH : publicKey.keyHash + return try convert(keyHash: keyHash, type: type) + } + } diff --git a/BitcoinCore/Classes/Helpers/Bip69.swift b/BitcoinCore/Classes/Helpers/Bip69.swift new file mode 100644 index 00000000..6bc3c007 --- /dev/null +++ b/BitcoinCore/Classes/Helpers/Bip69.swift @@ -0,0 +1,40 @@ +class Bip69 { + + static var outputComparator: ((Output, Output) -> Bool) = { o, o1 in + if o.value != o1.value { + return o.value < o1.value + } + + guard let keyHash1 = o.keyHash else { + return false + } + guard let keyHash2 = o1.keyHash else { + return true + } + + return compare(data: keyHash1, data2: keyHash2) + } + + static var inputComparator: ((UnspentOutput, UnspentOutput) -> Bool) = { o, o1 in + let result = Bip69.compare(data: o.output.transactionHash, data2: o1.output.transactionHash) + + return result || o.output.index < o1.output.index + } + + private static func compare(data: Data, data2: Data) -> Bool { + guard data.count == data2.count else { + return data.count < data2.count + } + + let count = data.count + for index in 0.. Data { - return CryptoKit.sha256sha256(data) + Kit.sha256sha256(data) } } diff --git a/BitcoinCore/BitcoinCore/Helpers/Helpers.swift b/BitcoinCore/Classes/Helpers/Helpers.swift similarity index 57% rename from BitcoinCore/BitcoinCore/Helpers/Helpers.swift rename to BitcoinCore/Classes/Helpers/Helpers.swift index 37e37bef..3782235d 100644 --- a/BitcoinCore/BitcoinCore/Helpers/Helpers.swift +++ b/BitcoinCore/Classes/Helpers/Helpers.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import UIExtensions func ipv4(from data: Data) -> String { return Data(data.dropFirst(12)).map { String($0) }.joined(separator: ".") @@ -19,12 +19,11 @@ func pton(_ address: String) -> Data { return buffer } -/// Version = 1 byte of 0 (zero); on the test network, this is 1 byte of 111 -/// Key hash = Version concatenated with RIPEMD-160(SHA-256(public key)) -/// Checksum = 1st 4 bytes of SHA-256(SHA-256(Key hash)) -/// Bitcoin Address = Base58Encode(Key hash concatenated with Checksum) -func publicKeyHashToAddress(_ hash: Data) -> String { - let checksum = CryptoKit.sha256sha256(hash).prefix(4) - let address = Base58.encode(hash + checksum) - return address +func byteArrayLittleEndian(int: Int) -> [UInt8] { + return [ + UInt8(int & 0x000000FF), + UInt8((int & 0x0000FF00) >> 8), + UInt8((int & 0x00FF0000) >> 16), + UInt8((int & 0xFF000000) >> 24) + ] } diff --git a/BitcoinCore/BitcoinCore/Helpers/MerkleBranch.swift b/BitcoinCore/Classes/Helpers/MerkleBranch.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Helpers/MerkleBranch.swift rename to BitcoinCore/Classes/Helpers/MerkleBranch.swift diff --git a/BitcoinCore/BitcoinCore/Helpers/PaymentAddressParser.swift b/BitcoinCore/Classes/Helpers/PaymentAddressParser.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Helpers/PaymentAddressParser.swift rename to BitcoinCore/Classes/Helpers/PaymentAddressParser.swift diff --git a/BitcoinCore/BitcoinCore/Helpers/Serialization.swift b/BitcoinCore/Classes/Helpers/Serialization.swift similarity index 59% rename from BitcoinCore/BitcoinCore/Helpers/Serialization.swift rename to BitcoinCore/Classes/Helpers/Serialization.swift index 5954ae1c..c0816978 100644 --- a/BitcoinCore/BitcoinCore/Helpers/Serialization.swift +++ b/BitcoinCore/Classes/Helpers/Serialization.swift @@ -32,29 +32,3 @@ public extension Data { return VarInt(value) } } - -extension Data { - public init?(hex: String) { - let len = hex.count / 2 - var data = Data(capacity: len) - for i in 0.. [Output] { + outputs.sorted(by: Bip69.outputComparator) + } + + func sort(unspentOutputs: [UnspentOutput]) -> [UnspentOutput] { + unspentOutputs.sorted(by: Bip69.inputComparator) + } + +} + +class ShuffleSorter: ITransactionDataSorter { + + func sort(outputs: [Output]) -> [Output] { + outputs.shuffled() + } + + func sort(unspentOutputs: [UnspentOutput]) -> [UnspentOutput] { + unspentOutputs.shuffled() + } + +} + +class StraightSorter: ITransactionDataSorter { + + func sort(outputs: [Output]) -> [Output] { + outputs + } + + func sort(unspentOutputs: [UnspentOutput]) -> [UnspentOutput] { + unspentOutputs + } + +} diff --git a/BitcoinCore/BitcoinCore/Helpers/UnspentOutputSelectorChain.swift b/BitcoinCore/Classes/Helpers/UnspentOutputSelectorChain.swift similarity index 70% rename from BitcoinCore/BitcoinCore/Helpers/UnspentOutputSelectorChain.swift rename to BitcoinCore/Classes/Helpers/UnspentOutputSelectorChain.swift index a95c1379..d55249aa 100644 --- a/BitcoinCore/BitcoinCore/Helpers/UnspentOutputSelectorChain.swift +++ b/BitcoinCore/Classes/Helpers/UnspentOutputSelectorChain.swift @@ -1,12 +1,12 @@ class UnspentOutputSelectorChain: IUnspentOutputSelector { var concreteSelectors = [IUnspentOutputSelector]() - func select(value: Int, feeRate: Int, outputScriptType: ScriptType, changeType: ScriptType, senderPay: Bool) throws -> SelectedUnspentOutputInfo { - var lastError: Error = BitcoinCoreErrors.Unexpected.unkown + func select(value: Int, feeRate: Int, outputScriptType: ScriptType, changeType: ScriptType, senderPay: Bool, pluginDataOutputSize: Int) throws -> SelectedUnspentOutputInfo { + var lastError: Error = BitcoinCoreErrors.Unexpected.unknown for selector in concreteSelectors { do { - return try selector.select(value: value, feeRate: feeRate, outputScriptType: outputScriptType, changeType: changeType, senderPay: senderPay) + return try selector.select(value: value, feeRate: feeRate, outputScriptType: outputScriptType, changeType: changeType, senderPay: senderPay, pluginDataOutputSize: pluginDataOutputSize) } catch { lastError = error } diff --git a/BitcoinCore/BitcoinCore/Managers/StateManager.swift b/BitcoinCore/Classes/Managers/ApiSyncStateManager.swift similarity index 85% rename from BitcoinCore/BitcoinCore/Managers/StateManager.swift rename to BitcoinCore/Classes/Managers/ApiSyncStateManager.swift index d3a9f98a..37fafb09 100644 --- a/BitcoinCore/BitcoinCore/Managers/StateManager.swift +++ b/BitcoinCore/Classes/Managers/ApiSyncStateManager.swift @@ -1,4 +1,4 @@ -class StateManager { +class ApiSyncStateManager { private let storage: IStorage private let restoreFromApi: Bool @@ -9,7 +9,7 @@ class StateManager { } -extension StateManager: IStateManager { +extension ApiSyncStateManager: IApiSyncStateManager { var restored: Bool { get { diff --git a/BitcoinCore/Classes/Managers/BloomFilterManager.swift b/BitcoinCore/Classes/Managers/BloomFilterManager.swift new file mode 100644 index 00000000..6e984db4 --- /dev/null +++ b/BitcoinCore/Classes/Managers/BloomFilterManager.swift @@ -0,0 +1,36 @@ +class BloomFilterManager { + class BloomFilterExpired: Error {} + + private var providers = [IBloomFilterProvider]() + + private let factory: IFactory + weak var delegate: IBloomFilterManagerDelegate? + + var bloomFilter: BloomFilter? + + init(factory: IFactory) { + self.factory = factory + } +} + +extension BloomFilterManager: IBloomFilterManager { + + func add(provider: IBloomFilterProvider) { + provider.bloomFilterManager = self + providers.append(provider) + } + + func regenerateBloomFilter() { + var elements = [Data]() + + for provider in providers { + elements.append(contentsOf: provider.filterElements()) + } + + if !elements.isEmpty { + bloomFilter = factory.bloomFilter(withElements: elements) + delegate?.bloomFilterUpdated(bloomFilter: bloomFilter!) + } + } + +} diff --git a/BitcoinCore/Classes/Managers/InitialSync/BCoinApi.swift b/BitcoinCore/Classes/Managers/InitialSync/BCoinApi.swift new file mode 100644 index 00000000..bc21be4f --- /dev/null +++ b/BitcoinCore/Classes/Managers/InitialSync/BCoinApi.swift @@ -0,0 +1,29 @@ +import RxSwift +import ObjectMapper +import Alamofire +import HsToolKit + +public class BCoinApi { + private let url: String + private let networkManager: NetworkManager + + public init(url: String, logger: Logger? = nil) { + self.url = url + networkManager = NetworkManager(logger: logger) + } + +} + +extension BCoinApi: ISyncTransactionApi { + + public func getTransactions(addresses: [String]) -> Single<[SyncTransactionItem]> { + let parameters: Parameters = [ + "addresses": addresses + ] + let path = "/tx/address" + + let request = networkManager.session.request(url + path, method: .post, parameters: parameters, encoding: JSONEncoding.default) + return networkManager.single(request: request) + } + +} diff --git a/BitcoinCore/Classes/Managers/InitialSync/BlockDiscoveryBatch.swift b/BitcoinCore/Classes/Managers/InitialSync/BlockDiscoveryBatch.swift new file mode 100644 index 00000000..1c83d8dc --- /dev/null +++ b/BitcoinCore/Classes/Managers/InitialSync/BlockDiscoveryBatch.swift @@ -0,0 +1,78 @@ +import Foundation +import RxSwift +import ObjectMapper +import HsToolKit + +class BlockDiscoveryBatch { + private let wallet: IHDWallet + private let blockHashFetcher: IBlockHashFetcher + + private let maxHeight: Int + private let gapLimit: Int + + init(checkpoint: Checkpoint, wallet: IHDWallet, blockHashFetcher: IBlockHashFetcher, logger: Logger? = nil) { + self.wallet = wallet + self.blockHashFetcher = blockHashFetcher + + maxHeight = checkpoint.block.height + gapLimit = wallet.gapLimit + } + + private func fetchRecursive(account: Int, blockHashes: [BlockHash] = [], externalBatchInfo: KeyBlockHashBatchInfo = KeyBlockHashBatchInfo(), internalBatchInfo: KeyBlockHashBatchInfo = KeyBlockHashBatchInfo()) -> Single<([PublicKey], [BlockHash])> { + let maxHeight = self.maxHeight + + let externalCount = gapLimit - externalBatchInfo.prevCount + externalBatchInfo.prevLastUsedIndex + 1 + let internalCount = gapLimit - internalBatchInfo.prevCount + internalBatchInfo.prevLastUsedIndex + 1 + + var externalNewKeys = [PublicKey]() + var internalNewKeys = [PublicKey]() + + do { + externalNewKeys.append(contentsOf: try wallet.publicKeys(account: account, indices: UInt32(externalBatchInfo.startIndex).. Single<([PublicKey], [BlockHash])> in + let resultBlockHashes = blockHashes + fetcherResponse.blockHashes.filter { $0.height <= maxHeight } + let externalPublicKeys = externalBatchInfo.publicKeys + externalNewKeys + let internalPublicKeys = internalBatchInfo.publicKeys + internalNewKeys + + let finishSingle = Single.just((externalPublicKeys + internalPublicKeys, resultBlockHashes)) + + if fetcherResponse.externalLastUsedIndex < 0 && fetcherResponse.internalLastUsedIndex < 0 { + return finishSingle + } else { + let externalBatch = KeyBlockHashBatchInfo(publicKeys: externalPublicKeys, prevCount: externalCount, prevLastUsedIndex: fetcherResponse.externalLastUsedIndex, startIndex: externalBatchInfo.startIndex + externalCount) + let internalBatch = KeyBlockHashBatchInfo(publicKeys: internalPublicKeys, prevCount: internalCount, prevLastUsedIndex: fetcherResponse.internalLastUsedIndex, startIndex: internalBatchInfo.startIndex + internalCount) + + return self?.fetchRecursive(account: account, blockHashes: resultBlockHashes, externalBatchInfo: externalBatch, internalBatchInfo: internalBatch) ?? finishSingle + } + } + } + +} + +extension BlockDiscoveryBatch: IBlockDiscovery { + + func discoverBlockHashes(account: Int) -> Single<([PublicKey], [BlockHash])> { + fetchRecursive(account: account) + } + +} + +class KeyBlockHashBatchInfo { + var publicKeys: [PublicKey] + var prevCount: Int + var prevLastUsedIndex: Int + var startIndex: Int + + init(publicKeys: [PublicKey] = [], prevCount: Int = 0, prevLastUsedIndex: Int = -1, startIndex: Int = 0) { + self.publicKeys = publicKeys + self.prevCount = prevCount + self.prevLastUsedIndex = prevLastUsedIndex + self.startIndex = startIndex + } + +} diff --git a/BitcoinCore/Classes/Managers/InitialSync/BlockHashFetcher.swift b/BitcoinCore/Classes/Managers/InitialSync/BlockHashFetcher.swift new file mode 100644 index 00000000..e6cfe540 --- /dev/null +++ b/BitcoinCore/Classes/Managers/InitialSync/BlockHashFetcher.swift @@ -0,0 +1,56 @@ +import RxSwift + +class BlockHashFetcher { + weak var listener: IApiSyncListener? + + private let restoreKeyConverter: IRestoreKeyConverter + private let apiManager: ISyncTransactionApi + private let helper: IBlockHashFetcherHelper + + init(restoreKeyConverter: IRestoreKeyConverter, apiManager: ISyncTransactionApi, helper: IBlockHashFetcherHelper) { + self.restoreKeyConverter = restoreKeyConverter + self.apiManager = apiManager + self.helper = helper + } + +} + +extension BlockHashFetcher: IBlockHashFetcher { + + func getBlockHashes(externalKeys: [PublicKey], internalKeys: [PublicKey]) -> Single { + let externalAddresses = externalKeys.map { + restoreKeyConverter.keysForApiRestore(publicKey: $0) + } + + let internalAddresses = internalKeys.map { + restoreKeyConverter.keysForApiRestore(publicKey: $0) + } + + let allAddresses = externalAddresses.flatMap { $0 } + internalAddresses.flatMap { $0 } + + return apiManager.getTransactions(addresses: allAddresses).map { [weak self] transactionResponses -> BlockHashesResponse in + if transactionResponses.isEmpty { + return BlockHashesResponse(blockHashes: [], externalLastUsedIndex: -1, internalLastUsedIndex: -1) + } + + self?.listener?.transactionsFound(count: transactionResponses.count) + + let outputs = transactionResponses.flatMap { $0.txOutputs } + let externalLastUsedIndex = self?.helper.lastUsedIndex(addresses: externalAddresses, outputs: outputs) + let internalLastUsedIndex = self?.helper.lastUsedIndex(addresses: internalAddresses, outputs: outputs) + + let blockHashes: [BlockHash] = transactionResponses.compactMap { + BlockHash(headerHashReversedHex: $0.blockHash, height: $0.blockHeight, sequence: 0) + } + + return BlockHashesResponse(blockHashes: blockHashes, externalLastUsedIndex: externalLastUsedIndex ?? -1, internalLastUsedIndex: internalLastUsedIndex ?? -1) + } + } + +} + +struct BlockHashesResponse { + let blockHashes: [BlockHash] + let externalLastUsedIndex: Int + let internalLastUsedIndex: Int +} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockHashFetcherHelper.swift b/BitcoinCore/Classes/Managers/InitialSync/BlockHashFetcherHelper.swift similarity index 90% rename from BitcoinCore/BitcoinCore/Managers/InitialSync/BlockHashFetcherHelper.swift rename to BitcoinCore/Classes/Managers/InitialSync/BlockHashFetcherHelper.swift index 3910269b..1370c296 100644 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/BlockHashFetcherHelper.swift +++ b/BitcoinCore/Classes/Managers/InitialSync/BlockHashFetcherHelper.swift @@ -1,6 +1,10 @@ class BlockHashFetcherHelper: IBlockHashFetcherHelper { func lastUsedIndex(addresses: [[String]], outputs: [SyncTransactionOutputItem]) -> Int { + guard addresses.count > 0 else { + return -1 + } + let searchAddressStrings = outputs.map { $0.address } let searchScriptStrings = outputs.map { $0.script } diff --git a/BitcoinCore/Classes/Managers/InitialSync/BlockchainComApi.swift b/BitcoinCore/Classes/Managers/InitialSync/BlockchainComApi.swift new file mode 100644 index 00000000..4c9efc68 --- /dev/null +++ b/BitcoinCore/Classes/Managers/InitialSync/BlockchainComApi.swift @@ -0,0 +1,163 @@ +import RxSwift +import ObjectMapper +import Alamofire +import HsToolKit + +public class BlockchainComApi { + private static let paginationLimit = 100 + private static let addressesLimit = 50 + + private let url: String + private let hsUrl: String + private let networkManager: NetworkManager + + private static var serialSchedulers = [String: SerialDispatchQueueScheduler]() + private let serialScheduler: SerialDispatchQueueScheduler + + public init(url: String, hsUrl: String, logger: Logger? = nil) { + self.url = url + self.hsUrl = hsUrl + networkManager = NetworkManager(logger: logger) + + if let scheduler = Self.serialSchedulers[url] { + serialScheduler = scheduler + } else { + serialScheduler = SerialDispatchQueueScheduler(qos: .utility) + Self.serialSchedulers[url] = serialScheduler + } + } + + private func addressesSingle(addresses: [String], offset: Int = 0) -> Single { + let parameters: Parameters = [ + "active": addresses.joined(separator: "|"), + "n": Self.paginationLimit, + "offset": offset + ] + + let request = networkManager.session.request("\(url)/multiaddr", method: .get, parameters: parameters) + return networkManager.single(request: request, sync: true, postDelay: 0.5) + } + + private func blocksSingle(heights: [Int]) -> Single<[BlockResponse]> { + let parameters: Parameters = [ + "numbers": heights.map { String($0) }.joined(separator: ",") + ] + + let request = networkManager.session.request("\(hsUrl)/hashes", method: .get, parameters: parameters) + return networkManager.single(request: request) + } + + private func itemsSingle(transactionResponses: [TransactionResponse]) -> Single<[SyncTransactionItem]> { + guard !transactionResponses.isEmpty else { + return Single.just([]) + } + + let blockHeights = Array(Set(transactionResponses.map { $0.blockHeight })) + + return blocksSingle(heights: blockHeights) + .map { blocks in + transactionResponses.compactMap { response in + guard let block = blocks.first(where: { $0.height == response.blockHeight }) else { + return nil + } + + return SyncTransactionItem( + hash: block.hash, + height: block.height, + txOutputs: response.outputs.map { + SyncTransactionOutputItem(script: $0.script, address: $0.address) + } + ) + } + } + } + + private func itemsSingle(addresses: [String], offset: Int) -> Single<[SyncTransactionItem]> { + addressesSingle(addresses: addresses, offset: offset) + .subscribeOn(serialScheduler) + .flatMap { [unowned self] response in + itemsSingle(transactionResponses: response.transactions) + } + } + + private func itemsSingle(addressChunk: [String], offset: Int = 0) -> Single<[SyncTransactionItem]> { + itemsSingle(addresses: addressChunk, offset: offset) + .flatMap { [unowned self] chunkItems in + if chunkItems.count < Self.paginationLimit { + return Single.just(chunkItems) + } + + return itemsSingle(addressChunk: addressChunk, offset: offset + Self.paginationLimit).map { items in + chunkItems + items + } + } + } + + public func itemsSingle(allAddresses: [String], index: Int = 0) -> Single<[SyncTransactionItem]> { + let startIndex = index * Self.addressesLimit + + guard startIndex <= allAddresses.count else { + return Single.just([]) + } + + let endIndex = min(allAddresses.count, (index + 1) * Self.addressesLimit) + let chunk = Array(allAddresses[startIndex.. Single<[SyncTransactionItem]> { + itemsSingle(allAddresses: addresses) + } + +} + +extension BlockchainComApi { + + struct AddressesResponse: ImmutableMappable { + let transactions: [TransactionResponse] + + init(map: Map) throws { + transactions = try map.value("txs") + } + } + + struct TransactionResponse: ImmutableMappable { + let blockHeight: Int + let outputs: [TransactionOutputResponse] + + init(map: Map) throws { + blockHeight = try map.value("block_height") + outputs = try map.value("out") + } + } + + struct TransactionOutputResponse: ImmutableMappable { + let script: String + let address: String? + + init(map: Map) throws { + script = try map.value("script") + address = try? map.value("addr") + } + } + + struct BlockResponse: ImmutableMappable { + let height: Int + let hash: String + + init(map: Map) throws { + height = try map.value("number") + hash = try map.value("hash") + } + } + +} diff --git a/BitcoinCore/Classes/Managers/InitialSync/InitialSyncer.swift b/BitcoinCore/Classes/Managers/InitialSync/InitialSyncer.swift new file mode 100644 index 00000000..f49fa546 --- /dev/null +++ b/BitcoinCore/Classes/Managers/InitialSync/InitialSyncer.swift @@ -0,0 +1,75 @@ +import HdWalletKit +import RxSwift +import HsToolKit + +class InitialSyncer { + weak var delegate: IInitialSyncerDelegate? + + private var disposeBag = DisposeBag() + + private let storage: IStorage + private let blockDiscovery: IBlockDiscovery + private let publicKeyManager: IPublicKeyManager + + private let logger: Logger? + + init(storage: IStorage, blockDiscovery: IBlockDiscovery, publicKeyManager: IPublicKeyManager, logger: Logger? = nil) { + self.storage = storage + self.blockDiscovery = blockDiscovery + self.publicKeyManager = publicKeyManager + + self.logger = logger + } + + private func sync(forAccount account: Int) { + var single = blockDiscovery.discoverBlockHashes(account: account) + .map { array -> ([PublicKey], [BlockHash]) in + let (keys, blockHashes) = array + let sortedUniqueBlockHashes = blockHashes.unique.sorted { a, b in a.height < b.height } + + return (keys, sortedUniqueBlockHashes) + } + + single.subscribe(onSuccess: { [weak self] keys, responses in + self?.handle(forAccount: account, keys: keys, blockHashes: responses) + }, onError: { [weak self] error in + self?.handle(error: error) + }) + .disposed(by: disposeBag) + } + + private func handle(forAccount account: Int, keys: [PublicKey], blockHashes: [BlockHash]) { + logger?.debug("Account \(account) has \(keys.count) keys and \(blockHashes.count) blocks") + publicKeyManager.addKeys(keys: keys) + + // If gap shift is found + if blockHashes.isEmpty { + handleSuccess() + } else { + storage.add(blockHashes: blockHashes) + sync(forAccount: account + 1) + } + } + + private func handleSuccess() { + delegate?.onSyncSuccess() + } + + private func handle(error: Error) { + logger?.error(error, context: ["apiSync"], save: true) + delegate?.onSyncFailed(error: error) + } + +} + +extension InitialSyncer: IInitialSyncer { + + func sync() { + sync(forAccount: 0) + } + + func terminate() { + disposeBag = DisposeBag() + } + +} diff --git a/BitcoinCore/Classes/Managers/InitialSync/InsightApi.swift b/BitcoinCore/Classes/Managers/InitialSync/InsightApi.swift new file mode 100644 index 00000000..823a8597 --- /dev/null +++ b/BitcoinCore/Classes/Managers/InitialSync/InsightApi.swift @@ -0,0 +1,118 @@ +import RxSwift +import ObjectMapper +import Alamofire +import HsToolKit + +public class InsightApi { + private static let paginationLimit = 50 + private static let addressesLimit = 99 + + private let url: String + private let networkManager: NetworkManager + + public init(url: String, logger: Logger? = nil) { + self.url = url + networkManager = NetworkManager(logger: logger) + } + +} + +extension InsightApi: ISyncTransactionApi { + + public func getTransactions(addresses: [String]) -> Single<[SyncTransactionItem]> { + sendAddressesRecursive(addresses: addresses) + } + + private func sendAddressesRecursive(addresses: [String], from: Int = 0, transactions: [SyncTransactionItem] = []) -> Single<[SyncTransactionItem]> { + let last = min(from + InsightApi.addressesLimit, addresses.count) + let chunk = addresses[from.. Single<[SyncTransactionItem]> in + let resultTransactions = transactions + result + + let finishSingle = Single.just(resultTransactions) + if last >= addresses.count { + return finishSingle + } else { + return self?.sendAddressesRecursive(addresses: addresses, from: from + InsightApi.addressesLimit, transactions: resultTransactions) ?? finishSingle + } + } + } + + private func getTransactionsRecursive(addresses: String, from: Int = 0, transactions: [SyncTransactionItem] = []) -> Single<[SyncTransactionItem]> { + getTransactions(addresses: addresses, from: from).flatMap { [weak self] result -> Single<[SyncTransactionItem]> in + let resultTransactions = transactions + result.transactionItems.map { $0 as SyncTransactionItem } + + let finishSingle = Single.just(resultTransactions) + if result.totalItems <= result.to { + return finishSingle + } else { + return self?.getTransactionsRecursive(addresses: addresses, from: result.to, transactions: resultTransactions) ?? finishSingle + } + } + } + + private func getTransactions(addresses: String, from: Int = 0) -> Single { + let parameters: Parameters = [ + "from": from, + "to": from + InsightApi.paginationLimit + ] + let path = "/addrs/\(addresses)/txs" + + let request = networkManager.session.request(url + path, method: .get, parameters: parameters) + + return networkManager.single(request: request) + } + + class InsightResponseItem: ImmutableMappable { + public let totalItems: Int + public let from: Int + public let to: Int + public let transactionItems: [InsightTransactionItem] + + public init(totalItems: Int, from: Int, to: Int, transactionItems: [InsightTransactionItem]) { + self.totalItems = totalItems + self.from = from + self.to = to + self.transactionItems = transactionItems + } + + required public init(map: Map) throws { + totalItems = try map.value("totalItems") + var fromInt: Int? + if let fromString: String = try? map.value("from") { + fromInt = Int(fromString) + } else { + fromInt = try? map.value("from") + } + guard let from = fromInt else { + throw MapError(key: "from", currentValue: "n/a", reason: "can't parse from value") + } + self.from = from + to = try map.value("to") + transactionItems = try map.value("items") + } + + } + + class InsightTransactionItem: SyncTransactionItem { + + required init(map: Map) throws { + let blockHash: String? = try? map.value("blockhash") + let blockHeight: Int? = try? map.value("blockheight") + let txOutputs: [InsightTransactionOutputItem] = (try? map.value("vout")) ?? [] + super.init(hash: blockHash, height: blockHeight, txOutputs: txOutputs.map { $0 as SyncTransactionOutputItem }) + } + + } + + class InsightTransactionOutputItem: SyncTransactionOutputItem { + required init(map: Map) throws { + let script: String = (try? map.value("scriptPubKey.hex")) ?? "" + let address: [String] = (try? map.value("scriptPubKey.addresses")) ?? [] + super.init(script: script, address: address.joined()) + } + + } + +} diff --git a/BitcoinCore/BitcoinCore/Managers/InitialSync/SyncTransactionItem.swift b/BitcoinCore/Classes/Managers/InitialSync/SyncTransactionItem.swift similarity index 59% rename from BitcoinCore/BitcoinCore/Managers/InitialSync/SyncTransactionItem.swift rename to BitcoinCore/Classes/Managers/InitialSync/SyncTransactionItem.swift index ecbf06c2..d1f0713a 100644 --- a/BitcoinCore/BitcoinCore/Managers/InitialSync/SyncTransactionItem.swift +++ b/BitcoinCore/Classes/Managers/InitialSync/SyncTransactionItem.swift @@ -2,33 +2,33 @@ import Foundation import ObjectMapper open class SyncTransactionItem: ImmutableMappable { - public let blockHash: String - public let blockHeight: Int + public let blockHash: String? + public let blockHeight: Int? public let txOutputs: [SyncTransactionOutputItem] - public init(hash: String, height: Int, txOutputs: [SyncTransactionOutputItem]) { + public init(hash: String?, height: Int?, txOutputs: [SyncTransactionOutputItem]) { self.blockHash = hash self.blockHeight = height self.txOutputs = txOutputs } required public init(map: Map) throws { - blockHash = try map.value("block") - blockHeight = try map.value("height") - txOutputs = try map.value("outputs") + blockHash = try? map.value("block") + blockHeight = try? map.value("height") + txOutputs = (try? map.value("outputs")) ?? [] } static func ==(lhs: SyncTransactionItem, rhs: SyncTransactionItem) -> Bool { - return lhs.blockHash == rhs.blockHash && lhs.blockHeight == rhs.blockHeight + lhs.blockHash == rhs.blockHash && lhs.blockHeight == rhs.blockHeight } } open class SyncTransactionOutputItem: ImmutableMappable { public let script: String - public let address: String + public let address: String? - public init(script: String, address: String) { + public init(script: String, address: String?) { self.script = script self.address = address } diff --git a/BitcoinCore/Classes/Managers/IrregularOutputFinder.swift b/BitcoinCore/Classes/Managers/IrregularOutputFinder.swift new file mode 100644 index 00000000..b9a26dae --- /dev/null +++ b/BitcoinCore/Classes/Managers/IrregularOutputFinder.swift @@ -0,0 +1,63 @@ +class IrregularOutputFinder { + + private let irregularScriptTypes: [ScriptType] = [.p2wpkh, .p2pk, .p2wpkhSh] + private let storage: IStorage + weak var bloomFilterManager: IBloomFilterManager? = nil + + init(storage: IStorage) { + self.storage = storage + } + + private func needToSetToBloomFilter(output: OutputWithPublicKey, bestBlockHeight: Int) -> Bool { + // Need to set if output is unspent + guard let _ = output.spendingInput else { + return true + } + + if let spendingBlockHeight = output.spendingBlockHeight { + // If output is spent, we still need to set to bloom filter if it hasn't at least 100 confirmations + return bestBlockHeight - spendingBlockHeight < 100 + } + + // if output is spent by a mempool transaction, that is, spending input's transaction has not a block + return true + } + +} + +extension IrregularOutputFinder: IIrregularOutputFinder { + + func hasIrregularOutput(outputs: [Output]) -> Bool { + for output in outputs { + if output.publicKeyPath != nil, irregularScriptTypes.contains(output.scriptType) { + return true + } + } + + return false + } + +} + +extension IrregularOutputFinder: IBloomFilterProvider { + + func filterElements() -> [Data] { + var elements = [Data]() + + var outputs = storage.outputsWithPublicKeys().filter { irregularScriptTypes.contains($0.output.scriptType) } + + if let bestBlockHeight = storage.lastBlock?.height { + outputs = outputs.filter { + self.needToSetToBloomFilter(output: $0, bestBlockHeight: bestBlockHeight) + } + } + + for outputWithPublicKey in outputs { + let outpoint = outputWithPublicKey.output.transactionHash + byteArrayLittleEndian(int: outputWithPublicKey.output.index) + elements.append(outpoint) + } + + return elements + } + +} diff --git a/BitcoinCore/Classes/Managers/PendingOutpointsProvider.swift b/BitcoinCore/Classes/Managers/PendingOutpointsProvider.swift new file mode 100644 index 00000000..18751f11 --- /dev/null +++ b/BitcoinCore/Classes/Managers/PendingOutpointsProvider.swift @@ -0,0 +1,22 @@ +class PendingOutpointsProvider { + private let storage: IStorage + + weak var bloomFilterManager: IBloomFilterManager? + + init(storage: IStorage) { + self.storage = storage + } + +} + +extension PendingOutpointsProvider: IBloomFilterProvider { + + func filterElements() -> [Data] { + let hashes = storage.incomingPendingTransactionHashes() + + return storage.inputs(byHashes: hashes).map { + $0.previousOutputTxHash + byteArrayLittleEndian(int: $0.previousOutputIndex) + } + } + +} \ No newline at end of file diff --git a/BitcoinCore/Classes/Managers/PluginManager.swift b/BitcoinCore/Classes/Managers/PluginManager.swift new file mode 100644 index 00000000..bdf90b75 --- /dev/null +++ b/BitcoinCore/Classes/Managers/PluginManager.swift @@ -0,0 +1,125 @@ +import HsToolKit + +class PluginManager { + enum PluginError: Error { + case pluginNotFound + } + + private let scriptConverter: IScriptConverter + private var plugins = [UInt8: IPlugin]() + + private let logger: Logger? + + init(scriptConverter: IScriptConverter, logger: Logger? = nil) { + self.scriptConverter = scriptConverter + self.logger = logger + } + +} + +extension PluginManager: IPluginManager { + + func validate(address: Address, pluginData: [UInt8: IPluginData]) throws { + for (key, _) in pluginData { + guard let plugin = plugins[key] else { + throw PluginError.pluginNotFound + } + + try plugin.validate(address: address) + } + } + + func maxSpendLimit(pluginData: [UInt8: IPluginData]) throws -> Int? { + try pluginData.compactMap({ key, data in + guard let plugin = plugins[key] else { + throw PluginError.pluginNotFound + } + + return plugin.maxSpendLimit + }).min() + } + + func add(plugin: IPlugin) { + plugins[plugin.id] = plugin + } + + func processOutputs(mutableTransaction: MutableTransaction, pluginData: [UInt8: IPluginData], skipChecks: Bool = false) throws { + for (key, data) in pluginData { + guard let plugin = plugins[key] else { + throw PluginError.pluginNotFound + } + + try plugin.processOutputs(mutableTransaction: mutableTransaction, pluginData: data, skipChecks: skipChecks) + } + } + + func processInputs(mutableTransaction: MutableTransaction) throws { + for inputToSign in mutableTransaction.inputsToSign { + guard let pluginId = inputToSign.previousOutput.pluginId else { + continue + } + + guard let plugin = plugins[pluginId] else { + throw PluginError.pluginNotFound + } + + inputToSign.input.sequence = try plugin.inputSequenceNumber(output: inputToSign.previousOutput) + } + } + + func processTransactionWithNullData(transaction: FullTransaction, nullDataOutput: Output) throws { + guard let script = try? scriptConverter.decode(data: nullDataOutput.lockingScript) else { + return + } + + var iterator = script.chunks.makeIterator() + + // the first byte OP_RETURN + _ = iterator.next() + + do { + while let pluginId = iterator.next() { + guard let plugin = plugins[pluginId.opCode] else { + break + } + + try plugin.processTransactionWithNullData(transaction: transaction, nullDataChunks: &iterator) + } + } catch { + logger?.error(error) + } + } + + func isSpendable(unspentOutput: UnspentOutput) -> Bool { + guard let pluginId = unspentOutput.output.pluginId else { + return true + } + + guard let plugin = plugins[pluginId] else { + return false + } + + return (try? plugin.isSpendable(unspentOutput: unspentOutput)) ?? true + } + + public func parsePluginData(fromPlugin pluginId: UInt8, pluginDataString: String, transactionTimestamp: Int) -> IPluginOutputData? { + guard let plugin = plugins[pluginId] else { + return nil + } + + return try? plugin.parsePluginData(from: pluginDataString, transactionTimestamp: transactionTimestamp) + } + +} + +extension PluginManager: IRestoreKeyConverter { + + public func keysForApiRestore(publicKey: PublicKey) -> [String] { + (try? plugins.flatMap({ try $0.value.keysForApiRestore(publicKey: publicKey) })) ?? [] + } + + public func bloomFilterElements(publicKey: PublicKey) -> [Data] { + [] + } + +} diff --git a/BitcoinCore/BitcoinCore/Managers/AddressManager.swift b/BitcoinCore/Classes/Managers/PublicKeyManager.swift similarity index 63% rename from BitcoinCore/BitcoinCore/Managers/AddressManager.swift rename to BitcoinCore/Classes/Managers/PublicKeyManager.swift index 3338c14a..f16c3f26 100644 --- a/BitcoinCore/BitcoinCore/Managers/AddressManager.swift +++ b/BitcoinCore/Classes/Managers/PublicKeyManager.swift @@ -1,21 +1,21 @@ -import HSHDWalletKit +import HdWalletKit -class AddressManager { +class PublicKeyManager { - enum AddressManagerError: Error { + enum PublicKeyManagerError: Error { case noUnusedPublicKey + case invalidPath } + private let restoreKeyConverter: IRestoreKeyConverter private let storage: IStorage private let hdWallet: IHDWallet - private let addressKeyHashConverter: IAddressKeyHashConverter? - private let addressConverter: IAddressConverter + weak var bloomFilterManager: IBloomFilterManager? - init(storage: IStorage, hdWallet: IHDWallet, addressConverter: IAddressConverter, addressKeyHashConverter: IAddressKeyHashConverter? = nil) { + init(storage: IStorage, hdWallet: IHDWallet, restoreKeyConverter: IRestoreKeyConverter) { self.storage = storage - self.addressConverter = addressConverter - self.addressKeyHashConverter = addressKeyHashConverter self.hdWallet = hdWallet + self.restoreKeyConverter = restoreKeyConverter } private func fillGap(publicKeysWithUsedStates: [PublicKeyWithUsedState], account: Int, external: Bool) throws { @@ -26,14 +26,13 @@ class AddressManager { if gapKeysCount < hdWallet.gapLimit { let allKeys = publicKeys.sorted(by: { $0.publicKey.index < $1.publicKey.index }) let lastIndex = allKeys.last?.publicKey.index ?? -1 + let newKeysStartIndex = lastIndex + 1 + let indices = UInt32(newKeysStartIndex).. Int { @@ -49,24 +48,21 @@ class AddressManager { .filter({ $0.publicKey.external == external && $0.publicKey.account == 0 && !$0.used }) .sorted(by: { $0.publicKey.index < $1.publicKey.index }) .first else { - throw AddressManagerError.noUnusedPublicKey + throw PublicKeyManagerError.noUnusedPublicKey } return unusedKey.publicKey } } -extension AddressManager: IAddressManager { +extension PublicKeyManager: IPublicKeyManager { func changePublicKey() throws -> PublicKey { return try publicKey(external: false) } - func receiveAddress(for type: ScriptType) throws -> String { - let keyHash = try publicKey(external: true).keyHash - let correctKeyHash = addressKeyHashConverter?.convert(keyHash: keyHash, type: type) ?? keyHash - - return try addressConverter.convert(keyHash: correctKeyHash, type: type).stringValue + func receivePublicKey() throws -> PublicKey { + return try publicKey(external: true) } func fillGap() throws { @@ -83,9 +79,11 @@ extension AddressManager: IAddressManager { try fillGap(publicKeysWithUsedStates: publicKeysWithUsedStates, account: i, external: true) try fillGap(publicKeysWithUsedStates: publicKeysWithUsedStates, account: i, external: false) } + + bloomFilterManager?.regenerateBloomFilter() } - func addKeys(keys: [PublicKey]) throws { + func addKeys(keys: [PublicKey]) { guard !keys.isEmpty else { return } @@ -113,12 +111,39 @@ extension AddressManager: IAddressManager { return false } + public func publicKey(byPath path: String) throws -> PublicKey { + let parts = path.split(separator: "/") + + guard parts.count == 3, let account = Int(parts[0]), let external = Int(parts[1]), let index = Int(parts[2]) else { + throw PublicKeyManagerError.invalidPath + } + + if let publicKey = storage.publicKey(byPath: path) { + return publicKey + } + + return try hdWallet.publicKey(account: account, index: index, external: external == 1) + } +} + +extension PublicKeyManager: IBloomFilterProvider { + + func filterElements() -> [Data] { + var elements = [Data]() + + for publicKey in storage.publicKeys() { + elements.append(contentsOf: restoreKeyConverter.bloomFilterElements(publicKey: publicKey)) + } + + return elements + } + } -extension AddressManager { +extension PublicKeyManager { - public static func instance(storage: IStorage, hdWallet: IHDWallet, addressConverter: IAddressConverter, addressKeyHashConverter: IAddressKeyHashConverter? = nil) -> AddressManager { - let addressManager = AddressManager(storage: storage, hdWallet: hdWallet, addressConverter: addressConverter, addressKeyHashConverter: addressKeyHashConverter) + public static func instance(storage: IStorage, hdWallet: IHDWallet, restoreKeyConverter: IRestoreKeyConverter) -> PublicKeyManager { + let addressManager = PublicKeyManager(storage: storage, hdWallet: hdWallet, restoreKeyConverter: restoreKeyConverter) try? addressManager.fillGap() return addressManager } diff --git a/BitcoinCore/Classes/Managers/SyncManager.swift b/BitcoinCore/Classes/Managers/SyncManager.swift new file mode 100644 index 00000000..f8969f29 --- /dev/null +++ b/BitcoinCore/Classes/Managers/SyncManager.swift @@ -0,0 +1,200 @@ +import RxSwift +import HsToolKit + +class SyncManager { + private var disposeBag = DisposeBag() + weak var delegate: ISyncManagerDelegate? + + private let reachabilityManager: IReachabilityManager + private let initialSyncer: IInitialSyncer + private let peerGroup: IPeerGroup + private let apiSyncStateManager: IApiSyncStateManager + + private var initialBestBlockHeight: Int32 + private var currentBestBlockHeight: Int32 + private var foundTransactionsCount: Int = 0 + + private(set) var syncState: BitcoinCore.KitState = .notSynced(error: BitcoinCore.StateError.notStarted) { + didSet { + if !(oldValue == syncState) { + delegate?.kitStateUpdated(state: syncState) + } + } + } + + private var syncIdle: Bool { + guard case .notSynced(error: let error) = syncState else { + return false + } + + if let stateError = error as? BitcoinCore.StateError, stateError == .notStarted { + return false + } + + return true + } + + private var peerGroupRunning: Bool { + switch syncState { + case .syncing, .synced: return true + default: return false + } + } + + init(reachabilityManager: IReachabilityManager, initialSyncer: IInitialSyncer, peerGroup: IPeerGroup, apiSyncStateManager: IApiSyncStateManager, bestBlockHeight: Int32) { + self.reachabilityManager = reachabilityManager + self.initialSyncer = initialSyncer + self.peerGroup = peerGroup + self.apiSyncStateManager = apiSyncStateManager + initialBestBlockHeight = bestBlockHeight + currentBestBlockHeight = bestBlockHeight + + reachabilityManager.reachabilityObservable + .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .subscribe(onNext: { [weak self] _ in + self?.onReachabilityChanged() + }) + .disposed(by: disposeBag) + + reachabilityManager.connectionTypeUpdatedObservable + .observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + .subscribe(onNext: { [weak self] _ in + self?.onConnectionTypeUpdated() + }) + .disposed(by: disposeBag) + + BackgroundModeObserver.shared.foregroundFromExpiredBackgroundObservable + .observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + .subscribe(onNext: { [weak self] _ in + self?.onEnterForegroundFromExpiredBackground() + }) + .disposed(by: disposeBag) + } + + private func onReachabilityChanged() { + if reachabilityManager.isReachable { + onReachable() + } else { + onUnreachable() + } + } + + private func onConnectionTypeUpdated() { + if peerGroupRunning { + peerGroup.reconnectPeers() + } + } + + private func onEnterForegroundFromExpiredBackground() { + if peerGroupRunning { + peerGroup.reconnectPeers() + } + } + + private func onReachable() { + if syncIdle { + startSync() + } + } + + private func onUnreachable() { + if peerGroupRunning { + peerGroup.stop() + syncState = .notSynced(error: ReachabilityManager.ReachabilityError.notReachable) + } + } + + private func startPeerGroup() { + syncState = .syncing(progress: 0) + peerGroup.start() + } + + private func startInitialSync() { + syncState = .apiSyncing(transactions: foundTransactionsCount) + initialSyncer.sync() + } + + private func startSync() { + if apiSyncStateManager.restored { + startPeerGroup() + } else { + startInitialSync() + } + } + +} + +extension SyncManager: ISyncManager { + + func start() { + guard case .notSynced(_) = syncState else { + return + } + + guard reachabilityManager.isReachable else { + syncState = .notSynced(error: ReachabilityManager.ReachabilityError.notReachable) + return + } + + startSync() + } + + func stop() { + switch syncState { + case .apiSyncing: + initialSyncer.terminate() + case .syncing, .synced: + peerGroup.stop() + default: () + } + + syncState = .notSynced(error: BitcoinCore.StateError.notStarted) + } + +} + +extension SyncManager: IInitialSyncerDelegate { + + func onSyncSuccess() { + apiSyncStateManager.restored = true + startPeerGroup() + } + + func onSyncFailed(error: Error) { + syncState = .notSynced(error: error) + } + +} + + +extension SyncManager: IApiSyncListener { + + func transactionsFound(count: Int) { + foundTransactionsCount += count + syncState = .apiSyncing(transactions: foundTransactionsCount) + } + +} + +extension SyncManager: IBlockSyncListener { + + func blocksSyncFinished() { + syncState = .synced + } + + func currentBestBlockHeightUpdated(height: Int32, maxBlockHeight: Int32) { + if currentBestBlockHeight < height { + currentBestBlockHeight = height + } + + let blocksDownloaded = currentBestBlockHeight - initialBestBlockHeight + let allBlocksToDownload = maxBlockHeight - initialBestBlockHeight + + if allBlocksToDownload <= 0 || allBlocksToDownload <= blocksDownloaded { + syncState = .synced + } else { + syncState = .syncing(progress: Double(blocksDownloaded) / Double(allBlocksToDownload)) + } + } + +} diff --git a/BitcoinCore/BitcoinCore/Managers/UnspentOutputProvider.swift b/BitcoinCore/Classes/Managers/UnspentOutputProvider.swift similarity index 59% rename from BitcoinCore/BitcoinCore/Managers/UnspentOutputProvider.swift rename to BitcoinCore/Classes/Managers/UnspentOutputProvider.swift index 13f3722e..cccc06c7 100644 --- a/BitcoinCore/BitcoinCore/Managers/UnspentOutputProvider.swift +++ b/BitcoinCore/Classes/Managers/UnspentOutputProvider.swift @@ -1,16 +1,9 @@ class UnspentOutputProvider { let storage: IStorage + let pluginManager: IPluginManager let confirmationsThreshold: Int - init(storage: IStorage, confirmationsThreshold: Int) { - self.storage = storage - self.confirmationsThreshold = confirmationsThreshold - } -} - -extension UnspentOutputProvider: IUnspentOutputProvider { - - var allUnspentOutputs: [UnspentOutput] { + private var confirmedUtxo: [UnspentOutput] { let lastBlockHeight = storage.lastBlock?.height ?? 0 // Output must have a public key, that is, must belong to the user @@ -32,4 +25,32 @@ extension UnspentOutputProvider: IUnspentOutputProvider { }) } -} \ No newline at end of file + private var unspendableUtxo: [UnspentOutput] { + confirmedUtxo.filter { !pluginManager.isSpendable(unspentOutput: $0) } + } + + init(storage: IStorage, pluginManager: IPluginManager, confirmationsThreshold: Int) { + self.storage = storage + self.pluginManager = pluginManager + self.confirmationsThreshold = confirmationsThreshold + } +} + +extension UnspentOutputProvider: IUnspentOutputProvider { + + var spendableUtxo: [UnspentOutput] { + confirmedUtxo.filter { pluginManager.isSpendable(unspentOutput: $0) } + } + +} + +extension UnspentOutputProvider: IBalanceProvider { + + var balanceInfo: BalanceInfo { + let spendable = spendableUtxo.map { $0.output.value }.reduce(0, +) + let unspendable = unspendableUtxo.map { $0.output.value }.reduce(0, +) + + return BalanceInfo(spendable: spendable, unspendable: unspendable) + } + +} diff --git a/BitcoinCore/Classes/Managers/UnspentOutputSelector.swift b/BitcoinCore/Classes/Managers/UnspentOutputSelector.swift new file mode 100644 index 00000000..fe59b865 --- /dev/null +++ b/BitcoinCore/Classes/Managers/UnspentOutputSelector.swift @@ -0,0 +1,110 @@ +import Foundation + +public struct SelectedUnspentOutputInfo { + public let unspentOutputs: [UnspentOutput] + public let recipientValue: Int // amount to set to recipient output + public let changeValue: Int? // amount to set to change output. No change output if nil + + public init(unspentOutputs: [UnspentOutput], recipientValue: Int, changeValue: Int?) { + self.unspentOutputs = unspentOutputs + self.recipientValue = recipientValue + self.changeValue = changeValue + } +} + +public class UnspentOutputSelector { + + private let calculator: ITransactionSizeCalculator + private let provider: IUnspentOutputProvider + private let dustCalculator: IDustCalculator + private let outputsLimit: Int? + + public init(calculator: ITransactionSizeCalculator, provider: IUnspentOutputProvider, dustCalculator: IDustCalculator, outputsLimit: Int? = nil) { + self.calculator = calculator + self.provider = provider + self.dustCalculator = dustCalculator + self.outputsLimit = outputsLimit + } + +} + +extension UnspentOutputSelector: IUnspentOutputSelector { + + public func select(value: Int, feeRate: Int, outputScriptType: ScriptType = .p2pkh, changeType: ScriptType = .p2pkh, senderPay: Bool, pluginDataOutputSize: Int) throws -> SelectedUnspentOutputInfo { + let unspentOutputs = provider.spendableUtxo + let recipientOutputDust = dustCalculator.dust(type: outputScriptType) + let changeOutputDust = dustCalculator.dust(type: changeType) + + // check if value is not dust. recipientValue may be less, but not more + guard value >= recipientOutputDust else { + throw BitcoinCoreErrors.SendValueErrors.dust + } + guard !unspentOutputs.isEmpty else { + throw BitcoinCoreErrors.SendValueErrors.emptyOutputs + } + + let sortedOutputs = unspentOutputs.sorted(by: { lhs, rhs in + (lhs.output.failedToSpend && !rhs.output.failedToSpend) || ( + lhs.output.failedToSpend == rhs.output.failedToSpend && lhs.output.value < rhs.output.value + ) + }) + + // select unspentOutputs with least value until we get needed value + var selectedOutputs = [UnspentOutput]() + var totalValue = 0 + var recipientValue = 0 + var sentValue = 0 + var fee = 0 + + for unspentOutput in sortedOutputs { + selectedOutputs.append(unspentOutput) + totalValue += unspentOutput.output.value + + if let outputsLimit = outputsLimit { + if (selectedOutputs.count > outputsLimit) { + guard let outputValueToExclude = selectedOutputs.first?.output.value else { + continue + } + selectedOutputs.remove(at: 0) + totalValue -= outputValueToExclude + } + } + fee = calculator.transactionSize(previousOutputs: selectedOutputs.map { $0.output }, outputScriptTypes: [outputScriptType], pluginDataOutputSize: pluginDataOutputSize) * feeRate + + recipientValue = senderPay ? value : value - fee + sentValue = senderPay ? value + fee : value + + if sentValue <= totalValue { // totalValue is enough + if recipientValue >= recipientOutputDust { // receivedValue won't be dust + break + } else { + // Here senderPay is false, because otherwise "dust" exception would throw far above. + // Adding more UTXOs will make fee even greater, making recipientValue even less and dust anyway + throw BitcoinCoreErrors.SendValueErrors.dust + } + } + } + + // if all unspentOutputs are selected and total value less than needed, then throw error + if totalValue < sentValue { + throw BitcoinCoreErrors.SendValueErrors.notEnough + } + + let changeOutputHavingTransactionFee = calculator.transactionSize(previousOutputs: selectedOutputs.map { $0.output }, outputScriptTypes: [outputScriptType, changeType], pluginDataOutputSize: pluginDataOutputSize) * feeRate + let withChangeRecipientValue = senderPay ? value : value - changeOutputHavingTransactionFee + let withChangeSentValue = senderPay ? value + changeOutputHavingTransactionFee : value + // if selected UTXOs total value >= recipientValue(toOutput value) + fee(for transaction with change output) + dust(minimum changeOutput value) + if totalValue >= withChangeRecipientValue + changeOutputHavingTransactionFee + changeOutputDust { + // totalValue is too much, we must have change output + guard withChangeRecipientValue >= recipientOutputDust else { + throw BitcoinCoreErrors.SendValueErrors.dust + } + + return SelectedUnspentOutputInfo(unspentOutputs: selectedOutputs, recipientValue: withChangeRecipientValue, changeValue: totalValue - withChangeSentValue) + } + + // No change needed + return SelectedUnspentOutputInfo(unspentOutputs: selectedOutputs, recipientValue: recipientValue, changeValue: nil) + } + +} diff --git a/BitcoinCore/Classes/Managers/UnspentOutputSelectorSingleNoChange.swift b/BitcoinCore/Classes/Managers/UnspentOutputSelectorSingleNoChange.swift new file mode 100644 index 00000000..bfdf704a --- /dev/null +++ b/BitcoinCore/Classes/Managers/UnspentOutputSelectorSingleNoChange.swift @@ -0,0 +1,52 @@ +import Foundation + +public class UnspentOutputSelectorSingleNoChange { + + private let calculator: ITransactionSizeCalculator + private let provider: IUnspentOutputProvider + private let dustCalculator: IDustCalculator + + public init(calculator: ITransactionSizeCalculator, provider: IUnspentOutputProvider, dustCalculator: IDustCalculator) { + self.calculator = calculator + self.provider = provider + self.dustCalculator = dustCalculator + } + +} + +extension UnspentOutputSelectorSingleNoChange: IUnspentOutputSelector { + + public func select(value: Int, feeRate: Int, outputScriptType: ScriptType = .p2pkh, changeType: ScriptType = .p2pkh, senderPay: Bool, pluginDataOutputSize: Int) throws -> SelectedUnspentOutputInfo { + let unspentOutputs = provider.spendableUtxo + let recipientOutputDust = dustCalculator.dust(type: outputScriptType) + let changeOutputDust = dustCalculator.dust(type: changeType) + + guard unspentOutputs.allSatisfy({ !$0.output.failedToSpend }) else { + throw BitcoinCoreErrors.SendValueErrors.singleNoChangeOutputNotFound + } + guard value >= recipientOutputDust else { + throw BitcoinCoreErrors.SendValueErrors.dust + } + guard !unspentOutputs.isEmpty else { + throw BitcoinCoreErrors.SendValueErrors.emptyOutputs + } + + // try to find 1 unspent output with exactly matching value + for unspentOutput in unspentOutputs { + let output = unspentOutput.output + let fee = calculator.transactionSize(previousOutputs: [output], outputScriptTypes: [outputScriptType], pluginDataOutputSize: pluginDataOutputSize) * feeRate + + let recipientValue = senderPay ? value : value - fee + let sentValue = senderPay ? value + fee : value + + if (sentValue <= output.value) && // output.value is enough + (recipientValue >= recipientOutputDust) && // receivedValue won't be dust + (output.value - sentValue < changeOutputDust) { // no need to add change output + return SelectedUnspentOutputInfo(unspentOutputs: [unspentOutput], recipientValue: recipientValue, changeValue: nil) + } + } + + throw BitcoinCoreErrors.SendValueErrors.singleNoChangeOutputNotFound + } + +} diff --git a/BitcoinCore/Classes/Managers/WatchedTransactionManager.swift b/BitcoinCore/Classes/Managers/WatchedTransactionManager.swift new file mode 100644 index 00000000..825f6f2b --- /dev/null +++ b/BitcoinCore/Classes/Managers/WatchedTransactionManager.swift @@ -0,0 +1,85 @@ +class WatchedTransactionManager { + + struct P2ShOutputFilter { + let hash: Data + let delegate: IWatchedTransactionDelegate + } + + struct OutpointFilter { + let transactionHash: Data + let outputIndex: Int + let delegate: IWatchedTransactionDelegate + } + + private var p2ShOutputFilters = [P2ShOutputFilter]() + private var outpointFilters = [OutpointFilter]() + private let queue: DispatchQueue + weak var bloomFilterManager: IBloomFilterManager? + + init(queue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.watched-transactions-manager", qos: .background)) { + self.queue = queue + } + + private func scan(transaction: FullTransaction) { + for filter in p2ShOutputFilters { + for output in transaction.outputs { + if output.scriptType == .p2sh && output.keyHash == filter.hash { + filter.delegate.transactionReceived(transaction: transaction, outputIndex: output.index) + return + } + } + } + + for filter in outpointFilters { + for (index, input) in transaction.inputs.enumerated() { + if input.previousOutputTxHash == filter.transactionHash && input.previousOutputIndex == filter.outputIndex { + filter.delegate.transactionReceived(transaction: transaction, inputIndex: index) + return + } + } + } + } + +} + +extension WatchedTransactionManager : IWatchedTransactionManager { + + func add(transactionFilter: BitcoinCore.TransactionFilter, delegatedTo delegate: IWatchedTransactionDelegate) { + switch transactionFilter { + case .p2shOutput(let scriptHash): + p2ShOutputFilters.append(P2ShOutputFilter(hash: scriptHash, delegate: delegate)) + case .outpoint(let transactionHash, let outputIndex): + outpointFilters.append(OutpointFilter(transactionHash: transactionHash, outputIndex: outputIndex, delegate: delegate)) + } + bloomFilterManager?.regenerateBloomFilter() + } + +} + +extension WatchedTransactionManager : ITransactionListener { + + func onReceive(transaction: FullTransaction) { + queue.async { + self.scan(transaction: transaction) + } + } + +} + +extension WatchedTransactionManager : IBloomFilterProvider { + + func filterElements() -> [Data] { + var elements = [Data]() + + for filter in p2ShOutputFilters { + elements.append(filter.hash) + } + + for filter in outpointFilters { + elements.append(filter.transactionHash + byteArrayLittleEndian(int: filter.outputIndex)) + } + + return elements + } + +} diff --git a/BitcoinCore/BitcoinCore/Models/Address.swift b/BitcoinCore/Classes/Models/Address.swift similarity index 51% rename from BitcoinCore/BitcoinCore/Models/Address.swift rename to BitcoinCore/Classes/Models/Address.swift index e4a9d9da..e7dada20 100644 --- a/BitcoinCore/BitcoinCore/Models/Address.swift +++ b/BitcoinCore/Classes/Models/Address.swift @@ -7,11 +7,12 @@ public protocol Address: class { var scriptType: ScriptType { get } var keyHash: Data { get } var stringValue: String { get } + var lockingScript: Data { get } } extension Address { - var scriptType: ScriptType { + public var scriptType: ScriptType { switch type { case .pubKeyHash: return .p2pkh case .scriptHash: return .p2sh @@ -20,18 +21,25 @@ extension Address { } -class LegacyAddress: Address, Equatable { - let type: AddressType - let keyHash: Data - let stringValue: String +public class LegacyAddress: Address, Equatable { + public let type: AddressType + public let keyHash: Data + public let stringValue: String - init(type: AddressType, keyHash: Data, base58: String) { + public var lockingScript: Data { + switch type { + case .pubKeyHash: return OpCode.p2pkhStart + OpCode.push(keyHash) + OpCode.p2pkhFinish + case .scriptHash: return OpCode.p2shStart + OpCode.push(keyHash) + OpCode.p2shFinish + } + } + + public init(type: AddressType, keyHash: Data, base58: String) { self.type = type self.keyHash = keyHash self.stringValue = base58 } - static func ==(lhs: LegacyAddress, rhs: T) -> Bool { + public static func ==(lhs: LegacyAddress, rhs: T) -> Bool { guard let rhs = rhs as? LegacyAddress else { return false } diff --git a/BitcoinCore/BitcoinCore/Models/BitcoinPaymentData.swift b/BitcoinCore/Classes/Models/BitcoinPaymentData.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Models/BitcoinPaymentData.swift rename to BitcoinCore/Classes/Models/BitcoinPaymentData.swift diff --git a/BitcoinCore/BitcoinCore/Models/Block.swift b/BitcoinCore/Classes/Models/Block.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Models/Block.swift rename to BitcoinCore/Classes/Models/Block.swift index bae8d888..e8c7bb30 100644 --- a/BitcoinCore/BitcoinCore/Models/Block.swift +++ b/BitcoinCore/Classes/Models/Block.swift @@ -1,14 +1,13 @@ -import HSCryptoKit import GRDB public class Block: Record { - var version: Int - var previousBlockHash: Data + public var version: Int + public var previousBlockHash: Data public var merkleRoot: Data public var timestamp: Int public var bits: Int - var nonce: Int + public var nonce: Int public var headerHash: Data public var height: Int diff --git a/BitcoinCore/BitcoinCore/Models/BlockHash.swift b/BitcoinCore/Classes/Models/BlockHash.swift similarity index 88% rename from BitcoinCore/BitcoinCore/Models/BlockHash.swift rename to BitcoinCore/Classes/Models/BlockHash.swift index 66d9bea7..8eb2f509 100644 --- a/BitcoinCore/BitcoinCore/Models/BlockHash.swift +++ b/BitcoinCore/Classes/Models/BlockHash.swift @@ -1,4 +1,5 @@ import GRDB +import UIExtensions public class BlockHash: Record { let headerHash: Data @@ -13,8 +14,8 @@ public class BlockHash: Record { super.init() } - init?(headerHashReversedHex: String, height: Int, sequence: Int) { - guard let headerHash = Data(hex: headerHashReversedHex) else { + init?(headerHashReversedHex: String?, height: Int?, sequence: Int) { + guard let hex = headerHashReversedHex, let height = height, let headerHash = Data(hex: hex) else { return nil } diff --git a/BitcoinCore/BitcoinCore/Models/BlockchainState.swift b/BitcoinCore/Classes/Models/BlockchainState.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Models/BlockchainState.swift rename to BitcoinCore/Classes/Models/BlockchainState.swift diff --git a/BitcoinCore/Classes/Models/Checkpoint.swift b/BitcoinCore/Classes/Models/Checkpoint.swift new file mode 100644 index 00000000..1d0ac1fe --- /dev/null +++ b/BitcoinCore/Classes/Models/Checkpoint.swift @@ -0,0 +1,74 @@ +import UIExtensions + +public struct Checkpoint { + public let block: Block + public let additionalBlocks: [Block] + + public init(block: Block, additionalBlocks: [Block]) { + self.block = block + self.additionalBlocks = additionalBlocks + } + + public init(podBundle: Bundle, bundleName: String, filename: String) throws { + guard let checkpointsBundleURL = podBundle.url(forResource: bundleName, withExtension: "bundle") else { + throw ParseError.invalidBundleUrl + } + guard let checkpointsBundle = Bundle(url: checkpointsBundleURL) else { + throw ParseError.invalidBundle + } + guard let fileURL = checkpointsBundle.url(forResource: filename, withExtension: "checkpoint") else { + throw ParseError.invalidFileUrl + } + + let string = try String(contentsOf: fileURL, encoding: .utf8) + var lines = string.components(separatedBy: .newlines).filter { !$0.isEmpty } + + guard !lines.isEmpty else { + throw ParseError.invalidFile + } + + block = try Checkpoint.readBlock(string: lines.removeFirst()) + additionalBlocks = try lines.map { try Checkpoint.readBlock(string: $0) } + } + + private static func readBlock(string: String) throws -> Block { + guard let data = Data(hex: string) else { + throw ParseError.invalidFile + } + + let byteStream = ByteStream(data) + + let version = Int(byteStream.read(Int32.self)) + let previousBlockHeaderHash = byteStream.read(Data.self, count: 32) + let merkleRoot = byteStream.read(Data.self, count: 32) + let timestamp = Int(byteStream.read(UInt32.self)) + let bits = Int(byteStream.read(UInt32.self)) + let nonce = Int(byteStream.read(UInt32.self)) + let height = Int(byteStream.read(UInt32.self)) + let headerHash = byteStream.read(Data.self, count: 32) + + let header = BlockHeader( + version: version, + headerHash: headerHash, + previousBlockHeaderHash: previousBlockHeaderHash, + merkleRoot: merkleRoot, + timestamp: timestamp, + bits: bits, + nonce: nonce + ) + + return Block(withHeader: header, height: height) + } + +} + +public extension Checkpoint { + + enum ParseError: Error { + case invalidBundleUrl + case invalidBundle + case invalidFileUrl + case invalidFile + } + +} diff --git a/BitcoinCore/BitcoinCore/Models/DataObjects.swift b/BitcoinCore/Classes/Models/DataObjects.swift similarity index 60% rename from BitcoinCore/BitcoinCore/Models/DataObjects.swift rename to BitcoinCore/Classes/Models/DataObjects.swift index 55aefd7a..ce2ae2a4 100644 --- a/BitcoinCore/BitcoinCore/Models/DataObjects.swift +++ b/BitcoinCore/Classes/Models/DataObjects.swift @@ -1,4 +1,5 @@ -import HSCryptoKit +import OpenSslKit +import UIExtensions public struct BlockHeader { @@ -22,23 +23,33 @@ public struct BlockHeader { } -public struct FullTransaction { +open class FullTransaction { public let header: Transaction public let inputs: [Input] public let outputs: [Output] + public let metaData = TransactionMetadata() - public init(header: Transaction, inputs: [Input], outputs: [Output]) { + public init(header: Transaction, inputs: [Input], outputs: [Output], forceHashUpdate: Bool = true) { self.header = header self.inputs = inputs self.outputs = outputs - self.header.dataHash = CryptoKit.sha256sha256(TransactionSerializer.serialize(transaction: self, withoutWitness: true)) - for input in self.inputs { - input.transactionHash = self.header.dataHash + if forceHashUpdate { + let hash = Kit.sha256sha256(TransactionSerializer.serialize(transaction: self, withoutWitness: true)) + set(hash: hash) } - for output in self.outputs { - output.transactionHash = self.header.dataHash + } + + public func set(hash: Data) { + header.dataHash = hash + metaData.transactionHash = hash + + for input in inputs { + input.transactionHash = header.dataHash + } + for output in outputs { + output.transactionHash = header.dataHash } } @@ -82,6 +93,13 @@ public struct UnspentOutput { public let transaction: Transaction public let blockHeight: Int? + public init(output: Output, publicKey: PublicKey, transaction: Transaction, blockHeight: Int? = nil) { + self.output = output + self.publicKey = publicKey + self.transaction = transaction + self.blockHeight = blockHeight + } + } public struct FullTransactionForInfo { @@ -89,6 +107,17 @@ public struct FullTransactionForInfo { public let transactionWithBlock: TransactionWithBlock let inputsWithPreviousOutputs: [InputWithPreviousOutput] let outputs: [Output] + let metaData: TransactionMetadata + + var rawTransaction: String { + let fullTransaction = FullTransaction( + header: transactionWithBlock.transaction, + inputs: inputsWithPreviousOutputs.map { $0.input }, + outputs: outputs + ) + + return TransactionSerializer.serialize(transaction: fullTransaction).hex + } } diff --git a/BitcoinCore/Classes/Models/InfoObjects.swift b/BitcoinCore/Classes/Models/InfoObjects.swift new file mode 100644 index 00000000..2e77a974 --- /dev/null +++ b/BitcoinCore/Classes/Models/InfoObjects.swift @@ -0,0 +1,84 @@ +import Foundation + +open class TransactionInfo: ITransactionInfo, Codable { + public let uid: String + public let transactionHash: String + public let transactionIndex: Int + public let inputs: [TransactionInputInfo] + public let outputs: [TransactionOutputInfo] + public let amount: Int + public let type: TransactionType + public let fee: Int? + public let blockHeight: Int? + public let timestamp: Int + public let status: TransactionStatus + public let conflictingHash: String? + + public required init(uid: String, transactionHash: String, transactionIndex: Int, inputs: [TransactionInputInfo], outputs: [TransactionOutputInfo], + amount: Int, type: TransactionType, fee: Int?, blockHeight: Int?, timestamp: Int, status: TransactionStatus, conflictingHash: String?) { + self.uid = uid + self.transactionHash = transactionHash + self.transactionIndex = transactionIndex + self.inputs = inputs + self.outputs = outputs + self.amount = amount + self.type = type + self.fee = fee + self.blockHeight = blockHeight + self.timestamp = timestamp + self.status = status + self.conflictingHash = conflictingHash + } + +} + +public class TransactionInputInfo: Codable { + public let mine: Bool + public let address: String? + public let value: Int? + + public init(mine: Bool, address: String?, value: Int?) { + self.mine = mine + self.address = address + self.value = value + } + +} + +public class TransactionOutputInfo: Codable { + public let mine: Bool + public let changeOutput: Bool + public let value: Int + public let address: String? + public var pluginId: UInt8? = nil + public var pluginData: IPluginOutputData? = nil + + var pluginDataString: String? = nil + + private enum CodingKeys: String, CodingKey { + case mine, changeOutput, value, address, pluginId, pluginDataString + } + + public init(mine: Bool, changeOutput: Bool, value: Int, address: String?) { + self.mine = mine + self.changeOutput = changeOutput + self.value = value + self.address = address + } + +} + +public struct BlockInfo { + public let headerHash: String + public let height: Int + public let timestamp: Int? +} + +public struct BalanceInfo : Equatable { + public let spendable: Int + public let unspendable: Int + + public static func ==(lhs: BalanceInfo, rhs: BalanceInfo) -> Bool { + lhs.spendable == rhs.spendable && lhs.unspendable == rhs.unspendable + } +} diff --git a/BitcoinCore/BitcoinCore/Models/Input.swift b/BitcoinCore/Classes/Models/Input.swift similarity index 95% rename from BitcoinCore/BitcoinCore/Models/Input.swift rename to BitcoinCore/Classes/Models/Input.swift index e769ecbd..6750f07a 100644 --- a/BitcoinCore/BitcoinCore/Models/Input.swift +++ b/BitcoinCore/Classes/Models/Input.swift @@ -5,7 +5,7 @@ public class Input: Record { public var previousOutputTxHash: Data var previousOutputIndex: Int - var signatureScript: Data + public var signatureScript: Data var sequence: Int var transactionHash = Data() var keyHash: Data? = nil @@ -23,7 +23,7 @@ public class Input: Record { override open class var databaseTableName: String { - return "inputs" + "inputs" } enum Columns: String, ColumnExpression, CaseIterable { @@ -66,4 +66,5 @@ public class Input: Record { enum SerializationError: Error { case noPreviousOutput case noPreviousTransaction + case noPreviousOutputScript } diff --git a/BitcoinCore/Classes/Models/InvalidTransaction.swift b/BitcoinCore/Classes/Models/InvalidTransaction.swift new file mode 100644 index 00000000..274bb9d7 --- /dev/null +++ b/BitcoinCore/Classes/Models/InvalidTransaction.swift @@ -0,0 +1,35 @@ +import Foundation +import GRDB + +public class InvalidTransaction: Transaction { + + init(uid: String, dataHash: Data, version: Int, lockTime: Int, timestamp: Int, order: Int, blockHash: Data?, isMine: Bool, isOutgoing: Bool, status: TransactionStatus, + segWit: Bool, conflictingTxHash: Data?, transactionInfoJson: Data, rawTransaction: String) { + super.init() + + self.uid = uid + self.dataHash = dataHash + self.version = version + self.lockTime = lockTime + self.timestamp = timestamp + self.order = order + self.blockHash = blockHash + self.isMine = isMine + self.isOutgoing = isOutgoing + self.status = status + self.segWit = segWit + self.conflictingTxHash = conflictingTxHash + self.transactionInfoJson = transactionInfoJson + self.rawTransaction = rawTransaction + } + + required init(row: Row) { + super.init(row: row) + } + + + override open class var databaseTableName: String { + "invalid_transactions" + } + +} diff --git a/BitcoinCore/Classes/Models/Output.swift b/BitcoinCore/Classes/Models/Output.swift new file mode 100644 index 00000000..046fbc9b --- /dev/null +++ b/BitcoinCore/Classes/Models/Output.swift @@ -0,0 +1,123 @@ +import Foundation +import GRDB + +public enum ScriptType: Int, DatabaseValueConvertible { + case unknown, p2pkh, p2pk, p2multi, p2sh, p2wsh, p2wpkh, p2wpkhSh, nullData + + var size: Int { + switch self { + case .p2pk: return 35 + case .p2pkh: return 25 + case .p2sh: return 23 + case .p2wsh: return 34 + case .p2wpkh: return 22 + case .p2wpkhSh: return 23 + default: return 0 + } + } + + var witness: Bool { + self == .p2wpkh || self == .p2wpkhSh || self == .p2wsh + } + + var nativeSegwit: Bool { + self == .p2wpkh || self == .p2wsh + } + +} + +public class Output: Record { + + public var value: Int + public var lockingScript: Data + public var index: Int + public var transactionHash: Data + var publicKeyPath: String? = nil + private(set) var changeOutput: Bool = false + public var scriptType: ScriptType = .unknown + public var redeemScript: Data? = nil + public var keyHash: Data? = nil + var address: String? = nil + var failedToSpend: Bool = false + + public var pluginId: UInt8? = nil + public var pluginData: String? = nil + public var signatureScriptFunction: (([Data]) -> Data)? = nil + + public func set(publicKey: PublicKey) { + self.publicKeyPath = publicKey.path + self.changeOutput = !publicKey.external + } + + public init(withValue value: Int, index: Int, lockingScript script: Data, transactionHash: Data = Data(), type: ScriptType = .unknown, redeemScript: Data? = nil, address: String? = nil, keyHash: Data? = nil, publicKey: PublicKey? = nil) { + self.value = value + self.lockingScript = script + self.index = index + self.transactionHash = transactionHash + self.scriptType = type + self.redeemScript = redeemScript + self.address = address + self.keyHash = keyHash + + super.init() + + if let publicKey = publicKey { + set(publicKey: publicKey) + } + } + + override open class var databaseTableName: String { + "outputs" + } + + enum Columns: String, ColumnExpression, CaseIterable { + case value + case lockingScript + case index + case transactionHash + case publicKeyPath + case changeOutput + case scriptType + case redeemScript + case keyHash + case address + case pluginId + case pluginData + case failedToSpend + } + + required init(row: Row) { + value = row[Columns.value] + lockingScript = row[Columns.lockingScript] + index = row[Columns.index] + transactionHash = row[Columns.transactionHash] + publicKeyPath = row[Columns.publicKeyPath] + changeOutput = row[Columns.changeOutput] + scriptType = row[Columns.scriptType] + redeemScript = row[Columns.redeemScript] + keyHash = row[Columns.keyHash] + address = row[Columns.address] + pluginId = row[Columns.pluginId] + pluginData = row[Columns.pluginData] + failedToSpend = row[Columns.failedToSpend] + + super.init(row: row) + } + + override open func encode(to container: inout PersistenceContainer) { + container[Columns.value] = value + container[Columns.lockingScript] = lockingScript + container[Columns.index] = index + container[Columns.transactionHash] = transactionHash + container[Columns.publicKeyPath] = publicKeyPath + container[Columns.changeOutput] = changeOutput + container[Columns.scriptType] = scriptType + container[Columns.redeemScript] = redeemScript + container[Columns.keyHash] = keyHash + container[Columns.address] = address + container[Columns.pluginId] = pluginId + container[Columns.pluginData] = pluginData + container[Columns.failedToSpend] = failedToSpend + } + +} diff --git a/BitcoinCore/BitcoinCore/Models/PeerAddress.swift b/BitcoinCore/Classes/Models/PeerAddress.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Models/PeerAddress.swift rename to BitcoinCore/Classes/Models/PeerAddress.swift diff --git a/BitcoinCore/BitcoinCore/Models/PublicKey.swift b/BitcoinCore/Classes/Models/PublicKey.swift similarity index 85% rename from BitcoinCore/BitcoinCore/Models/PublicKey.swift rename to BitcoinCore/Classes/Models/PublicKey.swift index 9153c1b8..17524f24 100644 --- a/BitcoinCore/BitcoinCore/Models/PublicKey.swift +++ b/BitcoinCore/Classes/Models/PublicKey.swift @@ -1,6 +1,6 @@ import Foundation -import HSCryptoKit import GRDB +import OpenSslKit public class PublicKey: Record { @@ -9,7 +9,7 @@ public class PublicKey: Record { case wrongNetwork } - let path: String + public let path: String public let account: Int public let index: Int public let external: Bool @@ -17,14 +17,14 @@ public class PublicKey: Record { public let keyHash: Data public let scriptHashForP2WPKH: Data - init(withAccount account: Int, index: Int, external: Bool, hdPublicKeyData data: Data) { + public init(withAccount account: Int, index: Int, external: Bool, hdPublicKeyData data: Data) { self.account = account self.index = index self.external = external path = "\(account)/\(external ? 1 : 0)/\(index)" raw = data - keyHash = CryptoKit.sha256ripemd160(data) - scriptHashForP2WPKH = CryptoKit.sha256ripemd160(OpCode.scriptWPKH(keyHash)) + keyHash = Kit.sha256ripemd160(data) + scriptHashForP2WPKH = Kit.sha256ripemd160(OpCode.scriptWPKH(keyHash)) super.init() } diff --git a/BitcoinCore/BitcoinCore/Models/SentTransaction.swift b/BitcoinCore/Classes/Models/SentTransaction.swift similarity index 67% rename from BitcoinCore/BitcoinCore/Models/SentTransaction.swift rename to BitcoinCore/Classes/Models/SentTransaction.swift index 73bc3574..9059316b 100644 --- a/BitcoinCore/BitcoinCore/Models/SentTransaction.swift +++ b/BitcoinCore/Classes/Models/SentTransaction.swift @@ -2,48 +2,48 @@ import GRDB public class SentTransaction: Record { let dataHash: Data - var firstSendTime: Double var lastSendTime: Double var retriesCount: Int + var sendSuccess: Bool - init(dataHash: Data, firstSendTime: Double, lastSendTime: Double, retriesCount: Int) { + init(dataHash: Data, lastSendTime: Double, retriesCount: Int, sendSuccess: Bool) { self.dataHash = dataHash - self.firstSendTime = firstSendTime self.lastSendTime = lastSendTime self.retriesCount = retriesCount + self.sendSuccess = sendSuccess super.init() } override open class var databaseTableName: String { - return "sentTransactions" + "sentTransactions" } convenience init(dataHash: Data) { - self.init(dataHash: dataHash, firstSendTime: CACurrentMediaTime(), lastSendTime: CACurrentMediaTime(), retriesCount: 0) + self.init(dataHash: dataHash, lastSendTime: CACurrentMediaTime(), retriesCount: 0, sendSuccess: false) } enum Columns: String, ColumnExpression { case dataHash - case firstSendTime case lastSendTime case retriesCount + case sendSuccess } required init(row: Row) { dataHash = row[Columns.dataHash] - firstSendTime = row[Columns.firstSendTime] lastSendTime = row[Columns.lastSendTime] retriesCount = row[Columns.retriesCount] + sendSuccess = row[Columns.sendSuccess] super.init(row: row) } override open func encode(to container: inout PersistenceContainer) { container[Columns.dataHash] = dataHash - container[Columns.firstSendTime] = firstSendTime container[Columns.lastSendTime] = lastSendTime container[Columns.retriesCount] = retriesCount + container[Columns.sendSuccess] = sendSuccess } } diff --git a/BitcoinCore/BitcoinCore/Models/SigHashType.swift b/BitcoinCore/Classes/Models/SigHashType.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Models/SigHashType.swift rename to BitcoinCore/Classes/Models/SigHashType.swift diff --git a/BitcoinCore/BitcoinCore/Models/Transaction.swift b/BitcoinCore/Classes/Models/Transaction.swift similarity index 67% rename from BitcoinCore/BitcoinCore/Models/Transaction.swift rename to BitcoinCore/Classes/Models/Transaction.swift index be51b4a4..205d689d 100644 --- a/BitcoinCore/BitcoinCore/Models/Transaction.swift +++ b/BitcoinCore/Classes/Models/Transaction.swift @@ -1,10 +1,10 @@ import Foundation -import HSCryptoKit import GRDB -public enum TransactionStatus: Int, DatabaseValueConvertible { case new, relayed, invalid } +public enum TransactionStatus: Int, DatabaseValueConvertible, Codable { case new, relayed, invalid } public class Transaction: Record { + public var uid: String public var dataHash: Data public var version: Int public var lockTime: Int @@ -15,23 +15,28 @@ public class Transaction: Record { public var isOutgoing: Bool = false public var status: TransactionStatus = .relayed public var segWit: Bool = false + public var conflictingTxHash: Data? = nil + public var transactionInfoJson: Data = Data() + public var rawTransaction: String? = nil - init(version: Int = 0, lockTime: Int = 0, timestamp: Int? = nil) { + public init(version: Int = 0, lockTime: Int = 0, timestamp: Int? = nil) { self.version = version self.lockTime = lockTime self.timestamp = timestamp ?? Int(Date().timeIntervalSince1970) self.order = 0 self.dataHash = Data() + self.uid = UUID().uuidString super.init() } override open class var databaseTableName: String { - return "transactions" + "transactions" } enum Columns: String, ColumnExpression, CaseIterable { + case uid case dataHash case version case lockTime @@ -42,9 +47,13 @@ public class Transaction: Record { case isOutgoing case status case segWit + case conflictingTxHash + case transactionInfoJson + case rawTransaction } required init(row: Row) { + uid = row[Columns.uid] dataHash = row[Columns.dataHash] version = row[Columns.version] lockTime = row[Columns.lockTime] @@ -55,11 +64,15 @@ public class Transaction: Record { isOutgoing = row[Columns.isOutgoing] status = row[Columns.status] segWit = row[Columns.segWit] + conflictingTxHash = row[Columns.conflictingTxHash] + transactionInfoJson = row[Columns.transactionInfoJson] + rawTransaction = row[Columns.rawTransaction] super.init(row: row) } override open func encode(to container: inout PersistenceContainer) { + container[Columns.uid] = uid container[Columns.dataHash] = dataHash container[Columns.version] = version container[Columns.lockTime] = lockTime @@ -70,6 +83,9 @@ public class Transaction: Record { container[Columns.isOutgoing] = isOutgoing container[Columns.status] = status container[Columns.segWit] = segWit + container[Columns.conflictingTxHash] = conflictingTxHash + container[Columns.transactionInfoJson] = transactionInfoJson + container[Columns.rawTransaction] = rawTransaction } } diff --git a/BitcoinCore/Classes/Models/TransactionDataSortType.swift b/BitcoinCore/Classes/Models/TransactionDataSortType.swift new file mode 100644 index 00000000..07769344 --- /dev/null +++ b/BitcoinCore/Classes/Models/TransactionDataSortType.swift @@ -0,0 +1 @@ +public enum TransactionDataSortType { case none, shuffle, bip69 } diff --git a/BitcoinCore/BitcoinCore/Models/TransactionInput.swift b/BitcoinCore/Classes/Models/TransactionInput.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Models/TransactionInput.swift rename to BitcoinCore/Classes/Models/TransactionInput.swift diff --git a/BitcoinCore/Classes/Models/TransactionMetadata.swift b/BitcoinCore/Classes/Models/TransactionMetadata.swift new file mode 100644 index 00000000..edc56174 --- /dev/null +++ b/BitcoinCore/Classes/Models/TransactionMetadata.swift @@ -0,0 +1,64 @@ +import Foundation +import GRDB + +public enum TransactionType: Int, DatabaseValueConvertible, Codable { + case incoming = 1 + case outgoing = 2 + case sentToSelf = 3 +} + +public enum TransactionFilterType { + case incoming, outgoing + + var types: [TransactionType] { + switch self { + case .incoming: return [.incoming, .sentToSelf] + case .outgoing: return [.outgoing, .sentToSelf] + } + } +} + + +public class TransactionMetadata: Record { + public var transactionHash: Data + public var amount: Int + public var type: TransactionType + public var fee: Int? + + public init(transactionHash: Data = Data(), amount: Int = 0, type: TransactionType = .incoming, fee: Int? = nil) { + self.transactionHash = transactionHash + self.amount = amount + self.type = type + self.fee = fee + + super.init() + } + + override open class var databaseTableName: String { + "transaction_metadata" + } + + enum Columns: String, ColumnExpression, CaseIterable { + case transactionHash + case amount + case type + case fee + } + + required init(row: Row) { + transactionHash = row[Columns.transactionHash] + amount = row[Columns.amount] + type = row[Columns.type] + fee = row[Columns.fee] + + super.init(row: row) + } + + override open func encode(to container: inout PersistenceContainer) { + container[Columns.transactionHash] = transactionHash + container[Columns.amount] = amount + container[Columns.type] = type + container[Columns.fee] = fee + } + +} diff --git a/BitcoinCore/BitcoinCore/Network/Helpers/ByteStream.swift b/BitcoinCore/Classes/Network/Helpers/ByteStream.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Network/Helpers/ByteStream.swift rename to BitcoinCore/Classes/Network/Helpers/ByteStream.swift index 8b53f879..61666762 100644 --- a/BitcoinCore/BitcoinCore/Network/Helpers/ByteStream.swift +++ b/BitcoinCore/Classes/Network/Helpers/ByteStream.swift @@ -32,6 +32,10 @@ public class ByteStream { } public func read(_ type: VarInt.Type) -> VarInt { + guard data.count > offset else { + return VarInt(0) + } + let len = data[offset..<(offset + 1)].to(type: UInt8.self) let length: UInt64 switch len { diff --git a/BitcoinCore/BitcoinCore/Network/Helpers/PeerDiscovery.swift b/BitcoinCore/Classes/Network/Helpers/PeerDiscovery.swift similarity index 77% rename from BitcoinCore/BitcoinCore/Network/Helpers/PeerDiscovery.swift rename to BitcoinCore/Classes/Network/Helpers/PeerDiscovery.swift index 3a82708c..038c8963 100644 --- a/BitcoinCore/BitcoinCore/Network/Helpers/PeerDiscovery.swift +++ b/BitcoinCore/Classes/Network/Helpers/PeerDiscovery.swift @@ -1,13 +1,25 @@ import Foundation +import UIExtensions class PeerDiscovery: IPeerDiscovery { weak var peerAddressManager: IPeerAddressManager? + private var inProgress = false + + func lookup(dnsSeeds: [String]) { + guard !inProgress else { + return + } + + inProgress = true - func lookup(dnsSeed: String) { DispatchQueue.global(qos: .background).async { [weak self] in - if let ips = self?._lookup(dnsSeed: dnsSeed) { - self?.peerAddressManager?.add(ips: ips) + for seed in dnsSeeds { + if let ips = self?._lookup(dnsSeed: seed) { + self?.peerAddressManager?.add(ips: ips) + } } + + self?.inProgress = false } } diff --git a/BitcoinCore/BitcoinCore/Network/Helpers/ServiceFlags.swift b/BitcoinCore/Classes/Network/Helpers/ServiceFlags.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Helpers/ServiceFlags.swift rename to BitcoinCore/Classes/Network/Helpers/ServiceFlags.swift diff --git a/BitcoinCore/BitcoinCore/Network/Helpers/VarInt.swift b/BitcoinCore/Classes/Network/Helpers/VarInt.swift similarity index 98% rename from BitcoinCore/BitcoinCore/Network/Helpers/VarInt.swift rename to BitcoinCore/Classes/Network/Helpers/VarInt.swift index e9e047a1..21ee7316 100644 --- a/BitcoinCore/BitcoinCore/Network/Helpers/VarInt.swift +++ b/BitcoinCore/Classes/Network/Helpers/VarInt.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit /// Integer can be encoded depending on the represented value to save space. /// Variable length integers always precede an array/vector of a type of data that may vary in length. diff --git a/BitcoinCore/BitcoinCore/Network/Helpers/VarString.swift b/BitcoinCore/Classes/Network/Helpers/VarString.swift similarity index 97% rename from BitcoinCore/BitcoinCore/Network/Helpers/VarString.swift rename to BitcoinCore/Classes/Network/Helpers/VarString.swift index 3f45966e..146e918b 100644 --- a/BitcoinCore/BitcoinCore/Network/Helpers/VarString.swift +++ b/BitcoinCore/Classes/Network/Helpers/VarString.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit /// Variable length string can be stored using a variable length integer followed by the string itself. public struct VarString : ExpressibleByStringLiteral { diff --git a/BitcoinCore/Classes/Network/INetwork.swift b/BitcoinCore/Classes/Network/INetwork.swift new file mode 100644 index 00000000..9effd12f --- /dev/null +++ b/BitcoinCore/Classes/Network/INetwork.swift @@ -0,0 +1,16 @@ +import Foundation + +public extension INetwork { + var protocolVersion: Int32 { 70015 } + var maxBlockSize: UInt32 { 1_000_000 } + var serviceFullNode: UInt64 { 1 } + + var bip44Checkpoint: Checkpoint { + try! Checkpoint(podBundle: Bundle(for: type(of: self)), bundleName: bundleName, filename: "\(String(describing: type(of: self)))-bip44") + } + + var lastCheckpoint: Checkpoint { + try! Checkpoint(podBundle: Bundle(for: type(of: self)), bundleName: bundleName, filename: "\(String(describing: type(of: self)))-last") + } + +} diff --git a/BitcoinCore/BitcoinCore/Network/Messages/AddressMessage.swift b/BitcoinCore/Classes/Network/Messages/AddressMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/AddressMessage.swift rename to BitcoinCore/Classes/Network/Messages/AddressMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/FilterLoadMessage.swift b/BitcoinCore/Classes/Network/Messages/FilterLoadMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/FilterLoadMessage.swift rename to BitcoinCore/Classes/Network/Messages/FilterLoadMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/GetBlocksMessage.swift b/BitcoinCore/Classes/Network/Messages/GetBlocksMessage.swift similarity index 97% rename from BitcoinCore/BitcoinCore/Network/Messages/GetBlocksMessage.swift rename to BitcoinCore/Classes/Network/Messages/GetBlocksMessage.swift index 5f88fe48..71adcf46 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/GetBlocksMessage.swift +++ b/BitcoinCore/Classes/Network/Messages/GetBlocksMessage.swift @@ -1,3 +1,5 @@ +import UIExtensions + struct GetBlocksMessage: IMessage { /// the protocol version let version: UInt32 diff --git a/BitcoinCore/BitcoinCore/Network/Messages/GetDataMessage.swift b/BitcoinCore/Classes/Network/Messages/GetDataMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/GetDataMessage.swift rename to BitcoinCore/Classes/Network/Messages/GetDataMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/GetHeadersMessage.swift b/BitcoinCore/Classes/Network/Messages/GetHeadersMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/GetHeadersMessage.swift rename to BitcoinCore/Classes/Network/Messages/GetHeadersMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/InventoryItem.swift b/BitcoinCore/Classes/Network/Messages/InventoryItem.swift similarity index 98% rename from BitcoinCore/BitcoinCore/Network/Messages/InventoryItem.swift rename to BitcoinCore/Classes/Network/Messages/InventoryItem.swift index 68f2169a..09595aaa 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/InventoryItem.swift +++ b/BitcoinCore/Classes/Network/Messages/InventoryItem.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit public struct InventoryItem { /// Identifies the object type linked to this inventory diff --git a/BitcoinCore/BitcoinCore/Network/Messages/InventoryMessage.swift b/BitcoinCore/Classes/Network/Messages/InventoryMessage.swift similarity index 97% rename from BitcoinCore/BitcoinCore/Network/Messages/InventoryMessage.swift rename to BitcoinCore/Classes/Network/Messages/InventoryMessage.swift index 746eaa6a..d247b1e4 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/InventoryMessage.swift +++ b/BitcoinCore/Classes/Network/Messages/InventoryMessage.swift @@ -1,3 +1,5 @@ +import UIExtensions + /// Allows a node to advertise its knowledge of one or more objects. It can be received unsolicited, or in reply to getblocks. struct InventoryMessage: IMessage { /// Number of inventory entries diff --git a/BitcoinCore/BitcoinCore/Network/Messages/MempoolMessage.swift b/BitcoinCore/Classes/Network/Messages/MempoolMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/MempoolMessage.swift rename to BitcoinCore/Classes/Network/Messages/MempoolMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/MerkleBlock.swift b/BitcoinCore/Classes/Network/Messages/MerkleBlock.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Network/Messages/MerkleBlock.swift rename to BitcoinCore/Classes/Network/Messages/MerkleBlock.swift index 4e7272ab..8f79c9f2 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/MerkleBlock.swift +++ b/BitcoinCore/Classes/Network/Messages/MerkleBlock.swift @@ -1,5 +1,4 @@ import Foundation -import HSCryptoKit public class MerkleBlock { let header: BlockHeader diff --git a/BitcoinCore/BitcoinCore/Network/Messages/MerkleBlockMessage.swift b/BitcoinCore/Classes/Network/Messages/MerkleBlockMessage.swift similarity index 97% rename from BitcoinCore/BitcoinCore/Network/Messages/MerkleBlockMessage.swift rename to BitcoinCore/Classes/Network/Messages/MerkleBlockMessage.swift index 7c837ffa..7b3bfbef 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/MerkleBlockMessage.swift +++ b/BitcoinCore/Classes/Network/Messages/MerkleBlockMessage.swift @@ -1,3 +1,5 @@ +import UIExtensions + struct MerkleBlockMessage: IMessage { let blockHeader: BlockHeader diff --git a/BitcoinCore/BitcoinCore/Network/Messages/NetworkAddress.swift b/BitcoinCore/Classes/Network/Messages/NetworkAddress.swift similarity index 90% rename from BitcoinCore/BitcoinCore/Network/Messages/NetworkAddress.swift rename to BitcoinCore/Classes/Network/Messages/NetworkAddress.swift index 5212cf6e..1ba55220 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/NetworkAddress.swift +++ b/BitcoinCore/Classes/Network/Messages/NetworkAddress.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit /// When a network address is needed somewhere, /// this structure is used. Network addresses are not prefixed with a timestamp in the version message. @@ -37,6 +37,10 @@ public struct NetworkAddress { return data } + func supportsBloomFilter() -> Bool { + ServiceFlags(rawValue: services).contains(ServiceFlags.bloom) + } + } extension NetworkAddress: CustomStringConvertible { diff --git a/BitcoinCore/BitcoinCore/Network/Messages/NetworkMessage.swift b/BitcoinCore/Classes/Network/Messages/NetworkMessage.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Network/Messages/NetworkMessage.swift rename to BitcoinCore/Classes/Network/Messages/NetworkMessage.swift index cfb49b1d..fb1aefd3 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/NetworkMessage.swift +++ b/BitcoinCore/Classes/Network/Messages/NetworkMessage.swift @@ -1,5 +1,4 @@ import Foundation -import HSCryptoKit struct NetworkMessage { static let minimumLength = 24 diff --git a/BitcoinCore/BitcoinCore/Network/Messages/PingMessage.swift b/BitcoinCore/Classes/Network/Messages/PingMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/PingMessage.swift rename to BitcoinCore/Classes/Network/Messages/PingMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/PongMessage.swift b/BitcoinCore/Classes/Network/Messages/PongMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/PongMessage.swift rename to BitcoinCore/Classes/Network/Messages/PongMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/RejectMessage.swift b/BitcoinCore/Classes/Network/Messages/RejectMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/RejectMessage.swift rename to BitcoinCore/Classes/Network/Messages/RejectMessage.swift diff --git a/BitcoinCore/Classes/Network/Messages/TransactionMessage.swift b/BitcoinCore/Classes/Network/Messages/TransactionMessage.swift new file mode 100644 index 00000000..c8b6ae49 --- /dev/null +++ b/BitcoinCore/Classes/Network/Messages/TransactionMessage.swift @@ -0,0 +1,16 @@ +import UIExtensions + +public struct TransactionMessage: IMessage { + let transaction: FullTransaction + let size: Int + + public init(transaction: FullTransaction, size: Int) { + self.transaction = transaction + self.size = size + } + + public var description: String { + "\(transaction.header.dataHash.reversedHex)" + } + +} diff --git a/BitcoinCore/BitcoinCore/Network/Messages/UnknownMessage.swift b/BitcoinCore/Classes/Network/Messages/UnknownMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/UnknownMessage.swift rename to BitcoinCore/Classes/Network/Messages/UnknownMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/VerackMessage.swift b/BitcoinCore/Classes/Network/Messages/VerackMessage.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Network/Messages/VerackMessage.swift rename to BitcoinCore/Classes/Network/Messages/VerackMessage.swift diff --git a/BitcoinCore/BitcoinCore/Network/Messages/VersionMessage.swift b/BitcoinCore/Classes/Network/Messages/VersionMessage.swift similarity index 80% rename from BitcoinCore/BitcoinCore/Network/Messages/VersionMessage.swift rename to BitcoinCore/Classes/Network/Messages/VersionMessage.swift index 3ca34db5..01cafdde 100644 --- a/BitcoinCore/BitcoinCore/Network/Messages/VersionMessage.swift +++ b/BitcoinCore/Classes/Network/Messages/VersionMessage.swift @@ -23,15 +23,15 @@ struct VersionMessage: IMessage { let relay: Bool? func hasBlockChain(network: INetwork) -> Bool { - return (services & network.serviceFullNode) == network.serviceFullNode + (services & network.serviceFullNode) == network.serviceFullNode } func supportsBloomFilter(network: INetwork) -> Bool { - return version >= network.bloomFilter + (version > 70000 && version < 70011) || (version > 70000 && ServiceFlags(rawValue: services).contains(ServiceFlags.bloom)) } var description: String { - return "\(version) --- \(userAgent?.value ?? "") --- \(ServiceFlags(rawValue: services)) -- \(String(describing: startHeight ?? 0))" + "\(version) --- \(userAgent?.value ?? "") --- \(ServiceFlags(rawValue: services)) -- \(String(describing: startHeight ?? 0))" } } diff --git a/BitcoinCore/BitcoinCore/Network/Parsers/NetworkMessageParser.swift b/BitcoinCore/Classes/Network/Parsers/NetworkMessageParser.swift similarity index 98% rename from BitcoinCore/BitcoinCore/Network/Parsers/NetworkMessageParser.swift rename to BitcoinCore/Classes/Network/Parsers/NetworkMessageParser.swift index 73fae823..80cfb5ef 100644 --- a/BitcoinCore/BitcoinCore/Network/Parsers/NetworkMessageParser.swift +++ b/BitcoinCore/Classes/Network/Parsers/NetworkMessageParser.swift @@ -1,4 +1,5 @@ -import HSCryptoKit +import OpenSslKit +import UIExtensions class NetworkMessageParser: INetworkMessageParser { private let magic: UInt32 @@ -28,7 +29,7 @@ class NetworkMessageParser: INetworkMessageParser { } let payload = byteStream.read(Data.self, count: Int(length)) - let checksumConfirm = CryptoKit.sha256sha256(payload).prefix(4) + let checksumConfirm = Kit.sha256sha256(payload).prefix(4) guard checksum == checksumConfirm else { return nil } diff --git a/BitcoinCore/BitcoinCore/Network/Parsers/NetworkMessageSerializer.swift b/BitcoinCore/Classes/Network/Parsers/NetworkMessageSerializer.swift similarity index 98% rename from BitcoinCore/BitcoinCore/Network/Parsers/NetworkMessageSerializer.swift rename to BitcoinCore/Classes/Network/Parsers/NetworkMessageSerializer.swift index d54c0b32..c11d56db 100644 --- a/BitcoinCore/BitcoinCore/Network/Parsers/NetworkMessageSerializer.swift +++ b/BitcoinCore/Classes/Network/Parsers/NetworkMessageSerializer.swift @@ -1,4 +1,4 @@ -import HSCryptoKit +import OpenSslKit class NetworkMessageSerializer: INetworkMessageSerializer { let magic: UInt32 @@ -27,7 +27,7 @@ class NetworkMessageSerializer: INetworkMessageSerializer { guard let serializer = resolvedSerializer, let messageData = resolvedMessageData else { throw BitcoinCoreErrors.MessageSerialization.noMessageSerializer } - let checksum = Data(CryptoKit.sha256sha256(messageData).prefix(4)) + let checksum = Data(Kit.sha256sha256(messageData).prefix(4)) let length = UInt32(messageData.count) var data = Data() diff --git a/BitcoinCore/BitcoinCore/Network/Peer/ConnectionTimeoutManager.swift b/BitcoinCore/Classes/Network/Peer/ConnectionTimeoutManager.swift similarity index 95% rename from BitcoinCore/BitcoinCore/Network/Peer/ConnectionTimeoutManager.swift rename to BitcoinCore/Classes/Network/Peer/ConnectionTimeoutManager.swift index 62f3bef4..531ad1a0 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/ConnectionTimeoutManager.swift +++ b/BitcoinCore/Classes/Network/Peer/ConnectionTimeoutManager.swift @@ -1,4 +1,5 @@ import Foundation +import HsToolKit class ConnectionTimeoutManager: IConnectionTimeoutManager { @@ -27,7 +28,7 @@ class ConnectionTimeoutManager: IConnectionTimeoutManager { func timePeriodPassed(peer: IPeer) { if let lastPingTime = lastPingTime { if (dateGenerator().timeIntervalSince1970 - lastPingTime > pingTimeout) { - logger?.error("Timed out. Closing connection", context: peer.logName) + logger?.error("Timed out. Closing connection", context: [peer.logName]) peer.disconnect(error: TimeoutError.pingTimedOut) } @@ -36,7 +37,7 @@ class ConnectionTimeoutManager: IConnectionTimeoutManager { if let messageLastReceivedTime = messageLastReceivedTime { if (dateGenerator().timeIntervalSince1970 - messageLastReceivedTime > maxIdleTime) { - logger?.debug("Timed out. Closing connection", context: peer.logName) + logger?.debug("Timed out. Closing connection", context: [peer.logName]) peer.sendPing(nonce: UInt64.random(in: 0.. Bool { - for hashes in requestedTransactions { - if hashes.value.contains(hash) { - return true + peersQueue.sync { + for hashes in self.requestedTransactions { + if hashes.value.contains(hash) { + return true + } } + return false } - return false } func subscribeTo(observable: Observable) { @@ -52,12 +63,9 @@ extension MempoolTransactions : IPeerTaskHandler { func handleCompletedTask(peer: IPeer, task: PeerTask) -> Bool { switch task { case let task as RequestTransactionsTask: - transactionSyncer.handle(transactions: task.transactions) + transactionSyncer.handleRelayed(transactions: task.transactions) removeFromRequestedTransactions(peerHost: peer.host, transactionHashes: task.transactions.map { $0.header.dataHash }) - return true - - case let task as SendTransactionTask: - transactionSyncer.handle(sentTransaction: task.transaction) + transactionSender.transactionsRelayed(transactions: task.transactions) return true default: return false @@ -65,6 +73,7 @@ extension MempoolTransactions : IPeerTaskHandler { } } + extension MempoolTransactions : IInventoryItemsHandler { func handleInventoryItems(peer: IPeer, inventoryItems: [InventoryItem]) { @@ -88,7 +97,9 @@ extension MempoolTransactions : IInventoryItemsHandler { extension MempoolTransactions { private func onPeerDisconnect(peer: IPeer, error: Error?) { - requestedTransactions[peer.host] = nil + peersQueue.async { + self.requestedTransactions[peer.host] = nil + } } } diff --git a/BitcoinCore/BitcoinCore/Network/Peer/Peer.swift b/BitcoinCore/Classes/Network/Peer/Peer.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Network/Peer/Peer.swift rename to BitcoinCore/Classes/Network/Peer/Peer.swift index bf8449f1..7f4c2779 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/Peer.swift +++ b/BitcoinCore/Classes/Network/Peer/Peer.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import HsToolKit class Peer { enum PeerError: Error { @@ -19,11 +19,11 @@ class Peer { private let connection: IPeerConnection private let connectionTimeoutManager: IConnectionTimeoutManager - private var tasks: [PeerTask] = [] private let network: INetwork private let logger: Logger? + var tasks: [PeerTask] = [] var announcedLastBlockHeight: Int32 = 0 var localBestBlockHeight: Int32 = 0 // TODO seems like property connected is not needed. It is always true in PeerManager. Need to check it and remove @@ -157,8 +157,8 @@ class Peer { delegate?.peer(self, didReceiveMessage: anyMessage) } - private func log(_ message: String, level: Logger.Level = .debug, file: String = #file, function: String = #function, line: Int = #line) { - logger?.log(level: level, message: message, file: file, function: function, line: line, context: logName) + private func log(_ message: String, level: Logger.Level = .debug) { + logger?.log(level: level, message: message, context: [logName]) } } diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerAddressManager.swift b/BitcoinCore/Classes/Network/Peer/PeerAddressManager.swift similarity index 62% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerAddressManager.swift rename to BitcoinCore/Classes/Network/Peer/PeerAddressManager.swift index 8b4c7549..d17c3a5a 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerAddressManager.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerAddressManager.swift @@ -1,3 +1,5 @@ +import HsToolKit + class PeerAddressManager { weak var delegate: IPeerAddressManagerDelegate? @@ -6,6 +8,7 @@ class PeerAddressManager { private var peerDiscovery: IPeerDiscovery private let state: PeerAddressManagerState private let logger: Logger? + private let queue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.peer-address-manager", qos: .background) init(storage: IStorage, dnsSeeds: [String], peerDiscovery: IPeerDiscovery, state: PeerAddressManagerState = PeerAddressManagerState(), logger: Logger? = nil) { self.storage = storage @@ -21,14 +24,13 @@ extension PeerAddressManager: IPeerAddressManager { var ip: String? { guard let ip = storage.leastScoreFastestPeerAddress(excludingIps: state.usedIps)?.ip else { - for dnsSeed in dnsSeeds { - peerDiscovery.lookup(dnsSeed: dnsSeed) - } - + peerDiscovery.lookup(dnsSeeds: dnsSeeds) return nil } - state.add(usedIp: ip) + queue.sync { + state.add(usedIp: ip) + } return ip } @@ -42,29 +44,39 @@ extension PeerAddressManager: IPeerAddressManager { } func markSuccess(ip: String) { - state.remove(usedIp: ip) - storage.increasePeerAddressScore(ip: ip) + queue.sync { + state.remove(usedIp: ip) + } } func markFailed(ip: String) { - state.remove(usedIp: ip) - storage.deletePeerAddress(byIp: ip) + queue.sync { + state.remove(usedIp: ip) + storage.deletePeerAddress(byIp: ip) + } } func add(ips: [String]) { - guard !ips.isEmpty else { + let newAddresses = ips + .filter { !storage.peerAddressExist(address: $0) } + .map { PeerAddress(ip: $0, score: 0) } + + guard !newAddresses.isEmpty else { return } - let newAddresses = ips.map { PeerAddress(ip: $0, score: 0) } logger?.debug("Adding new addresses: \(newAddresses.count)") + queue.sync { + storage.save(peerAddresses: newAddresses) + } - storage.save(peerAddresses: newAddresses) delegate?.newIpsAdded() } func markConnected(peer: IPeer) { - storage.set(connectionTime: peer.connectionTime, toPeerAddress: peer.host) + queue.sync { + storage.set(connectionTime: peer.connectionTime, toPeerAddress: peer.host) + } } } diff --git a/BitcoinCore/Classes/Network/Peer/PeerAddressManagerState.swift b/BitcoinCore/Classes/Network/Peer/PeerAddressManagerState.swift new file mode 100644 index 00000000..ba0b6d1b --- /dev/null +++ b/BitcoinCore/Classes/Network/Peer/PeerAddressManagerState.swift @@ -0,0 +1,13 @@ +import Foundation + +class PeerAddressManagerState { + private(set) var usedIps: [String] = [] + + func add(usedIp: String) { + usedIps.append(usedIp) + } + + func remove(usedIp: String) { + usedIps.removeAll(where: { $0 == usedIp }) + } +} diff --git a/BitcoinCore/Classes/Network/Peer/PeerConnection.swift b/BitcoinCore/Classes/Network/Peer/PeerConnection.swift new file mode 100644 index 00000000..774ca558 --- /dev/null +++ b/BitcoinCore/Classes/Network/Peer/PeerConnection.swift @@ -0,0 +1,161 @@ +import Foundation +import HdWalletKit +import HsToolKit +import NIO +import NIOFoundationCompat + +class PeerConnection: NSObject { + enum PeerConnectionError: Error { + case connectionClosedWithUnknownError + case connectionClosedByPeer + } + + let host: String + let port: Int + + weak var delegate: PeerConnectionDelegate? + + private let networkMessageParser: INetworkMessageParser + private let networkMessageSerializer: INetworkMessageSerializer + private let logger: Logger? + private let group: MultiThreadedEventLoopGroup + private var channel: Channel? + + private var waitingForDisconnect: Bool = false + private let interval = TimeAmount.seconds(1) + + var logName: String { + let index = abs(host.hash) % WordList.english.count + return "[\(WordList.english[index])]".uppercased() + } + + private var bootstrap: ClientBootstrap { + ClientBootstrap(group: group) + .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + .channelInitializer { [weak self] channel in + self?.initializeChannel(channel: channel) ?? channel.eventLoop.makeSucceededVoidFuture() + } + } + + init(host: String, port: Int, networkMessageParser: INetworkMessageParser, networkMessageSerializer: INetworkMessageSerializer, + eventLoopGroup: MultiThreadedEventLoopGroup, logger: Logger? = nil) { + self.host = host + self.port = port + self.networkMessageParser = networkMessageParser + self.networkMessageSerializer = networkMessageSerializer + self.group = eventLoopGroup + self.logger = logger + } + + deinit { + disconnect() + } + + private func log(_ message: @autoclosure () -> Any, level: Logger.Level = .debug) { + logger?.log(level: level, message: message(), context: [logName]) + } + + private func initializeChannel(channel: Channel) -> EventLoopFuture { + let handler = PeerMessageHandler(networkMessageParser: networkMessageParser) + handler.delegate = self + + return channel.pipeline.addHandler(handler) + } + + private func onConnected(channel: Channel) { + self.channel = channel + + channel.eventLoop.scheduleRepeatedTask(initialDelay: .zero, delay: interval, notifying: nil) { [weak self] task in + guard !(self?.waitingForDisconnect ?? true) else { + task.cancel() + return + } + + self?.delegate?.connectionTimePeriodPassed() + } + } + + private func onConnectFailure(error: Error) { + disconnect(error: error) + } + +} + +extension PeerConnection: IPeerConnection { + + func connect() { + let connectFuture = bootstrap.connect(host: host, port: port) + + connectFuture.whenSuccess { [weak self] channel in + self?.onConnected(channel: channel) + } + + connectFuture.whenFailure { [weak self] error in + self?.onConnectFailure(error: error) + } + } + + func disconnect(error: Error? = nil) { + guard !waitingForDisconnect else { + return + } + + channel = nil + waitingForDisconnect = true + delegate?.connectionDidDisconnect(withError: error) + } + + func send(message: IMessage) { + log("-> \(type(of: message)): \(message.description)") + do { + let data = try networkMessageSerializer.serialize(message: message) + guard !data.isEmpty, let channel = channel else { + return + } + + var buffer = channel.allocator.buffer(capacity: data.count) + buffer.writeBytes(data) + + channel.writeAndFlush(buffer) + } catch { + log("Connection can't send message \(message) with error \(error)", level: .error) //todo catch error when try send message not registered in serializers + } + } + +} + +extension PeerConnection: PeerMessageHandlerDelegate { + + func onChannelActive() { + delegate?.connectionReadyForWrite() + } + + func onChannelInactive() { + if !waitingForDisconnect { + disconnect(error: PeerConnectionError.connectionClosedWithUnknownError) + } + } + + func onChannelRead() { + delegate?.connectionAlive() + } + + func onMessageReceived(message: IMessage) { + log("<- \(type(of: message)): \(message.description)") + delegate?.connection(didReceiveMessage: message) + } + + func onErrorCaught(error: Error) { + log("Error received: \(error)") + disconnect(error: error) + } + +} + +protocol PeerConnectionDelegate: class { + func connectionAlive() + func connectionTimePeriodPassed() + func connectionReadyForWrite() + func connectionDidDisconnect(withError error: Error?) + func connection(didReceiveMessage message: IMessage) +} diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerGroup.swift b/BitcoinCore/Classes/Network/Peer/PeerGroup.swift similarity index 84% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerGroup.swift rename to BitcoinCore/Classes/Network/Peer/PeerGroup.swift index aaebfe0e..b397f882 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerGroup.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerGroup.swift @@ -1,5 +1,7 @@ import Foundation import RxSwift +import HsToolKit +import NIO public enum PeerGroupEvent { case onStart @@ -31,6 +33,7 @@ class PeerGroup { private let peersQueue: DispatchQueue private let inventoryQueue: DispatchQueue private let subjectQueue: DispatchQueue + private var eventLoopGroup: MultiThreadedEventLoopGroup private let logger: Logger? @@ -42,9 +45,9 @@ class PeerGroup { init(factory: IFactory, reachabilityManager: IReachabilityManager, peerAddressManager: IPeerAddressManager, peerCount: Int = 10, localDownloadedBestBlockHeight: Int32, - peerManager: IPeerManager, peersQueue: DispatchQueue = DispatchQueue(label: "PeerGroup Local Queue", qos: .userInitiated), - inventoryQueue: DispatchQueue = DispatchQueue(label: "PeerGroup Inventory Queue", qos: .background), - subjectQueue: DispatchQueue = DispatchQueue(label: "PeerGroup Subject Queue", qos: .background), + peerManager: IPeerManager, peersQueue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.peer-group.peers", qos: .userInitiated), + inventoryQueue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.peer-group.inventory", qos: .background), + subjectQueue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.peer-group.subject", qos: .background), scheduler: SchedulerType = SerialDispatchQueueScheduler(qos: .background), logger: Logger? = nil) { self.factory = factory @@ -58,6 +61,7 @@ class PeerGroup { self.peersQueue = peersQueue self.inventoryQueue = inventoryQueue self.subjectQueue = subjectQueue + self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: peerCount) self.logger = logger self.observable = subject.asObservable().observeOn(scheduler) @@ -65,6 +69,10 @@ class PeerGroup { self.peerAddressManager.delegate = self } + deinit { + eventLoopGroup.shutdownGracefully { _ in } + } + private func connectPeersIfRequired() { peersQueue.async { guard self.started, self.reachabilityManager.isReachable else { @@ -73,9 +81,9 @@ class PeerGroup { var peersToConnect = [IPeer]() - for _ in self.peerManager.totalPeersCount().. Bool { - return peer.ready + peer.ready } } @@ -158,7 +170,7 @@ extension PeerGroup: PeerDelegate { private func disconnectSlowestPeer(peerCountToConnect: Int) { if peerCountToConnect > peerCountConnected && peerCountToHold > 1 && peerAddressManager.hasFreshIps { - let sortedPeers = peerManager.sorted() + let sortedPeers = peerManager.sorted if sortedPeers.count >= peerCountToHold { sortedPeers.last?.disconnect(error: nil) } @@ -191,9 +203,11 @@ extension PeerGroup: PeerDelegate { func peer(_ peer: IPeer, didReceiveMessage message: IMessage) { switch message { case let addressMessage as AddressMessage: - self.peerAddressManager.add(ips: addressMessage.addressList.map { - $0.address - }) + let addresses = addressMessage.addressList + .filter { $0.supportsBloomFilter() } + .map { $0.address } + + peerAddressManager.add(ips: addresses) case let inventoryMessage as InventoryMessage: inventoryQueue.async { self.inventoryItemsHandler?.handleInventoryItems(peer: peer, inventoryItems: inventoryMessage.inventoryItems) diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerManager.swift b/BitcoinCore/Classes/Network/Peer/PeerManager.swift similarity index 60% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerManager.swift rename to BitcoinCore/Classes/Network/Peer/PeerManager.swift index 22f729e4..e072983b 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerManager.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerManager.swift @@ -3,6 +3,22 @@ import Foundation class PeerManager: IPeerManager { private var peers: [IPeer] = [] + var totalPeersCount: Int { + peers.count + } + + var connected: [IPeer] { + peers.filter({ $0.connected }) + } + + var sorted: [IPeer] { + connected.sorted(by: { $0.connectionTime < $1.connectionTime }) + } + + var readyPeers: [IPeer] { + peers.filter { $0.connected && $0.ready } + } + func add(peer: IPeer) { self.peers.append(peer) } @@ -19,16 +35,4 @@ class PeerManager: IPeerManager { } } - func totalPeersCount() -> Int { - return peers.count - } - - func connected() -> [IPeer] { - return peers.filter({ $0.connected }) - } - - func sorted() -> [IPeer] { - return connected().sorted(by: { $0.connectionTime < $1.connectionTime }) - } - } diff --git a/BitcoinCore/Classes/Network/Peer/PeerMessageHandler.swift b/BitcoinCore/Classes/Network/Peer/PeerMessageHandler.swift new file mode 100644 index 00000000..d3a05a74 --- /dev/null +++ b/BitcoinCore/Classes/Network/Peer/PeerMessageHandler.swift @@ -0,0 +1,64 @@ +import Foundation +import NIO + +protocol PeerMessageHandlerDelegate: AnyObject { + func onChannelActive() + func onChannelInactive() + func onChannelRead() + func onMessageReceived(message: IMessage) + func onErrorCaught(error: Error) +} + +class PeerMessageHandler: ChannelInboundHandler { + typealias InboundIn = ByteBuffer + typealias OutboundOut = ByteBuffer + + private var bufferSize = 4096 + private var packets: Data = Data() + + + private let networkMessageParser: INetworkMessageParser + + weak var delegate: PeerMessageHandlerDelegate? + + init(networkMessageParser: INetworkMessageParser) { + self.networkMessageParser = networkMessageParser + } + + func channelActive(context: ChannelHandlerContext) { + delegate?.onChannelActive() + } + + func channelInactive(context: ChannelHandlerContext) { + delegate?.onChannelInactive() + } + + func channelRead(context: ChannelHandlerContext, data: NIOAny) { + delegate?.onChannelRead() + + var buffer = unwrapInboundIn(data) + if let bytes = buffer.readData(length: buffer.readableBytes) { + packets += bytes + } + + while packets.count >= NetworkMessage.minimumLength { + guard let networkMessage = networkMessageParser.parse(data: packets) else { + break + } + + packets = Data(packets.dropFirst(NetworkMessage.minimumLength + Int(networkMessage.length))) + let message = networkMessage.message + + guard !(message is UnknownMessage) else { + continue + } + + delegate?.onMessageReceived(message: message) + } + } + + func errorCaught(context: ChannelHandlerContext, error: Error) { + delegate?.onErrorCaught(error: error) + } + +} diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/GetBlockHashesTask.swift b/BitcoinCore/Classes/Network/Peer/PeerTask/GetBlockHashesTask.swift similarity index 95% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerTask/GetBlockHashesTask.swift rename to BitcoinCore/Classes/Network/Peer/PeerTask/GetBlockHashesTask.swift index d9b8ec71..738e5db1 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/GetBlockHashesTask.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerTask/GetBlockHashesTask.swift @@ -35,11 +35,16 @@ class GetBlockHashesTask: PeerTask { super.init(dateGenerator: dateGenerator) } + override var state: String { + "expectedHashesMinCount: \(expectedHashesMinCount); allowedIdleTime: \(allowedIdleTime)" + } + override func start() { if let requester = requester { requester.send(message: GetBlocksMessage(protocolVersion: requester.protocolVersion, headerHashes: blockLocatorHashes)) } - resetTimer() + + super.start() } override func handle(message: IMessage) throws -> Bool { diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/GetMerkleBlocksTask.swift b/BitcoinCore/Classes/Network/Peer/PeerTask/GetMerkleBlocksTask.swift similarity index 96% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerTask/GetMerkleBlocksTask.swift rename to BitcoinCore/Classes/Network/Peer/PeerTask/GetMerkleBlocksTask.swift index 287a1a24..a5a85cf6 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/GetMerkleBlocksTask.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerTask/GetMerkleBlocksTask.swift @@ -41,6 +41,10 @@ class GetMerkleBlocksTask: PeerTask { super.init(dateGenerator: dateGenerator) } + override var state: String { + "minMerkleBlocksCount: \(minMerkleBlocksCount); minTransactionsCount: \(minTransactionsCount); minTransactionsSize: \(minTransactionsSize)" + } + override func start() { let items = blockHashes.map { blockHash in InventoryItem(type: InventoryItem.ObjectType.filteredBlockMessage.rawValue, hash: blockHash.headerHash) @@ -48,7 +52,8 @@ class GetMerkleBlocksTask: PeerTask { requester?.send(message: GetDataMessage(inventoryItems: items)) resumeWaiting() - resetTimer() + + super.start() } override func handle(message: IMessage) throws -> Bool { diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/PeerTask.swift b/BitcoinCore/Classes/Network/Peer/PeerTask/PeerTask.swift similarity index 94% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerTask/PeerTask.swift rename to BitcoinCore/Classes/Network/Peer/PeerTask/PeerTask.swift index 2fd6fb97..f747cb5d 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/PeerTask.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerTask/PeerTask.swift @@ -14,11 +14,14 @@ open class PeerTask { self.dateGenerator = dateGenerator } + open var state: String { "" } + open func start() { + resetTimer() } open func handle(message: IMessage) throws -> Bool { - return false + false } open func checkTimeout() { diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/RequestTransactionsTask.swift b/BitcoinCore/Classes/Network/Peer/PeerTask/RequestTransactionsTask.swift similarity index 89% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerTask/RequestTransactionsTask.swift rename to BitcoinCore/Classes/Network/Peer/PeerTask/RequestTransactionsTask.swift index 5227fc77..93555014 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/RequestTransactionsTask.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerTask/RequestTransactionsTask.swift @@ -9,12 +9,18 @@ class RequestTransactionsTask: PeerTask { self.hashes = hashes } + override var state: String { + "hashesCount: \(hashes.count); receivedTransactionsCount: \(transactions.count)" + } + override func start() { let items = hashes.map { hash in InventoryItem(type: InventoryItem.ObjectType.transaction.rawValue, hash: hash) } requester?.send(message: GetDataMessage(inventoryItems: items)) + + super.start() } override func handle(message: IMessage) throws -> Bool { diff --git a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/SendTransactionTask.swift b/BitcoinCore/Classes/Network/Peer/PeerTask/SendTransactionTask.swift similarity index 70% rename from BitcoinCore/BitcoinCore/Network/Peer/PeerTask/SendTransactionTask.swift rename to BitcoinCore/Classes/Network/Peer/PeerTask/SendTransactionTask.swift index ecc24168..cdf78c6f 100644 --- a/BitcoinCore/BitcoinCore/Network/Peer/PeerTask/SendTransactionTask.swift +++ b/BitcoinCore/Classes/Network/Peer/PeerTask/SendTransactionTask.swift @@ -1,11 +1,20 @@ import Foundation +import UIExtensions class SendTransactionTask: PeerTask { var transaction: FullTransaction + private let allowedIdleTime: TimeInterval - init(transaction: FullTransaction) { + init(transaction: FullTransaction, allowedIdleTime: TimeInterval = 30, dateGenerator: @escaping () -> Date = Date.init) { self.transaction = transaction + self.allowedIdleTime = allowedIdleTime + + super.init(dateGenerator: dateGenerator) + } + + override var state: String { + "transaction: \(transaction.header.dataHash.reversedHex)" } override func start() { @@ -14,6 +23,8 @@ class SendTransactionTask: PeerTask { ]) requester?.send(message: message) + + super.start() } override func handle(message: IMessage) throws -> Bool { @@ -33,6 +44,14 @@ class SendTransactionTask: PeerTask { return handled } + override func checkTimeout() { + if let lastActiveTime = lastActiveTime { + if dateGenerator().timeIntervalSince1970 - lastActiveTime > allowedIdleTime { + delegate?.handle(completedTask: self) + } + } + } + private func handle(getDataInventoryItem item: InventoryItem) -> Bool { guard item.objectType == .transaction && item.hash == transaction.header.dataHash else { return false diff --git a/BitcoinCore/Classes/Network/TransactionSendTimer.swift b/BitcoinCore/Classes/Network/TransactionSendTimer.swift new file mode 100644 index 00000000..0e4fa14d --- /dev/null +++ b/BitcoinCore/Classes/Network/TransactionSendTimer.swift @@ -0,0 +1,45 @@ +import Foundation + +class TransactionSendTimer { + let interval: TimeInterval + + weak var delegate: ITransactionSendTimerDelegate? + var runLoop: RunLoop? + var timer: Timer? + + init(interval: TimeInterval) { + self.interval = interval + } + +} + +extension TransactionSendTimer: ITransactionSendTimer { + + func startIfNotRunning() { + guard runLoop == nil else { + return + } + + DispatchQueue.global(qos: .background).async { + self.runLoop = .current + + let timer = Timer(timeInterval: self.interval, repeats: true, block: { [weak self] _ in self?.delegate?.timePassed() }) + self.timer = timer + + RunLoop.current.add(timer, forMode: .common) + RunLoop.current.run() + } + } + + func stop() { + if let runLoop = self.runLoop { + timer?.invalidate() + timer?.invalidate() + + CFRunLoopStop(runLoop.getCFRunLoop()) + timer = nil + self.runLoop = nil + } + } + +} diff --git a/BitcoinCore/Classes/Network/TransactionSender.swift b/BitcoinCore/Classes/Network/TransactionSender.swift new file mode 100644 index 00000000..36f821b5 --- /dev/null +++ b/BitcoinCore/Classes/Network/TransactionSender.swift @@ -0,0 +1,197 @@ +import RxSwift +import HsToolKit + +class TransactionSender { + static let minConnectedPeersCount = 2 + + private let disposeBag = DisposeBag() + + private let transactionSyncer: ITransactionSyncer + private let initialBlockDownload: IInitialBlockDownload + private let peerManager: IPeerManager + private let storage: IStorage + private let timer: ITransactionSendTimer + private let logger: Logger? + private let queue: DispatchQueue + + private let maxRetriesCount: Int + private let retriesPeriod: Double // seconds + + init(transactionSyncer: ITransactionSyncer, initialBlockDownload: IInitialBlockDownload, peerManager: IPeerManager, storage: IStorage, timer: ITransactionSendTimer, + logger: Logger? = nil, queue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.bitcoin-core.transaction-sender", qos: .background), + maxRetriesCount: Int = 3, retriesPeriod: Double = 60) { + self.transactionSyncer = transactionSyncer + self.initialBlockDownload = initialBlockDownload + self.peerManager = peerManager + self.storage = storage + self.timer = timer + self.logger = logger + self.queue = queue + self.maxRetriesCount = maxRetriesCount + self.retriesPeriod = retriesPeriod + } + + private func peersToSendTo() -> [IPeer] { + let syncedPeers = initialBlockDownload.syncedPeers + guard let freeSyncedPeer = syncedPeers.sorted(by: { !$0.ready && $1.ready }).first else { + return [] + } + + guard peerManager.totalPeersCount >= TransactionSender.minConnectedPeersCount else { + return [] + } + + let sortedPeers = peerManager.readyPeers + .filter { + freeSyncedPeer !== $0 + } + .sorted { (a: IPeer, b: IPeer) in + !syncedPeers.contains(where: { a === $0 }) && syncedPeers.contains(where: { b === $0 }) + } + + if sortedPeers.count == 1 { + return sortedPeers + } + + return Array(sortedPeers.prefix(sortedPeers.count / 2)) + } + + private func transactionsToSend(from transactions: [FullTransaction]) -> [FullTransaction] { + transactions.filter { transaction in + if let sentTransaction = storage.sentTransaction(byHash: transaction.header.dataHash) { + return sentTransaction.lastSendTime < CACurrentMediaTime() - self.retriesPeriod + } else { + return true + } + } + } + + private func transactionSendSuccess(sentTransaction transaction: FullTransaction) { + guard let sentTransaction = storage.sentTransaction(byHash: transaction.header.dataHash), + !sentTransaction.sendSuccess else { + return + } + + sentTransaction.retriesCount = sentTransaction.retriesCount + 1 + sentTransaction.sendSuccess = true + + if sentTransaction.retriesCount >= maxRetriesCount { + transactionSyncer.handleInvalid(fullTransaction: transaction) + storage.delete(sentTransaction: sentTransaction) + } else { + storage.update(sentTransaction: sentTransaction) + } + } + + private func transactionSendStart(transaction: FullTransaction) { + if let sentTransaction = storage.sentTransaction(byHash: transaction.header.dataHash) { + sentTransaction.lastSendTime = CACurrentMediaTime() + sentTransaction.sendSuccess = false + storage.update(sentTransaction: sentTransaction) + } else { + storage.add(sentTransaction: SentTransaction(dataHash: transaction.header.dataHash)) + } + } + + private func send(transactions: [FullTransaction]) { + let peers = peersToSendTo() + guard !peers.isEmpty else { + return + } + + timer.startIfNotRunning() + + for transaction in transactions { + transactionSendStart(transaction: transaction) + + for peer in peers { + peer.add(task: SendTransactionTask(transaction: transaction)) + } + } + } + + private func sendPendingTransactions() { + var transactions = transactionSyncer.newTransactions() + + guard !transactions.isEmpty else { + timer.stop() + return + } + + transactions = transactionsToSend(from: transactions) + + guard !transactions.isEmpty else { + return + } + + send(transactions: transactions) + } + +} + +extension TransactionSender: ITransactionSender { + + func verifyCanSend() throws { + if peersToSendTo().isEmpty { + throw BitcoinCoreErrors.TransactionSendError.peersNotSynced + } + } + + func send(pendingTransaction: FullTransaction) { + queue.async { + self.send(transactions: [pendingTransaction]) + } + } + + func transactionsRelayed(transactions: [FullTransaction]) { + queue.async { + for transaction in transactions { + if let sentTransaction = self.storage.sentTransaction(byHash: transaction.header.dataHash) { + self.storage.delete(sentTransaction: sentTransaction) + } + } + } + } + + func subscribeTo(observable: Observable) { + observable.subscribe( + onNext: { [weak self] in + switch $0 { + case .onAllPeersSynced: + self?.queue.async { + self?.sendPendingTransactions() + } + default: () + } + } + ) + .disposed(by: disposeBag) + } + +} + +extension TransactionSender: ITransactionSendTimerDelegate { + + func timePassed() { + queue.async { + self.sendPendingTransactions() + } + } + +} + +extension TransactionSender: IPeerTaskHandler { + + func handleCompletedTask(peer: IPeer, task: PeerTask) -> Bool { + switch task { + case let task as SendTransactionTask: + queue.async { + self.transactionSendSuccess(sentTransaction: task.transaction) + } + return true + + default: return false + } + } + +} diff --git a/BitcoinKit/BitcoinKit/SegWit/Bech32.swift b/BitcoinCore/Classes/SegWit/Bech32.swift similarity index 100% rename from BitcoinKit/BitcoinKit/SegWit/Bech32.swift rename to BitcoinCore/Classes/SegWit/Bech32.swift diff --git a/BitcoinKit/BitcoinKit/SegWit/SegWitAddress.swift b/BitcoinCore/Classes/SegWit/SegWitAddress.swift similarity index 83% rename from BitcoinKit/BitcoinKit/SegWit/SegWitAddress.swift rename to BitcoinCore/Classes/SegWit/SegWitAddress.swift index f0d56718..19bf1153 100644 --- a/BitcoinKit/BitcoinKit/SegWit/SegWitAddress.swift +++ b/BitcoinCore/Classes/SegWit/SegWitAddress.swift @@ -1,5 +1,3 @@ -import BitcoinCore - public class SegWitAddress: Address, Equatable { public let type: AddressType public let keyHash: Data @@ -13,6 +11,11 @@ public class SegWitAddress: Address, Equatable { } } + public var lockingScript: Data { + // Data[0] - version byte, Data[1] - push keyHash + OpCode.push(Int(version)) + OpCode.push(keyHash) + } + public init(type: AddressType, keyHash: Data, bech32: String, version: UInt8) { self.type = type self.keyHash = keyHash diff --git a/BitcoinKit/BitcoinKit/SegWit/SegWitBech32.swift b/BitcoinCore/Classes/SegWit/SegWitBech32.swift similarity index 98% rename from BitcoinKit/BitcoinKit/SegWit/SegWitBech32.swift rename to BitcoinCore/Classes/SegWit/SegWitBech32.swift index 4a1f1bb9..33accb54 100644 --- a/BitcoinKit/BitcoinKit/SegWit/SegWitBech32.swift +++ b/BitcoinCore/Classes/SegWit/SegWitBech32.swift @@ -9,7 +9,6 @@ // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki // Inspired by Pieter Wuille C++ implementation -import BitcoinCore import Foundation /// Segregated Witness Address encoder/decoder @@ -100,7 +99,7 @@ extension SegWitBech32 { case .hrpMismatch(let got, let expected): return "Human-readable-part \"\(got)\" does not match requested \"\(expected)\"" case .segwitV0ProgramSizeMismatch(let size): - return "Segwit program size \(size) does not meet version 0 requirments" + return "Segwit program size \(size) does not meet version 0 requirements" case .segwitVersionNotSupported(let version): return "Segwit version \(version) is not supported by this decoder" } diff --git a/BitcoinKit/BitcoinKit/SegWit/SegWitBech32AddressConverter.swift b/BitcoinCore/Classes/SegWit/SegWitBech32AddressConverter.swift similarity index 76% rename from BitcoinKit/BitcoinKit/SegWit/SegWitBech32AddressConverter.swift rename to BitcoinCore/Classes/SegWit/SegWitBech32AddressConverter.swift index 747999bc..d6bf1508 100644 --- a/BitcoinKit/BitcoinKit/SegWit/SegWitBech32AddressConverter.swift +++ b/BitcoinCore/Classes/SegWit/SegWitBech32AddressConverter.swift @@ -1,15 +1,13 @@ -import BitcoinCore - -class SegWitBech32AddressConverter: IAddressConverter { +public class SegWitBech32AddressConverter: IAddressConverter { private let prefix: String - private let scriptConverter: IBitcoinScriptConverter + private let scriptConverter: IScriptConverter - init(prefix: String, scriptConverter: IBitcoinScriptConverter) { + public init(prefix: String, scriptConverter: IScriptConverter) { self.prefix = prefix self.scriptConverter = scriptConverter } - func convert(address: String) throws -> Address { + public func convert(address: String) throws -> Address { if let segWitData = try? SegWitBech32.decode(hrp: prefix, addr: address) { var type: AddressType = .pubKeyHash if segWitData.version == 0 { @@ -23,7 +21,7 @@ class SegWitBech32AddressConverter: IAddressConverter { throw BitcoinCoreErrors.AddressConversion.unknownAddressType } - func convert(keyHash: Data, type: ScriptType) throws -> Address { + public func convert(keyHash: Data, type: ScriptType) throws -> Address { let script = try scriptConverter.decode(data: keyHash) guard script.chunks.count == 2, let versionCode = script.chunks.first?.opCode, @@ -43,4 +41,8 @@ class SegWitBech32AddressConverter: IAddressConverter { return SegWitAddress(type: addressType, keyHash: keyHash, bech32: bech32, version: versionByte) } + public func convert(publicKey: PublicKey, type: ScriptType) throws -> Address { + try convert(keyHash: OpCode.scriptWPKH(publicKey.keyHash), type: type) + } + } diff --git a/BitcoinCore/BitcoinCore/Serializers/DataListSerializer.swift b/BitcoinCore/Classes/Serializers/DataListSerializer.swift similarity index 95% rename from BitcoinCore/BitcoinCore/Serializers/DataListSerializer.swift rename to BitcoinCore/Classes/Serializers/DataListSerializer.swift index 2955839b..07b225f4 100644 --- a/BitcoinCore/BitcoinCore/Serializers/DataListSerializer.swift +++ b/BitcoinCore/Classes/Serializers/DataListSerializer.swift @@ -1,6 +1,6 @@ import Foundation -class DataListSerializer { +public class DataListSerializer { static func serialize(dataList: [Data]) -> Data { var data = Data() diff --git a/BitcoinCore/Classes/Serializers/SignatureScriptSerializer.swift b/BitcoinCore/Classes/Serializers/SignatureScriptSerializer.swift new file mode 100644 index 00000000..00675dc5 --- /dev/null +++ b/BitcoinCore/Classes/Serializers/SignatureScriptSerializer.swift @@ -0,0 +1,43 @@ +import Foundation + +public class SignatureScriptSerializer { + + static func deserialize(byteStream: ByteStream) -> [Data] { + var data = [Data]() + + while byteStream.availableBytes > 0 { + let dataSize = byteStream.read(VarInt.self) + + switch dataSize.underlyingValue { + case 0x00: + data.append(Data()) + case 0x01...0x4b: + data.append(byteStream.read(Data.self, count: Int(dataSize.underlyingValue))) + case 0x4c: + let dataSize2 = byteStream.read(UInt8.self).littleEndian + data.append(byteStream.read(Data.self, count: Int(dataSize2))) + case 0x4d: + let dataSize2 = byteStream.read(UInt16.self).littleEndian + data.append(byteStream.read(Data.self, count: Int(dataSize2))) + case 0x4e: + let dataSize2 = byteStream.read(UInt32.self).littleEndian + data.append(byteStream.read(Data.self, count: Int(dataSize2))) + case 0x4f: + data.append(Data(from: Int8(-1))) + case 0x51: + data.append(Data([UInt8(0x51)])) + case 0x52...0x60: + data.append(Data([UInt8(dataSize.underlyingValue - 0x50)])) + default: + () + } + } + + return data + } + + public static func deserialize(data: Data) -> [Data] { + return deserialize(byteStream: ByteStream(data)) + } + +} diff --git a/BitcoinCore/BitcoinCore/Serializers/TransactionInputSerializer.swift b/BitcoinCore/Classes/Serializers/TransactionInputSerializer.swift similarity index 78% rename from BitcoinCore/BitcoinCore/Serializers/TransactionInputSerializer.swift rename to BitcoinCore/Classes/Serializers/TransactionInputSerializer.swift index cfd19d68..a80a8ef4 100644 --- a/BitcoinCore/BitcoinCore/Serializers/TransactionInputSerializer.swift +++ b/BitcoinCore/Classes/Serializers/TransactionInputSerializer.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit class TransactionInputSerializer { @@ -34,9 +34,20 @@ class TransactionInputSerializer { data += UInt32(output.index) if forCurrentInputSignature { - let scriptLength = VarInt(output.lockingScript.count) + let script: Data + switch inputToSign.previousOutput.scriptType { + case .p2sh: + guard let redeemScript = inputToSign.previousOutput.redeemScript else { + throw SerializationError.noPreviousOutputScript + } + script = redeemScript + default: + script = output.lockingScript + } + + let scriptLength = VarInt(script.count) data += scriptLength.serialized() - data += output.lockingScript + data += script } else { data += VarInt(0).serialized() } diff --git a/BitcoinCore/BitcoinCore/Serializers/TransactionOutputSerializer.swift b/BitcoinCore/Classes/Serializers/TransactionOutputSerializer.swift similarity index 97% rename from BitcoinCore/BitcoinCore/Serializers/TransactionOutputSerializer.swift rename to BitcoinCore/Classes/Serializers/TransactionOutputSerializer.swift index 2a5455e1..2d51020f 100644 --- a/BitcoinCore/BitcoinCore/Serializers/TransactionOutputSerializer.swift +++ b/BitcoinCore/Classes/Serializers/TransactionOutputSerializer.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit class TransactionOutputSerializer { diff --git a/BitcoinCore/BitcoinCore/Serializers/TransactionSerializer.swift b/BitcoinCore/Classes/Serializers/TransactionSerializer.swift similarity index 84% rename from BitcoinCore/BitcoinCore/Serializers/TransactionSerializer.swift rename to BitcoinCore/Classes/Serializers/TransactionSerializer.swift index 1249f9b8..4b7b1524 100644 --- a/BitcoinCore/BitcoinCore/Serializers/TransactionSerializer.swift +++ b/BitcoinCore/Classes/Serializers/TransactionSerializer.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit public class TransactionSerializer { @@ -35,23 +35,35 @@ public class TransactionSerializer { let hashPrevouts = try inputsToSign.flatMap { input in try TransactionInputSerializer.serializedOutPoint(input: input) } - data += CryptoKit.sha256sha256((Data(hashPrevouts))) + data += Kit.sha256sha256((Data(hashPrevouts))) var sequences = Data() for inputToSign in inputsToSign { sequences += UInt32(inputToSign.input.sequence) } - data += CryptoKit.sha256sha256(sequences) + data += Kit.sha256sha256(sequences) let inputToSign = inputsToSign[inputIndex] data += try TransactionInputSerializer.serializedOutPoint(input: inputToSign) - data += OpCode.push(OpCode.p2pkhStart + OpCode.push(inputToSign.previousOutput.keyHash!) + OpCode.p2pkhFinish) + + switch inputToSign.previousOutput.scriptType { + case .p2sh: + guard let script = inputToSign.previousOutput.redeemScript else { + throw SerializationError.noPreviousOutputScript + } + let scriptLength = VarInt(script.count) + data += scriptLength.serialized() + data += script + default: + data += OpCode.push(OpCode.p2pkhStart + OpCode.push(inputToSign.previousOutput.keyHash!) + OpCode.p2pkhFinish) + } + data += inputToSign.previousOutput.value data += UInt32(inputToSign.input.sequence) let hashOutputs = outputs.flatMap { TransactionOutputSerializer.serialize(output: $0) } - data += CryptoKit.sha256sha256((Data(hashOutputs))) + data += Kit.sha256sha256((Data(hashOutputs))) } else { data += UInt32(transaction.version) data += VarInt(inputsToSign.count).serialized() diff --git a/BitcoinCore/BitcoinCore/Storage/GrdbStorage.swift b/BitcoinCore/Classes/Storage/GrdbStorage.swift similarity index 54% rename from BitcoinCore/BitcoinCore/Storage/GrdbStorage.swift rename to BitcoinCore/Classes/Storage/GrdbStorage.swift index 3abcac52..c2c09f86 100644 --- a/BitcoinCore/BitcoinCore/Storage/GrdbStorage.swift +++ b/BitcoinCore/Classes/Storage/GrdbStorage.swift @@ -1,9 +1,9 @@ import RxSwift import GRDB +import UIExtensions open class GrdbStorage { public var dbPool: DatabasePool - private var dbsInTransaction = [Int: Database]() public init(databaseFilePath: String) { dbPool = try! DatabasePool(path: databaseFilePath) @@ -45,7 +45,6 @@ open class GrdbStorage { migrator.registerMigration("createSentTransactions") { db in try db.create(table: SentTransaction.databaseTableName) { t in t.column(SentTransaction.Columns.dataHash.name, .text).notNull() - t.column(SentTransaction.Columns.firstSendTime.name, .double).notNull() t.column(SentTransaction.Columns.lastSendTime.name, .double).notNull() t.column(SentTransaction.Columns.retriesCount.name, .integer).notNull() @@ -91,6 +90,7 @@ open class GrdbStorage { migrator.registerMigration("createTransactions") { db in try db.create(table: Transaction.databaseTableName) { t in + t.column(Transaction.Columns.uid.name, .text).notNull() t.column(Transaction.Columns.dataHash.name, .text).notNull() t.column(Transaction.Columns.version.name, .integer).notNull() t.column(Transaction.Columns.lockTime.name, .integer).notNull() @@ -130,6 +130,7 @@ open class GrdbStorage { t.column(Output.Columns.index.name, .integer).notNull() t.column(Output.Columns.transactionHash.name, .text).notNull() t.column(Output.Columns.publicKeyPath.name, .text) + t.column(Output.Columns.changeOutput.name, .boolean) t.column(Output.Columns.scriptType.name, .integer) t.column(Output.Columns.keyHash.name, .blob) t.column(Output.Columns.address.name, .text) @@ -154,16 +155,230 @@ open class GrdbStorage { try db.execute(sql: "UPDATE \(Block.databaseTableName) SET \(Block.Columns.hasTransactions.name) = true") } + migrator.registerMigration("setCorrectTimestampForCheckpointBlock578592") { db in + try db.execute(sql: "UPDATE \(Block.databaseTableName) SET \(Block.Columns.timestamp.name) = 1559256184 WHERE \(Block.Columns.height.name) == 578592 AND \(Block.Columns.timestamp.name) == 1559277784") + } + + migrator.registerMigration("addRedeemScriptToOutput") { db in + try db.alter(table: Output.databaseTableName) { t in + t.add(column: Output.Columns.redeemScript.name, .blob) + } + } + + migrator.registerMigration("addPluginInfoToOutput") { db in + try db.alter(table: Output.databaseTableName) { t in + t.add(column: Output.Columns.pluginId.name, .integer) + t.add(column: Output.Columns.pluginData.name, .text) + } + } + + migrator.registerMigration("addSendSuccessToSentTransaction") { db in + try db.alter(table: SentTransaction.databaseTableName) { t in + t.add(column: SentTransaction.Columns.sendSuccess.name, .boolean) + } + } + + migrator.registerMigration("createInvalidTransactions") { db in + try db.create(table: InvalidTransaction.databaseTableName) { t in + t.column(Transaction.Columns.uid.name, .text).notNull() + t.column(Transaction.Columns.dataHash.name, .text).notNull() + t.column(Transaction.Columns.version.name, .integer).notNull() + t.column(Transaction.Columns.lockTime.name, .integer).notNull() + t.column(Transaction.Columns.timestamp.name, .integer).notNull() + t.column(Transaction.Columns.order.name, .integer).notNull() + t.column(Transaction.Columns.blockHash.name, .text) + t.column(Transaction.Columns.isMine.name, .boolean) + t.column(Transaction.Columns.isOutgoing.name, .boolean) + t.column(Transaction.Columns.status.name, .integer) + t.column(Transaction.Columns.segWit.name, .boolean) + t.column(Transaction.Columns.transactionInfoJson.name, .blob) + } + } + + migrator.registerMigration("addConflictingTxHashAndTxInfoToTransaction") { db in + try db.alter(table: Transaction.databaseTableName) { t in + t.add(column: Transaction.Columns.transactionInfoJson.name, .blob).defaults(to: Data()) + t.add(column: Transaction.Columns.conflictingTxHash.name, .text) + } + } + + migrator.registerMigration("addConflictingTxHashToInvalidTransaction") { db in + try db.alter(table: InvalidTransaction.databaseTableName) { t in + t.add(column: Transaction.Columns.conflictingTxHash.name, .text) + } + } + + migrator.registerMigration("addRawTransactionToTransactionAndInvalidTransaction") { db in + try db.alter(table: Transaction.databaseTableName) { t in + t.add(column: Transaction.Columns.rawTransaction.name, .text) + } + try db.alter(table: InvalidTransaction.databaseTableName) { t in + t.add(column: Transaction.Columns.rawTransaction.name, .text) + } + } + + migrator.registerMigration("addFailedToSpendToOutputs") { db in + try db.alter(table: Output.databaseTableName) { t in + t.add(column: Output.Columns.failedToSpend.name, .boolean).notNull().defaults(to: false) + } + } + + migrator.registerMigration("createTransactionMetaData") { db in + class AnonymousOutputStorage: IOutputStorage { + let storage: GrdbStorage + let db: Database + + init(storage: GrdbStorage, db: Database) { + self.storage = storage + self.db = db + } + + func previousOutput(ofInput input: Input) -> Output? { + try! storage._previousOutput(ofInput: input, db: db) + } + + func outputsWithPublicKeys() -> [OutputWithPublicKey] { + try! storage._outputsWithPublicKeys(db: db) + } + + } + + let anonymousOutputStorage = AnonymousOutputStorage(storage: self, db: db) + let extractor = TransactionMetadataExtractor(storage: anonymousOutputStorage) + + try db.create(table: TransactionMetadata.databaseTableName) { t in + t.column(TransactionMetadata.Columns.transactionHash.name, .text).primaryKey(onConflict: .replace) + t.column(TransactionMetadata.Columns.amount.name, .integer).notNull().defaults(to: 0) + t.column(TransactionMetadata.Columns.type.name, .integer).notNull().defaults(to: 0) + t.column(TransactionMetadata.Columns.fee.name, .integer) + } + + for transaction in try Transaction.order([Transaction.Columns.timestamp, Transaction.Columns.order]).fetchAll(db) { + let fullTransaction = FullTransaction( + header: transaction, + inputs: try self._inputs(transactionHash: transaction.dataHash, db: db), + outputs: try self._outputs(transactionHash: transaction.dataHash, db: db) + ) + + extractor.extract(transaction: fullTransaction) + try fullTransaction.metaData.insert(db) + } + + try InvalidTransaction.deleteAll(db) + } + return migrator } + private func fullTransaction(transaction: Transaction) -> FullTransaction { + FullTransaction( + header: transaction, + inputs: inputs(transactionHash: transaction.dataHash), + outputs: outputs(transactionHash: transaction.dataHash) + ) + } + + private func _add(transaction: FullTransaction, db: Database) throws { + try transaction.header.insert(db) + try transaction.metaData.insert(db) + + for input in transaction.inputs { + try input.insert(db) + } + + for output in transaction.outputs { + try output.insert(db) + } + } + + private func _update(transaction: FullTransaction, db: Database) throws { + try transaction.header.update(db) + try transaction.metaData.update(db) + for input in transaction.inputs { + try input.update(db) + } + for output in transaction.outputs { + try output.update(db) + } + } + + private func _outputsWithPublicKeys(db: Database) throws -> [OutputWithPublicKey] { + let outputC = Output.Columns.allCases.count + let publicKeyC = PublicKey.Columns.allCases.count + let inputC = Input.Columns.allCases.count + + let adapter = ScopeAdapter([ + "output": RangeRowAdapter(0.. [Input] { + try Input.filter(Input.Columns.transactionHash == transactionHash).fetchAll(db) + } + + private func _outputs(transactionHash: Data, db: Database) throws -> [Output] { + try Output.filter(Output.Columns.transactionHash == transactionHash).fetchAll(db) + } + + private func _previousOutput(ofInput input: Input, db: Database) throws -> Output? { + try Output + .filter(Output.Columns.transactionHash == input.previousOutputTxHash) + .filter(Output.Columns.index == input.previousOutputIndex) + .fetchOne(db) + } + + private func inputsWithPreviousOutputs(transactionHashes: [Data], db: Database) throws -> [InputWithPreviousOutput] { + var inputs = [InputWithPreviousOutput]() + + let inputC = Input.Columns.allCases.count + let outputC = Output.Columns.allCases.count + + let adapter = ScopeAdapter([ + "input": RangeRowAdapter(0.. PeerAddress? { - return try! dbPool.read { db in + try! dbPool.read { db in try PeerAddress .filter(!excludingIps.contains(PeerAddress.Columns.ip)) .order(PeerAddress.Columns.score.asc, PeerAddress.Columns.connectionTime.asc) @@ -187,19 +402,18 @@ extension GrdbStorage: IStorage { } } - public func save(peerAddresses: [PeerAddress]) { - _ = try! dbPool.write { db in - for peerAddress in peerAddresses { - try peerAddress.insert(db) - } + public func peerAddressExist(address: String) -> Bool { + try! dbPool.read { db in + try PeerAddress + .filter(PeerAddress.Columns.ip == address) + .fetchCount(db) > 0 } } - public func increasePeerAddressScore(ip: String) { + public func save(peerAddresses: [PeerAddress]) { _ = try! dbPool.write { db in - if let peerAddress = try PeerAddress.filter(PeerAddress.Columns.ip == ip).fetchOne(db) { - peerAddress.score += 1 - try peerAddress.save(db) + for peerAddress in peerAddresses { + try peerAddress.insert(db) } } } @@ -214,6 +428,7 @@ extension GrdbStorage: IStorage { _ = try! dbPool.write { db in if let peerAddress = try PeerAddress.filter(PeerAddress.Columns.ip == ip).fetchOne(db) { peerAddress.connectionTime = connectionTime + peerAddress.score += 1 try peerAddress.save(db) } } @@ -222,25 +437,25 @@ extension GrdbStorage: IStorage { // BlockHash public var blockchainBlockHashes: [BlockHash] { - return try! dbPool.read { db in + try! dbPool.read { db in try BlockHash.filter(BlockHash.Columns.height == 0).fetchAll(db) } } public var lastBlockchainBlockHash: BlockHash? { - return try! dbPool.read { db in + try! dbPool.read { db in try BlockHash.filter(BlockHash.Columns.height == 0).order(BlockHash.Columns.sequence.desc).fetchOne(db) } } public var lastBlockHash: BlockHash? { - return try! dbPool.read { db in + try! dbPool.read { db in try BlockHash.order(BlockHash.Columns.sequence.desc).fetchOne(db) } } public var blockHashHeaderHashes: [Data] { - return try! dbPool.read { db in + try! dbPool.read { db in let rows = try Row.fetchCursor(db, sql: "SELECT headerHash from blockHashes") var hashes = [Data]() @@ -252,13 +467,15 @@ extension GrdbStorage: IStorage { } } - public func blockHashHeaderHashes(except excludedHash: Data) -> [String] { - return try! dbPool.read { db in - let rows = try Row.fetchCursor(db, sql: "SELECT headerHash from blockHashes WHERE headerHash != ?", arguments: [excludedHash]) - var hexes = [String]() + public func blockHashHeaderHashes(except excludedHashes: [Data]) -> [Data] { + try! dbPool.read { db in + let hashesExpression = excludedHashes.map { _ in "?" }.joined(separator: ",") + let hashesArgs = StatementArguments(excludedHashes) + let rows = try Row.fetchCursor(db, sql: "SELECT headerHash from blockHashes WHERE headerHash NOT IN (\(hashesExpression))", arguments: hashesArgs) + var hexes = [Data]() while let row = try rows.next() { - hexes.append(row[0] as String) + hexes.append(row[0] as Data) } return hexes @@ -266,8 +483,8 @@ extension GrdbStorage: IStorage { } public func blockHashesSortedBySequenceAndHeight(limit: Int) -> [BlockHash] { - return try! dbPool.read { db in - try BlockHash.order(BlockHash.Columns.sequence.asc).order(BlockHash.Columns.height.asc).limit(limit).fetchAll(db) + try! dbPool.read { db in + try BlockHash.order(BlockHash.Columns.sequence.asc, BlockHash.Columns.height.asc).limit(limit).fetchAll(db) } } @@ -304,19 +521,19 @@ extension GrdbStorage: IStorage { // Block public var blocksCount: Int { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.fetchCount(db) } } public var lastBlock: Block? { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.order(Block.Columns.height.desc).fetchOne(db) } } public func blocksCount(headerHashes: [Data]) -> Int { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.filter(headerHashes.contains(Block.Columns.headerHash)).fetchCount(db) } } @@ -334,49 +551,55 @@ extension GrdbStorage: IStorage { } public func blocks(heightGreaterThan leastHeight: Int, sortedBy sortField: Block.Columns, limit: Int) -> [Block] { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.filter(Block.Columns.height > leastHeight).order(sortField.desc).limit(limit).fetchAll(db) } } public func blocks(from startHeight: Int, to endHeight: Int, ascending: Bool) -> [Block] { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.filter(Block.Columns.height >= startHeight).filter(Block.Columns.height <= endHeight).order(ascending ? Block.Columns.height.asc : Block.Columns.height.desc).fetchAll(db) } } - public func blocks(byHexes hexes: [String]) -> [Block] { - return try! dbPool.read { db in + public func blocks(byHexes hexes: [Data]) -> [Block] { + try! dbPool.read { db in try Block.filter(hexes.contains(Block.Columns.headerHash)).fetchAll(db) } } public func blocks(heightGreaterThanOrEqualTo height: Int, stale: Bool) -> [Block] { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.filter(Block.Columns.stale == stale).filter(Block.Columns.height >= height).fetchAll(db) } } public func blocks(stale: Bool) -> [Block] { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.filter(Block.Columns.stale == stale).fetchAll(db) } } + public func blockByHeightStalePrioritized(height: Int) -> Block? { + try! dbPool.read { db in + try Block.filter(Block.Columns.height == height).order(Block.Columns.stale.desc).fetchOne(db) + } + } + public func block(byHeight height: Int) -> Block? { - return try! dbPool.read { db in + try! dbPool.read { db in try Block.filter(Block.Columns.height == height).fetchOne(db) } } public func block(byHash hash: Data) -> Block? { - return try! dbPool.read { db in - return try Block.filter(Block.Columns.headerHash == hash).fetchOne(db) + try! dbPool.read { db in + try Block.filter(Block.Columns.headerHash == hash).fetchOne(db) } } public func block(stale: Bool, sortedHeight: String) -> Block? { - return try! dbPool.read { db in + try! dbPool.read { db in let order = sortedHeight == "ASC" ? Block.Columns.height.asc : Block.Columns.height.desc return try Block.filter(Block.Columns.stale == stale).order(order).fetchOne(db) } @@ -409,31 +632,109 @@ extension GrdbStorage: IStorage { } } + public func timestamps(from startHeight: Int, to endHeight: Int) -> [Int] { + try! dbPool.read { db in + var timestamps = [Int]() + + let sql = "SELECT blocks.timestamp FROM blocks WHERE blocks.height >= \(startHeight) AND blocks.height <= \(endHeight) ORDER BY blocks.timestamp ASC" + let rows = try Row.fetchCursor(db, sql: sql) + + while let row = try rows.next() { + if let timestamp = Int.fromDatabaseValue(row["timestamp"]) { + timestamps.append(timestamp) + } + } + + return timestamps + } + } + // Transaction + public func fullTransaction(byHash hash: Data) -> FullTransaction? { + try! dbPool.read { db in + try Transaction.filter(Transaction.Columns.dataHash == hash).fetchOne(db) + }.flatMap { fullTransaction(transaction: $0) } + } + public func transaction(byHash hash: Data) -> Transaction? { - return try! dbPool.read { db in + try! dbPool.read { db in try Transaction.filter(Transaction.Columns.dataHash == hash).fetchOne(db) } } + public func invalidTransaction(byHash hash: Data) -> InvalidTransaction? { + try! dbPool.read { db in + try InvalidTransaction.filter(Transaction.Columns.dataHash == hash).fetchOne(db) + } + } + + public func validOrInvalidTransaction(byUid uid: String) -> Transaction? { + try! dbPool.read { db in + let transactionC = Transaction.Columns.allCases.count + + let adapter = ScopeAdapter([ + "transaction": RangeRowAdapter(0.. [Data] { + try! dbPool.read { db in + try Transaction + .filter(Transaction.Columns.blockHash == nil) + .filter(Transaction.Columns.isOutgoing == false) + .fetchAll(db) + }.map { $0.dataHash } + } + + public func incomingPendingTransactionsExist() -> Bool { + try! dbPool.read { db in + try Transaction + .filter(Transaction.Columns.blockHash == nil) + .filter(Transaction.Columns.isMine == true) + .filter(Transaction.Columns.isOutgoing == false) + .fetchCount(db) > 0 + } + } + + public func inputs(byHashes hashes: [Data]) -> [Input] { + try! dbPool.read { db in + try Input.filter(hashes.contains(Input.Columns.transactionHash)).fetchAll(db) + } + } + public func transactionExists(byHash hash: Data) -> Bool { - return transaction(byHash: hash) != nil + transaction(byHash: hash) != nil } public func transactions(ofBlock block: Block) -> [Transaction] { - return try! dbPool.read { db in + try! dbPool.read { db in try Transaction.filter(Transaction.Columns.blockHash == block.headerHash).fetchAll(db) } } - public func newTransactions() -> [Transaction] { - return try! dbPool.read { db in + public func newTransactions() -> [FullTransaction] { + try! dbPool.read { db in try Transaction.filter(Transaction.Columns.status == TransactionStatus.new).fetchAll(db) - } + }.map { fullTransaction(transaction: $0) } } public func newTransaction(byHash hash: Data) -> Transaction? { - return try! dbPool.read { db in + try! dbPool.read { db in try Transaction .filter(Transaction.Columns.status == TransactionStatus.new) .filter(Transaction.Columns.dataHash == hash) @@ -442,7 +743,7 @@ extension GrdbStorage: IStorage { } public func relayedTransactionExists(byHash hash: Data) -> Bool { - return try! dbPool.read { db in + try! dbPool.read { db in try Transaction .filter(Transaction.Columns.status == TransactionStatus.relayed) .filter(Transaction.Columns.dataHash == hash) @@ -452,15 +753,13 @@ extension GrdbStorage: IStorage { public func add(transaction: FullTransaction) throws { _ = try! dbPool.write { db in - try transaction.header.insert(db) - - for input in transaction.inputs { - try input.insert(db) - } + try _add(transaction: transaction, db: db) + } + } - for output in transaction.outputs { - try output.insert(db) - } + public func update(transaction: FullTransaction) throws { + _ = try! dbPool.write { db in + try _update(transaction: transaction, db: db) } } @@ -471,45 +770,30 @@ extension GrdbStorage: IStorage { } public func fullInfo(forTransactions transactionsWithBlocks: [TransactionWithBlock]) -> [FullTransactionForInfo] { - let transactionHashes: [Data] = transactionsWithBlocks.map({ $0.transaction.dataHash }) + let transactionHashes: [Data] = transactionsWithBlocks.filter({ $0.transaction.status != .invalid }).map({ $0.transaction.dataHash }) var inputs = [InputWithPreviousOutput]() var outputs = [Output]() + var metadata = [TransactionMetadata]() try! dbPool.read { db in for transactionHashChunks in transactionHashes.chunked(into: 999) { - let inputC = Input.Columns.allCases.count - let outputC = Output.Columns.allCases.count - - let adapter = ScopeAdapter([ - "input": RangeRowAdapter(0.. FullTransactionForInfo? { + public func transactionFullInfo(byHash hash: Data) -> FullTransactionForInfo? { var transaction: TransactionWithBlock? = nil try! dbPool.read { db in @@ -549,11 +833,11 @@ extension GrdbStorage: IStorage { return fullInfo(forTransactions: [transactionWithBlock]).first } - public func fullTransactionsInfo(fromTimestamp: Int?, fromOrder: Int?, limit: Int?) -> [FullTransactionForInfo] { + public func validOrInvalidTransactionsFullInfo(fromTimestamp: Int?, fromOrder: Int?, type: TransactionFilterType?, limit: Int?) -> [FullTransactionForInfo] { var transactions = [TransactionWithBlock]() try! dbPool.read { db in - let transactionC = Transaction.Columns.allCases.count + let transactionC = Transaction.Columns.allCases.count + 1 let adapter = ScopeAdapter([ "transaction": RangeRowAdapter(0.. 0 { + sql += " WHERE \(whereConditions.joined(separator: " AND "))" } sql += " ORDER BY transactions.timestamp DESC, transactions.\"order\" DESC" @@ -578,7 +874,17 @@ extension GrdbStorage: IStorage { let rows = try Row.fetchCursor(db, sql: sql, adapter: adapter) while let row = try rows.next() { - transactions.append(TransactionWithBlock(transaction: row["transaction"], blockHeight: row["blockHeight"])) + let status: TransactionStatus = row[Transaction.Columns.status] + let transaction: Transaction + + if status == .invalid { + let invalidTransaction: InvalidTransaction = row["transaction"] + transaction = invalidTransaction + } else { + transaction = row["transaction"] + } + + transactions.append(TransactionWithBlock(transaction: transaction, blockHeight: row["blockHeight"])) } } @@ -586,42 +892,47 @@ extension GrdbStorage: IStorage { return fullInfo(forTransactions: transactions) } + public func moveTransactionsTo(invalidTransactions: [InvalidTransaction]) throws { + try! dbPool.writeInTransaction { db in + for invalidTransaction in invalidTransactions { + try invalidTransaction.insert(db) + + let inputs = try inputsWithPreviousOutputs(transactionHashes: [invalidTransaction.dataHash], db: db) + for input in inputs { + if let previousOutput = input.previousOutput { + previousOutput.failedToSpend = true + try previousOutput.update(db) + } + } - // Inputs and Outputs + try Input.filter(Input.Columns.transactionHash == invalidTransaction.dataHash).deleteAll(db) + try Output.filter(Output.Columns.transactionHash == invalidTransaction.dataHash).deleteAll(db) + try Transaction.filter(Transaction.Columns.dataHash == invalidTransaction.dataHash).deleteAll(db) + } - public func outputsWithPublicKeys() -> [OutputWithPublicKey] { - return try! dbPool.read { db in - let outputC = Output.Columns.allCases.count - let publicKeyC = PublicKey.Columns.allCases.count - let inputC = Input.Columns.allCases.count + return .commit + } + } - let adapter = ScopeAdapter([ - "output": RangeRowAdapter(0.. [OutputWithPublicKey] { + try! dbPool.read { db in + try _outputsWithPublicKeys(db: db) } } public func unspentOutputs() -> [UnspentOutput] { - return try! dbPool.read { db in + try! dbPool.read { db in let inputs = try Input.fetchAll(db) let outputC = Output.Columns.allCases.count @@ -658,30 +969,40 @@ extension GrdbStorage: IStorage { } public func inputs(transactionHash: Data) -> [Input] { - return try! dbPool.read { db in - try Input.filter(Input.Columns.transactionHash == transactionHash).fetchAll(db) + try! dbPool.read { db in + try _inputs(transactionHash: transactionHash, db: db) } } public func outputs(transactionHash: Data) -> [Output] { - return try! dbPool.read { db in - try Output.filter(Output.Columns.transactionHash == transactionHash).fetchAll(db) + try! dbPool.read { db in + try _outputs(transactionHash: transactionHash, db: db) } } public func previousOutput(ofInput input: Input) -> Output? { - return try! dbPool.read { db in - try Output - .filter(Output.Columns.transactionHash == input.previousOutputTxHash) - .filter(Output.Columns.index == input.previousOutputIndex) - .fetchOne(db) + try! dbPool.read { db in + try _previousOutput(ofInput: input, db: db) } } + public func inputsUsingOutputs(withTransactionHash transactionHash: Data) -> [Input] { + try! dbPool.read { db in + try Input.filter(Input.Columns.previousOutputTxHash == transactionHash).fetchAll(db) + } + } + + public func inputsUsing(previousOutputTxHash: Data, previousOutputIndex: Int) -> [Input] { + try! dbPool.read { db in + try Input.filter(Input.Columns.previousOutputTxHash == previousOutputTxHash) + .filter(Input.Columns.previousOutputIndex == previousOutputIndex) + .fetchAll(db) + } + } // SentTransaction public func sentTransaction(byHash hash: Data) -> SentTransaction? { - return try! dbPool.read { db in + try! dbPool.read { db in try SentTransaction.filter(SentTransaction.Columns.dataHash == hash).fetchOne(db) } } @@ -692,6 +1013,12 @@ extension GrdbStorage: IStorage { } } + public func delete(sentTransaction: SentTransaction) { + _ = try! dbPool.write { db in + try sentTransaction.delete(db) + } + } + public func add(sentTransaction: SentTransaction) { _ = try! dbPool.write { db in try sentTransaction.insert(db) @@ -700,19 +1027,19 @@ extension GrdbStorage: IStorage { // PublicKeys public func publicKeys() -> [PublicKey] { - return try! dbPool.read { db in + try! dbPool.read { db in try PublicKey.fetchAll(db) } } public func publicKey(byScriptHashForP2WPKH hash: Data) -> PublicKey? { - return try! dbPool.read { db in + try! dbPool.read { db in try PublicKey.filter(PublicKey.Columns.scriptHashForP2WPKH == hash).fetchOne(db) } } public func publicKey(byRawOrKeyHash hash: Data) -> PublicKey? { - return try! dbPool.read { db in + try! dbPool.read { db in try PublicKey.filter(PublicKey.Columns.raw == hash || PublicKey.Columns.keyHash == hash).fetchOne(db) } } @@ -726,7 +1053,7 @@ extension GrdbStorage: IStorage { } public func publicKeysWithUsedState() -> [PublicKeyWithUsedState] { - return try! dbPool.read { db in + try! dbPool.read { db in let publicKeyC = PublicKey.Columns.allCases.count let adapter = ScopeAdapter([ @@ -737,6 +1064,7 @@ extension GrdbStorage: IStorage { SELECT publicKeys.*, outputs.transactionHash FROM publicKeys LEFT JOIN outputs ON publicKeys.path = outputs.publicKeyPath + GROUP BY publicKeys.path """ let rows = try Row.fetchCursor(db, sql: sql, adapter: adapter) @@ -749,4 +1077,10 @@ extension GrdbStorage: IStorage { } } + public func publicKey(byPath path: String) -> PublicKey? { + try! dbPool.read { db in + try PublicKey.filter(PublicKey.Columns.path == path).fetchOne(db) + } + } + } diff --git a/BitcoinCore/Classes/Transactions/BlockTransactionProcessor.swift b/BitcoinCore/Classes/Transactions/BlockTransactionProcessor.swift new file mode 100644 index 00000000..55035b90 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/BlockTransactionProcessor.swift @@ -0,0 +1,109 @@ +import RxSwift + +class BlockTransactionProcessor { + private let storage: IStorage + private let extractor: ITransactionExtractor + private let publicKeyManager: IPublicKeyManager + private let irregularOutputFinder: IIrregularOutputFinder + private let conflictsResolver: TransactionConflictsResolver + private let invalidator: TransactionInvalidator + + weak var listener: IBlockchainDataListener? + weak var transactionListener: ITransactionListener? + + private let queue: DispatchQueue + + init(storage: IStorage, extractor: ITransactionExtractor, publicKeyManager: IPublicKeyManager, irregularOutputFinder: IIrregularOutputFinder, + conflictsResolver: TransactionConflictsResolver, invalidator: TransactionInvalidator, listener: IBlockchainDataListener? = nil, queue: DispatchQueue) { + self.storage = storage + self.extractor = extractor + self.publicKeyManager = publicKeyManager + self.irregularOutputFinder = irregularOutputFinder + self.conflictsResolver = conflictsResolver + self.invalidator = invalidator + self.listener = listener + self.queue = queue + } + + private func relay(transaction: Transaction, inBlock block: Block, order: Int) { + transaction.blockHash = block.headerHash + transaction.timestamp = block.timestamp + transaction.conflictingTxHash = nil + transaction.status = .relayed + transaction.order = order + } + +} + +extension BlockTransactionProcessor: IBlockTransactionProcessor { + + func processReceived(transactions: [FullTransaction], inBlock block: Block, skipCheckBloomFilter: Bool) throws { + var needToUpdateBloomFilter = false + + var updated = [Transaction]() + var inserted = [Transaction]() + + try queue.sync { + for (index, fullTransaction) in transactions.inTopologicalOrder().enumerated() { + let transaction = fullTransaction.header + if let existingTransaction = storage.fullTransaction(byHash: fullTransaction.header.dataHash) { + extractor.extract(transaction: existingTransaction) + transactionListener?.onReceive(transaction: existingTransaction) + relay(transaction: existingTransaction.header, inBlock: block, order: index) + + try storage.update(transaction: existingTransaction) + updated.append(existingTransaction.header) + + continue + } + + extractor.extract(transaction: fullTransaction) + transactionListener?.onReceive(transaction: fullTransaction) + + guard transaction.isMine else { + for tx in conflictsResolver.incomingPendingTransactionsConflicting(with: fullTransaction) { + tx.conflictingTxHash = fullTransaction.header.dataHash + invalidator.invalidate(transaction: tx) + needToUpdateBloomFilter = true + } + + continue + } + + relay(transaction: transaction, inBlock: block, order: index) + + conflictsResolver.transactionsConflicting(withInblockTransaction: fullTransaction).forEach { + $0.conflictingTxHash = fullTransaction.header.dataHash + invalidator.invalidate(transaction: $0) + } + + if let invalidTransaction = storage.invalidTransaction(byHash: transaction.dataHash) { + try storage.move(invalidTransaction: invalidTransaction, toTransactions: fullTransaction) + updated.append(transaction) + } else { + try storage.add(transaction: fullTransaction) + inserted.append(fullTransaction.header) + } + + if !skipCheckBloomFilter { + needToUpdateBloomFilter = needToUpdateBloomFilter || + publicKeyManager.gapShifts() || + irregularOutputFinder.hasIrregularOutput(outputs: fullTransaction.outputs) + } + } + } + + if !updated.isEmpty || !inserted.isEmpty { + if !block.hasTransactions { + block.hasTransactions = true + storage.update(block: block) + } + + listener?.onUpdate(updated: updated, inserted: inserted, inBlock: block) + } + + if needToUpdateBloomFilter { + throw BloomFilterManager.BloomFilterExpired() + } + } +} diff --git a/BitcoinCore/Classes/Transactions/Builder/DustCalculator.swift b/BitcoinCore/Classes/Transactions/Builder/DustCalculator.swift new file mode 100644 index 00000000..45af1e4b --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/DustCalculator.swift @@ -0,0 +1,31 @@ +public class DustCalculator { + + private let minFeeRate: Int + private let sizeCalculator: ITransactionSizeCalculator + + public init(dustRelayTxFee: Int, sizeCalculator: ITransactionSizeCalculator) { + // https://github.com/bitcoin/bitcoin/blob/master/src/policy/feerate.cpp#L26 + minFeeRate = dustRelayTxFee / 1000 + + self.sizeCalculator = sizeCalculator + } + +} + +extension DustCalculator: IDustCalculator { + + public func dust(type: ScriptType) -> Int { + // https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp#L14 + + var size = sizeCalculator.outputSize(type: type) + + if type.nativeSegwit { + size += sizeCalculator.inputSize(type: .p2wpkh) + sizeCalculator.witnessSize(type: .p2wpkh) / 4 + } else { + size += sizeCalculator.inputSize(type: .p2pkh) + } + + return size * minFeeRate + } + +} diff --git a/BitcoinCore/Classes/Transactions/Builder/InputSetter.swift b/BitcoinCore/Classes/Transactions/Builder/InputSetter.swift new file mode 100644 index 00000000..e5fd7890 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/InputSetter.swift @@ -0,0 +1,94 @@ +class InputSetter { + enum UnspentOutputError: Error { + case feeMoreThanValue + case notSupportedScriptType + } + + private let unspentOutputSelector: IUnspentOutputSelector + private let transactionSizeCalculator: ITransactionSizeCalculator + private let addressConverter: IAddressConverter + private let publicKeyManager: IPublicKeyManager + private let factory: IFactory + private let pluginManager: IPluginManager + private let dustCalculator: IDustCalculator + private let changeScriptType: ScriptType + private let inputSorterFactory: ITransactionDataSorterFactory + + init(unspentOutputSelector: IUnspentOutputSelector, transactionSizeCalculator: ITransactionSizeCalculator, addressConverter: IAddressConverter, publicKeyManager: IPublicKeyManager, + factory: IFactory, pluginManager: IPluginManager, dustCalculator: IDustCalculator, changeScriptType: ScriptType, inputSorterFactory: ITransactionDataSorterFactory) { + self.unspentOutputSelector = unspentOutputSelector + self.transactionSizeCalculator = transactionSizeCalculator + self.addressConverter = addressConverter + self.publicKeyManager = publicKeyManager + self.factory = factory + self.pluginManager = pluginManager + self.dustCalculator = dustCalculator + self.changeScriptType = changeScriptType + self.inputSorterFactory = inputSorterFactory + } + + private func input(fromUnspentOutput unspentOutput: UnspentOutput) throws -> InputToSign { + if unspentOutput.output.scriptType == .p2wpkh { + // todo: refactoring version byte! + // witness key hashes stored with program byte and push code to determine + // version (current only 0), but for sign we need only public kee hash + unspentOutput.output.keyHash?.removeFirst(2) + } + + // Maximum nSequence value (0xFFFFFFFF) disables nLockTime. + // According to BIP-125, any value less than 0xFFFFFFFE makes a Replace-by-Fee(RBF) opted in. + let sequence = 0xFFFFFFFE + + return factory.inputToSign(withPreviousOutput: unspentOutput, script: Data(), sequence: sequence) + } + +} + +extension InputSetter: IInputSetter { + + func setInputs(to mutableTransaction: MutableTransaction, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType) throws { + let value = mutableTransaction.recipientValue + let unspentOutputInfo = try unspentOutputSelector.select( + value: value, feeRate: feeRate, + outputScriptType: mutableTransaction.recipientAddress.scriptType, changeType: changeScriptType, + senderPay: senderPay, pluginDataOutputSize: mutableTransaction.pluginDataOutputSize + ) + let unspentOutputs = inputSorterFactory.sorter(for: sortType).sort(unspentOutputs: unspentOutputInfo.unspentOutputs) + + for unspentOutput in unspentOutputs { + mutableTransaction.add(inputToSign: try input(fromUnspentOutput: unspentOutput)) + } + + mutableTransaction.recipientValue = unspentOutputInfo.recipientValue + + // Add change output if needed + if let changeValue = unspentOutputInfo.changeValue { + let changePubKey = try publicKeyManager.changePublicKey() + let changeAddress = try addressConverter.convert(publicKey: changePubKey, type: changeScriptType) + + mutableTransaction.changeAddress = changeAddress + mutableTransaction.changeValue = changeValue + } + + try pluginManager.processInputs(mutableTransaction: mutableTransaction) + } + + func setInputs(to mutableTransaction: MutableTransaction, fromUnspentOutput unspentOutput: UnspentOutput, feeRate: Int) throws { + guard unspentOutput.output.scriptType == .p2sh else { + throw UnspentOutputError.notSupportedScriptType + } + + // Calculate fee + let transactionSize = transactionSizeCalculator.transactionSize(previousOutputs: [unspentOutput.output], outputScriptTypes: [mutableTransaction.recipientAddress.scriptType], pluginDataOutputSize: 0) + let fee = transactionSize * feeRate + + guard fee < unspentOutput.output.value else { + throw UnspentOutputError.feeMoreThanValue + } + + // Add to mutable transaction + mutableTransaction.add(inputToSign: try input(fromUnspentOutput: unspentOutput)) + mutableTransaction.recipientValue = unspentOutput.output.value - fee + } + +} diff --git a/BitcoinCore/BitcoinCore/Transactions/Builder/InputSigner.swift b/BitcoinCore/Classes/Transactions/Builder/InputSigner.swift similarity index 79% rename from BitcoinCore/BitcoinCore/Transactions/Builder/InputSigner.swift rename to BitcoinCore/Classes/Transactions/Builder/InputSigner.swift index 159bdbc9..6b433e7d 100644 --- a/BitcoinCore/BitcoinCore/Transactions/Builder/InputSigner.swift +++ b/BitcoinCore/Classes/Transactions/Builder/InputSigner.swift @@ -1,5 +1,6 @@ -import HSCryptoKit -import HSHDWalletKit +import HdWalletKit +import OpenSslKit +import Secp256k1Kit class InputSigner { enum SignError: Error { @@ -8,10 +9,10 @@ class InputSigner { case noPrivateKey } - let hdWallet: IHDWallet + let hdWallet: IPrivateHDWallet let network: INetwork - init(hdWallet: IHDWallet, network: INetwork) { + init(hdWallet: IPrivateHDWallet, network: INetwork) { self.hdWallet = hdWallet self.network = network } @@ -33,8 +34,8 @@ extension InputSigner: IInputSigner { var serializedTransaction = try TransactionSerializer.serializedForSignature(transaction: transaction, inputsToSign: inputsToSign, outputs: outputs, inputIndex: index, forked: witness || network.sigHash.forked) serializedTransaction += UInt32(network.sigHash.value) - let signatureHash = CryptoKit.sha256sha256(serializedTransaction) - let signature = try CryptoKit.sign(data: signatureHash, privateKey: privateKeyData) + Data([network.sigHash.value]) + let signatureHash = Kit.sha256sha256(serializedTransaction) + let signature = try Kit.sign(data: signatureHash, privateKey: privateKeyData) + Data([network.sigHash.value]) switch previousOutput.scriptType { case .p2pk: return [signature] diff --git a/BitcoinCore/Classes/Transactions/Builder/LockTimeSetter.swift b/BitcoinCore/Classes/Transactions/Builder/LockTimeSetter.swift new file mode 100644 index 00000000..c3352e16 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/LockTimeSetter.swift @@ -0,0 +1,16 @@ +class LockTimeSetter { + private let storage: IStorage + + init(storage: IStorage) { + self.storage = storage + } + +} + +extension LockTimeSetter: ILockTimeSetter { + + func setLockTime(to mutableTransaction: MutableTransaction) { + mutableTransaction.transaction.lockTime = storage.lastBlock?.height ?? 0 + } + +} diff --git a/BitcoinCore/Classes/Transactions/Builder/MutableTransaction.swift b/BitcoinCore/Classes/Transactions/Builder/MutableTransaction.swift new file mode 100644 index 00000000..3bb9d36e --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/MutableTransaction.swift @@ -0,0 +1,35 @@ +public class MutableTransaction { + var transaction = Transaction(version: 2, lockTime: 0) + var inputsToSign = [InputToSign]() + var outputs = [Output]() + + public var recipientAddress: Address! + public var recipientValue = 0 + var changeAddress: Address? = nil + var changeValue = 0 + + private(set) var pluginData = [UInt8: Data]() + + var pluginDataOutputSize: Int { + pluginData.count > 0 ? 1 + pluginData.reduce(into: 0) { $0 += 1 + $1.value.count } : 0 // OP_RETURN (PLUGIN_ID PLUGIN_DATA) + } + + public init(outgoing: Bool = true) { + transaction.status = .new + transaction.isMine = true + transaction.isOutgoing = outgoing + } + + public func add(pluginData: Data, pluginId: UInt8) { + self.pluginData[pluginId] = pluginData + } + + func add(inputToSign: InputToSign) { + inputsToSign.append(inputToSign) + } + + public func build() -> FullTransaction { + FullTransaction(header: transaction, inputs: inputsToSign.map { $0.input }, outputs: outputs) + } + +} diff --git a/BitcoinCore/Classes/Transactions/Builder/OutputSetter.swift b/BitcoinCore/Classes/Transactions/Builder/OutputSetter.swift new file mode 100644 index 00000000..fad00d13 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/OutputSetter.swift @@ -0,0 +1,43 @@ +class OutputSetter { + private let outputSorterFactory: ITransactionDataSorterFactory + private let factory: IFactory + + init(outputSorterFactory: ITransactionDataSorterFactory, factory: IFactory) { + self.outputSorterFactory = outputSorterFactory + self.factory = factory + } + +} + +extension OutputSetter: IOutputSetter { + + func setOutputs(to transaction: MutableTransaction, sortType: TransactionDataSortType) { + var outputs = [Output]() + + if let address = transaction.recipientAddress { + outputs.append(factory.output(withIndex: 0, address: address, value: transaction.recipientValue, publicKey: nil)) + } + + if let address = transaction.changeAddress { + outputs.append(factory.output(withIndex: 0, address: address, value: transaction.changeValue, publicKey: nil)) + } + + if !transaction.pluginData.isEmpty { + var data = Data([OpCode.op_return]) + + transaction.pluginData.forEach { key, value in + data += Data([key]) + value + } + + outputs.append(factory.nullDataOutput(data: data)) + } + + let sorted = outputSorterFactory.sorter(for: sortType).sort(outputs: outputs) + sorted.enumerated().forEach { index, transactionOutput in + transactionOutput.index = index + } + + transaction.outputs = sorted + } + +} diff --git a/BitcoinCore/Classes/Transactions/Builder/RecipientSetter.swift b/BitcoinCore/Classes/Transactions/Builder/RecipientSetter.swift new file mode 100644 index 00000000..938dda79 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/RecipientSetter.swift @@ -0,0 +1,21 @@ +class RecipientSetter { + private let addressConverter: IAddressConverter + private let pluginManager: IPluginManager + + init(addressConverter: IAddressConverter, pluginManager: IPluginManager) { + self.addressConverter = addressConverter + self.pluginManager = pluginManager + } + +} + +extension RecipientSetter: IRecipientSetter { + + func setRecipient(to mutableTransaction: MutableTransaction, toAddress: String, value: Int, pluginData: [UInt8: IPluginData], skipChecks: Bool = false) throws { + mutableTransaction.recipientAddress = try addressConverter.convert(address: toAddress) + mutableTransaction.recipientValue = value + + try pluginManager.processOutputs(mutableTransaction: mutableTransaction, pluginData: pluginData, skipChecks: skipChecks) + } + +} diff --git a/BitcoinCore/Classes/Transactions/Builder/TransactionBuilder.swift b/BitcoinCore/Classes/Transactions/Builder/TransactionBuilder.swift new file mode 100644 index 00000000..435c4393 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/TransactionBuilder.swift @@ -0,0 +1,46 @@ +class TransactionBuilder { + private let recipientSetter: IRecipientSetter + private let inputSetter: IInputSetter + private let lockTimeSetter: ILockTimeSetter + private let outputSetter: IOutputSetter + private let signer: TransactionSigner + + init(recipientSetter: IRecipientSetter, inputSetter: IInputSetter, lockTimeSetter: ILockTimeSetter, outputSetter: IOutputSetter, signer: TransactionSigner) { + self.recipientSetter = recipientSetter + self.inputSetter = inputSetter + self.lockTimeSetter = lockTimeSetter + self.outputSetter = outputSetter + self.signer = signer + } + +} + +extension TransactionBuilder: ITransactionBuilder { + + func buildTransaction(toAddress: String, value: Int, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData]) throws -> FullTransaction { + let mutableTransaction = MutableTransaction() + + try recipientSetter.setRecipient(to: mutableTransaction, toAddress: toAddress, value: value, pluginData: pluginData, skipChecks: false) + try inputSetter.setInputs(to: mutableTransaction, feeRate: feeRate, senderPay: senderPay, sortType: sortType) + lockTimeSetter.setLockTime(to: mutableTransaction) + + outputSetter.setOutputs(to: mutableTransaction, sortType: sortType) + try signer.sign(mutableTransaction: mutableTransaction) + + return mutableTransaction.build() + } + + func buildTransaction(from unspentOutput: UnspentOutput, toAddress: String, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction { + let mutableTransaction = MutableTransaction(outgoing: false) + + try recipientSetter.setRecipient(to: mutableTransaction, toAddress: toAddress, value: unspentOutput.output.value, pluginData: [:], skipChecks: false) + try inputSetter.setInputs(to: mutableTransaction, fromUnspentOutput: unspentOutput, feeRate: feeRate) + lockTimeSetter.setLockTime(to: mutableTransaction) + + outputSetter.setOutputs(to: mutableTransaction, sortType: sortType) + try signer.sign(mutableTransaction: mutableTransaction) + + return mutableTransaction.build() + } + +} diff --git a/BitcoinCore/Classes/Transactions/Builder/TransactionSigner.swift b/BitcoinCore/Classes/Transactions/Builder/TransactionSigner.swift new file mode 100644 index 00000000..a683ebf6 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Builder/TransactionSigner.swift @@ -0,0 +1,63 @@ +class TransactionSigner { + enum SignError: Error { + case notSupportedScriptType + case noRedeemScript + } + + private let inputSigner: IInputSigner + + init(inputSigner: IInputSigner) { + self.inputSigner = inputSigner + } + + private func signatureScript(from sigScriptData: [Data]) -> Data { + sigScriptData.reduce(Data()) { + $0 + OpCode.push($1) + } + } + +} + +extension TransactionSigner: ITransactionSigner { + + func sign(mutableTransaction: MutableTransaction) throws { + for (index, inputToSign) in mutableTransaction.inputsToSign.enumerated() { + let previousOutput = inputToSign.previousOutput + let publicKey = inputToSign.previousOutputPublicKey + + var sigScriptData = try inputSigner.sigScriptData( + transaction: mutableTransaction.transaction, + inputsToSign: mutableTransaction.inputsToSign, + outputs: mutableTransaction.outputs, + index: index + ) + + switch previousOutput.scriptType { + case .p2pkh: + inputToSign.input.signatureScript = signatureScript(from: sigScriptData) + case .p2wpkh: + mutableTransaction.transaction.segWit = true + inputToSign.input.witnessData = sigScriptData + case .p2wpkhSh: + mutableTransaction.transaction.segWit = true + inputToSign.input.witnessData = sigScriptData + inputToSign.input.signatureScript = OpCode.push(OpCode.scriptWPKH(publicKey.keyHash)) + case .p2sh: + guard let redeemScript = previousOutput.redeemScript else { + throw SignError.noRedeemScript + } + + if let signatureScriptFunction = previousOutput.signatureScriptFunction { + // non-standard P2SH signature script + inputToSign.input.signatureScript = signatureScriptFunction(sigScriptData) + } else { + // standard (signature, publicKey, redeemScript) signature script + sigScriptData.append(redeemScript) + inputToSign.input.signatureScript = signatureScript(from: sigScriptData) + } + default: throw SignError.notSupportedScriptType + } + } + } + +} diff --git a/BitcoinCore/Classes/Transactions/Extractors/MyOutputsCache.swift b/BitcoinCore/Classes/Transactions/Extractors/MyOutputsCache.swift new file mode 100644 index 00000000..d3a9f6a5 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Extractors/MyOutputsCache.swift @@ -0,0 +1,39 @@ +class MyOutputsCache { + private var outputs = [Data: [Int: Int]]() // [TxHash: [OutputIndex: OutputValue]] +} + +extension MyOutputsCache: IOutputsCache { + + func add(outputs: [Output]) { + for output in outputs { + if output.publicKeyPath != nil { + if self.outputs[output.transactionHash] != nil { + self.outputs[output.transactionHash]?[output.index] = output.value + } else { + self.outputs[output.transactionHash] = [output.index: output.value] + } + } + } + } + + func valueSpent(by input: Input) -> Int? { + outputs[input.previousOutputTxHash]?[input.previousOutputIndex] + } + + func clear() { + outputs.removeAll() + } + +} + +extension MyOutputsCache { + + static func instance(storage: IOutputStorage) -> MyOutputsCache { + let instance = MyOutputsCache() + let outputs = storage.outputsWithPublicKeys() + instance.add(outputs: outputs.map { $0.output }) + + return instance + } + +} diff --git a/BitcoinCore/Classes/Transactions/Extractors/TransactionExtractor.swift b/BitcoinCore/Classes/Transactions/Extractors/TransactionExtractor.swift new file mode 100644 index 00000000..b19160e9 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Extractors/TransactionExtractor.swift @@ -0,0 +1,30 @@ +import RxSwift + +class TransactionExtractor { + private let outputExtractor: ITransactionExtractor + private let inputExtractor: ITransactionExtractor + private let outputAddressExtractor: ITransactionExtractor + private let metaDataExtractor: ITransactionExtractor + + init(outputExtractor: ITransactionExtractor, inputExtractor: ITransactionExtractor, metaDataExtractor: ITransactionExtractor, outputAddressExtractor: ITransactionExtractor) { + self.outputExtractor = outputExtractor + self.inputExtractor = inputExtractor + self.outputAddressExtractor = outputAddressExtractor + self.metaDataExtractor = metaDataExtractor + } + +} + +extension TransactionExtractor: ITransactionExtractor { + + func extract(transaction: FullTransaction) { + outputExtractor.extract(transaction: transaction) + metaDataExtractor.extract(transaction: transaction) + + if transaction.header.isMine { + outputAddressExtractor.extract(transaction: transaction) + inputExtractor.extract(transaction: transaction) + } + } + +} diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionInputExtractor.swift b/BitcoinCore/Classes/Transactions/Extractors/TransactionInputExtractor.swift similarity index 97% rename from BitcoinCore/BitcoinCore/Transactions/TransactionInputExtractor.swift rename to BitcoinCore/Classes/Transactions/Extractors/TransactionInputExtractor.swift index 69d66174..9c8391db 100644 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionInputExtractor.swift +++ b/BitcoinCore/Classes/Transactions/Extractors/TransactionInputExtractor.swift @@ -1,4 +1,5 @@ -import HSCryptoKit +import OpenSslKit +import HsToolKit enum ScriptError: Error { case wrongScriptLength, wrongSequence } @@ -69,7 +70,7 @@ extension TransactionInputExtractor: ITransactionExtractor { validScriptType = .p2wpkhSh } if let payload = payload { - let keyHash = CryptoKit.sha256ripemd160(payload) + let keyHash = Kit.sha256ripemd160(payload) if let address = try? addressConverter.convert(keyHash: keyHash, type: validScriptType) { input.keyHash = address.keyHash input.address = address.stringValue diff --git a/BitcoinCore/Classes/Transactions/Extractors/TransactionMetadataExtractor.swift b/BitcoinCore/Classes/Transactions/Extractors/TransactionMetadataExtractor.swift new file mode 100644 index 00000000..0b48507d --- /dev/null +++ b/BitcoinCore/Classes/Transactions/Extractors/TransactionMetadataExtractor.swift @@ -0,0 +1,93 @@ +import RxSwift + +class TransactionMetadataExtractor { + private let myOutputsCache: IOutputsCache + private let storage: IOutputStorage + + init(storage: IOutputStorage) { + myOutputsCache = MyOutputsCache.instance(storage: storage) + self.storage = storage + } + +} + +extension TransactionMetadataExtractor: ITransactionExtractor { + + func extract(transaction: FullTransaction) { + var myInputsTotalValue: Int = 0 + var myOutputsTotalValue: Int = 0 + var myChangeOutputsTotalValue: Int = 0 + var outputsTotalValue: Int = 0 + var allInputsMine = true + + for input in transaction.inputs { + if let value = myOutputsCache.valueSpent(by: input) { + myInputsTotalValue += value + } else { + allInputsMine = false + } + } + + for output in transaction.outputs { + guard output.value > 0 else { + continue + } + + outputsTotalValue += output.value + + if output.publicKeyPath != nil { + myOutputsTotalValue += output.value + if output.changeOutput { + myChangeOutputsTotalValue += output.value + } + } + } + + guard myInputsTotalValue > 0 || myOutputsTotalValue > 0 else { + return + } + + transaction.header.isMine = true + if myInputsTotalValue > 0 { + transaction.header.isOutgoing = true + } + + var amount = myOutputsTotalValue - myInputsTotalValue + var fee: Int? = nil + + if allInputsMine { + fee = myInputsTotalValue - outputsTotalValue + amount += fee! + } else { + var inputsTotalValue = 0 + var allInputsHaveValue = true + for input in transaction.inputs { + if let previousOutput = storage.previousOutput(ofInput: input) { + inputsTotalValue += previousOutput.value + } else { + allInputsHaveValue = false + break + } + } + + fee = allInputsHaveValue ? inputsTotalValue - outputsTotalValue : nil + } + + if amount > 0 { + transaction.metaData.amount = amount + transaction.metaData.type = .incoming + } else if amount < 0 { + transaction.metaData.amount = abs(amount) + transaction.metaData.type = .outgoing + } else { + transaction.metaData.amount = abs(myOutputsTotalValue - myChangeOutputsTotalValue) + transaction.metaData.type = .sentToSelf + } + transaction.metaData.fee = fee + + if myOutputsTotalValue > 0 { + myOutputsCache.add(outputs: transaction.outputs) + } + } + +} diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionOutputAddressExtractor.swift b/BitcoinCore/Classes/Transactions/Extractors/TransactionOutputAddressExtractor.swift similarity index 73% rename from BitcoinCore/BitcoinCore/Transactions/TransactionOutputAddressExtractor.swift rename to BitcoinCore/Classes/Transactions/Extractors/TransactionOutputAddressExtractor.swift index ef3de04c..c8b7166e 100644 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionOutputAddressExtractor.swift +++ b/BitcoinCore/Classes/Transactions/Extractors/TransactionOutputAddressExtractor.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit class TransactionOutputAddressExtractor { private let storage: IStorage @@ -12,9 +12,9 @@ class TransactionOutputAddressExtractor { } -extension TransactionOutputAddressExtractor: ITransactionOutputAddressExtractor { +extension TransactionOutputAddressExtractor: ITransactionExtractor { - public func extractOutputAddresses(transaction: FullTransaction) { + public func extract(transaction: FullTransaction) { for output in transaction.outputs { guard let key = output.keyHash else { continue @@ -23,9 +23,9 @@ extension TransactionOutputAddressExtractor: ITransactionOutputAddressExtractor switch output.scriptType { case .p2pk: - keyHash = CryptoKit.sha256ripemd160(key) + keyHash = Kit.sha256ripemd160(key) case .p2wpkhSh: - keyHash = CryptoKit.sha256ripemd160(OpCode.scriptWPKH(key)) + keyHash = Kit.sha256ripemd160(OpCode.scriptWPKH(key)) default: keyHash = key } diff --git a/BitcoinCore/BitcoinCore/Transactions/TransactionOutputExtractor.swift b/BitcoinCore/Classes/Transactions/Extractors/TransactionOutputExtractor.swift similarity index 78% rename from BitcoinCore/BitcoinCore/Transactions/TransactionOutputExtractor.swift rename to BitcoinCore/Classes/Transactions/Extractors/TransactionOutputExtractor.swift index 932377ae..e13a2a62 100644 --- a/BitcoinCore/BitcoinCore/Transactions/TransactionOutputExtractor.swift +++ b/BitcoinCore/Classes/Transactions/Extractors/TransactionOutputExtractor.swift @@ -1,11 +1,13 @@ -import HSCryptoKit +import HsToolKit class TransactionOutputExtractor { let transactionKeySetter: ITransactionPublicKeySetter + let pluginManager: IPluginManager let logger: Logger? - init(transactionKeySetter: ITransactionPublicKeySetter, logger: Logger? = nil) { + init(transactionKeySetter: ITransactionPublicKeySetter, pluginManager: IPluginManager, logger: Logger? = nil) { self.transactionKeySetter = transactionKeySetter + self.pluginManager = pluginManager self.logger = logger } @@ -14,6 +16,8 @@ class TransactionOutputExtractor { extension TransactionOutputExtractor: ITransactionExtractor { func extract(transaction: FullTransaction) { + var nullDataOutput: Output? = nil + for output in transaction.outputs { var payload: Data? var validScriptType: ScriptType = .unknown @@ -49,15 +53,20 @@ extension TransactionOutputExtractor: ITransactionExtractor { // parse P2WPKH transaction output payload = lockingScript.subdata(in: 0.. 0 && lockingScript[0] == OpCode.op_return { // nullData output + payload = lockingScript.subdata(in: 0.. Bool { + public func set(output: Output) { if let key = output.keyHash { var correctKey = key if output.scriptType == .p2wpkh, key.count > 2 { @@ -18,18 +18,17 @@ extension TransactionPublicKeySetter: ITransactionPublicKeySetter { } if output.scriptType == .p2sh { if let publicKey = storage.publicKey(byScriptHashForP2WPKH: correctKey) { - output.publicKeyPath = publicKey.path + output.set(publicKey: publicKey) output.keyHash = publicKey.keyHash output.scriptType = .p2wpkhSh - return true + return } } if let publicKey = storage.publicKey(byRawOrKeyHash: correctKey) { - output.publicKeyPath = publicKey.path - return true + output.set(publicKey: publicKey) + return } } - return false } } diff --git a/BitcoinCore/Classes/Transactions/PendingTransactionProcessor.swift b/BitcoinCore/Classes/Transactions/PendingTransactionProcessor.swift new file mode 100644 index 00000000..6273ebca --- /dev/null +++ b/BitcoinCore/Classes/Transactions/PendingTransactionProcessor.swift @@ -0,0 +1,133 @@ +import RxSwift + +class PendingTransactionProcessor { + private let storage: IStorage + private let extractor: ITransactionExtractor + private let publicKeyManager: IPublicKeyManager + private let irregularOutputFinder: IIrregularOutputFinder + private let conflictsResolver: ITransactionConflictsResolver + + weak var listener: IBlockchainDataListener? + weak var transactionListener: ITransactionListener? + + private let queue: DispatchQueue + + private var notMineTransactions = Set() + + init(storage: IStorage, extractor: ITransactionExtractor, publicKeyManager: IPublicKeyManager, irregularOutputFinder: IIrregularOutputFinder, conflictsResolver: ITransactionConflictsResolver, + listener: IBlockchainDataListener? = nil, queue: DispatchQueue) { + self.storage = storage + self.extractor = extractor + self.publicKeyManager = publicKeyManager + self.irregularOutputFinder = irregularOutputFinder + self.conflictsResolver = conflictsResolver + self.listener = listener + self.queue = queue + } + + private func relay(transaction: Transaction, order: Int) { + transaction.status = .relayed + transaction.order = order + } + +} + +extension PendingTransactionProcessor: IPendingTransactionProcessor { + + func processReceived(transactions: [FullTransaction], skipCheckBloomFilter: Bool) throws { + var needToUpdateBloomFilter = false + + var updated = [Transaction]() + var inserted = [Transaction]() + + try queue.sync { + for (index, transaction) in transactions.inTopologicalOrder().enumerated() { + if notMineTransactions.contains(transaction.header.dataHash) { + // already processed this transaction with same state + continue + } + + let invalidTransaction = storage.invalidTransaction(byHash: transaction.header.dataHash) + if invalidTransaction != nil { + // if some peer send us transaction after it's invalidated, we must ignore it + continue + } + + if let existingTransaction = storage.transaction(byHash: transaction.header.dataHash) { + if existingTransaction.status == .relayed { + // if comes again from memPool we don't need to update it + continue + } + + relay(transaction: existingTransaction, order: index) + + try storage.update(transaction: existingTransaction) + updated.append(existingTransaction) + + continue + } + + relay(transaction: transaction.header, order: index) + extractor.extract(transaction: transaction) + transactionListener?.onReceive(transaction: transaction) + + guard transaction.header.isMine else { + notMineTransactions.insert(transaction.header.dataHash) + + for tx in conflictsResolver.incomingPendingTransactionsConflicting(with: transaction) { + // Former incoming transaction is conflicting with current transaction + tx.conflictingTxHash = transaction.header.dataHash + try storage.update(transaction: tx) + updated.append(tx) + } + + continue + } + + let conflictingTransactions = conflictsResolver.transactionsConflicting(withPendingTransaction: transaction) + if !conflictingTransactions.isEmpty { + // Ignore current transaction and mark former transactions as conflicting with current transaction + for tx in conflictingTransactions { + tx.conflictingTxHash = transaction.header.dataHash + try storage.update(transaction: tx) + updated.append(tx) + } + } else { + try storage.add(transaction: transaction) + inserted.append(transaction.header) + } + + let needToCheckDoubleSpend = !transaction.header.isOutgoing + if !skipCheckBloomFilter { + needToUpdateBloomFilter = needToUpdateBloomFilter || + needToCheckDoubleSpend || + publicKeyManager.gapShifts() || + irregularOutputFinder.hasIrregularOutput(outputs: transaction.outputs) + } + } + } + + if !updated.isEmpty || !inserted.isEmpty { + listener?.onUpdate(updated: updated, inserted: inserted, inBlock: nil) + } + + if needToUpdateBloomFilter { + throw BloomFilterManager.BloomFilterExpired() + } + } + + func processCreated(transaction: FullTransaction) throws { + guard storage.transaction(byHash: transaction.header.dataHash) == nil else { + throw TransactionCreator.CreationError.transactionAlreadyExists + } + + extractor.extract(transaction: transaction) + try storage.add(transaction: transaction) + listener?.onUpdate(updated: [], inserted: [transaction.header], inBlock: nil) + + if irregularOutputFinder.hasIrregularOutput(outputs: transaction.outputs) { + throw BloomFilterManager.BloomFilterExpired() + } + } + +} diff --git a/BitcoinCore/BitcoinCore/Transactions/Scripts/Chunk.swift b/BitcoinCore/Classes/Transactions/Scripts/Chunk.swift similarity index 89% rename from BitcoinCore/BitcoinCore/Transactions/Scripts/Chunk.swift rename to BitcoinCore/Classes/Transactions/Scripts/Chunk.swift index 9406cc36..7a8da02e 100644 --- a/BitcoinCore/BitcoinCore/Transactions/Scripts/Chunk.swift +++ b/BitcoinCore/Classes/Transactions/Scripts/Chunk.swift @@ -13,7 +13,7 @@ public class Chunk: Equatable { return scriptData.subdata(in: payloadRange) } - init(scriptData: Data, index: Int, payloadRange: Range? = nil) { + public init(scriptData: Data, index: Int, payloadRange: Range? = nil) { self.scriptData = scriptData self.index = index self.payloadRange = payloadRange diff --git a/BitcoinCore/BitcoinCore/Transactions/Extractors/OpCode.swift b/BitcoinCore/Classes/Transactions/Scripts/OpCode.swift similarity index 85% rename from BitcoinCore/BitcoinCore/Transactions/Extractors/OpCode.swift rename to BitcoinCore/Classes/Transactions/Scripts/OpCode.swift index 859e4996..8c86a749 100644 --- a/BitcoinCore/BitcoinCore/Transactions/Extractors/OpCode.swift +++ b/BitcoinCore/Classes/Transactions/Scripts/OpCode.swift @@ -1,5 +1,5 @@ import Foundation -import HSCryptoKit +import OpenSslKit public class OpCode { public static let p2pkhStart = Data([OpCode.dup, OpCode.hash160]) @@ -15,15 +15,23 @@ public class OpCode { public static let pushData1: UInt8 = 0x4c public static let pushData2: UInt8 = 0x4d public static let pushData4: UInt8 = 0x4e + public static let drop: UInt8 = 0x75 public static let dup: UInt8 = 0x76 + public static let sha256: UInt8 = 0xA8 public static let hash160: UInt8 = 0xA9 + public static let size: UInt8 = 0x82 public static let equal: UInt8 = 0x87 public static let equalVerify: UInt8 = 0x88 public static let checkSig: UInt8 = 0xAC public static let checkSigVerify: UInt8 = 0xAD public static let checkMultiSig: UInt8 = 0xAE public static let checkMultiSigVerify: UInt8 = 0xAF + public static let checkLockTimeVerify: UInt8 = 0xB1 + public static let checkSequenceVerify: UInt8 = 0xB2 + public static let _if: UInt8 = 0x63 + public static let _else: UInt8 = 0x67 public static let endIf: UInt8 = 0x68 + public static let op_return: UInt8 = 0x6a public static func value(fromPush code: UInt8) -> UInt8? { if code == 0 { diff --git a/BitcoinCore/BitcoinCore/Transactions/Scripts/Script.swift b/BitcoinCore/Classes/Transactions/Scripts/Script.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Transactions/Scripts/Script.swift rename to BitcoinCore/Classes/Transactions/Scripts/Script.swift diff --git a/BitcoinCore/BitcoinCore/Transactions/Scripts/ScriptConverter.swift b/BitcoinCore/Classes/Transactions/Scripts/ScriptConverter.swift similarity index 100% rename from BitcoinCore/BitcoinCore/Transactions/Scripts/ScriptConverter.swift rename to BitcoinCore/Classes/Transactions/Scripts/ScriptConverter.swift diff --git a/BitcoinCore/Classes/Transactions/TransactionConflictsResolver.swift b/BitcoinCore/Classes/Transactions/TransactionConflictsResolver.swift new file mode 100644 index 00000000..2efd01a0 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/TransactionConflictsResolver.swift @@ -0,0 +1,69 @@ +class TransactionConflictsResolver { + private let storage: IStorage + + init(storage: IStorage) { + self.storage = storage + } + + private func conflictingTransactions(for transaction: FullTransaction) -> [Transaction] { + let storageTransactionHashes = transaction.inputs.compactMap { input in + storage.inputsUsing(previousOutputTxHash: input.previousOutputTxHash, previousOutputIndex: input.previousOutputIndex) + .filter { + $0.transactionHash != transaction.header.dataHash + }.first?.transactionHash + } + guard !storageTransactionHashes.isEmpty else { + return [] + } + + return Array(Set(storageTransactionHashes)).compactMap { + storage.transaction(byHash: $0) + } + } + +} + +extension TransactionConflictsResolver: ITransactionConflictsResolver { + + // Only pending transactions may be conflicting with a transaction in block. No need to check that + func transactionsConflicting(withInblockTransaction transaction: FullTransaction) -> [Transaction] { + self.conflictingTransactions(for: transaction) + } + + func transactionsConflicting(withPendingTransaction transaction: FullTransaction) -> [Transaction] { + let conflictingTransactions = self.conflictingTransactions(for: transaction) + + guard !conflictingTransactions.isEmpty else { + return [] + } + + // If any of conflicting transactions is already in a block, then current transaction is invalid and non of them is conflicting with it. + guard conflictingTransactions.allSatisfy({ $0.blockHash == nil }) else { + return [] + } + + return conflictingTransactions + } + + func incomingPendingTransactionsConflicting(with transaction: FullTransaction) -> [Transaction] { + let pendingTxHashes = storage.incomingPendingTransactionHashes() + if pendingTxHashes.isEmpty { + return [] + } + + let conflictingTransactionHashes = storage + .inputs(byHashes: pendingTxHashes) + .filter { input in + transaction.inputs.contains { $0.previousOutputIndex == input.previousOutputIndex && $0.previousOutputTxHash == input.previousOutputTxHash } + } + .map { $0.transactionHash } + if conflictingTransactionHashes.isEmpty { // handle if transaction has conflicting inputs, otherwise it's false-positive tx + return [] + } + + return Array(Set(conflictingTransactionHashes)) // make unique elements + .compactMap { storage.transaction(byHash: $0) } // get transactions for each input + .filter { $0.blockHash == nil } // exclude all transactions in blocks + } + +} diff --git a/BitcoinCore/Classes/Transactions/TransactionCreator.swift b/BitcoinCore/Classes/Transactions/TransactionCreator.swift new file mode 100644 index 00000000..d964219c --- /dev/null +++ b/BitcoinCore/Classes/Transactions/TransactionCreator.swift @@ -0,0 +1,68 @@ +class TransactionCreator { + enum CreationError: Error { + case transactionAlreadyExists + } + + private let transactionBuilder: ITransactionBuilder + private let transactionProcessor: IPendingTransactionProcessor + private let transactionSender: ITransactionSender + private let bloomFilterManager: IBloomFilterManager + + init(transactionBuilder: ITransactionBuilder, transactionProcessor: IPendingTransactionProcessor, transactionSender: ITransactionSender, bloomFilterManager: IBloomFilterManager) { + self.transactionBuilder = transactionBuilder + self.transactionProcessor = transactionProcessor + self.transactionSender = transactionSender + self.bloomFilterManager = bloomFilterManager + } + + private func processAndSend(transaction: FullTransaction) throws { + try transactionSender.verifyCanSend() + + do { + try transactionProcessor.processCreated(transaction: transaction) + } catch _ as BloomFilterManager.BloomFilterExpired { + bloomFilterManager.regenerateBloomFilter() + } + + transactionSender.send(pendingTransaction: transaction) + } + +} + +extension TransactionCreator: ITransactionCreator { + + func create(to address: String, value: Int, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) throws -> FullTransaction { + let transaction = try transactionBuilder.buildTransaction( + toAddress: address, + value: value, + feeRate: feeRate, + senderPay: senderPay, + sortType: sortType, + pluginData: pluginData + ) + + try processAndSend(transaction: transaction) + return transaction + } + + func create(from unspentOutput: UnspentOutput, to address: String, feeRate: Int, sortType: TransactionDataSortType) throws -> FullTransaction { + let transaction = try transactionBuilder.buildTransaction(from: unspentOutput, toAddress: address, feeRate: feeRate, sortType: sortType) + + try processAndSend(transaction: transaction) + return transaction + } + + func createRawTransaction(to address: String, value: Int, feeRate: Int, senderPay: Bool, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) throws -> Data { + let transaction = try transactionBuilder.buildTransaction( + toAddress: address, + value: value, + feeRate: feeRate, + senderPay: senderPay, + sortType: sortType, + pluginData: pluginData + ) + + return TransactionSerializer.serialize(transaction: transaction) + } + +} diff --git a/BitcoinCore/Classes/Transactions/TransactionFeeCalculator.swift b/BitcoinCore/Classes/Transactions/TransactionFeeCalculator.swift new file mode 100644 index 00000000..3d8c5ca2 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/TransactionFeeCalculator.swift @@ -0,0 +1,36 @@ +class TransactionFeeCalculator { + + private let recipientSetter: IRecipientSetter + private let inputSetter: IInputSetter + private let addressConverter: IAddressConverter + private let publicKeyManager: IPublicKeyManager + private let changeScriptType: ScriptType + + init(recipientSetter: IRecipientSetter, inputSetter: IInputSetter, addressConverter: IAddressConverter, publicKeyManager: IPublicKeyManager, changeScriptType: ScriptType) { + self.recipientSetter = recipientSetter + self.inputSetter = inputSetter + self.addressConverter = addressConverter + self.publicKeyManager = publicKeyManager + self.changeScriptType = changeScriptType + } + + private func sampleAddress() throws -> String { + try addressConverter.convert(publicKey: try publicKeyManager.changePublicKey(), type: changeScriptType).stringValue + } +} + +extension TransactionFeeCalculator: ITransactionFeeCalculator { + + func fee(for value: Int, feeRate: Int, senderPay: Bool, toAddress: String?, pluginData: [UInt8: IPluginData] = [:]) throws -> Int { + let mutableTransaction = MutableTransaction() + + try recipientSetter.setRecipient(to: mutableTransaction, toAddress: toAddress ?? (try sampleAddress()), value: value, pluginData: pluginData, skipChecks: true) + try inputSetter.setInputs(to: mutableTransaction, feeRate: feeRate, senderPay: senderPay, sortType: .none) + + let inputsTotalValue = mutableTransaction.inputsToSign.reduce(0) { total, input in total + input.previousOutput.value } + let outputsTotalValue = mutableTransaction.recipientValue + mutableTransaction.changeValue + + return inputsTotalValue - outputsTotalValue + } + +} diff --git a/BitcoinCore/Classes/Transactions/TransactionInvalidator.swift b/BitcoinCore/Classes/Transactions/TransactionInvalidator.swift new file mode 100644 index 00000000..01c7bec6 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/TransactionInvalidator.swift @@ -0,0 +1,61 @@ +class TransactionInvalidator { + private let storage: IStorage + private let transactionInfoConverter: ITransactionInfoConverter + + weak var listener: IBlockchainDataListener? + + init(storage: IStorage, transactionInfoConverter: ITransactionInfoConverter, listener: IBlockchainDataListener? = nil) { + self.storage = storage + self.transactionInfoConverter = transactionInfoConverter + self.listener = listener + } + + private func descendantTransactionsFullInfo(of transactionHash: Data) -> [FullTransactionForInfo] { + guard let fullTransactionInfo = storage.transactionFullInfo(byHash: transactionHash) else { + return [] + } + + return storage + .inputsUsingOutputs(withTransactionHash: transactionHash) + .reduce(into: [fullTransactionInfo]) { list, input in + list.append(contentsOf: descendantTransactionsFullInfo(of: input.transactionHash)) + } + } + +} + +extension TransactionInvalidator: ITransactionInvalidator { + + public func invalidate(transaction: Transaction) { + let invalidTransactionsFullInfo = descendantTransactionsFullInfo(of: transaction.dataHash) + + guard !invalidTransactionsFullInfo.isEmpty else { + return + } + + invalidTransactionsFullInfo.forEach { + $0.transactionWithBlock.transaction.status = .invalid + } + + let invalidTransactions: [InvalidTransaction] = invalidTransactionsFullInfo.map { transactionFullInfo in + let transactionInfo = transactionInfoConverter.transactionInfo(fromTransaction: transactionFullInfo) + var transactionInfoJson = Data() + if let jsonData = try? JSONEncoder.init().encode(transactionInfo) { + transactionInfoJson = jsonData + } + + let transaction = transactionFullInfo.transactionWithBlock.transaction + return InvalidTransaction( + uid: transaction.uid, dataHash: transaction.dataHash, version: transaction.version, lockTime: transaction.lockTime, timestamp: transaction.timestamp, + order: transaction.order, blockHash: transaction.blockHash, isMine: transaction.isMine, isOutgoing: transaction.isOutgoing, + status: transaction.status, segWit: transaction.segWit, conflictingTxHash: transaction.conflictingTxHash, + transactionInfoJson: transactionInfoJson, rawTransaction: transactionFullInfo.rawTransaction + ) + } + + + try? storage.moveTransactionsTo(invalidTransactions: invalidTransactions) + listener?.onUpdate(updated: invalidTransactions, inserted: [], inBlock: nil) + } + +} diff --git a/BitcoinCore/Classes/Transactions/TransactionSizeCalculator.swift b/BitcoinCore/Classes/Transactions/TransactionSizeCalculator.swift new file mode 100644 index 00000000..d52684a5 --- /dev/null +++ b/BitcoinCore/Classes/Transactions/TransactionSizeCalculator.swift @@ -0,0 +1,110 @@ +public class TransactionSizeCalculator { + static let legacyTx = 16 + 4 + 4 + 16 //40 Version + number of inputs + number of outputs + locktime + static let legacyWitnessData = 1 //1 Only 0x00 for legacy input + static let witnessData = 1 + signatureLength + pubKeyLength //108 Number of stack items for input + Size of stack item 0 + Stack item 0, signature + Size of stack item 1 + Stack item 1, pubkey + static let witnessTx = legacyTx + 1 + 1 //42 SegWit marker + SegWit flag + + static let signatureLength = 72 + 1 // signature length plus pushByte + static let pubKeyLength = 33 + 1 // pubKey length plus pushByte + static let p2wpkhShLength = 22 + 1 // 0014<20byte-scriptHash> plus pushByte + + public init() {} + + private func outputSize(lockingScriptSize: Int) -> Int { + 8 + 1 + lockingScriptSize // spentValue + scriptLength + script + } + + private func inputSize(output: Output) -> Int { // in real bytes + // Here we calculate size for only those inputs, which we can sign later in TransactionSigner.swift + // Any other inputs will fail to sign later, so no need to calculate size here + + let sigScriptLength: Int + switch output.scriptType { + case .p2pkh: sigScriptLength = TransactionSizeCalculator.signatureLength + TransactionSizeCalculator.pubKeyLength + case .p2pk: sigScriptLength = TransactionSizeCalculator.signatureLength + case .p2wpkhSh: sigScriptLength = TransactionSizeCalculator.p2wpkhShLength + case .p2sh: + if let redeemScript = output.redeemScript { + if let signatureScriptFunction = output.signatureScriptFunction { + // non-standard P2SH signature script + let emptySignature = Data(repeating: 0, count: TransactionSizeCalculator.signatureLength) + let emptyPublicKey = Data(repeating: 0, count: TransactionSizeCalculator.pubKeyLength) + + sigScriptLength = signatureScriptFunction([emptySignature, emptyPublicKey]).count + } else { + // standard (signature, publicKey, redeemScript) signature script + sigScriptLength = TransactionSizeCalculator.signatureLength + TransactionSizeCalculator.pubKeyLength + OpCode.push(redeemScript).count + } + } else { + sigScriptLength = 0 + } + default: sigScriptLength = 0 + } + let inputTxSize: Int = 32 + 4 + 1 + sigScriptLength + 4 // PreviousOutputHex + InputIndex + sigLength + sigScript + sequence + return inputTxSize + } +} + +extension TransactionSizeCalculator: ITransactionSizeCalculator { + + public func transactionSize(previousOutputs: [Output], outputScriptTypes: [ScriptType]) -> Int { // in real bytes upped to int + transactionSize(previousOutputs: previousOutputs, outputScriptTypes: outputScriptTypes, pluginDataOutputSize: 0) + } + + public func transactionSize(previousOutputs: [Output], outputScriptTypes: [ScriptType], pluginDataOutputSize: Int) -> Int { // in real bytes upped to int + var segWit = false + var inputWeight = 0 + + for previousOutput in previousOutputs { + if previousOutput.scriptType.witness { + segWit = true + break + } + } + + previousOutputs.forEach { previousOutput in + inputWeight += inputSize(output: previousOutput) * 4 // to vbytes + if segWit { + inputWeight += witnessSize(type: previousOutput.scriptType) + } + } + + var outputWeight: Int = outputScriptTypes.reduce(0) { $0 + outputSize(type: $1) } * 4 // in vbytes + if pluginDataOutputSize > 0 { + outputWeight += outputSize(lockingScriptSize: pluginDataOutputSize) * 4 + } + let txWeight = segWit ? TransactionSizeCalculator.witnessTx : TransactionSizeCalculator.legacyTx + + return toBytes(fee: txWeight + inputWeight + outputWeight) + } + + public func outputSize(type: ScriptType) -> Int { // in real bytes + outputSize(lockingScriptSize: Int(type.size)) + } + + public func inputSize(type: ScriptType) -> Int { // in real bytes + let sigScriptLength: Int + switch type { + case .p2pkh: sigScriptLength = TransactionSizeCalculator.signatureLength + TransactionSizeCalculator.pubKeyLength + case .p2pk: sigScriptLength = TransactionSizeCalculator.signatureLength + case .p2wpkhSh: sigScriptLength = TransactionSizeCalculator.p2wpkhShLength + default: sigScriptLength = 0 + } + let inputTxSize: Int = 32 + 4 + 1 + sigScriptLength + 4 // PreviousOutputHex + InputIndex + sigLength + sigScript + sequence + return inputTxSize + } + + public func witnessSize(type: ScriptType) -> Int { // in vbytes + // We assume that only P2WPKH or P2WPKH(SH) outputs can be here + + if type.witness { + return TransactionSizeCalculator.witnessData + } + return TransactionSizeCalculator.legacyWitnessData + } + + public func toBytes(fee: Int) -> Int { + fee / 4 + (fee % 4 == 0 ? 0 : 1) + } + +} diff --git a/BitcoinCore/Classes/Transactions/TransactionSyncer.swift b/BitcoinCore/Classes/Transactions/TransactionSyncer.swift new file mode 100644 index 00000000..45e0a65a --- /dev/null +++ b/BitcoinCore/Classes/Transactions/TransactionSyncer.swift @@ -0,0 +1,51 @@ +import Foundation + +public class TransactionSyncer { + private let storage: IStorage + private let processor: IPendingTransactionProcessor + private let invalidator: TransactionInvalidator + private let publicKeyManager: IPublicKeyManager + + init(storage: IStorage, processor: IPendingTransactionProcessor, invalidator: TransactionInvalidator, publicKeyManager: IPublicKeyManager) { + self.storage = storage + self.processor = processor + self.invalidator = invalidator + self.publicKeyManager = publicKeyManager + } + +} + +extension TransactionSyncer: ITransactionSyncer { + + public func newTransactions() -> [FullTransaction] { + storage.newTransactions() + } + + public func handleRelayed(transactions: [FullTransaction]) { + guard !transactions.isEmpty else { + return + } + + var needToUpdateBloomFilter = false + + do { + try self.processor.processReceived(transactions: transactions, skipCheckBloomFilter: false) + } catch _ as BloomFilterManager.BloomFilterExpired { + needToUpdateBloomFilter = true + } catch { + } + + if needToUpdateBloomFilter { + try? publicKeyManager.fillGap() + } + } + + public func handleInvalid(fullTransaction: FullTransaction) { + invalidator.invalidate(transaction: fullTransaction.header) + } + + public func shouldRequestTransaction(hash: Data) -> Bool { + !storage.relayedTransactionExists(byHash: hash) + } + +} diff --git a/BitcoinKit.swift.podspec b/BitcoinKit.swift.podspec index 03410101..21a666fa 100644 --- a/BitcoinKit.swift.podspec +++ b/BitcoinKit.swift.podspec @@ -1,29 +1,35 @@ -Pod::Spec.new do |spec| - spec.name = 'BitcoinKit.swift' - spec.module_name = 'BitcoinKit' - spec.version = '0.6' - spec.summary = 'Bitcoin library for Swift' - spec.description = <<-DESC - BitcoinKit implements Bitcoin protocol in Swift. - ``` - DESC - spec.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' - spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' } - spec.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } - spec.social_media_url = 'http://horizontalsystems.io/' +Pod::Spec.new do |s| + s.name = 'BitcoinKit.swift' + s.module_name = 'BitcoinKit' + s.version = '0.18' + s.summary = 'Bitcoin library for Swift.' - spec.requires_arc = true - spec.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "#{spec.version}" } - spec.source_files = 'BitcoinKit/BitcoinKit/**/*.{h,m,swift}' - spec.ios.deployment_target = '11.0' - spec.swift_version = '5' + s.description = <<-DESC +BitcoinKit implements Bitcoin protocol in Swift. + DESC - spec.dependency 'BitcoinCore.swift', '~> 0.6' - spec.dependency 'HSCryptoKit', '~> 1.4' - spec.dependency 'HSHDWalletKit', '~> 1.1' - spec.dependency 'Alamofire', '~> 4.0' - spec.dependency 'ObjectMapper', '~> 3.0' - spec.dependency 'RxSwift', '~> 5.0' - spec.dependency 'BigInt', '~> 4.0' - spec.dependency 'GRDB.swift', '~> 4.0' + s.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } + s.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "bitcoin-#{s.version}" } + s.social_media_url = 'http://horizontalsystems.io/' + + s.ios.deployment_target = '13.0' + s.swift_version = '5' + + s.source_files = 'BitcoinKit/Classes/**/*' + s.resource_bundle = { 'BitcoinKit' => 'BitcoinKit/Assets/Checkpoints/*' } + + s.requires_arc = true + + s.dependency 'BitcoinCore.swift', '~> 0.18' + s.dependency 'Hodler.swift', '~> 0.18' + s.dependency 'OpenSslKit.swift', '~> 1.0' + s.dependency 'Secp256k1Kit.swift', '~> 1.0' + s.dependency 'HdWalletKit.swift', '~> 1.5' + + s.dependency 'ObjectMapper', '~> 4.0' + s.dependency 'RxSwift', '~> 5.0' + s.dependency 'BigInt', '~> 5.0' + s.dependency 'GRDB.swift', '~> 5.0' end diff --git a/BitcoinKit.xcworkspace/contents.xcworkspacedata b/BitcoinKit.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 0737a152..00000000 --- a/BitcoinKit.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/BitcoinKit/Assets/Checkpoints/MainNet-bip44.checkpoint b/BitcoinKit/Assets/Checkpoints/MainNet-bip44.checkpoint new file mode 100644 index 00000000..0db7fab6 --- /dev/null +++ b/BitcoinKit/Assets/Checkpoints/MainNet-bip44.checkpoint @@ -0,0 +1 @@ +02000000ba3f2b4208ec0495b2e3743465cae2b44d8f1c778b44cf6b0000000000000000d287e52e8045c060c1cee47d1cc7559c7b8ab8db580539fb55fc579a998ea14efe0e50538c9d001926c0c180a08504003f72e59e0db5b38e5210369dc2fb4831ab1e81f3b5dbec3d0000000000000000 diff --git a/BitcoinKit/Assets/Checkpoints/MainNet-last.checkpoint b/BitcoinKit/Assets/Checkpoints/MainNet-last.checkpoint new file mode 100644 index 00000000..d9eb9ffa --- /dev/null +++ b/BitcoinKit/Assets/Checkpoints/MainNet-last.checkpoint @@ -0,0 +1 @@ +0080a826d29528a6f4bf6904a9e7c008714375e724bde284478a020000000000000000002ee7546e3e7a53eed051bdbbf23933f688976f660c8b6208d1569ac6ab4cd7a702da206394c808175dd6d69040810b006b1e660da0a957c31695d8f4edcb7f17b39c0a6ddca905000000000000000000 \ No newline at end of file diff --git a/BitcoinKit/Assets/Checkpoints/TestNet-bip44.checkpoint b/BitcoinKit/Assets/Checkpoints/TestNet-bip44.checkpoint new file mode 100644 index 00000000..eda653e7 --- /dev/null +++ b/BitcoinKit/Assets/Checkpoints/TestNet-bip44.checkpoint @@ -0,0 +1 @@ +0200000097f2b61897ba2bed756cca30058bcc1c2dfbb4ed0e962f47f749dc03000000006b80079a1eda8071424e294fa56849370e331c8ff7e95034576c9789c8db0fa6da551153ab80011c9bdaca25a00b03009a259d2c34148908a4e71853349f033fd7ac5cbc29bd833adebb000000000000 diff --git a/BitcoinKit/Assets/Checkpoints/TestNet-last.checkpoint b/BitcoinKit/Assets/Checkpoints/TestNet-last.checkpoint new file mode 100644 index 00000000..5a1f5847 --- /dev/null +++ b/BitcoinKit/Assets/Checkpoints/TestNet-last.checkpoint @@ -0,0 +1 @@ +00008020ad1e9a485afba8ac359523ab81edeb10e20190bf4e0a0d152700000000000000824add0d224563c9ab984439000ba0e918d9aab373932d196ce01cd03075afd2eb8d2363050a38195db8af0c80ce23002db4cf8d22dc399d8385b1c4d73e22ca86ed516e57b66dc91000000000000000 \ No newline at end of file diff --git a/BitcoinKit/BitcoinKit.xcodeproj/project.pbxproj b/BitcoinKit/BitcoinKit.xcodeproj/project.pbxproj deleted file mode 100644 index 4e73f42f..00000000 --- a/BitcoinKit/BitcoinKit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,671 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 04150B364C51651E10E22534 /* Pods_BitcoinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8070C4F00BDE4AE3C085080B /* Pods_BitcoinKit.framework */; }; - 3A7A7D1F226842B10063D6AD /* BitcoinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7A7D15226842B00063D6AD /* BitcoinKit.framework */; }; - 3A7A7D26226842B10063D6AD /* BitcoinKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A7A7D18226842B00063D6AD /* BitcoinKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3A7A7D79226842FE0063D6AD /* BitcoinCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7A7D78226842FE0063D6AD /* BitcoinCore.framework */; }; - 58AAA0CFE31FD40385CDF506 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFAAC6AABE2400485238 /* Protocols.swift */; }; - 58AAA117188974008B5F563E /* BitcoinKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5CDCF8B52BC912CC0E2 /* BitcoinKit.swift */; }; - 58AAA3098471BB324159712D /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA8AA13E5099C73B8E79C /* GeneratedMocks.swift */; }; - 58AAA323FA5EDE02F99E25AE /* SegWitAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA71716FABEBBE3FFAB45 /* SegWitAddress.swift */; }; - 58AAA49B8012402356C135CE /* TestNet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6A33CBF3D1A0782C365 /* TestNet.swift */; }; - 58AAA4F949AA4261158F54B1 /* SegWitBech32AddressConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5A526DD240851A5115F /* SegWitBech32AddressConverterTests.swift */; }; - 58AAA57AF8182EA3F11C0FD9 /* SegWitBech32KeyHashConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAABD4F082706A8D771796 /* SegWitBech32KeyHashConverter.swift */; }; - 58AAA63A4D5CB80B7D260F71 /* MainNet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5DC79D60B7733E52BEC /* MainNet.swift */; }; - 58AAA64BD96CE9A79484D42E /* SegWitBech32AddressConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA79DC50FFF8761BAFD89 /* SegWitBech32AddressConverter.swift */; }; - 58AAA663ECDC727231CA8863 /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA97073BF8F4DCA68CBE1 /* Bech32.swift */; }; - 58AAA68E9670087996AA628F /* SegWitScriptBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA628741DC6017350532 /* SegWitScriptBuilderTests.swift */; }; - 58AAA8268F45036A886ECECF /* BitcoinCoreCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACDBF9DF3DCB75EE0BA6 /* BitcoinCoreCompatibility.swift */; }; - 58AAA9FF45FDE9548830D54F /* RegTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACD1AA455F909A7E4378 /* RegTest.swift */; }; - 58AAAAAAA18E7D96FE94AADE /* BitcoinKitErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3E253113621E4F2F704 /* BitcoinKitErrors.swift */; }; - 58AAAC2EE10933D65DA4B027 /* SegWitScriptBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACD3F4604233235F3FDD /* SegWitScriptBuilder.swift */; }; - 58AAACDD4F0F73E5CD9E82A4 /* SegWitBech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC5F67BD7DA7392C6D82 /* SegWitBech32.swift */; }; - 87CF6AFD0A939977303D4F38 /* Pods_BitcoinKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37709BCA1C4277BCD57A6208 /* Pods_BitcoinKitTests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 3A7A7D20226842B10063D6AD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 3A7A7D0C226842B00063D6AD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3A7A7D14226842B00063D6AD; - remoteInfo = BitcoinKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 0DAA1DDC94E942CFE5A74E41 /* Pods-BitcoinKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKit.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinKit/Pods-BitcoinKit.debug.xcconfig"; sourceTree = ""; }; - 18DFF340E08ECC5053A9C402 /* Pods-BitcoinKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKitTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests.release.xcconfig"; sourceTree = ""; }; - 37709BCA1C4277BCD57A6208 /* Pods_BitcoinKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitcoinKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D15226842B00063D6AD /* BitcoinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BitcoinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D18226842B00063D6AD /* BitcoinKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BitcoinKit.h; sourceTree = ""; }; - 3A7A7D19226842B00063D6AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7A7D1E226842B10063D6AD /* BitcoinKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitcoinKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D25226842B10063D6AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7A7D78226842FE0063D6AD /* BitcoinCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 58AAA3E253113621E4F2F704 /* BitcoinKitErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinKitErrors.swift; sourceTree = ""; }; - 58AAA5A526DD240851A5115F /* SegWitBech32AddressConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitBech32AddressConverterTests.swift; sourceTree = ""; }; - 58AAA5CDCF8B52BC912CC0E2 /* BitcoinKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinKit.swift; sourceTree = ""; }; - 58AAA5DC79D60B7733E52BEC /* MainNet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainNet.swift; sourceTree = ""; }; - 58AAA6A33CBF3D1A0782C365 /* TestNet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNet.swift; sourceTree = ""; }; - 58AAA71716FABEBBE3FFAB45 /* SegWitAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitAddress.swift; sourceTree = ""; }; - 58AAA79DC50FFF8761BAFD89 /* SegWitBech32AddressConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitBech32AddressConverter.swift; sourceTree = ""; }; - 58AAA8AA13E5099C73B8E79C /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; - 58AAA97073BF8F4DCA68CBE1 /* Bech32.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = ""; }; - 58AAAA628741DC6017350532 /* SegWitScriptBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitScriptBuilderTests.swift; sourceTree = ""; }; - 58AAABD4F082706A8D771796 /* SegWitBech32KeyHashConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitBech32KeyHashConverter.swift; sourceTree = ""; }; - 58AAAC5F67BD7DA7392C6D82 /* SegWitBech32.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitBech32.swift; sourceTree = ""; }; - 58AAACD1AA455F909A7E4378 /* RegTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegTest.swift; sourceTree = ""; }; - 58AAACD3F4604233235F3FDD /* SegWitScriptBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitScriptBuilder.swift; sourceTree = ""; }; - 58AAACDBF9DF3DCB75EE0BA6 /* BitcoinCoreCompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCoreCompatibility.swift; sourceTree = ""; }; - 58AAAFAAC6AABE2400485238 /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; - 66A9A33F4AB6913B8F8FEDF1 /* Pods-BitcoinKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKitTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests.debug.xcconfig"; sourceTree = ""; }; - 8070C4F00BDE4AE3C085080B /* Pods_BitcoinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitcoinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - AA30715685070B6D052BA4C7 /* Pods-BitcoinKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKit.release.xcconfig"; path = "../Pods/Target Support Files/Pods-BitcoinKit/Pods-BitcoinKit.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 3A7A7D12226842B00063D6AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D79226842FE0063D6AD /* BitcoinCore.framework in Frameworks */, - 04150B364C51651E10E22534 /* Pods_BitcoinKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D1B226842B10063D6AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D1F226842B10063D6AD /* BitcoinKit.framework in Frameworks */, - 87CF6AFD0A939977303D4F38 /* Pods_BitcoinKitTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3A7A7D0B226842B00063D6AD = { - isa = PBXGroup; - children = ( - 3A7A7D17226842B00063D6AD /* BitcoinKit */, - 3A7A7D22226842B10063D6AD /* BitcoinKitTests */, - 3A7A7D16226842B00063D6AD /* Products */, - 3A7A7D77226842FE0063D6AD /* Frameworks */, - 570B4C36C213F6DD92F764A2 /* Pods */, - ); - sourceTree = ""; - }; - 3A7A7D16226842B00063D6AD /* Products */ = { - isa = PBXGroup; - children = ( - 3A7A7D15226842B00063D6AD /* BitcoinKit.framework */, - 3A7A7D1E226842B10063D6AD /* BitcoinKitTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 3A7A7D17226842B00063D6AD /* BitcoinKit */ = { - isa = PBXGroup; - children = ( - 58AAAE0C7049B36BF673EDC6 /* Core */, - 58AAA74DE2AA0162A3856513 /* Network */, - 58AAAC302AE9A97C4EE20005 /* SegWit */, - 3A7A7D18226842B00063D6AD /* BitcoinKit.h */, - 3A7A7D19226842B00063D6AD /* Info.plist */, - ); - path = BitcoinKit; - sourceTree = ""; - }; - 3A7A7D22226842B10063D6AD /* BitcoinKitTests */ = { - isa = PBXGroup; - children = ( - 58AAA1DDEE3D8654083DF2B6 /* SegWit */, - 3A7A7D25226842B10063D6AD /* Info.plist */, - 58AAA8AA13E5099C73B8E79C /* GeneratedMocks.swift */, - ); - path = BitcoinKitTests; - sourceTree = ""; - }; - 3A7A7D77226842FE0063D6AD /* Frameworks */ = { - isa = PBXGroup; - children = ( - 3A7A7D78226842FE0063D6AD /* BitcoinCore.framework */, - 8070C4F00BDE4AE3C085080B /* Pods_BitcoinKit.framework */, - 37709BCA1C4277BCD57A6208 /* Pods_BitcoinKitTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 570B4C36C213F6DD92F764A2 /* Pods */ = { - isa = PBXGroup; - children = ( - 0DAA1DDC94E942CFE5A74E41 /* Pods-BitcoinKit.debug.xcconfig */, - AA30715685070B6D052BA4C7 /* Pods-BitcoinKit.release.xcconfig */, - 66A9A33F4AB6913B8F8FEDF1 /* Pods-BitcoinKitTests.debug.xcconfig */, - 18DFF340E08ECC5053A9C402 /* Pods-BitcoinKitTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - 58AAA1DDEE3D8654083DF2B6 /* SegWit */ = { - isa = PBXGroup; - children = ( - 58AAA5A526DD240851A5115F /* SegWitBech32AddressConverterTests.swift */, - 58AAAA628741DC6017350532 /* SegWitScriptBuilderTests.swift */, - ); - path = SegWit; - sourceTree = ""; - }; - 58AAA74DE2AA0162A3856513 /* Network */ = { - isa = PBXGroup; - children = ( - 58AAA5DC79D60B7733E52BEC /* MainNet.swift */, - 58AAA6A33CBF3D1A0782C365 /* TestNet.swift */, - 58AAACD1AA455F909A7E4378 /* RegTest.swift */, - ); - path = Network; - sourceTree = ""; - }; - 58AAAC302AE9A97C4EE20005 /* SegWit */ = { - isa = PBXGroup; - children = ( - 58AAAC5F67BD7DA7392C6D82 /* SegWitBech32.swift */, - 58AAA79DC50FFF8761BAFD89 /* SegWitBech32AddressConverter.swift */, - 58AAA97073BF8F4DCA68CBE1 /* Bech32.swift */, - 58AAA71716FABEBBE3FFAB45 /* SegWitAddress.swift */, - 58AAACD3F4604233235F3FDD /* SegWitScriptBuilder.swift */, - 58AAABD4F082706A8D771796 /* SegWitBech32KeyHashConverter.swift */, - ); - path = SegWit; - sourceTree = ""; - }; - 58AAAE0C7049B36BF673EDC6 /* Core */ = { - isa = PBXGroup; - children = ( - 58AAA5CDCF8B52BC912CC0E2 /* BitcoinKit.swift */, - 58AAACDBF9DF3DCB75EE0BA6 /* BitcoinCoreCompatibility.swift */, - 58AAAFAAC6AABE2400485238 /* Protocols.swift */, - 58AAA3E253113621E4F2F704 /* BitcoinKitErrors.swift */, - ); - path = Core; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 3A7A7D10226842B00063D6AD /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D26226842B10063D6AD /* BitcoinKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 3A7A7D14226842B00063D6AD /* BitcoinKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7A7D29226842B10063D6AD /* Build configuration list for PBXNativeTarget "BitcoinKit" */; - buildPhases = ( - F95824F282940DB758A3B1B8 /* [CP] Check Pods Manifest.lock */, - 3A7A7D10226842B00063D6AD /* Headers */, - 3A7A7D11226842B00063D6AD /* Sources */, - 3A7A7D12226842B00063D6AD /* Frameworks */, - 3A7A7D13226842B00063D6AD /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = BitcoinKit; - productName = BitcoinKit; - productReference = 3A7A7D15226842B00063D6AD /* BitcoinKit.framework */; - productType = "com.apple.product-type.framework"; - }; - 3A7A7D1D226842B10063D6AD /* BitcoinKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7A7D2C226842B10063D6AD /* Build configuration list for PBXNativeTarget "BitcoinKitTests" */; - buildPhases = ( - 567C9F8044A6D7B7512F860C /* [CP] Check Pods Manifest.lock */, - 3A5B1465226D7D4C00DF70E2 /* Cuckoo */, - 3A7A7D1A226842B10063D6AD /* Sources */, - 3A7A7D1B226842B10063D6AD /* Frameworks */, - 3A7A7D1C226842B10063D6AD /* Resources */, - DFE98FA01228E4B754A121E7 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 3A7A7D21226842B10063D6AD /* PBXTargetDependency */, - ); - name = BitcoinKitTests; - productName = BitcoinKitTests; - productReference = 3A7A7D1E226842B10063D6AD /* BitcoinKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 3A7A7D0C226842B00063D6AD /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1010; - LastUpgradeCheck = 1010; - ORGANIZATIONNAME = "Horizontal Systems"; - TargetAttributes = { - 3A7A7D14226842B00063D6AD = { - CreatedOnToolsVersion = 10.1; - LastSwiftMigration = 1020; - }; - 3A7A7D1D226842B10063D6AD = { - CreatedOnToolsVersion = 10.1; - }; - }; - }; - buildConfigurationList = 3A7A7D0F226842B00063D6AD /* Build configuration list for PBXProject "BitcoinKit" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 3A7A7D0B226842B00063D6AD; - productRefGroup = 3A7A7D16226842B00063D6AD /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 3A7A7D14226842B00063D6AD /* BitcoinKit */, - 3A7A7D1D226842B10063D6AD /* BitcoinKitTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 3A7A7D13226842B00063D6AD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D1C226842B10063D6AD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3A5B1465226D7D4C00DF70E2 /* Cuckoo */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/${PROJECT_NAME}/Core/Protocols.swift", - ); - name = Cuckoo; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Define output file. Change \"$PROJECT_DIR/${PROJECT_NAME}Tests\" to your test's root source folder, if it's not the default name.\nOUTPUT_FILE=\"$PROJECT_DIR/${PROJECT_NAME}Tests/GeneratedMocks.swift\"\necho \"Generated Mocks File = $OUTPUT_FILE\"\n\n# Define input directory. Change \"${PROJECT_DIR}/${PROJECT_NAME}\" to your project's root source folder, if it's not the default name.\nINPUT_DIR=\"${PROJECT_DIR}/${PROJECT_NAME}\"\necho \"Mocks Input Directory = $INPUT_DIR\"\n\n# Generate mock files, include as many input files as you'd like to create mocks for.\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"$PROJECT_NAME\" \\\n--output \"${OUTPUT_FILE}\"\n"; - }; - 567C9F8044A6D7B7512F860C /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BitcoinKitTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - DFE98FA01228E4B754A121E7 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - F95824F282940DB758A3B1B8 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-BitcoinKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 3A7A7D11226842B00063D6AD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 58AAA117188974008B5F563E /* BitcoinKit.swift in Sources */, - 58AAA63A4D5CB80B7D260F71 /* MainNet.swift in Sources */, - 58AAA49B8012402356C135CE /* TestNet.swift in Sources */, - 58AAA9FF45FDE9548830D54F /* RegTest.swift in Sources */, - 58AAA64BD96CE9A79484D42E /* SegWitBech32AddressConverter.swift in Sources */, - 58AAACDD4F0F73E5CD9E82A4 /* SegWitBech32.swift in Sources */, - 58AAA8268F45036A886ECECF /* BitcoinCoreCompatibility.swift in Sources */, - 58AAA0CFE31FD40385CDF506 /* Protocols.swift in Sources */, - 58AAA663ECDC727231CA8863 /* Bech32.swift in Sources */, - 58AAA323FA5EDE02F99E25AE /* SegWitAddress.swift in Sources */, - 58AAAC2EE10933D65DA4B027 /* SegWitScriptBuilder.swift in Sources */, - 58AAAAAAA18E7D96FE94AADE /* BitcoinKitErrors.swift in Sources */, - 58AAA57AF8182EA3F11C0FD9 /* SegWitBech32KeyHashConverter.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D1A226842B10063D6AD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 58AAA4F949AA4261158F54B1 /* SegWitBech32AddressConverterTests.swift in Sources */, - 58AAA3098471BB324159712D /* GeneratedMocks.swift in Sources */, - 58AAA68E9670087996AA628F /* SegWitScriptBuilderTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 3A7A7D21226842B10063D6AD /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3A7A7D14226842B00063D6AD /* BitcoinKit */; - targetProxy = 3A7A7D20226842B10063D6AD /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 3A7A7D27226842B10063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 3A7A7D28226842B10063D6AD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 3A7A7D2A226842B10063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 0DAA1DDC94E942CFE5A74E41 /* Pods-BitcoinKit.debug.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = HC4MCAXJ66; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = BitcoinKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3A7A7D2B226842B10063D6AD /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AA30715685070B6D052BA4C7 /* Pods-BitcoinKit.release.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = HC4MCAXJ66; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = BitcoinKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 3A7A7D2D226842B10063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 66A9A33F4AB6913B8F8FEDF1 /* Pods-BitcoinKitTests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HC4MCAXJ66; - INFOPLIST_FILE = BitcoinKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3A7A7D2E226842B10063D6AD /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 18DFF340E08ECC5053A9C402 /* Pods-BitcoinKitTests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HC4MCAXJ66; - INFOPLIST_FILE = BitcoinKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.BitcoinKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 3A7A7D0F226842B00063D6AD /* Build configuration list for PBXProject "BitcoinKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D27226842B10063D6AD /* Debug */, - 3A7A7D28226842B10063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3A7A7D29226842B10063D6AD /* Build configuration list for PBXNativeTarget "BitcoinKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D2A226842B10063D6AD /* Debug */, - 3A7A7D2B226842B10063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3A7A7D2C226842B10063D6AD /* Build configuration list for PBXNativeTarget "BitcoinKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D2D226842B10063D6AD /* Debug */, - 3A7A7D2E226842B10063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 3A7A7D0C226842B00063D6AD /* Project object */; -} diff --git a/BitcoinKit/BitcoinKit/BitcoinKit.h b/BitcoinKit/BitcoinKit/BitcoinKit.h deleted file mode 100644 index eec57554..00000000 --- a/BitcoinKit/BitcoinKit/BitcoinKit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// BitcoinKit.h -// BitcoinKit -// -// Created by Anton Stavnichiy on 4/18/19. -// Copyright © 2019 Horizontal Systems. All rights reserved. -// - -#import - -//! Project version number for BitcoinKit. -FOUNDATION_EXPORT double BitcoinKitVersionNumber; - -//! Project version string for BitcoinKit. -FOUNDATION_EXPORT const unsigned char BitcoinKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/BitcoinKit/BitcoinKit/Core/BitcoinCoreCompatibility.swift b/BitcoinKit/BitcoinKit/Core/BitcoinCoreCompatibility.swift deleted file mode 100644 index 94372245..00000000 --- a/BitcoinKit/BitcoinKit/Core/BitcoinCoreCompatibility.swift +++ /dev/null @@ -1,3 +0,0 @@ -import BitcoinCore - -extension ScriptConverter: IBitcoinScriptConverter {} diff --git a/BitcoinKit/BitcoinKit/Core/BitcoinKit.swift b/BitcoinKit/BitcoinKit/Core/BitcoinKit.swift deleted file mode 100644 index 769f349a..00000000 --- a/BitcoinKit/BitcoinKit/Core/BitcoinKit.swift +++ /dev/null @@ -1,97 +0,0 @@ -import BitcoinCore -import HSHDWalletKit -import BigInt -import HSCryptoKit -import RxSwift - -public class BitcoinKit: AbstractKit { - public static func clear() throws { - try DirectoryHelper.removeDirectory("BitcoinKit") - } - - public enum NetworkType { case mainNet, testNet, regTest } - - private let storage: IStorage - private let bech32AddressConverter: IAddressConverter - - public weak var delegate: BitcoinCoreDelegate? { - didSet { - bitcoinCore.delegate = delegate - } - } - - public init(withWords words: [String], walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet, confirmationsThreshold: Int = 6, minLogLevel: Logger.Level = .verbose) throws { - let network: INetwork - let initialSyncApiUrl: String - - switch networkType { - case .mainNet: - network = MainNet() - initialSyncApiUrl = "https://btc.horizontalsystems.xyz/apg" - case .testNet: - network = TestNet() - initialSyncApiUrl = "http://btc-testnet.horizontalsystems.xyz/apg" - case .regTest: - network = RegTest() - initialSyncApiUrl = "" - } - let initialSyncApi = BCoinApi(url: initialSyncApiUrl) - - let databaseFilePath = try DirectoryHelper.directoryURL(for: "BitcoinKit").appendingPathComponent("\(walletId)-\(networkType)").path - let storage = GrdbStorage(databaseFilePath: databaseFilePath) - self.storage = storage - - let paymentAddressParser = PaymentAddressParser(validScheme: "bitcoin", removeScheme: true) - let addressSelector = BitcoinAddressSelector() - let addressKeyHashConverter = SegWitBech32KeyHashConverter() - - let bitcoinCore = try BitcoinCoreBuilder(minLogLevel: minLogLevel) - .set(network: network) - .set(initialSyncApi: initialSyncApi) - .set(words: words) - .set(paymentAddressParser: paymentAddressParser) - .set(addressSelector: addressSelector) - .set(addressKeyHashConverter: addressKeyHashConverter) - .set(walletId: walletId) - .set(confirmationsThreshold: confirmationsThreshold) - .set(peerSize: 10) - .set(syncMode: syncMode) - .set(storage: storage) - .build() - - let scriptConverter = ScriptConverter() - bech32AddressConverter = SegWitBech32AddressConverter(prefix: network.bech32PrefixPattern, scriptConverter: scriptConverter) - - super.init(bitcoinCore: bitcoinCore, network: network) - - // extending BitcoinCore - - bitcoinCore.prepend(scriptBuilder: SegWitScriptBuilder()) - bitcoinCore.prepend(addressConverter: bech32AddressConverter) - - let blockHelper = BlockValidatorHelper(storage: storage) - let difficultyEncoder = DifficultyEncoder() - - switch networkType { - case .mainNet: - bitcoinCore.add(blockValidator: LegacyDifficultyAdjustmentValidator(encoder: difficultyEncoder, blockValidatorHelper: blockHelper, heightInterval: BitcoinCore.heightInterval, targetTimespan: BitcoinCore.heightInterval * BitcoinCore.targetSpacing, maxTargetBits: BitcoinCore.maxTargetBits)) - bitcoinCore.add(blockValidator: BitsValidator()) - case .regTest, .testNet: - bitcoinCore.add(blockValidator: LegacyDifficultyAdjustmentValidator(encoder: difficultyEncoder, blockValidatorHelper: blockHelper, heightInterval: BitcoinCore.heightInterval, targetTimespan: BitcoinCore.heightInterval * BitcoinCore.targetSpacing, maxTargetBits: BitcoinCore.maxTargetBits)) - bitcoinCore.add(blockValidator: LegacyTestNetDifficultyValidator(blockHelper: blockHelper, heightInterval: BitcoinCore.heightInterval, targetSpacing: BitcoinCore.targetSpacing, maxTargetBits: BitcoinCore.maxTargetBits)) - } - } - - override open var debugInfo: String { - var lines = [String](arrayLiteral: bitcoinCore.debugInfo) - let pubKeys = storage.publicKeys().sorted(by: { $0.index < $1.index }) - - lines.append("--------------- Bitcoin Segwit (zero program) addresses --------------------") - for pubKey in pubKeys { - lines.append("acc: \(pubKey.account) - inx: \(pubKey.index) - ext: \(pubKey.external) : \(try! bech32AddressConverter.convert(keyHash: Data([0x00, 0x14]) + pubKey.keyHash, type: .p2wpkh).stringValue)") - } - - return lines.joined(separator: "\n") - } - -} diff --git a/BitcoinKit/BitcoinKit/Core/BitcoinKitErrors.swift b/BitcoinKit/BitcoinKit/Core/BitcoinKitErrors.swift deleted file mode 100644 index 74d61230..00000000 --- a/BitcoinKit/BitcoinKit/Core/BitcoinKitErrors.swift +++ /dev/null @@ -1,8 +0,0 @@ -public class BitcoinKitErrors { - - public enum AddressConversion: Error { - case noSegWitAddress - case noSegWitType - } - -} diff --git a/BitcoinKit/BitcoinKit/Core/Protocols.swift b/BitcoinKit/BitcoinKit/Core/Protocols.swift deleted file mode 100644 index 6624fa9e..00000000 --- a/BitcoinKit/BitcoinKit/Core/Protocols.swift +++ /dev/null @@ -1,7 +0,0 @@ -import BitcoinCore - -// BitcoinCore Compatibility - -protocol IBitcoinScriptConverter { - func decode(data: Data) throws -> Script -} \ No newline at end of file diff --git a/BitcoinKit/BitcoinKit/Info.plist b/BitcoinKit/BitcoinKit/Info.plist deleted file mode 100644 index e1fe4cfb..00000000 --- a/BitcoinKit/BitcoinKit/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/BitcoinKit/BitcoinKit/Network/MainNet.swift b/BitcoinKit/BitcoinKit/Network/MainNet.swift deleted file mode 100644 index 6fab7053..00000000 --- a/BitcoinKit/BitcoinKit/Network/MainNet.swift +++ /dev/null @@ -1,55 +0,0 @@ -import BitcoinCore - -class MainNet: INetwork { - - let name = "bitcoin-main-net" - let pubKeyHash: UInt8 = 0x00 - let privateKey: UInt8 = 0x80 - let scriptHash: UInt8 = 0x05 - let bech32PrefixPattern: String = "bc" - let xPubKey: UInt32 = 0x0488b21e - let xPrivKey: UInt32 = 0x0488ade4 - let magic: UInt32 = 0xf9beb4d9 - let port: UInt32 = 8333 - let coinType: UInt32 = 0 - let sigHash: SigHashType = .bitcoinAll - var syncableFromApi: Bool = true - - let dnsSeeds = [ - "seed.bitcoin.sipa.be", // Pieter Wuille - "dnsseed.bluematt.me", // Matt Corallo - "dnsseed.bitcoin.dashjr.org", // Luke Dashjr - "seed.bitcoinstats.com", // Chris Decker - "seed.bitnodes.io", // Addy Yeow - "seed.bitcoin.jonasschnelli.ch",// Jonas Schnelli - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 2, - headerHash: "00000000000000003decdbb5f3811eab3148fbc29d3610528eb3b50d9ee5723f".reversedData!, - previousBlockHeaderHash: "00000000000000006bcf448b771c8f4db4e2ca653474e3b29504ec08422b3fba".reversedData!, - merkleRoot: "4ea18e999a57fc55fb390558dbb88a7b9c55c71c7de4cec160c045802ee587d2".reversedData!, - timestamp: 1397755646, - bits: 419470732, - nonce: 2160181286 - ), - height: 296352) - } - - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 0x20000000, - headerHash: "00000000000000000001791f463d849ce5363d751c91f7d3cd2ff18981ae221d".reversedData!, - previousBlockHeaderHash: "0000000000000000000485ab94f5ea60203aacfc9740b3e42700d7e7012f76d7".reversedData!, - merkleRoot: "2e76c50d3dcecc46264b7ff8e653d5c9f06680f4d88f5b239d58a531a3c12279".reversedData!, - timestamp: 1559277784, - bits: 0x1725bb76, - nonce: 0x423310ae - ), - height: 578592) - } - -} diff --git a/BitcoinKit/BitcoinKit/Network/RegTest.swift b/BitcoinKit/BitcoinKit/Network/RegTest.swift deleted file mode 100644 index 0ebd8657..00000000 --- a/BitcoinKit/BitcoinKit/Network/RegTest.swift +++ /dev/null @@ -1,53 +0,0 @@ -import BitcoinCore - -class RegTest: INetwork { - - let name = "bitcoin-reg-test" - let pubKeyHash: UInt8 = 0x6f - let privateKey: UInt8 = 0xef - let scriptHash: UInt8 = 0xc4 - let bech32PrefixPattern: String = "bcrt" - let xPubKey: UInt32 = 0x043587cf - let xPrivKey: UInt32 = 0x04358394 - let magic: UInt32 = 0xfabfb5da - let port: UInt32 = 18444 - let coinType: UInt32 = 1 - let sigHash: SigHashType = .bitcoinAll - var syncableFromApi: Bool = false - - let dnsSeeds = [ - "btc-regtest.horizontalsystems.xyz", - "btc01-regtest.horizontalsystems.xyz", - "btc02-regtest.horizontalsystems.xyz", - "btc03-regtest.horizontalsystems.xyz", - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 1, - headerHash: Data(repeating: 0, count: 32), - previousBlockHeaderHash: Data(repeating: 0, count: 32), - merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".reversedData!, - timestamp: 1296688602, - bits: 545259519, - nonce: 2 - ), - height: 0) - } - - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 1, - headerHash: Data(repeating: 0, count: 32), - previousBlockHeaderHash: Data(repeating: 0, count: 32), - merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".reversedData!, - timestamp: 1296688602, - bits: 545259519, - nonce: 2 - ), - height: 0) - } - -} diff --git a/BitcoinKit/BitcoinKit/Network/TestNet.swift b/BitcoinKit/BitcoinKit/Network/TestNet.swift deleted file mode 100644 index ce6b7585..00000000 --- a/BitcoinKit/BitcoinKit/Network/TestNet.swift +++ /dev/null @@ -1,55 +0,0 @@ -import BitcoinCore - -class TestNet: INetwork { - private static let testNetDiffDate = 1329264000 // February 16th 2012 - - let name = "bitcoin-test-net" - let pubKeyHash: UInt8 = 0x6f - let privateKey: UInt8 = 0xef - let scriptHash: UInt8 = 0xc4 - let bech32PrefixPattern: String = "tb" - let xPubKey: UInt32 = 0x043587cf - let xPrivKey: UInt32 = 0x04358394 - let magic: UInt32 = 0x0b110907 - let port: UInt32 = 18333 - let coinType: UInt32 = 1 - let sigHash: SigHashType = .bitcoinAll - var syncableFromApi: Bool = true - - let dnsSeeds = [ - "testnet-seed.bitcoin.petertodd.org", // Peter Todd - "testnet-seed.bitcoin.jonasschnelli.ch", // Jonas Schnelli - "testnet-seed.bluematt.me", // Matt Corallo - "testnet-seed.bitcoin.schildbach.de", // Andreas Schildbach - "bitcoin-testnet.bloqseeds.net", // Bloq - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 2, - headerHash: "000000000000bbde3a83bd29bc5cacd73f039f345318e7a4088914342c9d259a".reversedData!, - previousBlockHeaderHash: "0000000003dc49f7472f960eedb4fb2d1ccc8b0530ca6c75ed2bba9718b6f297".reversedData!, - merkleRoot: "a60fdbc889976c573450e9f78f1c330e374968a54f294e427180da1e9a07806b".reversedData!, - timestamp: 1393645018, - bits: 0x1c0180ab, - nonce: 634051227 - ), - height: 199584) - } - - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 0x20000000, - headerHash: "000000000000011b820755b3bbe03de7f7b8854b9f03307f41dafea4694eee7b".reversedData!, - previousBlockHeaderHash: "00000000000001d6874b4d88e387098c0b7100ff674d99781fc7045a78216a15".reversedData!, - merkleRoot: "d108b1c6229e1bc0c5506307779c6a51b1cb4c8edf3f91bef36dd1a2c30dfc99".reversedData!, - timestamp: 1558613325, - bits: 436289093, - nonce: 2472615319 - ), - height: 1518048) - } - -} diff --git a/BitcoinKit/BitcoinKit/SegWit/SegWitBech32KeyHashConverter.swift b/BitcoinKit/BitcoinKit/SegWit/SegWitBech32KeyHashConverter.swift deleted file mode 100644 index 4c786925..00000000 --- a/BitcoinKit/BitcoinKit/SegWit/SegWitBech32KeyHashConverter.swift +++ /dev/null @@ -1,14 +0,0 @@ -import BitcoinCore -import HSCryptoKit - -class SegWitBech32KeyHashConverter: IAddressKeyHashConverter { - - func convert(keyHash: Data, type: ScriptType) -> Data { - switch type { - case .p2wpkh: return OpCode.scriptWPKH(keyHash) - case .p2wpkhSh:return CryptoKit.sha256ripemd160(OpCode.scriptWPKH(keyHash)) - default: return keyHash - } - } - -} diff --git a/BitcoinKit/BitcoinKit/SegWit/SegWitScriptBuilder.swift b/BitcoinKit/BitcoinKit/SegWit/SegWitScriptBuilder.swift deleted file mode 100644 index 36a17db4..00000000 --- a/BitcoinKit/BitcoinKit/SegWit/SegWitScriptBuilder.swift +++ /dev/null @@ -1,19 +0,0 @@ -import BitcoinCore - -class SegWitScriptBuilder: IScriptBuilder { - - func lockingScript(for address: Address) throws -> Data { - var data = [Data]() - guard let segWitAddress = address as? SegWitAddress else { - throw BitcoinKitErrors.AddressConversion.noSegWitAddress - } - - data.append(address.keyHash) - - switch address.scriptType { - case .p2wsh, .p2wpkh: return OpCode.push(Int(segWitAddress.version)) + OpCode.push(address.keyHash) // Data[0] - version byte, Data[1] - push keyHash - default: throw BitcoinKitErrors.AddressConversion.noSegWitType // todo: SegWitAddress can't differ p2wsh or p2wpkh - } - } - -} diff --git a/BitcoinKit/BitcoinKitTests/SegWit/SegWitScriptBuilderTests.swift b/BitcoinKit/BitcoinKitTests/SegWit/SegWitScriptBuilderTests.swift deleted file mode 100644 index ebad726b..00000000 --- a/BitcoinKit/BitcoinKitTests/SegWit/SegWitScriptBuilderTests.swift +++ /dev/null @@ -1,58 +0,0 @@ -import XCTest -import Cuckoo -@testable import BitcoinKit -@testable import BitcoinCore - -class SegWitScriptBuilderTests: XCTestCase { - - private var builder: SegWitScriptBuilder! - - override func setUp() { - super.setUp() - - builder = SegWitScriptBuilder() - } - - override func tearDown() { - builder = nil - - super.tearDown() - } - - func testP2WPKH() { - let data = Data(hex: "751e76e8199196d454941c45d1b3a323f1433bd6")! - let script = Data(hex: "0014751e76e8199196d454941c45d1b3a323f1433bd6")! - let address = SegWitAddress(type: .pubKeyHash, keyHash: data, bech32: "", version: 0) - do { - let test = try builder.lockingScript(for: address) - XCTAssertEqual(test, script) - } catch { - XCTFail("\(error) Exception Thrown") - } - } - - func testP2WSH() { - let data = Data(hex: "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262")! - let script = Data(hex: "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262")! - let address = SegWitAddress(type: .pubKeyHash, keyHash: data, bech32: "", version: 0) - do { - let test = try builder.lockingScript(for: address) - XCTAssertEqual(test, script) - } catch { - XCTFail("\(error) Exception Thrown") - } - } - - func testNonSegwitAddress() { - let address = LegacyAddress(type: .pubKeyHash, keyHash: Data(), base58: "") - do { - _ = try builder.lockingScript(for: address) - XCTFail("Must throw exception") - } catch let error as BitcoinKitErrors.AddressConversion { - XCTAssertEqual(error, BitcoinKitErrors.AddressConversion.noSegWitAddress) - } catch { - XCTFail("\(error) Wrong Exception Thrown") - } - } - -} diff --git a/BitcoinKit/Classes/Core/Kit.swift b/BitcoinKit/Classes/Core/Kit.swift new file mode 100644 index 00000000..b2191286 --- /dev/null +++ b/BitcoinKit/Classes/Core/Kit.swift @@ -0,0 +1,116 @@ +import BitcoinCore +import HdWalletKit +import Hodler +import BigInt +import RxSwift +import HsToolKit + +public class Kit: AbstractKit { + private static let heightInterval = 2016 // Default block count in difficulty change circle ( Bitcoin ) + private static let targetSpacing = 10 * 60 // Time to mining one block ( 10 min. Bitcoin ) + private static let maxTargetBits = 0x1d00ffff // Initially and max. target difficulty for blocks + + private static let name = "BitcoinKit" + + public enum NetworkType: String, CaseIterable { case mainNet, testNet, regTest } + + public weak var delegate: BitcoinCoreDelegate? { + didSet { + bitcoinCore.delegate = delegate + } + } + + public init(seed: Data, bip: Bip, walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet, confirmationsThreshold: Int = 6, logger: Logger?) throws { + let network: INetwork + let logger = logger ?? Logger(minLogLevel: .verbose) + + let initialSyncApi: ISyncTransactionApi? + switch networkType { + case .mainNet: + network = MainNet() + initialSyncApi = BlockchainComApi(url: "https://blockchain.info", hsUrl: "https://api.blocksdecoded.com/v1/blockchains/bitcoin", logger: logger) + case .testNet: + network = TestNet() + initialSyncApi = BCoinApi(url: "https://btc-testnet.horizontalsystems.xyz/api", logger: logger) + case .regTest: + network = RegTest() + initialSyncApi = nil + } + + let databaseFilePath = try DirectoryHelper.directoryURL(for: Kit.name).appendingPathComponent(Kit.databaseFileName(walletId: walletId, networkType: networkType, bip: bip, syncMode: syncMode)).path + let storage = GrdbStorage(databaseFilePath: databaseFilePath) + + let paymentAddressParser = PaymentAddressParser(validScheme: "bitcoin", removeScheme: true) + let scriptConverter = ScriptConverter() + let bech32AddressConverter = SegWitBech32AddressConverter(prefix: network.bech32PrefixPattern, scriptConverter: scriptConverter) + let base58AddressConverter = Base58AddressConverter(addressVersion: network.pubKeyHash, addressScriptVersion: network.scriptHash) + + let bitcoinCoreBuilder = BitcoinCoreBuilder(logger: logger) + + let difficultyEncoder = DifficultyEncoder() + + let blockValidatorSet = BlockValidatorSet() + blockValidatorSet.add(blockValidator: ProofOfWorkValidator(difficultyEncoder: difficultyEncoder)) + + let blockValidatorChain = BlockValidatorChain() + let blockHelper = BlockValidatorHelper(storage: storage) + + switch networkType { + case .mainNet: + blockValidatorChain.add(blockValidator: LegacyDifficultyAdjustmentValidator(encoder: difficultyEncoder, blockValidatorHelper: blockHelper, heightInterval: Kit.heightInterval, targetTimespan: Kit.heightInterval * Kit.targetSpacing, maxTargetBits: Kit.maxTargetBits)) + blockValidatorChain.add(blockValidator: BitsValidator()) + case .regTest, .testNet: + blockValidatorChain.add(blockValidator: LegacyDifficultyAdjustmentValidator(encoder: difficultyEncoder, blockValidatorHelper: blockHelper, heightInterval: Kit.heightInterval, targetTimespan: Kit.heightInterval * Kit.targetSpacing, maxTargetBits: Kit.maxTargetBits)) + blockValidatorChain.add(blockValidator: LegacyTestNetDifficultyValidator(blockHelper: blockHelper, heightInterval: Kit.heightInterval, targetSpacing: Kit.targetSpacing, maxTargetBits: Kit.maxTargetBits)) + } + + blockValidatorSet.add(blockValidator: blockValidatorChain) + + let hodler = HodlerPlugin(addressConverter: bitcoinCoreBuilder.addressConverter, blockMedianTimeHelper: BlockMedianTimeHelper(storage: storage), publicKeyStorage: storage) + + let bitcoinCore = try bitcoinCoreBuilder + .set(network: network) + .set(initialSyncApi: initialSyncApi) + .set(seed: seed) + .set(bip: bip) + .set(paymentAddressParser: paymentAddressParser) + .set(walletId: walletId) + .set(confirmationsThreshold: confirmationsThreshold) + .set(peerSize: 10) + .set(syncMode: syncMode) + .set(storage: storage) + .set(blockValidator: blockValidatorSet) + .add(plugin: hodler) + .build() + + super.init(bitcoinCore: bitcoinCore, network: network) + + // extending BitcoinCore + + bitcoinCore.prepend(addressConverter: bech32AddressConverter) + + switch bip { + case .bip44: + bitcoinCore.add(restoreKeyConverter: Bip44RestoreKeyConverter(addressConverter: base58AddressConverter)) + bitcoinCore.add(restoreKeyConverter: Bip49RestoreKeyConverter(addressConverter: base58AddressConverter)) + bitcoinCore.add(restoreKeyConverter: Bip84RestoreKeyConverter(addressConverter: bech32AddressConverter)) + case .bip49: + bitcoinCore.add(restoreKeyConverter: Bip49RestoreKeyConverter(addressConverter: base58AddressConverter)) + case .bip84: + bitcoinCore.add(restoreKeyConverter: Bip84RestoreKeyConverter(addressConverter: bech32AddressConverter)) + } + } + +} + +extension Kit { + + public static func clear(exceptFor walletIdsToExclude: [String] = []) throws { + try DirectoryHelper.removeAll(inDirectory: Kit.name, except: walletIdsToExclude) + } + + private static func databaseFileName(walletId: String, networkType: NetworkType, bip: Bip, syncMode: BitcoinCore.SyncMode) -> String { + "\(walletId)-\(networkType.rawValue)-\(bip.description)-\(syncMode)" + } + +} diff --git a/BitcoinKit/Classes/Network/MainNet.swift b/BitcoinKit/Classes/Network/MainNet.swift new file mode 100644 index 00000000..1a0ac896 --- /dev/null +++ b/BitcoinKit/Classes/Network/MainNet.swift @@ -0,0 +1,33 @@ +import BitcoinCore + +public class MainNet: INetwork { + public let bundleName = "BitcoinKit" + + public let pubKeyHash: UInt8 = 0x00 + public let privateKey: UInt8 = 0x80 + public let scriptHash: UInt8 = 0x05 + public let bech32PrefixPattern: String = "bc" + public let xPubKey: UInt32 = 0x0488b21e + public let xPrivKey: UInt32 = 0x0488ade4 + public let magic: UInt32 = 0xf9beb4d9 + public let port = 8333 + public let coinType: UInt32 = 0 + public let sigHash: SigHashType = .bitcoinAll + public var syncableFromApi: Bool = true + + public let dnsSeeds = [ + "x5.seed.bitcoin.sipa.be", // Pieter Wuille + "x5.dnsseed.bluematt.me", // Matt Corallo + "x5.seed.bitcoinstats.com", // Chris Decker + "x5.seed.btc.petertodd.org", // Peter Todd + "x5.seed.bitcoin.sprovoost.nl", // Sjors Provoost + "x5.seed.bitnodes.io", // Addy Yeow + "x5.dnsseed.emzy.de", // Stephan Oeste + "x5.seed.bitcoin.wiz.biz" // Jason Maurice + ] + + public let dustRelayTxFee = 3000 // https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.h#L52 + + public init() {} + +} diff --git a/BitcoinKit/Classes/Network/RegTest.swift b/BitcoinKit/Classes/Network/RegTest.swift new file mode 100644 index 00000000..6320a2e8 --- /dev/null +++ b/BitcoinKit/Classes/Network/RegTest.swift @@ -0,0 +1,26 @@ +import BitcoinCore + +class RegTest: INetwork { + let bundleName = "BitcoinKit" + + let pubKeyHash: UInt8 = 0x6f + let privateKey: UInt8 = 0xef + let scriptHash: UInt8 = 0xc4 + let bech32PrefixPattern: String = "bcrt" + let xPubKey: UInt32 = 0x043587cf + let xPrivKey: UInt32 = 0x04358394 + let magic: UInt32 = 0xfabfb5da + let port = 18444 + let coinType: UInt32 = 1 + let sigHash: SigHashType = .bitcoinAll + var syncableFromApi: Bool = false + + let dnsSeeds = [ + "btc-regtest.horizontalsystems.xyz", + "btc01-regtest.horizontalsystems.xyz", + "btc02-regtest.horizontalsystems.xyz", + "btc03-regtest.horizontalsystems.xyz", + ] + + let dustRelayTxFee = 3000 // https://github.com/bitcoin/bitcoin/blob/c536dfbcb00fb15963bf5d507b7017c241718bf6/src/policy/policy.h#L50 +} diff --git a/BitcoinKit/Classes/Network/TestNet.swift b/BitcoinKit/Classes/Network/TestNet.swift new file mode 100644 index 00000000..5e6e7b29 --- /dev/null +++ b/BitcoinKit/Classes/Network/TestNet.swift @@ -0,0 +1,29 @@ +import BitcoinCore + +class TestNet: INetwork { + private static let testNetDiffDate = 1329264000 // February 16th 2012 + + let bundleName = "BitcoinKit" + + let pubKeyHash: UInt8 = 0x6f + let privateKey: UInt8 = 0xef + let scriptHash: UInt8 = 0xc4 + let bech32PrefixPattern: String = "tb" + let xPubKey: UInt32 = 0x043587cf + let xPrivKey: UInt32 = 0x04358394 + let magic: UInt32 = 0x0b110907 + let port = 18333 + let coinType: UInt32 = 1 + let sigHash: SigHashType = .bitcoinAll + var syncableFromApi: Bool = true + + let dnsSeeds = [ + "testnet-seed.bitcoin.petertodd.org", // Peter Todd + "testnet-seed.bitcoin.jonasschnelli.ch", // Jonas Schnelli + "testnet-seed.bluematt.me", // Matt Corallo + "testnet-seed.bitcoin.schildbach.de", // Andreas Schildbach + "bitcoin-testnet.bloqseeds.net", // Bloq + ] + + let dustRelayTxFee = 3000 // https://github.com/bitcoin/bitcoin/blob/c536dfbcb00fb15963bf5d507b7017c241718bf6/src/policy/policy.h#L50 +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..eb0549a7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Change Log +All notable changes to this project will be documented in this file. + +## Current Version + +## 0.18.0 + +* Add transaction filters [ **non-back-compatible api change** ] + +## 0.17.1 + +* Increase minimum iOS version to 13.0 [ **non-back-compatible api change** ] +* Accept seed data instead of words array when getting instance of all kits [ **non-back-compatible api change** ] + +## 0.16.1 + +## 0.16.0 + +* `BitcoinCashKit` + + * add support for two coin types: `0` and `145` + * `NetworkType` enum now has `coinType` associated value in `main` case [ **non-back-compatible api change** ] + + +* rename core class names of all kits to `Kit` [ **non-back-compatible api change** ] diff --git a/DashKit.swift.podspec b/DashKit.swift.podspec index ab40788d..65a3efed 100644 --- a/DashKit.swift.podspec +++ b/DashKit.swift.podspec @@ -1,31 +1,36 @@ -Pod::Spec.new do |spec| - spec.name = 'DashKit.swift' - spec.module_name = 'DashKit' - spec.version = '0.6' - spec.summary = 'Dash library for Swift' - spec.description = <<-DESC - DashKit implements Dash protocol in Swift. - ``` - DESC - spec.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' - spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' } - spec.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } - spec.social_media_url = 'http://horizontalsystems.io/' +Pod::Spec.new do |s| + s.name = 'DashKit.swift' + s.module_name = 'DashKit' + s.version = '0.18' + s.summary = 'Dash library for Swift.' - spec.requires_arc = true - spec.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "#{spec.version}" } - spec.source_files = 'DashKit/DashKit/**/*.{h,m,mm,swift}' - spec.ios.deployment_target = '11.0' - spec.swift_version = '5' + s.description = <<-DESC +DashKit implements Dash protocol in Swift. + DESC - spec.dependency 'BitcoinCore.swift', '~> 0.6' - spec.dependency 'HSCryptoKit', '~> 1.4' - spec.dependency 'HSHDWalletKit', '~> 1.1' - spec.dependency 'CryptoBLS.swift', '~> 1.1' - spec.dependency 'CryptoX11.swift', '~> 1.1' - spec.dependency 'Alamofire', '~> 4.0' - spec.dependency 'ObjectMapper', '~> 3.0' - spec.dependency 'RxSwift', '~> 5.0' - spec.dependency 'BigInt', '~> 4.0' - spec.dependency 'GRDB.swift', '~> 4.0' + s.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } + s.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "dash-#{s.version}" } + s.social_media_url = 'http://horizontalsystems.io/' + + s.ios.deployment_target = '13.0' + s.swift_version = '5' + + s.source_files = 'DashKit/Classes/**/*' + s.resource_bundle = { 'DashKit' => 'DashKit/Assets/Checkpoints/*' } + + s.requires_arc = true + + s.dependency 'BitcoinCore.swift', '~> 0.18' + s.dependency 'OpenSslKit.swift', '~> 1.0' + s.dependency 'Secp256k1Kit.swift', '~> 1.0' + s.dependency 'BlsKit.swift', '~> 1.0' + s.dependency 'X11Kit.swift', '~> 1.0' + s.dependency 'HdWalletKit.swift', '~> 1.5' + + s.dependency 'ObjectMapper', '~> 4.0' + s.dependency 'RxSwift', '~> 5.0' + s.dependency 'BigInt', '~> 5.0' + s.dependency 'GRDB.swift', '~> 5.0' end diff --git a/DashKit/Assets/Checkpoints/MainNet-bip44.checkpoint b/DashKit/Assets/Checkpoints/MainNet-bip44.checkpoint new file mode 100644 index 00000000..1c0d5803 --- /dev/null +++ b/DashKit/Assets/Checkpoints/MainNet-bip44.checkpoint @@ -0,0 +1,24 @@ +02000000fa029e39ca627f2b2d4140b5825e48f254ba3f3fa411d3e2c2641b00000000004360ee43af75123dbf202153f78c5a5a0f60367324bd85b0605d5d85d8973e0f46fa11532849011c970a400088650000dc10be5ef167075d45b69ae388668f262522452131423457bc89d00000000000 +02000000adcce9364a714874a1afb2a24d423a2e0a3655c0c0f28250d10f8e0000000000034c43a7dbfc6e8bed9dceb8ed88cec72478741e6184b96bbf628b6e732178bb84f911533449011cab2a1c0087650000fa029e39ca627f2b2d4140b5825e48f254ba3f3fa411d3e2c2641b0000000000 +0200000022dfb410c8d52af4b4daf60b9e6b9333c4a3fed79156083744080300000000008d105952fd0b550e9522f3fe55a304a7c0cf386298f11e1441d98965a32a69cbf2f81153de64011cca876f0086650000adcce9364a714874a1afb2a24d423a2e0a3655c0c0f28250d10f8e0000000000 +020000008e4a92eabd305bb1c400ef724a464042a9bfa604e734e55a3565360100000000a851146c1458f4512766671995ce28d9fa5a5439d8543442f664b4754d22629390f811533949011cc78fe7008565000022dfb410c8d52af4b4daf60b9e6b9333c4a3fed7915608374408030000000000 +020000000f395f8c0da314d4bf98bc93cfa0194506ffff77a653b1a3bf8d79000000000032c0c29a80e6b0060d45cbedd95c7d040835c3cfcbc7f1949ab91c150d6eacb1d4f71153cd64011c0e193900846500008e4a92eabd305bb1c400ef724a464042a9bfa604e734e55a3565360100000000 +02000000181325530e7bae746c9c728e6ff471cffa3b25ada956e9345bdb0f0100000000d86510bae9e6ed590d36df2a57e9aa2d70d385ebcbf109534fcef8b712c669cd86f71153eb64011c4c771400836500000f395f8c0da314d4bf98bc93cfa0194506ffff77a653b1a3bf8d790000000000 +020000003e748632e0e2a81e683c831d444d88a414ee21f22a8fa43656777e0000000000cd6a92ba5a57746a8566ab4d5eaf1d59367ce8551a7f613a72420898472945d1fbf61153b064011c4336150082650000181325530e7bae746c9c728e6ff471cffa3b25ada956e9345bdb0f0100000000 +0200000039fc860aee959bfc90f3e72427518ca666aba9bc03d27a40699e02010000000030e2dacb2d40c6151f0a38d7ccd819d5eeaf692f228d908707c631e5cf3a31eb51f611531365011ce6d73600816500003e748632e0e2a81e683c831d444d88a414ee21f22a8fa43656777e0000000000 +0200000085e38ff12d09af3675c5fb7c9d3011b8e7b32784f3f7104f0bc0fe0000000000d313bee2654f93703f25b268df396927d859895fa274845587a59b9863ff6a6fdef511530c48011c980ed3768065000039fc860aee959bfc90f3e72427518ca666aba9bc03d27a40699e020100000000 +02000000da416992da83a6043c91e2588d71786f1da42b9022066af8c722160000000000e2ef21b3a5a7532583b6eb01fe46eed995e11986c852fe80ae33256b24f77acfcdf411537147011c2b3380007f65000085e38ff12d09af3675c5fb7c9d3011b8e7b32784f3f7104f0bc0fe0000000000 +020000005242774ca1ef80a0ebb6a7f5d98549b04ddcfe5f46bcda035fcf060100000000827c436d0816585576c03e986ea71c458e4da7d3d25ab7d7fbcdbb0c029164d313f411536448011c3e868a007e650000da416992da83a6043c91e2588d71786f1da42b9022066af8c722160000000000 +02000000cb9a992b05c60b523e23b57ce7a39f5c95f1aed5ea3a9cf89af6bd0000000000004e6211a08ee36c0e7f8769a7b58584e5b66e3eaaa69ee873a788ca5f4bd795b8f311535c45011c7eab26007d6500005242774ca1ef80a0ebb6a7f5d98549b04ddcfe5f46bcda035fcf060100000000 +020000003a5b9d1c66488db710dd0112ea09b4ce0a11a289fde448bb4b15970000000000e9399ef53bcf614785dc39ebbf782d1aedfc3695f1fe809ee6bd432db9fa829486f211531748011c95780e007c650000cb9a992b05c60b523e23b57ce7a39f5c95f1aed5ea3a9cf89af6bd0000000000 +02000000d3c8f6275432186fccbed47a116c22205dbd6aaab3f989c83d55210000000000e20c78502f83ea3b915e0505959ec64f3a4803f18114f74aad4cee4448c1aa457bf211539465011c8cb42a007b6500003a5b9d1c66488db710dd0112ea09b4ce0a11a289fde448bb4b15970000000000 +020000000487c065b7eb9b9cf1ad03f52a5e4c66ae51dc3a9ee90347a86798000000000070bbbe23a780d538b66afbb24c48e0bf7c4d37611028642df50702241b2022ff51f21153c644011c5ee313007a650000d3c8f6275432186fccbed47a116c22205dbd6aaab3f989c83d55210000000000 +02000000996c0204ec8d1780549d65b6a950e5b4574100d56a021e137935a40000000000d5bc62113ede383825a722a2d0c9ab1f83cd1ca54d078eeca0f5b35e55e69030b3f01153f545011c6027baff796500000487c065b7eb9b9cf1ad03f52a5e4c66ae51dc3a9ee90347a867980000000000 +02000000931bc6eb4792f9f1061f8a72e1ecd7b047bb7c735c296b785c2a510100000000cbe2d3a31a5f0ccd516f549e3471fef0353acf9309481466bb5ed9b248478a4d63f01153b147011c7620250178650000996c0204ec8d1780549d65b6a950e5b4574100d56a021e137935a40000000000 +020000003e3a9b7c9a6c7e9dc2396a2669c621731a7c5abcd811379d11085b000000000080c1b033583290356474859dcfb026033965ba645fb3cdf3e4da22b3f0ae69571af01153c465011c6aae870577650000931bc6eb4792f9f1061f8a72e1ecd7b047bb7c735c296b785c2a510100000000 +020000006d7826f0a9c1d79200e1160157335fbc1201022f98b6de003b158e000000000089148da4cf9f98001b5f53c9b128fa4efe805c021acf3d6bcaf4b1701ed30df2b5ef11534866011c01ba5600766500003e3a9b7c9a6c7e9dc2396a2669c621731a7c5abcd811379d11085b0000000000 +02000000bfd7f8a88965d66f168712b3baab617e935ddefdde76af8e13b95a0000000000af662ca09cdae2354b383e323bd2d0ba7a1bbe85252f8cd734a103ba7004c8a9a7ef1153fd66011cd804a505756500006d7826f0a9c1d79200e1160157335fbc1201022f98b6de003b158e0000000000 +02000000671fbb8ae24afac35f5a319dc8a4a41b98f97c831bf64ab970740701000000000e8bad5aa06b556d0711641d51f71624654f528e7b5c44a84aef4fc4f186138c4def11536c46011ce2f8260074650000bfd7f8a88965d66f168712b3baab617e935ddefdde76af8e13b95a0000000000 +020000007de6343400672358903cfbbd4b64fd4d64b934c5bb909aa76d0542000000000061d188bb4e1e679039dc329d6419f2a6d95372b28ae217c861546a7b775d92b9a2ed11537e40011c7128cb0073650000671fbb8ae24afac35f5a319dc8a4a41b98f97c831bf64ab97074070100000000 +0200000093ff0fa01b2f254f54bdc9ecb8737733f9b271be8ba7f4b014e2fb00000000007c6d109ea8a7761faa997e524fb3776993aa58206f39e015082c3beb88b2e82b14ec1153dd42011cbf0e0500726500007de6343400672358903cfbbd4b64fd4d64b934c5bb909aa76d05420000000000 +020000001469c0cbe42d3c7a8e52bbc75e94916ac35603e4003b4c617e21b90000000000fbe74995dc243e4e5592f2fef61c6761aa1739f87716e327ba4233d5018d0fcbcceb11533644011c99e404007165000093ff0fa01b2f254f54bdc9ecb8737733f9b271be8ba7f4b014e2fb0000000000 diff --git a/DashKit/Assets/Checkpoints/MainNet-last.checkpoint b/DashKit/Assets/Checkpoints/MainNet-last.checkpoint new file mode 100644 index 00000000..407682b0 --- /dev/null +++ b/DashKit/Assets/Checkpoints/MainNet-last.checkpoint @@ -0,0 +1,24 @@ +00000020a4f80d109481788e69f7500517ecdfc5d620563b8755155b1600000000000000b82b77334a4547af4d732d1633bb30c1a67b86e1c010254921e3f1df0dd97ac90fc8e8623d543319b26db0e420291a00587fa89b1f0a31b31d790b65fd43b4e587146d3145be4e922a00000000000000 +000000209e148800ed63971271915030c1b5694ad53d5ac082c73bc915000000000000008880aa9e8a6fedfd111ad9e765b015e36d6aff0e0d4981f4914298c892552c2318c7e862583033193f5210991f291a00a4f80d109481788e69f7500517ecdfc5d620563b8755155b1600000000000000 +000000201d244eb37fccda7090074b1bcea15705138ebdaacd194f5c0d00000000000000eb49d6ee1a0816f8e87dc99516c594c5b2e947be7018af04208262058cf4eceef2c6e86211223319574aba071e291a009e148800ed63971271915030c1b5694ad53d5ac082c73bc91500000000000000 +00000020cde0fca9f810df7e8f468752a51ff7b4e3ca4191d4ca9cf524000000000000000da21ec46dd4bfb77ff58856c2bdc4f415b250d474241afb5a149d193a68358edec6e8622a5b2e19fa687c9a1d291a001d244eb37fccda7090074b1bcea15705138ebdaacd194f5c0d00000000000000 +000000208336ac776539dac8a3d922d7c3c74159a70ec050cf708f652c000000000000006a05c61dc7fba9eb17e5ceb69f0b429b4ed540e3aab09f388086cd6acd31fd7a4ec5e862025434195899636e1c291a00cde0fca9f810df7e8f468752a51ff7b4e3ca4191d4ca9cf52400000000000000 +00000020b0198663c28d1e1bf175c4142fe32077d1273eca60009bba14000000000000007519d59548623598d8a8bab4e6ec13b163a2b8892d0c85000fc9f7cd910794991fc5e862d94b3119a392d0b61b291a008336ac776539dac8a3d922d7c3c74159a70ec050cf708f652c00000000000000 +00000020fa985702679e1e8899e50802121bc77a38e69ba66b8812201700000000000000c1d9b6e6089b20926efd57bcd454e133f52818050cd1203b678d126d8f1ba0822ec4e86294d93019305bf2be1a291a00b0198663c28d1e1bf175c4142fe32077d1273eca60009bba1400000000000000 +00000020c378111f70701f232164fe5b148b375e3c922b53b540e4730d0000000000000016e7c4d3b5c9bd374096413adf91aa6e2327d9a695ef65f2d454748923ea26100ec4e862f48d3019451c39dd19291a00fa985702679e1e8899e50802121bc77a38e69ba66b8812201700000000000000 +00000020ee4d765d745bc21cfa6197472da5baeabb6fe411a63075d325000000000000002634e752fa10d881b9d5005868a6076b0b5549b78a389ac726710de3c3833e86ecc3e86216992f19cd10733f18291a00c378111f70701f232164fe5b148b375e3c922b53b540e4730d00000000000000 +00000020f83e4a797441a98af567408e97a3df78659d7ced3737a60514000000000000000fb9368729d457dc67e870af567e5fa32f9c0aab81ee734ea4b8bc198adc2701a6c3e86296e3311907668d7417291a00ee4d765d745bc21cfa6197472da5baeabb6fe411a63075d32500000000000000 +00000020ce66630eb1adfcee82c13d1f937f2dec74417bb0cf511df114000000000000001eda681c1178ab0b4fab2327ca6ba42a6b5c0fdeadfd396eefe8755805e16df8dcc2e8627eb62919b121cf7116291a00f83e4a797441a98af567408e97a3df78659d7ced3737a6051400000000000000 +00000020eeb2b1a341ef1581752982e475db357964b848232857deaf1d00000000000000ac28e9b1d755ab84228c5c355116a4a83a093c18f97633857a972dfd0a084a4e0ec0e862c58c2b19c659186c15291a00ce66630eb1adfcee82c13d1f937f2dec74417bb0cf511df11400000000000000 +00000020c7f2bd2c4660d3fce6f7defca73fcdb857d32961ccdf1dec180000000000000049459929af90dad65634b3c74286d17d094f04413790547b7792cb1d53b90e8ee7bfe8628cbc2a197b55a05d14291a00eeb2b1a341ef1581752982e475db357964b848232857deaf1d00000000000000 +00000020c523ffcac473710ea4513e52caf3df1a1b840cb99a24c4520e0000000000000016369d7e367380f1c871836f3152af61692ba14ace6a62c0172d13296787073533bfe862d5692c198a5b74b113291a00c7f2bd2c4660d3fce6f7defca73fcdb857d32961ccdf1dec1800000000000000 +00000020b5b83cdbaf80523110479921790e8d837a1ad10c0fb4aee71900000000000000e723501fc5c72b3ba8e26151041850dc59ce6859430241965db3dabdbc0be5ac08bfe8624ced29196315a0d812291a00c523ffcac473710ea4513e52caf3df1a1b840cb99a24c4520e00000000000000 +00000020eb5a1b13b7cae85a8be1e6cf0aec5f09099f83b6ccff048900000000000000006596d96a83458e80f8bfffb740c5a7701a14b124e7ba4e9c89b3e99bb1ade9ee6ebde862ea9b2919a559a24d11291a00b5b83cdbaf80523110479921790e8d837a1ad10c0fb4aee71900000000000000 +0000002016a11ca875d0018afd17ce413cc6f06e462d16258915a2050e00000000000000090fb23e5a89feeda5dc649a8bf67c8feaa01db7132e173c83f1ff37d09e81bd35bde8628084221957186adc10291a00eb5a1b13b7cae85a8be1e6cf0aec5f09099f83b6ccff04890000000000000000 +00000020e6157a7f4f0c358fff7f654613da93aa0d2fa0904a53ecd808000000000000005b9d117ea1aec1a676ca2374c37aa637e6a3de6b569dabc17cb2e06ed98f08c33dbae8620e7523196da239a70f291a0016a11ca875d0018afd17ce413cc6f06e462d16258915a2050e00000000000000 +00000020e25418a29928c0f71278899a6592c5ec484d5159771463d02400000000000000aa414ca16a2e664cd003526e5146ec70ef93f517aa3aaaa1703d1450a3e36dcc1dbae862274d2419355dea840e291a00e6157a7f4f0c358fff7f654613da93aa0d2fa0904a53ecd80800000000000000 +00000020f7e4e1b796c020d7e7beb8272f15c5b8740b0b693ec9106f060000000000000035ce278e31d1352472f0630ec9082cd8dd9debdb0bd1fc8c6e2f4987c9d4b41106bae86226192619cc58ee460d291a00e25418a29928c0f71278899a6592c5ec484d5159771463d02400000000000000 +00000020ee7a16855521896f8facc490a9962d7f6a6dc2908f0d3974110000000000000048e5a4131cbf753077d0b0f3e776b39e0b13a718d4238f16344025d08a3187d6beb9e862746c2619215c82610c291a00f7e4e1b796c020d7e7beb8272f15c5b8740b0b693ec9106f0600000000000000 +000000201e836d7fc446ff78f1a10d214e55b7d7eeb72b4b82a4e7c6020000000000000021a7e124f1e350f690707a09e92089fb2f292229b9dc7f6e67628d74122a0d6d8bb9e862b748261957019fce0b291a00ee7a16855521896f8facc490a9962d7f6a6dc2908f0d39741100000000000000 +0000002046ef63ae337a7a8e3b562e333f4a32ee80be8230a539fb1d1b000000000000009a43b229ae07f834a1acf133c476fb4cd585be501da91134ff3484bfd315a26a93b8e862d2472c19497a46b60a291a001e836d7fc446ff78f1a10d214e55b7d7eeb72b4b82a4e7c60200000000000000 +000000207730439938ba3cdd977b21fc607624cba859f00162c6d6762200000000000000bb94bba0018abb76c34a70180e6b5846dfe9e772d696594bf1d504ee3440f3ee50b7e8629dd12b19a81e6ac309291a0046ef63ae337a7a8e3b562e333f4a32ee80be8230a539fb1d1b00000000000000 diff --git a/DashKit/Assets/Checkpoints/TestNet-bip44.checkpoint b/DashKit/Assets/Checkpoints/TestNet-bip44.checkpoint new file mode 100644 index 00000000..a0417b26 --- /dev/null +++ b/DashKit/Assets/Checkpoints/TestNet-bip44.checkpoint @@ -0,0 +1,24 @@ +020000009d8aba465508848988491bb13a72093a84a47533cc50807010bd5e2ef7010000b86ce8d74370ecb66b12c776e822f6b3c39d17c85ca5cfb308702e2a06b442d1583fc953f0ff0f1ec4eb0000300000003c1d1daf5d63ca78a757de2e606a371cdc50e352bf99e8333730c2832c050000 +02000000e06abb1efb3553e7de64517363008e38a6eb16373b9e00f97ddbf3b64107000097d03465f7804dfbaecf3a5c9d8fe812068ebba34c8dae5553cba03dd97fc059563fc953f0ff0f1e23f500002f0000009d8aba465508848988491bb13a72093a84a47533cc50807010bd5e2ef7010000 +02000000baa600ea9bd552ea56686a6cb0362e2f4766a49d6b67cf91847414ff570d0000d15140ac63af921c21d1001b5018127ccdc8c8473dd8dbf76a9ed2aebc38c39d533fc953f0ff0f1e473301002e000000e06abb1efb3553e7de64517363008e38a6eb16373b9e00f97ddbf3b641070000 +0200000028358ee185479ae0488211d394be0a44ee929a3369be889c3043e2317c060000ff2540ab637a9327e3defab5eb68a005a206a9f3a543081e062b0768af2bce6d503fc953f0ff0f1ec24601002d000000baa600ea9bd552ea56686a6cb0362e2f4766a49d6b67cf91847414ff570d0000 +02000000fd50b6bc05839a891ae063afe23486c3c3e0f9986cf973ffb58231d2620b00009f482443cf1339621cbf9d94d0a5e8dd69312812b160d27fcd46a0d71573b4d04d3fc953f0ff0f1eb38c08002c00000028358ee185479ae0488211d394be0a44ee929a3369be889c3043e2317c060000 +02000000183c92e0c21e6eab0da773f6d5d9371019aed51bc9d520882474a047eb0f0000c756fd8aba9e4000320636d4a66618ada15ecc00af5716195398632770a410eb373fc953f0ff0f1e4bf101002b000000fd50b6bc05839a891ae063afe23486c3c3e0f9986cf973ffb58231d2620b0000 +02000000bade9bbe1d805ea00fed36a051ba24e748065ff745e009b5cbde5316fd040000fe2087d0accc4a2f5282e34e0d3695dae3d440ec4fb87954b8cb988050790ac9323fc953f0ff0f1eb89c00002a000000183c92e0c21e6eab0da773f6d5d9371019aed51bc9d520882474a047eb0f0000 +02000000b9b76c2b82dd415c4434efc93e4081c070f4711566f6ce5fef45beeab10f00003d1cd18a0042ae85d1dea63ca5c1550e49d55b5ef77a639db20060fa82f4e007313fc953f0ff0f1e5d43040029000000bade9bbe1d805ea00fed36a051ba24e748065ff745e009b5cbde5316fd040000 +02000000a08556b1aef5758ec1bb3ccc690b0d2e3bad913c1941e9c221413c4afc0300004b9956f8e9d38b48082f325a9222bfa97784ca5a248d46de7b7c40e1fde21111263fc953f0ff0f1e6bc8000028000000b9b76c2b82dd415c4434efc93e4081c070f4711566f6ce5fef45beeab10f0000 +02000000979da9e4ea4fcfb6967502b66f9d89410c8d3b2178be8f3c09aa4b23f803000082d641514dacfce1bac3c9bbbc3358a8faaa89dc1d7a5b205b8022cc78f030f3243fc953f0ff0f1e5ea5010027000000a08556b1aef5758ec1bb3ccc690b0d2e3bad913c1941e9c221413c4afc030000 +0200000067ac3acd7d1c0d00948238c14d4a497e6353fe19c37e32a72398d1f3170d0000882e59d72fea00f0563f3c80411138d8633a80d5bcc67041c8c2dba02a41c769203fc953f0ff0f1e2daa000026000000979da9e4ea4fcfb6967502b66f9d89410c8d3b2178be8f3c09aa4b23f8030000 +020000008f48755f2778402a7857f5ed970a14a731e1471a150a256cb97e4dfe9607000024c930e500c78cefa0b37d2506f120e68e5a496678c9220c115a2e03dc66e3aa0c3fc953f0ff0f1e74ea20002500000067ac3acd7d1c0d00948238c14d4a497e6353fe19c37e32a72398d1f3170d0000 +020000008c168dc008c76dd242b662d751a8b43de32ec41c54a11b8984bd7f1550090000de4233697696679f76afd49a20af6bf74d9f2a950be5281108d91528c13e249ae53ec953f0ff0f1e98a40000240000008f48755f2778402a7857f5ed970a14a731e1471a150a256cb97e4dfe96070000 +02000000751192ac5d41d7414f2b024204f484b6494d07e6c4148c8901488f78160f0000478c0b03bfe1e2685a255902fa6207331ea74ce6efdcda4bf58a615405ebbb9ae43ec953f0ff0f1eac6c0300230000008c168dc008c76dd242b662d751a8b43de32ec41c54a11b8984bd7f1550090000 +0200000039a1a433827b67e40c1b8aa780ab094d148128dbfbb5bff80dc6c15a6d0d00004835e3d567a5312ecb6cfcfb853a581bfab61a4369a82b2dd39fa85329671b91d53ec953f0ff0f1ec058050022000000751192ac5d41d7414f2b024204f484b6494d07e6c4148c8901488f78160f0000 +02000000a422296ed4d35d429c09f675998c8a2c20812db130faf113b340aeba1f0d00005326fbc769c7067bf52430e8ca0089c0e6fa437c1229c567f98b1a78e444d1c3cf3ec953f0ff0f1e052e07002100000039a1a433827b67e40c1b8aa780ab094d148128dbfbb5bff80dc6c15a6d0d0000 +02000000ab1f3ff7709decd075d1f37d4e95c8d50dc4a151e0e551d675483726630100003fc82b967a8a9bfbc2d2ec8c4a49f45005759a23267298929cba005b065805b0c73ec953f0ff0f1ecd57050020000000a422296ed4d35d429c09f675998c8a2c20812db130faf113b340aeba1f0d0000 +020000008fc2a4ea645b8f0bf49281f171aa4f5bf2c596fd8edb648671355e4f55080000a287eeebb4624e5a19a22b5a11cfaa24a4c4cd089a9555625d08cd585c513e92c03ec953f0ff0f1e7e9003001f000000ab1f3ff7709decd075d1f37d4e95c8d50dc4a151e0e551d67548372663010000 +02000000886dca1e1d85999f028299d84faf557aa5e4d033c8a0de508e337b96020d00003b1576afdfae95f791af01ef8c44f96ba6afe9b4cff8dc6acc795666720214ccb13ec953f0ff0f1ed5d700001e0000008fc2a4ea645b8f0bf49281f171aa4f5bf2c596fd8edb648671355e4f55080000 +02000000aaf6cda39eeda63fb9f47d03a55257d1b46e253530bb12852f249ab52d0a000014fc57176256888ae97a1ae5325a46544f745819a1f785beeb0a59fe5b9ff6d6ad3ec953f0ff0f1e542f02001d000000886dca1e1d85999f028299d84faf557aa5e4d033c8a0de508e337b96020d0000 +02000000999b7ed6bdf5aa964d461c26c21a51a62a7b2954ddf62de03982a9fc2201000060113ce6c394fb155b3c7ef653a9329c2b1d4b5ab16181a96fe554c32318a1adaa3ec953f0ff0f1e0fd809001c000000aaf6cda39eeda63fb9f47d03a55257d1b46e253530bb12852f249ab52d0a0000 +02000000d160799a385be1c968328aa2f8c488c8db0b5a908ddd37171102a3a85300000013440ec308e8b69db21a9960e6d3d14b56d7c8660c0be7f6d49908d7004ee5409e3ec953f0ff0f1e094b03001b000000999b7ed6bdf5aa964d461c26c21a51a62a7b2954ddf62de03982a9fc22010000 +020000007ad835393c079dba2298672267d0a1ea57134c57fbf75086b81de242710600002caf899d0b99dd054faa66e93501dd3e6d1e44c969f7c8994a58b312ff027dd6913ec953f0ff0f1e1d4402001a000000d160799a385be1c968328aa2f8c488c8db0b5a908ddd37171102a3a853000000 +0200000061f6e5d25232bb4ffc9794a65ac67009b8f88c7cc38aa2ef30a566e6f10d0000dca66a6906e2c09dc91aa5ff29fed65e140eb381e4154da42d881d3b89c850408e3ec953f0ff0f1e55610500190000007ad835393c079dba2298672267d0a1ea57134c57fbf75086b81de24271060000 diff --git a/DashKit/Assets/Checkpoints/TestNet-last.checkpoint b/DashKit/Assets/Checkpoints/TestNet-last.checkpoint new file mode 100644 index 00000000..e5911a9a --- /dev/null +++ b/DashKit/Assets/Checkpoints/TestNet-last.checkpoint @@ -0,0 +1,24 @@ +0000002046a013abd643c64bab5b1d4650ef4e27b8de36d8bbe53296cbaddf23ef0000007f1a79eb8f8ec688a0b179cd201c9e78948fc1b7ba4e23fabe91fd0ce4eccef7d5fce16220f4001e7151000038bf0b00728ad4fd0f0ed4cb9d960be1399697a21736cdacb8a8e0df529fef1ede000000 +0000002053790e52a7ae8c7d5867d6fc799ab9041cd2cad2eb90068e0267b33be7000000fe5500c2a649cd0e4a57ced6ab4394afe1f447713095c23a419a4e700afc13f1c5fce16281f8001e48db020037bf0b0046a013abd643c64bab5b1d4650ef4e27b8de36d8bbe53296cbaddf23ef000000 +00000020ca6b9b650e5aceb89376d867a41a1a3d624f200fca9a158da91c85d1d4000000798f0739b380e89c4d850b9ba067301f8f071b4a464c5dfd46f7da81ea6ef9d8a6fce16252fb001e3e69010036bf0b0053790e52a7ae8c7d5867d6fc799ab9041cd2cad2eb90068e0267b33be7000000 +0000002069699b9e8042ff5976f22b3219d865e7b007e5f6f39fcfc498d7d769190000008ce28bf3539b49b11b810eff3cca8e75f03b3a94c997995e8a862ce3dcb0fe7836fce162c918011e238d010035bf0b00ca6b9b650e5aceb89376d867a41a1a3d624f200fca9a158da91c85d1d4000000 +00000020ee67579bf44ee81daee153c148f7da93e69e8acce718122071546cdb1c01000086b6958cfa2a74ea1e26aee5a821c853e83492d52c2ef148398644641e677f9016fce1629f2c011e41b80a0034bf0b0069699b9e8042ff5976f22b3219d865e7b007e5f6f39fcfc498d7d76919000000 +000000203c223305833d80a49b62081b8614cb75ab8c8b86b7ad626a7b78b1de55000000a8f187d93e197155add2ffcb8939af484a0ac13487d79215748e2e6dc739188702fce162e135011ee99e0d0033bf0b00ee67579bf44ee81daee153c148f7da93e69e8acce718122071546cdb1c010000 +000000209ce44d3b24ea7104fd92c0c7a22a32f406959d380090dad86dde339b380000004198cc765f1265f79159a4f6eff2c734133eea0418ce6395700f602597beeb7ef9fbe162f732011e7f36020032bf0b003c223305833d80a49b62081b8614cb75ab8c8b86b7ad626a7b78b1de55000000 +00000020b66739125ecaa2607f37bc32512a55c5ecb11de6ca55848ec572f0ed810000003842c4cd0864d6819fc876c20aabfcbaa59aff17e655084c9c694802a28f6eba91fbe162991e011e409e010031bf0b009ce44d3b24ea7104fd92c0c7a22a32f406959d380090dad86dde339b38000000 +000000207cb3deb82debf0fdd9dba197b4934cfa6b2447106cb6ea92eb036dbe14010000ef0fd75965597f867c92a41d6077b606470b1d08fa1cb4a61f999169afc79bcf62fae162b132011ebd2b060030bf0b00b66739125ecaa2607f37bc32512a55c5ecb11de6ca55848ec572f0ed81000000 +000000206ade203e449ae165f5f0612291b9eaf7d0ed43ab1c58c8af2b311be550000000c94d4785d7248485ed7ac1f1af47c6761131705e2e9b6353e46d4044868920f20dfae162c738011e905c09002fbf0b007cb3deb82debf0fdd9dba197b4934cfa6b2447106cb6ea92eb036dbe14010000 +000000206fce0e5ec7d1a35aac7620fcaf399a0dea484ee7d3e87e2a3febeca84a0000000b4b1c3bd6b8e38269e889d8d1eb98cbc2d233696e5e0487d0256e1262d9f695a4f9e1628143011e54530b002ebf0b006ade203e449ae165f5f0612291b9eaf7d0ed43ab1c58c8af2b311be550000000 +0000002089e46b95d23b0051f630d4339e102c110e0ac5a53f0ee158c2b712be010000001257ce6b07f28f4fc69ffe8da3140743914c66263158ae68f65f3d721f96779e6cf9e1629951011e868600002dbf0b006fce0e5ec7d1a35aac7620fcaf399a0dea484ee7d3e87e2a3febeca84a000000 +0000002019e6a8b308cccc79ab2d3b690f9b754428b8f68d5f30e3c1654b880fa3000000b3e52d2503a55d892dc1daca020df4815e5844f347318d8aa4a653d2c562ad8641f9e1622d53011e818b03002cbf0b0089e46b95d23b0051f630d4339e102c110e0ac5a53f0ee158c2b712be01000000 +00000020854085a16417f3aa37599fa5df3c8735b4a6f2bf2c13cad7cae5e451e0000000c0f15ef7a0f64b3be90e80bdb1c15ad6124cc1486aab8d6975639d87ab89f261d4f8e162dc5c011e1d160e002bbf0b0019e6a8b308cccc79ab2d3b690f9b754428b8f68d5f30e3c1654b880fa3000000 +000000207fd5e0b5914c69572470ea559edf552bdcd4d8c7e4924815cd012646420000009857ad25594bd2535e13864c73cef2b158eefd8b85a21b33eab1b52b601785701cf8e1629273011e1a3f0d002abf0b00854085a16417f3aa37599fa5df3c8735b4a6f2bf2c13cad7cae5e451e0000000 +00000020340400d20f587509ddb6874d7277ad57bdd618c32e57ecb5856df2eda8000000b332ca9ba931021d48d8961fb82ee455f4b59750307f89cf59ba90354b6e67f205f8e162ee74011e232c040029bf0b007fd5e0b5914c69572470ea559edf552bdcd4d8c7e4924815cd01264642000000 +000000202d8e586acc2f1eb9bc2bf535b8eb4a93b436da97b7bcf23b39a92df881000000ac03a1abb3621a724bee348138097f23f68cf5998bc0dad30b7d9fdc6698986b73f7e162ee79011eae780e0028bf0b00340400d20f587509ddb6874d7277ad57bdd618c32e57ecb5856df2eda8000000 +00000020e71c501bc04838d7a27a2bb598fd1956275e1ce3ab06323d18a0b3de64010000201ad7c93649725146da536ad153da70acd6f34337b0414fbcf399918f63e9fbedf6e162c894011e9580030027bf0b002d8e586acc2f1eb9bc2bf535b8eb4a93b436da97b7bcf23b39a92df881000000 +00000020d5b748f7bbf310eacc0d24e486e61be248203fd6a69ceb17c91e8b5821010000cdcfa16c08a8c3a18b9e89f525b4150563018ca535ba7f6abb244c939ca6b578ddf6e1624b82011ea78c0d0026bf0b00e71c501bc04838d7a27a2bb598fd1956275e1ce3ab06323d18a0b3de64010000 +00000020f62482c16fd990ff62fa8c8eb4d481ecf7ec6f0bd5f735041209602af900000053a445e14a6ae7ecaddcbd82b6b659af4276c95d3a39e1321a8eb852291819a5c7f5e162c078011e8c39000025bf0b00d5b748f7bbf310eacc0d24e486e61be248203fd6a69ceb17c91e8b5821010000 +00000020342da3dfb4035e5915cad5a2db37c28a20a293c85e19cd541f4e93ccf20000002aa7dab3778a0d9dc6cc3f6b5112e29d1530e9a871656abade6499c039af7d5808f5e1629383011e928f050024bf0b00f62482c16fd990ff62fa8c8eb4d481ecf7ec6f0bd5f735041209602af9000000 +00000020af5fc0041a82d8c4cd07134097dd75d42ebdfaf83d1ff10f5aaa3d835c000000aa96d4e2216608c47898657f776875f7aece7274dca039edf5c7b419343daadf90f4e1621768011e0d2e050023bf0b00342da3dfb4035e5915cad5a2db37c28a20a293c85e19cd541f4e93ccf2000000 +00000020861b7be6aca651f9d86a2987ac12e1a7cb5ed57c6070d820d6054ba1f3000000bee074397e404a4de0f6c98a84f5c42ae8f948a2ea6bdfc31b8a7a15d18672a257f3e162d653011e283c010022bf0b00af5fc0041a82d8c4cd07134097dd75d42ebdfaf83d1ff10f5aaa3d835c000000 +0000002090577746cc3fdf9fca77ea39a7b8cfde4f6597d1101fc4dd676a0a83fc000000f6cecac358f6d57e4947ab09b2c2c1507e3f249c22a5ad7ff3977a9a5c6c8be580f2e1625761011ef145090021bf0b00861b7be6aca651f9d86a2987ac12e1a7cb5ed57c6070d820d6054ba1f3000000 diff --git a/DashKit/DashKit/Blocks/Validators/DarkGravityWaveTestNetValidator.swift b/DashKit/Classes/Blocks/Validators/DarkGravityWaveTestNetValidator.swift similarity index 95% rename from DashKit/DashKit/Blocks/Validators/DarkGravityWaveTestNetValidator.swift rename to DashKit/Classes/Blocks/Validators/DarkGravityWaveTestNetValidator.swift index fe338db2..edacf3e1 100644 --- a/DashKit/DashKit/Blocks/Validators/DarkGravityWaveTestNetValidator.swift +++ b/DashKit/Classes/Blocks/Validators/DarkGravityWaveTestNetValidator.swift @@ -1,6 +1,6 @@ import BitcoinCore -class DarkGravityWaveTestNetValidator: IBlockValidator { +class DarkGravityWaveTestNetValidator: IBlockChainedValidator { private let difficultyEncoder: IDashDifficultyEncoder private let targetSpacing: Int diff --git a/DashKit/DashKit/Blocks/Validators/DarkGravityWaveValidator.swift b/DashKit/Classes/Blocks/Validators/DarkGravityWaveValidator.swift similarity index 82% rename from DashKit/DashKit/Blocks/Validators/DarkGravityWaveValidator.swift rename to DashKit/Classes/Blocks/Validators/DarkGravityWaveValidator.swift index ff72d663..10ae73e8 100644 --- a/DashKit/DashKit/Blocks/Validators/DarkGravityWaveValidator.swift +++ b/DashKit/Classes/Blocks/Validators/DarkGravityWaveValidator.swift @@ -1,32 +1,26 @@ import BitcoinCore import BigInt -class DarkGravityWaveValidator: IBlockValidator { +class DarkGravityWaveValidator: IBlockChainedValidator { private let difficultyEncoder: IDashDifficultyEncoder private let blockHelper: IDashBlockValidatorHelper private let heightInterval: Int private let targetTimeSpan: Int private let maxTargetBits: Int - private let firstCheckpointHeight: Int private let powDGWHeight: Int - init(encoder: IDashDifficultyEncoder, blockHelper: IDashBlockValidatorHelper, heightInterval: Int, targetTimeSpan: Int, maxTargetBits: Int, firstCheckpointHeight: Int, powDGWHeight: Int) { + init(encoder: IDashDifficultyEncoder, blockHelper: IDashBlockValidatorHelper, heightInterval: Int, targetTimeSpan: Int, maxTargetBits: Int, powDGWHeight: Int) { self.difficultyEncoder = encoder self.blockHelper = blockHelper self.heightInterval = heightInterval self.targetTimeSpan = targetTimeSpan self.maxTargetBits = maxTargetBits - self.firstCheckpointHeight = firstCheckpointHeight self.powDGWHeight = powDGWHeight } func validate(block: Block, previousBlock: Block) throws { - guard previousBlock.height >= firstCheckpointHeight + heightInterval else { // we must trust first 24 blocks from checkpoint, because can't calculate it's bits - return - } - let blockTarget = difficultyEncoder.decodeCompact(bits: previousBlock.bits) var actualTimeSpan = 0 @@ -62,7 +56,7 @@ class DarkGravityWaveValidator: IBlockValidator { } func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { - return block.height >= powDGWHeight + block.height >= powDGWHeight } } diff --git a/DashKit/DashKit/Core/BitcoinCoreCompatibility.swift b/DashKit/Classes/Core/BitcoinCoreCompatibility.swift similarity index 100% rename from DashKit/DashKit/Core/BitcoinCoreCompatibility.swift rename to DashKit/Classes/Core/BitcoinCoreCompatibility.swift diff --git a/DashKit/DashKit/Core/DashExtensions.swift b/DashKit/Classes/Core/DashExtensions.swift similarity index 89% rename from DashKit/DashKit/Core/DashExtensions.swift rename to DashKit/Classes/Core/DashExtensions.swift index bdd6657a..7dfe880f 100644 --- a/DashKit/DashKit/Core/DashExtensions.swift +++ b/DashKit/Classes/Core/DashExtensions.swift @@ -1,7 +1,7 @@ extension Data: Comparable { public static func <(lhs: Data, rhs: Data) -> Bool { - guard lhs.count == lhs.count else { + guard lhs.count == rhs.count else { return lhs.count < rhs.count } diff --git a/DashKit/DashKit/Core/DashTransactionInfoConverter.swift b/DashKit/Classes/Core/DashTransactionInfoConverter.swift similarity index 69% rename from DashKit/DashKit/Core/DashTransactionInfoConverter.swift rename to DashKit/Classes/Core/DashTransactionInfoConverter.swift index 3a7ac6d1..e8909f69 100644 --- a/DashKit/DashKit/Core/DashTransactionInfoConverter.swift +++ b/DashKit/Classes/Core/DashTransactionInfoConverter.swift @@ -1,11 +1,10 @@ import BitcoinCore class DashTransactionInfoConverter: ITransactionInfoConverter { - private let baseTransactionInfoConverter: IBaseTransactionInfoConverter + public var baseTransactionInfoConverter: IBaseTransactionInfoConverter! private let instantTransactionManager: IInstantTransactionManager - init(baseTransactionInfoConverter: IBaseTransactionInfoConverter, instantTransactionManager: IInstantTransactionManager) { - self.baseTransactionInfoConverter = baseTransactionInfoConverter + init(instantTransactionManager: IInstantTransactionManager) { self.instantTransactionManager = instantTransactionManager } diff --git a/DashKit/DashKit/Core/DashKit.swift b/DashKit/Classes/Core/Kit.swift similarity index 69% rename from DashKit/DashKit/Core/DashKit.swift rename to DashKit/Classes/Core/Kit.swift index 898c5109..59ccf4a4 100644 --- a/DashKit/DashKit/Core/DashKit.swift +++ b/DashKit/Classes/Core/Kit.swift @@ -1,19 +1,16 @@ import BitcoinCore -import HSHDWalletKit +import HdWalletKit import BigInt -import HSCryptoKit import RxSwift +import HsToolKit -public class DashKit: AbstractKit { +public class Kit: AbstractKit { + private static let name = "DashKit" private static let heightInterval = 24 // Blocks count in window for calculating difficulty private static let targetSpacing = 150 // Time to mining one block ( 2.5 min. Dash ) private static let maxTargetBits = 0x1e0fffff // Initially and max. target difficulty for blocks ( Dash ) - public static func clear() throws { - try DirectoryHelper.removeDirectory("DashKit") - } - - public enum NetworkType { case mainNet, testNet } + public enum NetworkType: String, CaseIterable { case mainNet, testNet } weak public var delegate: DashKitDelegate? @@ -23,28 +20,28 @@ public class DashKit: AbstractKit { private var instantSend: InstantSend? private let dashTransactionInfoConverter: ITransactionInfoConverter - public init(withWords words: [String], walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet, confirmationsThreshold: Int = 6, minLogLevel: Logger.Level = .verbose) throws { + public init(seed: Data, walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet, confirmationsThreshold: Int = 6, logger: Logger?) throws { let network: INetwork var initialSyncApiUrl: String switch networkType { case .mainNet: network = MainNet() - initialSyncApiUrl = "https://dash.horizontalsystems.xyz/apg" + initialSyncApiUrl = "https://insight.dash.org/insight-api" case .testNet: network = TestNet() initialSyncApiUrl = "http://dash-testnet.horizontalsystems.xyz/apg" } - let initialSyncApi = InsightApi(url: initialSyncApiUrl) - let logger = Logger(network: network, minLogLevel: minLogLevel) + let logger = logger ?? Logger(minLogLevel: .verbose) + + let initialSyncApi = InsightApi(url: initialSyncApiUrl, logger: logger) - let databaseFilePath = try DirectoryHelper.directoryURL(for: "DashKit").appendingPathComponent("\(walletId)-\(networkType)").path + let databaseFilePath = try DirectoryHelper.directoryURL(for: Kit.name).appendingPathComponent(Kit.databaseFileName(walletId: walletId, networkType: networkType, syncMode: syncMode)).path let storage = DashGrdbStorage(databaseFilePath: databaseFilePath) self.storage = storage let paymentAddressParser = PaymentAddressParser(validScheme: "dash", removeScheme: true) - let addressSelector = DashAddressSelector() let singleHasher = SingleHasher() // Use single sha256 for hash let doubleShaHasher = DoubleShaHasher() // Use doubleSha256 for hash @@ -54,14 +51,32 @@ public class DashKit: AbstractKit { let instantTransactionState = InstantTransactionState() let instantTransactionManager = InstantTransactionManager(storage: storage, instantSendFactory: instantSendFactory, instantTransactionState: instantTransactionState) - dashTransactionInfoConverter = DashTransactionInfoConverter(baseTransactionInfoConverter: BaseTransactionInfoConverter(), instantTransactionManager: instantTransactionManager) + dashTransactionInfoConverter = DashTransactionInfoConverter(instantTransactionManager: instantTransactionManager) + + let difficultyEncoder = DifficultyEncoder() - let bitcoinCore = try BitcoinCoreBuilder(minLogLevel: minLogLevel) + let blockValidatorSet = BlockValidatorSet() + blockValidatorSet.add(blockValidator: ProofOfWorkValidator(difficultyEncoder: difficultyEncoder)) + + let blockValidatorChain = BlockValidatorChain() + let blockHelper = BlockValidatorHelper(storage: storage) + + let targetTimespan = Kit.heightInterval * Kit.targetSpacing // Time to mining all 24 blocks in circle + switch networkType { + case .mainNet: + blockValidatorChain.add(blockValidator: DarkGravityWaveValidator(encoder: difficultyEncoder, blockHelper: blockHelper, heightInterval: Kit.heightInterval , targetTimeSpan: targetTimespan, maxTargetBits: Kit.maxTargetBits, powDGWHeight: 68589)) + case .testNet: + blockValidatorChain.add(blockValidator: DarkGravityWaveTestNetValidator(difficultyEncoder: difficultyEncoder, targetSpacing: Kit.targetSpacing, targetTimeSpan: targetTimespan, maxTargetBits: Kit.maxTargetBits, powDGWHeight: 4002)) + blockValidatorChain.add(blockValidator: DarkGravityWaveValidator(encoder: difficultyEncoder, blockHelper: blockHelper, heightInterval: Kit.heightInterval, targetTimeSpan: targetTimespan, maxTargetBits: Kit.maxTargetBits, powDGWHeight: 4002)) + } + + blockValidatorSet.add(blockValidator: blockValidatorChain) + + let bitcoinCore = try BitcoinCoreBuilder(logger: logger) .set(network: network) - .set(words: words) + .set(seed: seed) .set(initialSyncApi: initialSyncApi) .set(paymentAddressParser: paymentAddressParser) - .set(addressSelector: addressSelector) .set(walletId: walletId) .set(confirmationsThreshold: confirmationsThreshold) .set(peerSize: 10) @@ -69,6 +84,7 @@ public class DashKit: AbstractKit { .set(syncMode: syncMode) .set(blockHeaderHasher: x11Hasher) .set(transactionInfoConverter: dashTransactionInfoConverter) + .set(blockValidator: blockValidatorSet) .build() super.init(bitcoinCore: bitcoinCore, network: network) bitcoinCore.delegate = self @@ -82,21 +98,10 @@ public class DashKit: AbstractKit { .add(messageParser: TransactionLockVoteMessageParser()) .add(messageParser: MasternodeListDiffMessageParser(masternodeParser: masternodeParser, quorumParser: quorumParser)) .add(messageParser: ISLockParser(hasher: doubleShaHasher)) + .add(messageParser: TransactionMessageParser(hasher: doubleShaHasher)) bitcoinCore.add(messageSerializer: GetMasternodeListDiffMessageSerializer()) - let blockHelper = BlockValidatorHelper(storage: storage) - let difficultyEncoder = DifficultyEncoder() - - let targetTimespan = DashKit.heightInterval * DashKit.targetSpacing // Time to mining all 24 blocks in circle - switch networkType { - case .mainNet: - bitcoinCore.add(blockValidator: DarkGravityWaveValidator(encoder: difficultyEncoder, blockHelper: blockHelper, heightInterval: DashKit.heightInterval , targetTimeSpan: targetTimespan, maxTargetBits: DashKit.maxTargetBits, firstCheckpointHeight: network.lastCheckpointBlock.height, powDGWHeight: 68589)) - case .testNet: - bitcoinCore.add(blockValidator: DarkGravityWaveTestNetValidator(difficultyEncoder: difficultyEncoder, targetSpacing: DashKit.targetSpacing, targetTimeSpan: targetTimespan, maxTargetBits: DashKit.maxTargetBits, powDGWHeight: 4002)) - bitcoinCore.add(blockValidator: DarkGravityWaveValidator(encoder: difficultyEncoder, blockHelper: blockHelper, heightInterval: DashKit.heightInterval, targetTimeSpan: targetTimespan, maxTargetBits: DashKit.maxTargetBits, firstCheckpointHeight: network.lastCheckpointBlock.height, powDGWHeight: 4002)) - } - let merkleBranch = MerkleBranch(hasher: doubleShaHasher) let masternodeSerializer = MasternodeSerializer() @@ -121,8 +126,10 @@ public class DashKit: AbstractKit { let calculator = TransactionSizeCalculator() let confirmedUnspentOutputProvider = ConfirmedUnspentOutputProvider(storage: storage, confirmationsThreshold: confirmationsThreshold) - bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelector(calculator: calculator, provider: confirmedUnspentOutputProvider)) - bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelectorSingleNoChange(calculator: calculator, provider: confirmedUnspentOutputProvider)) + let dustCalculator = DustCalculator(dustRelayTxFee: network.dustRelayTxFee, sizeCalculator: calculator) + + bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelector(calculator: calculator, provider: confirmedUnspentOutputProvider, dustCalculator: dustCalculator)) + bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelectorSingleNoChange(calculator: calculator, provider: confirmedUnspentOutputProvider, dustCalculator: dustCalculator)) // -------------------------------------- let transactionLockVoteValidator = TransactionLockVoteValidator(storage: storage, hasher: singleHasher) let instantSendLockValidator = InstantSendLockValidator(quorumListManager: quorumListManager, hasher: doubleShaHasher) @@ -142,24 +149,29 @@ public class DashKit: AbstractKit { bitcoinCore.add(peerTaskHandler: instantSend) bitcoinCore.add(inventoryItemsHandler: instantSend) // -------------------------------------- - + let base58AddressConverter = Base58AddressConverter(addressVersion: network.pubKeyHash, addressScriptVersion: network.scriptHash) + bitcoinCore.add(restoreKeyConverter: Bip44RestoreKeyConverter(addressConverter: base58AddressConverter)) } private func cast(transactionInfos:[TransactionInfo]) -> [DashTransactionInfo] { - return transactionInfos.compactMap { $0 as? DashTransactionInfo } + transactionInfos.compactMap { $0 as? DashTransactionInfo } + } + + public override func send(to address: String, value: Int, feeRate: Int, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData]) throws -> FullTransaction { + try super.send(to: address, value: value, feeRate: feeRate, sortType: sortType) } - public override func send(to address: String, value: Int, feeRate: Int) throws { - try super.send(to: address, value: value, feeRate: feeRate) + public func transactions(fromUid: String? = nil, type: TransactionFilterType?, limit: Int? = nil) -> Single<[DashTransactionInfo]> { + super.transactions(fromUid: fromUid, type: type, limit: limit).map { self.cast(transactionInfos: $0) } } - public func transactions(fromHash: String?, limit: Int?) -> Single<[DashTransactionInfo]> { - return super.transactions(fromHash: fromHash, limit: limit).map { self.cast(transactionInfos: $0) } + override public func transaction(hash: String) -> DashTransactionInfo? { + super.transaction(hash: hash) as? DashTransactionInfo } } -extension DashKit: BitcoinCoreDelegate { +extension Kit: BitcoinCoreDelegate { public func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) { // check for all new transactions if it's has instant lock @@ -172,7 +184,7 @@ extension DashKit: BitcoinCoreDelegate { delegate?.transactionsDeleted(hashes: hashes) } - public func balanceUpdated(balance: Int) { + public func balanceUpdated(balance: BalanceInfo) { delegate?.balanceUpdated(balance: balance) } @@ -186,10 +198,10 @@ extension DashKit: BitcoinCoreDelegate { } -extension DashKit: IInstantTransactionDelegate { +extension Kit: IInstantTransactionDelegate { public func onUpdateInstant(transactionHash: Data) { - guard let transaction = storage.fullTransactionInfo(byHash: transactionHash) else { + guard let transaction = storage.transactionFullInfo(byHash: transactionHash) else { return } let transactionInfo = dashTransactionInfoConverter.transactionInfo(fromTransaction: transaction) @@ -201,3 +213,15 @@ extension DashKit: IInstantTransactionDelegate { } } + +extension Kit { + + public static func clear(exceptFor walletIdsToExclude: [String] = []) throws { + try DirectoryHelper.removeAll(inDirectory: Kit.name, except: walletIdsToExclude) + } + + private static func databaseFileName(walletId: String, networkType: NetworkType, syncMode: BitcoinCore.SyncMode) -> String { + "\(walletId)-\(networkType.rawValue)-\(syncMode)" + } + +} diff --git a/DashKit/DashKit/Core/Protocols.swift b/DashKit/Classes/Core/Protocols.swift similarity index 95% rename from DashKit/DashKit/Core/Protocols.swift rename to DashKit/Classes/Core/Protocols.swift index e4cca286..8cc710b4 100644 --- a/DashKit/DashKit/Core/Protocols.swift +++ b/DashKit/Classes/Core/Protocols.swift @@ -17,14 +17,14 @@ protocol IDashBlockValidatorHelper { } protocol IDashTransactionSizeCalculator { - func transactionSize(inputs: [ScriptType], outputScriptTypes: [ScriptType]) -> Int + func transactionSize(previousOutputs: [Output], outputScriptTypes: [ScriptType]) -> Int func outputSize(type: ScriptType) -> Int func inputSize(type: ScriptType) -> Int func toBytes(fee: Int) -> Int } protocol IDashTransactionSyncer { - func handle(transactions: [FullTransaction]) + func handleRelayed(transactions: [FullTransaction]) } protocol IDashPeer: IPeer { @@ -51,7 +51,7 @@ protocol IDashPeer: IPeer { public protocol DashKitDelegate: class { func transactionsUpdated(inserted: [DashTransactionInfo], updated: [DashTransactionInfo]) func transactionsDeleted(hashes: [String]) - func balanceUpdated(balance: Int) + func balanceUpdated(balance: BalanceInfo) func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) func kitStateUpdated(state: BitcoinCore.KitState) } @@ -95,7 +95,7 @@ protocol IDashStorage { func unspentOutputs() -> [UnspentOutput] func transactionExists(byHash: Data) -> Bool - func fullTransactionInfo(byHash hash: Data) -> FullTransactionForInfo? + func transactionFullInfo(byHash hash: Data) -> FullTransactionForInfo? } protocol IInstantSendFactory { diff --git a/DashKit/DashKit/DashKitErrors.swift b/DashKit/Classes/DashKitErrors.swift similarity index 100% rename from DashKit/DashKit/DashKitErrors.swift rename to DashKit/Classes/DashKitErrors.swift diff --git a/DashKit/DashKit/InstantSend/InstantSend.swift b/DashKit/Classes/InstantSend/InstantSend.swift similarity index 94% rename from DashKit/DashKit/InstantSend/InstantSend.swift rename to DashKit/Classes/InstantSend/InstantSend.swift index e20501c6..b9745458 100644 --- a/DashKit/DashKit/InstantSend/InstantSend.swift +++ b/DashKit/Classes/InstantSend/InstantSend.swift @@ -1,4 +1,5 @@ import BitcoinCore +import HsToolKit enum DashInventoryType: Int32 { case msgTxLockRequest = 4, msgTxLockVote = 5, msgIsLock = 30 } @@ -11,7 +12,7 @@ class InstantSend { private let instantSendLockHandler: IInstantSendLockHandler private let logger: Logger? - init(transactionSyncer: IDashTransactionSyncer, transactionLockVoteHandler: ITransactionLockVoteHandler, instantSendLockHandler: IInstantSendLockHandler, dispatchQueue: DispatchQueue = DispatchQueue(label: "DashInstantSend", qos: .userInitiated), logger: Logger? = nil) { + init(transactionSyncer: IDashTransactionSyncer, transactionLockVoteHandler: ITransactionLockVoteHandler, instantSendLockHandler: IInstantSendLockHandler, dispatchQueue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.dash-kit.instant-send", qos: .userInitiated), logger: Logger? = nil) { self.transactionSyncer = transactionSyncer self.transactionLockVoteHandler = transactionLockVoteHandler self.instantSendLockHandler = instantSendLockHandler @@ -53,7 +54,7 @@ extension InstantSend: IPeerTaskHandler { } private func handle(transactions: [FullTransaction]) { - transactionSyncer.handle(transactions: transactions) + transactionSyncer.handleRelayed(transactions: transactions) for transaction in transactions { transactionLockVoteHandler.handle(transaction: transaction) diff --git a/DashKit/DashKit/InstantSend/InstantSendFactory.swift b/DashKit/Classes/InstantSend/InstantSendFactory.swift similarity index 100% rename from DashKit/DashKit/InstantSend/InstantSendFactory.swift rename to DashKit/Classes/InstantSend/InstantSendFactory.swift diff --git a/DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockHandler.swift b/DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockHandler.swift similarity index 99% rename from DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockHandler.swift rename to DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockHandler.swift index bc82bdb2..0a9fc7e7 100644 --- a/DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockHandler.swift +++ b/DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockHandler.swift @@ -1,4 +1,5 @@ import BitcoinCore +import HsToolKit class InstantSendLockHandler: IInstantSendLockHandler { private let instantTransactionManager: IInstantTransactionManager diff --git a/DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockManager.swift b/DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockManager.swift similarity index 100% rename from DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockManager.swift rename to DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockManager.swift diff --git a/DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockValidator.swift b/DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockValidator.swift similarity index 86% rename from DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockValidator.swift rename to DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockValidator.swift index dd2df73b..89b3d7f0 100644 --- a/DashKit/DashKit/InstantSend/InstantSendLock/InstantSendLockValidator.swift +++ b/DashKit/Classes/InstantSend/InstantSendLock/InstantSendLockValidator.swift @@ -1,4 +1,4 @@ -import CryptoBLS +import BlsKit class InstantSendLockValidator: IInstantSendLockValidator { private let quorumListManager: IQuorumListManager @@ -20,7 +20,7 @@ class InstantSendLockValidator: IInstantSendLockValidator { signId = hasher.hash(data: signId) // 03. Verify signature by BLS - let verified = CryptoBLS.verify(messageDigest: signId, pubKey: quorum.quorumPublicKey, signature: isLock.sign) + let verified = BlsKit.Kit.verify(messageDigest: signId, pubKey: quorum.quorumPublicKey, signature: isLock.sign) guard verified else { throw DashKitErrors.ISLockValidation.signatureNotValid diff --git a/DashKit/DashKit/InstantSend/InstantTransactionManager.swift b/DashKit/Classes/InstantSend/InstantTransactionManager.swift similarity index 100% rename from DashKit/DashKit/InstantSend/InstantTransactionManager.swift rename to DashKit/Classes/InstantSend/InstantTransactionManager.swift diff --git a/DashKit/DashKit/InstantSend/InstantTransactionSyncer.swift b/DashKit/Classes/InstantSend/InstantTransactionSyncer.swift similarity index 66% rename from DashKit/DashKit/InstantSend/InstantTransactionSyncer.swift rename to DashKit/Classes/InstantSend/InstantTransactionSyncer.swift index a454568b..0f9127bd 100644 --- a/DashKit/DashKit/InstantSend/InstantTransactionSyncer.swift +++ b/DashKit/Classes/InstantSend/InstantTransactionSyncer.swift @@ -7,8 +7,8 @@ class InstantTransactionSyncer: IDashTransactionSyncer { self.transactionSyncer = transactionSyncer } - func handle(transactions: [FullTransaction]) { - transactionSyncer.handle(transactions: transactions) + func handleRelayed(transactions: [FullTransaction]) { + transactionSyncer.handleRelayed(transactions: transactions) } } diff --git a/DashKit/DashKit/InstantSend/TransactionLockVote/QorumMasternode.swift b/DashKit/Classes/InstantSend/TransactionLockVote/QorumMasternode.swift similarity index 100% rename from DashKit/DashKit/InstantSend/TransactionLockVote/QorumMasternode.swift rename to DashKit/Classes/InstantSend/TransactionLockVote/QorumMasternode.swift diff --git a/DashKit/DashKit/InstantSend/TransactionLockVote/SingleHasher.swift b/DashKit/Classes/InstantSend/TransactionLockVote/SingleHasher.swift similarity index 63% rename from DashKit/DashKit/InstantSend/TransactionLockVote/SingleHasher.swift rename to DashKit/Classes/InstantSend/TransactionLockVote/SingleHasher.swift index 78b5380b..38301992 100644 --- a/DashKit/DashKit/InstantSend/TransactionLockVote/SingleHasher.swift +++ b/DashKit/Classes/InstantSend/TransactionLockVote/SingleHasher.swift @@ -1,10 +1,10 @@ -import HSCryptoKit +import OpenSslKit import BitcoinCore class SingleHasher: IDashHasher { func hash(data: Data) -> Data { - return CryptoKit.sha256(data) + OpenSslKit.Kit.sha256(data) } } diff --git a/DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteHandler.swift b/DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteHandler.swift similarity index 99% rename from DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteHandler.swift rename to DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteHandler.swift index d4edb86c..9a875486 100644 --- a/DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteHandler.swift +++ b/DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteHandler.swift @@ -1,4 +1,5 @@ import BitcoinCore +import HsToolKit class TransactionLockVoteHandler: ITransactionLockVoteHandler { private let requiredVoteCount: Int diff --git a/DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteManager.swift b/DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteManager.swift similarity index 100% rename from DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteManager.swift rename to DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteManager.swift diff --git a/DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteValidator.swift b/DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteValidator.swift similarity index 90% rename from DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteValidator.swift rename to DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteValidator.swift index 193eb33d..f215f99f 100644 --- a/DashKit/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteValidator.swift +++ b/DashKit/Classes/InstantSend/TransactionLockVote/TransactionLockVoteValidator.swift @@ -1,6 +1,5 @@ -import HSCryptoKit import BitcoinCore -import CryptoBLS +import BlsKit class TransactionLockVoteValidator: ITransactionLockVoteValidator { private let storage: IDashStorage @@ -42,7 +41,7 @@ class TransactionLockVoteValidator: ITransactionLockVoteValidator { // 5. Check signature of masternode let masternode = quorumMasternodes[index].masternode - if !CryptoBLS.verify(messageDigest: lockVote.hash, pubKey: masternode.pubKeyOperator, signature: lockVote.vchMasternodeSignature) { + if !BlsKit.Kit.verify(messageDigest: lockVote.hash, pubKey: masternode.pubKeyOperator, signature: lockVote.vchMasternodeSignature) { throw DashKitErrors.LockVoteValidation.signatureNotValid } } diff --git a/DashKit/DashKit/Managers/ConfirmedUnspentOutputProvider.swift b/DashKit/Classes/Managers/ConfirmedUnspentOutputProvider.swift similarity index 95% rename from DashKit/DashKit/Managers/ConfirmedUnspentOutputProvider.swift rename to DashKit/Classes/Managers/ConfirmedUnspentOutputProvider.swift index 4724eb18..422f4727 100644 --- a/DashKit/DashKit/Managers/ConfirmedUnspentOutputProvider.swift +++ b/DashKit/Classes/Managers/ConfirmedUnspentOutputProvider.swift @@ -13,7 +13,7 @@ class ConfirmedUnspentOutputProvider { extension ConfirmedUnspentOutputProvider: IUnspentOutputProvider { - var allUnspentOutputs: [UnspentOutput] { + var spendableUtxo: [UnspentOutput] { let lastBlockHeight = storage.lastBlock?.height ?? 0 // Output must have a public key, that is, must belong to the user diff --git a/DashKit/DashKit/MasternodeList/MasternodeCbTxHashCalculator.swift b/DashKit/Classes/MasternodeList/MasternodeCbTxHashCalculator.swift similarity index 100% rename from DashKit/DashKit/MasternodeList/MasternodeCbTxHashCalculator.swift rename to DashKit/Classes/MasternodeList/MasternodeCbTxHashCalculator.swift diff --git a/DashKit/DashKit/MasternodeList/MasternodeListManager.swift b/DashKit/Classes/MasternodeList/MasternodeListManager.swift similarity index 100% rename from DashKit/DashKit/MasternodeList/MasternodeListManager.swift rename to DashKit/Classes/MasternodeList/MasternodeListManager.swift diff --git a/DashKit/DashKit/MasternodeList/MasternodeListMerkleRootCalculator.swift b/DashKit/Classes/MasternodeList/MasternodeListMerkleRootCalculator.swift similarity index 100% rename from DashKit/DashKit/MasternodeList/MasternodeListMerkleRootCalculator.swift rename to DashKit/Classes/MasternodeList/MasternodeListMerkleRootCalculator.swift diff --git a/DashKit/DashKit/MasternodeList/MasternodeListSyncer.swift b/DashKit/Classes/MasternodeList/MasternodeListSyncer.swift similarity index 96% rename from DashKit/DashKit/MasternodeList/MasternodeListSyncer.swift rename to DashKit/Classes/MasternodeList/MasternodeListSyncer.swift index 5527b693..b2c568ff 100644 --- a/DashKit/DashKit/MasternodeList/MasternodeListSyncer.swift +++ b/DashKit/Classes/MasternodeList/MasternodeListSyncer.swift @@ -12,7 +12,7 @@ class MasternodeListSyncer: IMasternodeListSyncer { private let queue: DispatchQueue init(bitcoinCore: BitcoinCore, initialBlockDownload: IInitialBlockDownload, peerTaskFactory: IPeerTaskFactory, masternodeListManager: IMasternodeListManager, - queue: DispatchQueue = DispatchQueue(label: "MasternodeListSyncer", qos: .background)) { + queue: DispatchQueue = DispatchQueue(label: "io.horizontalsystems.dash-kit.masternode-list-syncer", qos: .background)) { self.bitcoinCore = bitcoinCore self.initialBlockDownload = initialBlockDownload self.peerTaskFactory = peerTaskFactory diff --git a/DashKit/DashKit/MasternodeList/MasternodeSortedList.swift b/DashKit/Classes/MasternodeList/MasternodeSortedList.swift similarity index 100% rename from DashKit/DashKit/MasternodeList/MasternodeSortedList.swift rename to DashKit/Classes/MasternodeList/MasternodeSortedList.swift diff --git a/DashKit/DashKit/MasternodeList/MerkleRootCreator.swift b/DashKit/Classes/MasternodeList/MerkleRootCreator.swift similarity index 100% rename from DashKit/DashKit/MasternodeList/MerkleRootCreator.swift rename to DashKit/Classes/MasternodeList/MerkleRootCreator.swift diff --git a/DashKit/DashKit/Models/CoinbaseTransaction.swift b/DashKit/Classes/Models/CoinbaseTransaction.swift similarity index 100% rename from DashKit/DashKit/Models/CoinbaseTransaction.swift rename to DashKit/Classes/Models/CoinbaseTransaction.swift diff --git a/DashKit/Classes/Models/DashTransactionInfo.swift b/DashKit/Classes/Models/DashTransactionInfo.swift new file mode 100644 index 00000000..5350cf01 --- /dev/null +++ b/DashKit/Classes/Models/DashTransactionInfo.swift @@ -0,0 +1,28 @@ +import BitcoinCore + +public class DashTransactionInfo: TransactionInfo { + public var instantTx: Bool = false + + private enum CodingKeys: String, CodingKey { + case instantTx + } + + public required init(uid: String, transactionHash: String, transactionIndex: Int, inputs: [TransactionInputInfo], outputs: [TransactionOutputInfo], amount: Int, type: TransactionType, fee: Int?, blockHeight: Int?, timestamp: Int, status: TransactionStatus, conflictingHash: String?) { + super.init(uid: uid, transactionHash: transactionHash, transactionIndex: transactionIndex, inputs: inputs, outputs: outputs, amount: amount, type: type, fee: fee, blockHeight: blockHeight, timestamp: timestamp, status: status, conflictingHash: conflictingHash) + } + + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + let container = try decoder.container(keyedBy: CodingKeys.self) + instantTx = try container.decode(Bool.self, forKey: .instantTx) + } + + override public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(instantTx, forKey: .instantTx) + + try super.encode(to: encoder) + } + +} diff --git a/DashKit/DashKit/Models/InstantTransactionHash.swift b/DashKit/Classes/Models/InstantTransactionHash.swift similarity index 100% rename from DashKit/DashKit/Models/InstantTransactionHash.swift rename to DashKit/Classes/Models/InstantTransactionHash.swift diff --git a/DashKit/DashKit/Models/InstantTransactionInput.swift b/DashKit/Classes/Models/InstantTransactionInput.swift similarity index 100% rename from DashKit/DashKit/Models/InstantTransactionInput.swift rename to DashKit/Classes/Models/InstantTransactionInput.swift diff --git a/DashKit/DashKit/Models/InstantTransactionState.swift b/DashKit/Classes/Models/InstantTransactionState.swift similarity index 100% rename from DashKit/DashKit/Models/InstantTransactionState.swift rename to DashKit/Classes/Models/InstantTransactionState.swift diff --git a/DashKit/DashKit/Models/Masternode.swift b/DashKit/Classes/Models/Masternode.swift similarity index 100% rename from DashKit/DashKit/Models/Masternode.swift rename to DashKit/Classes/Models/Masternode.swift diff --git a/DashKit/DashKit/Models/MasternodeListState.swift b/DashKit/Classes/Models/MasternodeListState.swift similarity index 100% rename from DashKit/DashKit/Models/MasternodeListState.swift rename to DashKit/Classes/Models/MasternodeListState.swift diff --git a/DashKit/DashKit/Models/ModelSerializers/CoinbaseTransactionSerializer.swift b/DashKit/Classes/Models/ModelSerializers/CoinbaseTransactionSerializer.swift similarity index 100% rename from DashKit/DashKit/Models/ModelSerializers/CoinbaseTransactionSerializer.swift rename to DashKit/Classes/Models/ModelSerializers/CoinbaseTransactionSerializer.swift diff --git a/DashKit/DashKit/Models/ModelSerializers/MasternodeSerializer.swift b/DashKit/Classes/Models/ModelSerializers/MasternodeSerializer.swift similarity index 100% rename from DashKit/DashKit/Models/ModelSerializers/MasternodeSerializer.swift rename to DashKit/Classes/Models/ModelSerializers/MasternodeSerializer.swift diff --git a/DashKit/DashKit/Models/Quorum.swift b/DashKit/Classes/Models/Quorum.swift similarity index 87% rename from DashKit/DashKit/Models/Quorum.swift rename to DashKit/Classes/Models/Quorum.swift index 90cc80e8..f94c7461 100644 --- a/DashKit/DashKit/Models/Quorum.swift +++ b/DashKit/Classes/Models/Quorum.swift @@ -1,3 +1,4 @@ +import Foundation import GRDB class Quorum: Record { @@ -6,6 +7,7 @@ class Quorum: Record { let type: UInt8 let quorumHash: Data let typeWithQuorumHash: Data + let quorumIndex: UInt16? let signers: Data let validMembers: Data let quorumPublicKey: Data @@ -14,7 +16,7 @@ class Quorum: Record { let sig: Data override class var databaseTableName: String { - return "quorums" + "quorums" } enum Columns: String, ColumnExpression { @@ -23,6 +25,7 @@ class Quorum: Record { case type case quorumHash case typeWithQuorumHash + case quorumIndex case signers case validMembers case quorumPublicKey @@ -37,6 +40,7 @@ class Quorum: Record { type = row[Columns.type] quorumHash = row[Columns.quorumHash] typeWithQuorumHash = row[Columns.typeWithQuorumHash] + quorumIndex = row[Columns.quorumIndex] signers = row[Columns.signers] validMembers = row[Columns.validMembers] quorumPublicKey = row[Columns.quorumPublicKey] @@ -53,6 +57,7 @@ class Quorum: Record { container[Columns.type] = type container[Columns.quorumHash] = quorumHash container[Columns.typeWithQuorumHash] = typeWithQuorumHash + container[Columns.quorumIndex] = quorumIndex container[Columns.signers] = signers container[Columns.validMembers] = validMembers container[Columns.quorumPublicKey] = quorumPublicKey @@ -61,12 +66,13 @@ class Quorum: Record { container[Columns.sig] = sig } - init(hash: Data, version: UInt16, type: UInt8, quorumHash: Data, typeWithQuorumHash: Data, signers: Data, validMembers: Data, quorumPublicKey: Data, quorumVvecHash: Data, quorumSig: Data, sig: Data) { + init(hash: Data, version: UInt16, type: UInt8, quorumHash: Data, typeWithQuorumHash: Data, quorumIndex: UInt16?, signers: Data, validMembers: Data, quorumPublicKey: Data, quorumVvecHash: Data, quorumSig: Data, sig: Data) { self.dataHash = hash self.version = version self.type = type self.quorumHash = quorumHash self.typeWithQuorumHash = typeWithQuorumHash + self.quorumIndex = quorumIndex self.signers = signers self.validMembers = validMembers self.quorumPublicKey = quorumPublicKey diff --git a/DashKit/Classes/Models/SpecialTransaction.swift b/DashKit/Classes/Models/SpecialTransaction.swift new file mode 100644 index 00000000..1f88a878 --- /dev/null +++ b/DashKit/Classes/Models/SpecialTransaction.swift @@ -0,0 +1,11 @@ +import Foundation +import BitcoinCore + +class SpecialTransaction: FullTransaction { + let extraPayload: Data + + init(transaction: FullTransaction, extraPayload: Data, forceHashUpdate: Bool = true) { + self.extraPayload = extraPayload + super.init(header: transaction.header, inputs: transaction.inputs, outputs: transaction.outputs, forceHashUpdate: forceHashUpdate) + } +} diff --git a/DashKit/Classes/Network/MainNet.swift b/DashKit/Classes/Network/MainNet.swift new file mode 100644 index 00000000..be6099f6 --- /dev/null +++ b/DashKit/Classes/Network/MainNet.swift @@ -0,0 +1,30 @@ +import BitcoinCore + +public class MainNet: INetwork { + public let protocolVersion: Int32 = 70220 + + public let bundleName = "DashKit" + + public let maxBlockSize: UInt32 = 2_000_000_000 + public let pubKeyHash: UInt8 = 0x4c + public let privateKey: UInt8 = 0x80 + public let scriptHash: UInt8 = 0x10 + public let bech32PrefixPattern: String = "bc" + public let xPubKey: UInt32 = 0x0488b21e + public let xPrivKey: UInt32 = 0x0488ade4 + public let magic: UInt32 = 0xbf0c6bbd + public let port = 9999 + public let coinType: UInt32 = 5 + public let sigHash: SigHashType = .bitcoinAll + public var syncableFromApi: Bool = true + + public let dnsSeeds = [ + "dnsseed.dash.org", + "x5.dnsseed.dashdot.io", + "dnsseed.masternode.io", + ] + + public let dustRelayTxFee = 3000 // https://github.com/dashpay/dash/blob/master/src/policy/policy.h#L38 + + public init() {} +} diff --git a/DashKit/DashKit/Network/Messages/GetMasternodeListDiffMessage.swift b/DashKit/Classes/Network/Messages/GetMasternodeListDiffMessage.swift similarity index 100% rename from DashKit/DashKit/Network/Messages/GetMasternodeListDiffMessage.swift rename to DashKit/Classes/Network/Messages/GetMasternodeListDiffMessage.swift diff --git a/DashKit/DashKit/Network/Messages/ISLockMessage.swift b/DashKit/Classes/Network/Messages/ISLockMessage.swift similarity index 100% rename from DashKit/DashKit/Network/Messages/ISLockMessage.swift rename to DashKit/Classes/Network/Messages/ISLockMessage.swift diff --git a/DashKit/DashKit/Network/Messages/MasternodeListDiffMessage.swift b/DashKit/Classes/Network/Messages/MasternodeListDiffMessage.swift similarity index 100% rename from DashKit/DashKit/Network/Messages/MasternodeListDiffMessage.swift rename to DashKit/Classes/Network/Messages/MasternodeListDiffMessage.swift diff --git a/DashKit/DashKit/Network/Messages/Outpoint.swift b/DashKit/Classes/Network/Messages/Outpoint.swift similarity index 100% rename from DashKit/DashKit/Network/Messages/Outpoint.swift rename to DashKit/Classes/Network/Messages/Outpoint.swift diff --git a/DashKit/DashKit/Network/Messages/TransactionLockMessage.swift b/DashKit/Classes/Network/Messages/TransactionLockMessage.swift similarity index 100% rename from DashKit/DashKit/Network/Messages/TransactionLockMessage.swift rename to DashKit/Classes/Network/Messages/TransactionLockMessage.swift diff --git a/DashKit/DashKit/Network/Messages/TransactionLockVoteMessage.swift b/DashKit/Classes/Network/Messages/TransactionLockVoteMessage.swift similarity index 100% rename from DashKit/DashKit/Network/Messages/TransactionLockVoteMessage.swift rename to DashKit/Classes/Network/Messages/TransactionLockVoteMessage.swift diff --git a/DashKit/Classes/Network/Messages/TransactionMessageParser.swift b/DashKit/Classes/Network/Messages/TransactionMessageParser.swift new file mode 100644 index 00000000..7676bccc --- /dev/null +++ b/DashKit/Classes/Network/Messages/TransactionMessageParser.swift @@ -0,0 +1,52 @@ +import Foundation +import BitcoinCore +import OpenSslKit + +class TransactionMessageParser: IMessageParser { + let id: String = "tx" + + let hasher: IDashHasher + + init(hasher: IDashHasher) { + self.hasher = hasher + } + + private func parseSpecialTxData(input: ByteStream, transaction: FullTransaction) throws -> SpecialTransaction { + let payloadSize = input.read(VarInt.self) + guard payloadSize.underlyingValue != 0 else { + throw SpecialTransactionError.noExtraPayload + } + + let payload = input.read(Data.self, count: Int(payloadSize.underlyingValue)) + + var output = TransactionSerializer.serialize(transaction: transaction) + output += payloadSize.data + output += payload + + let hash = hasher.hash(data: output) + transaction.set(hash: hash) + + return SpecialTransaction(transaction: transaction, extraPayload: payload, forceHashUpdate: false) + } + + func parse(data: Data) -> IMessage { + let byteStream = ByteStream(data) + var transaction = TransactionSerializer.deserialize(byteStream: byteStream) + + let version = Data(from: transaction.header.version) + + // Version type is last 2 bytes of version. Special txs has none zero type and has extraPayload + let isSpecialTransaction = (Int(version[2]) + Int(version[3])) > 0 + if isSpecialTransaction, let specialTransaction = try? parseSpecialTxData(input: byteStream, transaction: transaction) { + transaction = specialTransaction + } + + return TransactionMessage(transaction: transaction, size: data.count) + } +} + +extension TransactionMessageParser { + enum SpecialTransactionError: Error { + case noExtraPayload + } +} diff --git a/DashKit/DashKit/Network/Parsers/ISLockParser.swift b/DashKit/Classes/Network/Parsers/ISLockParser.swift similarity index 100% rename from DashKit/DashKit/Network/Parsers/ISLockParser.swift rename to DashKit/Classes/Network/Parsers/ISLockParser.swift diff --git a/DashKit/DashKit/Network/Parsers/MasternodeListDiffMessageParser.swift b/DashKit/Classes/Network/Parsers/MasternodeListDiffMessageParser.swift similarity index 99% rename from DashKit/DashKit/Network/Parsers/MasternodeListDiffMessageParser.swift rename to DashKit/Classes/Network/Parsers/MasternodeListDiffMessageParser.swift index 2347120b..b4058e8b 100644 --- a/DashKit/DashKit/Network/Parsers/MasternodeListDiffMessageParser.swift +++ b/DashKit/Classes/Network/Parsers/MasternodeListDiffMessageParser.swift @@ -1,4 +1,3 @@ -import HSCryptoKit import BitcoinCore class MasternodeListDiffMessageParser: IMessageParser { diff --git a/DashKit/DashKit/Network/Parsers/MasternodeParser.swift b/DashKit/Classes/Network/Parsers/MasternodeParser.swift similarity index 98% rename from DashKit/DashKit/Network/Parsers/MasternodeParser.swift rename to DashKit/Classes/Network/Parsers/MasternodeParser.swift index fec99d0c..b25459c3 100644 --- a/DashKit/DashKit/Network/Parsers/MasternodeParser.swift +++ b/DashKit/Classes/Network/Parsers/MasternodeParser.swift @@ -1,4 +1,3 @@ -import HSCryptoKit import BitcoinCore class MasternodeParser: IMasternodeParser { diff --git a/DashKit/DashKit/Network/Parsers/QuorumParser.swift b/DashKit/Classes/Network/Parsers/QuorumParser.swift similarity index 60% rename from DashKit/DashKit/Network/Parsers/QuorumParser.swift rename to DashKit/Classes/Network/Parsers/QuorumParser.swift index e1a7cadd..85affdce 100644 --- a/DashKit/DashKit/Network/Parsers/QuorumParser.swift +++ b/DashKit/Classes/Network/Parsers/QuorumParser.swift @@ -1,5 +1,6 @@ -import HSCryptoKit import BitcoinCore +import Foundation +import OpenSslKit class QuorumParser: IQuorumParser { let hasher: IDashHasher @@ -16,6 +17,14 @@ class QuorumParser: IQuorumParser { let type = typeWithQuorumHash[0] let quorumHash = typeWithQuorumHash.subdata(in: Range(uncheckedBounds: (lower: 1, upper: 33))) + var quorumIndexData: Data? + var quorumIndex: UInt16? + if version == 2 { // read v2 quorumIndex + let indexData = byteStream.read(Data.self, count: 2) + quorumIndex = indexData.to(type: UInt16.self).littleEndian + quorumIndexData = indexData + } + let signerSizeVarInt = byteStream.read(VarInt.self) let signerSize = Int(signerSizeVarInt.underlyingValue) @@ -31,19 +40,23 @@ class QuorumParser: IQuorumParser { let quorumSig = byteStream.read(Data.self, count: 96) let sig = byteStream.read(Data.self, count: 96) - let data = versionData + - type + - quorumHash + - signerSizeVarInt.data + - signers + - validMemberSizeVarInt.data + - members + - quorumPublicKey + - quorumVvecHash + - quorumSig + - sig + + var data = versionData + + type + + quorumHash + if let index = quorumIndexData { + data += index + } + data += signerSizeVarInt.data + + signers + + validMemberSizeVarInt.data + + members + + quorumPublicKey + + quorumVvecHash + + quorumSig + + sig let hash = hasher.hash(data: data) - return Quorum(hash: hash, version: version, type: type, quorumHash: quorumHash, typeWithQuorumHash: typeWithQuorumHash, signers: signers, validMembers: members, quorumPublicKey: quorumPublicKey, quorumVvecHash: quorumVvecHash, quorumSig: quorumSig, sig: sig) + return Quorum(hash: hash, version: version, type: type, quorumHash: quorumHash, typeWithQuorumHash: typeWithQuorumHash, quorumIndex: quorumIndex, signers: signers, validMembers: members, quorumPublicKey: quorumPublicKey, quorumVvecHash: quorumVvecHash, quorumSig: quorumSig, sig: sig) } } diff --git a/DashKit/DashKit/Network/Parsers/TransactionLockMessageParser.swift b/DashKit/Classes/Network/Parsers/TransactionLockMessageParser.swift similarity index 100% rename from DashKit/DashKit/Network/Parsers/TransactionLockMessageParser.swift rename to DashKit/Classes/Network/Parsers/TransactionLockMessageParser.swift diff --git a/DashKit/DashKit/Network/Parsers/TransactionLockVoteMessageParser.swift b/DashKit/Classes/Network/Parsers/TransactionLockVoteMessageParser.swift similarity index 92% rename from DashKit/DashKit/Network/Parsers/TransactionLockVoteMessageParser.swift rename to DashKit/Classes/Network/Parsers/TransactionLockVoteMessageParser.swift index ed385229..43d687bd 100644 --- a/DashKit/DashKit/Network/Parsers/TransactionLockVoteMessageParser.swift +++ b/DashKit/Classes/Network/Parsers/TransactionLockVoteMessageParser.swift @@ -1,5 +1,5 @@ -import HSCryptoKit import BitcoinCore +import OpenSslKit class TransactionLockVoteMessageParser: IMessageParser { var id: String { return "txlvote" } @@ -15,7 +15,7 @@ class TransactionLockVoteMessageParser: IMessageParser { let signatureLength = byteStream.read(VarInt.self) let vchMasternodeSignature = byteStream.read(Data.self, count: Int(signatureLength.underlyingValue)) - let hash = CryptoKit.sha256sha256(data.prefix(168)) + let hash = OpenSslKit.Kit.sha256sha256(data.prefix(168)) return TransactionLockVoteMessage(txHash: txHash, outpoint: outpoint, diff --git a/DashKit/DashKit/Network/Peer/PeerTask/PeerTaskFactory.swift b/DashKit/Classes/Network/Peer/PeerTask/PeerTaskFactory.swift similarity index 100% rename from DashKit/DashKit/Network/Peer/PeerTask/PeerTaskFactory.swift rename to DashKit/Classes/Network/Peer/PeerTask/PeerTaskFactory.swift diff --git a/DashKit/DashKit/Network/Peer/PeerTask/RequestLlmqInstantLocksTask.swift b/DashKit/Classes/Network/Peer/PeerTask/RequestLlmqInstantLocksTask.swift similarity index 97% rename from DashKit/DashKit/Network/Peer/PeerTask/RequestLlmqInstantLocksTask.swift rename to DashKit/Classes/Network/Peer/PeerTask/RequestLlmqInstantLocksTask.swift index c239ba8c..dc4bbbbb 100644 --- a/DashKit/DashKit/Network/Peer/PeerTask/RequestLlmqInstantLocksTask.swift +++ b/DashKit/Classes/Network/Peer/PeerTask/RequestLlmqInstantLocksTask.swift @@ -14,7 +14,8 @@ class RequestLlmqInstantLocksTask: PeerTask { override func start() { let items = hashes.map { hash in InventoryItem(type: DashInventoryType.msgIsLock.rawValue, hash: hash) } requester?.send(message: GetDataMessage(inventoryItems: items)) - resetTimer() + + super.start() } override func handle(message: IMessage) -> Bool { diff --git a/DashKit/DashKit/Network/Peer/PeerTask/RequestMasternodeListDiffTask.swift b/DashKit/Classes/Network/Peer/PeerTask/RequestMasternodeListDiffTask.swift similarity index 97% rename from DashKit/DashKit/Network/Peer/PeerTask/RequestMasternodeListDiffTask.swift rename to DashKit/Classes/Network/Peer/PeerTask/RequestMasternodeListDiffTask.swift index c48453a0..a6910cc4 100644 --- a/DashKit/DashKit/Network/Peer/PeerTask/RequestMasternodeListDiffTask.swift +++ b/DashKit/Classes/Network/Peer/PeerTask/RequestMasternodeListDiffTask.swift @@ -15,6 +15,8 @@ class RequestMasternodeListDiffTask: PeerTask { let message = GetMasternodeListDiffMessage(baseBlockHash: baseBlockHash, blockHash: blockHash) requester?.send(message: message) + + super.start() } override func handle(message: IMessage) -> Bool { diff --git a/DashKit/DashKit/Network/Peer/PeerTask/RequestTransactionLockRequestsTask.swift b/DashKit/Classes/Network/Peer/PeerTask/RequestTransactionLockRequestsTask.swift similarity index 98% rename from DashKit/DashKit/Network/Peer/PeerTask/RequestTransactionLockRequestsTask.swift rename to DashKit/Classes/Network/Peer/PeerTask/RequestTransactionLockRequestsTask.swift index 437633a6..2ec16a2a 100644 --- a/DashKit/DashKit/Network/Peer/PeerTask/RequestTransactionLockRequestsTask.swift +++ b/DashKit/Classes/Network/Peer/PeerTask/RequestTransactionLockRequestsTask.swift @@ -14,7 +14,8 @@ class RequestTransactionLockRequestsTask: PeerTask { override func start() { let items = hashes.map { hash in InventoryItem(type: DashInventoryType.msgTxLockRequest.rawValue, hash: hash) } requester?.send(message: GetDataMessage(inventoryItems: items)) - resetTimer() + + super.start() } override func handle(message: IMessage) -> Bool { diff --git a/DashKit/DashKit/Network/Peer/PeerTask/RequestTransactionLockVotesTask.swift b/DashKit/Classes/Network/Peer/PeerTask/RequestTransactionLockVotesTask.swift similarity index 98% rename from DashKit/DashKit/Network/Peer/PeerTask/RequestTransactionLockVotesTask.swift rename to DashKit/Classes/Network/Peer/PeerTask/RequestTransactionLockVotesTask.swift index 8582eda1..00a21753 100644 --- a/DashKit/DashKit/Network/Peer/PeerTask/RequestTransactionLockVotesTask.swift +++ b/DashKit/Classes/Network/Peer/PeerTask/RequestTransactionLockVotesTask.swift @@ -14,7 +14,8 @@ class RequestTransactionLockVotesTask: PeerTask { override func start() { let items = hashes.map { hash in InventoryItem(type: DashInventoryType.msgTxLockVote.rawValue, hash: hash) } requester?.send(message: GetDataMessage(inventoryItems: items)) - resetTimer() + + super.start() } override func handle(message: IMessage) -> Bool { diff --git a/DashKit/DashKit/Network/Serializers/GetMasternodeListDiffMessageSerializer.swift b/DashKit/Classes/Network/Serializers/GetMasternodeListDiffMessageSerializer.swift similarity index 100% rename from DashKit/DashKit/Network/Serializers/GetMasternodeListDiffMessageSerializer.swift rename to DashKit/Classes/Network/Serializers/GetMasternodeListDiffMessageSerializer.swift diff --git a/DashKit/Classes/Network/TestNet.swift b/DashKit/Classes/Network/TestNet.swift new file mode 100644 index 00000000..7c101ea8 --- /dev/null +++ b/DashKit/Classes/Network/TestNet.swift @@ -0,0 +1,27 @@ +import BitcoinCore + +class TestNet: INetwork { + let protocolVersion: Int32 = 70214 + + let bundleName = "DashKit" + + let maxBlockSize: UInt32 = 1_000_000_000 + let pubKeyHash: UInt8 = 0x8c + let privateKey: UInt8 = 0x80 + let scriptHash: UInt8 = 0x13 + let bech32PrefixPattern: String = "bc" + let xPubKey: UInt32 = 0x0488b21e + let xPrivKey: UInt32 = 0x0488ade4 + let magic: UInt32 = 0xcee2caff + let port = 19999 + let coinType: UInt32 = 1 + let sigHash: SigHashType = .bitcoinAll + var syncableFromApi: Bool = true + + let dnsSeeds = [ + "testnet-seed.dashdot.io", + "test.dnsseed.masternode.io" + ] + + let dustRelayTxFee = 1000 // https://github.com/dashpay/dash/blob/master/src/policy/policy.h#L36 +} diff --git a/DashKit/DashKit/Network/X11Hasher.swift b/DashKit/Classes/Network/X11Hasher.swift similarity index 63% rename from DashKit/DashKit/Network/X11Hasher.swift rename to DashKit/Classes/Network/X11Hasher.swift index 3b481a59..5527bce4 100644 --- a/DashKit/DashKit/Network/X11Hasher.swift +++ b/DashKit/Classes/Network/X11Hasher.swift @@ -1,10 +1,10 @@ -import HSCryptoX11 +import X11Kit import BitcoinCore class X11Hasher: IDashHasher, IHasher { func hash(data: Data) -> Data { - return CryptoX11.x11(from: data) + X11Kit.Kit.x11(from: data) } } diff --git a/DashKit/DashKit/QuorumList/QuorumListManager.swift b/DashKit/Classes/QuorumList/QuorumListManager.swift similarity index 100% rename from DashKit/DashKit/QuorumList/QuorumListManager.swift rename to DashKit/Classes/QuorumList/QuorumListManager.swift diff --git a/DashKit/DashKit/QuorumList/QuorumListMerkleRootCalculator.swift b/DashKit/Classes/QuorumList/QuorumListMerkleRootCalculator.swift similarity index 100% rename from DashKit/DashKit/QuorumList/QuorumListMerkleRootCalculator.swift rename to DashKit/Classes/QuorumList/QuorumListMerkleRootCalculator.swift diff --git a/DashKit/DashKit/QuorumList/QuorumSortedList.swift b/DashKit/Classes/QuorumList/QuorumSortedList.swift similarity index 100% rename from DashKit/DashKit/QuorumList/QuorumSortedList.swift rename to DashKit/Classes/QuorumList/QuorumSortedList.swift diff --git a/DashKit/DashKit/QuorumList/QuorumType.swift b/DashKit/Classes/QuorumList/QuorumType.swift similarity index 100% rename from DashKit/DashKit/QuorumList/QuorumType.swift rename to DashKit/Classes/QuorumList/QuorumType.swift diff --git a/DashKit/DashKit/Storage/DashGrdbStorage.swift b/DashKit/Classes/Storage/DashGrdbStorage.swift similarity index 96% rename from DashKit/DashKit/Storage/DashGrdbStorage.swift rename to DashKit/Classes/Storage/DashGrdbStorage.swift index d8f56fda..494b2f63 100644 --- a/DashKit/DashKit/Storage/DashGrdbStorage.swift +++ b/DashKit/Classes/Storage/DashGrdbStorage.swift @@ -66,6 +66,11 @@ class DashGrdbStorage: GrdbStorage { t.primaryKey([Quorum.Columns.hash.name], onConflict: .replace) } } + migrator.registerMigration("add index column to Quorum") { db in + try db.alter(table: Quorum.databaseTableName) { t in + t.add(column: Quorum.Columns.quorumIndex.name, .integer) + } + } return migrator } diff --git a/DashKit/DashKit.xcodeproj/project.pbxproj b/DashKit/DashKit.xcodeproj/project.pbxproj deleted file mode 100644 index 19814a55..00000000 --- a/DashKit/DashKit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1101 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 3A7A7D67226842DD0063D6AD /* DashKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7A7D5D226842DD0063D6AD /* DashKit.framework */; }; - 3A7A7D6E226842DD0063D6AD /* DashKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A7A7D60226842DD0063D6AD /* DashKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3A7A7D7F2268430D0063D6AD /* BitcoinCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7A7D7E2268430D0063D6AD /* BitcoinCore.framework */; }; - 58AAA04D4CDB79715654E978 /* TransactionLockVoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA1776A1D0A594FC7FD18 /* TransactionLockVoteMessage.swift */; }; - 58AAA050B036789F4EB37F6E /* RequestTransactionLockVotesTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6F46B5CED8D35657996 /* RequestTransactionLockVotesTask.swift */; }; - 58AAA055180E96AA33EF2C16 /* TransactionLockVoteManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0731BC44688D2072C72 /* TransactionLockVoteManagerTests.swift */; }; - 58AAA0581D001F2AA294802B /* TestNet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE510575878A4AB340C1 /* TestNet.swift */; }; - 58AAA0C13ECAF509575AC590 /* BitcoinCoreCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA81B4D4D5458EE3FE964 /* BitcoinCoreCompatibility.swift */; }; - 58AAA104320FC00205E999D2 /* InstantSendFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACB2B535583527AD9294 /* InstantSendFactory.swift */; }; - 58AAA15491096368AD69A2D5 /* Masternode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAEB5DE9557F4E41A7907 /* Masternode.swift */; }; - 58AAA15A8BE49BEF4F6FA045 /* InstantSendLockHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD905E4A544A442269AF /* InstantSendLockHandler.swift */; }; - 58AAA1992857F107D48947E9 /* DashTransactionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6FCD2841CF6180EBFB0 /* DashTransactionInfo.swift */; }; - 58AAA1D98DEB1F6230E4EB27 /* TransactionLockVoteValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACFBF2C153954A53AE70 /* TransactionLockVoteValidatorTests.swift */; }; - 58AAA1FC05748154358748CE /* InstantTransactionInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF60A1E9BD745D645A7D /* InstantTransactionInput.swift */; }; - 58AAA2287611FC00ADEEB601 /* QuorumParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAACB945F427DAB7C7783A /* QuorumParser.swift */; }; - 58AAA2515EBD6650E0C005AA /* MasternodeSortedListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0398DAC6811AAD8FF86 /* MasternodeSortedListTests.swift */; }; - 58AAA2BA0BE15B3B5212A3E3 /* InstantSendLockManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5CCE40D60646530BDEC /* InstantSendLockManager.swift */; }; - 58AAA2C9BDA51E4733F3AC0C /* MasternodeListMerkleRootCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA9C78E24573D08995F0 /* MasternodeListMerkleRootCalculatorTests.swift */; }; - 58AAA2E6B0E12CC4C67E294C /* TransactionLockVoteHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAEB8362D398F7229AF3C /* TransactionLockVoteHandlerTests.swift */; }; - 58AAA2EBAFA03520E5C8F1F0 /* InstantTransactionSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2B4C33E32CF46D2D98B /* InstantTransactionSyncer.swift */; }; - 58AAA41C7BA7F4AE78A99578 /* MasternodeListDiffMessageParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA294A297E74A7E6CE52 /* MasternodeListDiffMessageParser.swift */; }; - 58AAA445C4B1CF142B33C236 /* MasternodeListMerkleRootCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB4D9D73D00D4F098FB5 /* MasternodeListMerkleRootCalculator.swift */; }; - 58AAA4484F1C34B6B4E1F2E4 /* DarkGravityWaveValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA60B71D68ABDACE2F8FA /* DarkGravityWaveValidatorTests.swift */; }; - 58AAA48909AC29CFA2761F65 /* QuorumListMerkleRootCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA4AC566C9EC1630A1D83 /* QuorumListMerkleRootCalculator.swift */; }; - 58AAA497CB4425796D178DFF /* MasternodeListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0605DA659EF9FE6A8EA /* MasternodeListState.swift */; }; - 58AAA5020E3111ADB26B2261 /* ConfirmedUnspentOutputProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA135CAE12BD33A6D7D2E /* ConfirmedUnspentOutputProvider.swift */; }; - 58AAA51780FC3C29D1DEC96B /* InstantTransactionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAD1F0767070DBC89C8F /* InstantTransactionManager.swift */; }; - 58AAA51AE7098469E57E970F /* InstantSendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA8F6E9EC26E29F4587B0 /* InstantSendTests.swift */; }; - 58AAA58B34781990B616307A /* Quorum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE54B9246E927E19C091 /* Quorum.swift */; }; - 58AAA59182DEFEC059D60216 /* InstantTransactionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAEA87DCEBE51E9F79BEA /* InstantTransactionManagerTests.swift */; }; - 58AAA59BBD76BAB11AAC061A /* RequestMasternodeListDiffTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2AF3D5C879040D2E7AE /* RequestMasternodeListDiffTask.swift */; }; - 58AAA5DC4702D36F1C7A05B7 /* CoinbaseTransactionSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD56C817AE1E7BB86FDC /* CoinbaseTransactionSerializer.swift */; }; - 58AAA5F7AF7CF8E3A909BD56 /* InstantTransactionHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE8C1DD28970F1631067 /* InstantTransactionHash.swift */; }; - 58AAA64303E7973D3D8D2CD0 /* MasternodeCbTxHashCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA71B32E48969A3E1E52B /* MasternodeCbTxHashCalculator.swift */; }; - 58AAA656BDEBDEE092B93C5F /* DarkGravityWaveValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA26075017A2987200D87 /* DarkGravityWaveValidator.swift */; }; - 58AAA65979EDF654C84E786F /* ISLockParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA9D06F311DA7412688B5 /* ISLockParser.swift */; }; - 58AAA67497C667DD6794E85B /* TransactionLockVoteHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB1C6D209CEB8839FABA /* TransactionLockVoteHandler.swift */; }; - 58AAA6966A02C430A3B46BE6 /* MerkleRootCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAE0DB889BE114C53B2E /* MerkleRootCreatorTests.swift */; }; - 58AAA6C601A18EA4FF399D05 /* QuorumType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB14B3DCDCBED15D0BE5 /* QuorumType.swift */; }; - 58AAA741B1FD041F7B434CD5 /* TransactionLockVoteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA110961D4988D83E86E /* TransactionLockVoteManager.swift */; }; - 58AAA7C359F5952EE5807FDB /* MasternodeParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAF1192518AD77095979C /* MasternodeParser.swift */; }; - 58AAA7D71380E4555B03B662 /* TransactionLockVoteValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA74142320578EB118DB0 /* TransactionLockVoteValidator.swift */; }; - 58AAA7FC48828C89D56C1110 /* MasternodeListSyncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0373C0AAF259D678D34 /* MasternodeListSyncer.swift */; }; - 58AAA831BD89687D9A128590 /* GetMasternodeListDiffMessageSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA59886999B6239DD0E0A /* GetMasternodeListDiffMessageSerializer.swift */; }; - 58AAA88B80E723DCE1D3DA15 /* TransactionLockMessageParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA1C1D2E2538CBC33B663 /* TransactionLockMessageParser.swift */; }; - 58AAA8A21156727FB34BD469 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA7CBEEC5D08B11A7C6DB /* GeneratedMocks.swift */; }; - 58AAA8A7E766E534A92D1A42 /* DashTransactionInfoConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD4E2C46D1C1038E68FD /* DashTransactionInfoConverter.swift */; }; - 58AAA8CF8CB14661F3757639 /* Outpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA85F9F3918BB7E6A2C91 /* Outpoint.swift */; }; - 58AAA95A5CD91D6EC2364416 /* MasternodeSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB557638C1D92ECCA51F /* MasternodeSerializer.swift */; }; - 58AAA95B92844E2FC089DBB2 /* DashTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC14D905BB97DE6AD45B /* DashTestData.swift */; }; - 58AAA96D0CEBDA919FE95A8D /* DashGrdbStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA7CEBD06DD533915741E /* DashGrdbStorage.swift */; }; - 58AAA99C55992149EBC964E0 /* DashExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA96578DB9BE7A8E1AA65 /* DashExtensions.swift */; }; - 58AAA9A24E2E28955B893151 /* QuorumSortedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA879961D8A283F21BB9D /* QuorumSortedList.swift */; }; - 58AAAA13B65A94F50CDE64C1 /* ISLockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA9C4B7A2015BBE7EEF4B /* ISLockMessage.swift */; }; - 58AAAA292D3A413E0E9F44A7 /* DashAddressSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA1A86BE6A17469E53400 /* DashAddressSelector.swift */; }; - 58AAAA59995E41B1661BBBAB /* MasternodeListDiffMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB4372CC0F55CBBE60C9 /* MasternodeListDiffMessage.swift */; }; - 58AAAA8392E527FFED910B01 /* MainNet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA839FB2C7EA770A036D /* MainNet.swift */; }; - 58AAAA9B99A71DC2D87BBCDA /* InstantSend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA0399119E61DC3C34677 /* InstantSend.swift */; }; - 58AAAAC5B216571E16B02AB9 /* DashKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAA2DA43B76925444C8F6 /* DashKit.swift */; }; - 58AAAAF33AF064F43E1A3129 /* CoinbaseTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAADC512D5340724ADCE13 /* CoinbaseTransaction.swift */; }; - 58AAAB37BB32AC64A697BB91 /* MasternodeListSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFDB68C6C388232EE26B /* MasternodeListSyncerTests.swift */; }; - 58AAAB6554710D83D6C2F372 /* MasternodeListManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5554604118DB100DAD3 /* MasternodeListManagerTests.swift */; }; - 58AAAB768B1B3D5501958643 /* QuorumListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA3EBFD8F2F023852F70B /* QuorumListManager.swift */; }; - 58AAAB7ECD59374B52739E52 /* MasternodeCbTxHashCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA67A09CB1D83F0DB016E /* MasternodeCbTxHashCalculatorTests.swift */; }; - 58AAABDCC27012ACCBF0CE45 /* RequestTransactionLockRequestsTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2DDA1326C29293D55B5 /* RequestTransactionLockRequestsTask.swift */; }; - 58AAABEA6F0463A71A950334 /* DarkGravityWaveTestNetValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2017DB5CEA5C9B9D720 /* DarkGravityWaveTestNetValidator.swift */; }; - 58AAAC4E69BC0FF9EE882D49 /* X11Hasher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6C2797950C38AA3F125 /* X11Hasher.swift */; }; - 58AAAC5CC9A83D7C280E1ACF /* InstantSendLockHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAADC9676645FAF1E6B185 /* InstantSendLockHandlerTests.swift */; }; - 58AAAC7B6DD2F0BE59353036 /* TransactionLockVoteMessageParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC1F302551317CABA7AC /* TransactionLockVoteMessageParser.swift */; }; - 58AAAC7E3D2B161ACFEB3276 /* InstantSendLockValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAB949FDA9A6E33CA983E /* InstantSendLockValidatorTests.swift */; }; - 58AAAC7FAFF35B1182E6CA45 /* GetMasternodeListDiffMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD26E8F6F0D81E444A37 /* GetMasternodeListDiffMessage.swift */; }; - 58AAACBDFF8204102BA20514 /* MasternodeListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA8B13589FA16E57D165C /* MasternodeListManager.swift */; }; - 58AAAD0C7E66CD27B52CE667 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC100B6F2FA333989819 /* Protocols.swift */; }; - 58AAADFD826478EDA93605D5 /* TransactionLockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5430DB3AED5094E6996 /* TransactionLockMessage.swift */; }; - 58AAAE24A6D0888B5549F1C8 /* QorumMasternode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA5F935C9CEC4E2C143F3 /* QorumMasternode.swift */; }; - 58AAAE764318B3B62C9D7F76 /* InstantSendLockValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA64AC0729742A80FDCB5 /* InstantSendLockValidator.swift */; }; - 58AAAE8B13F1DD739661FB19 /* DarkGravityWaveTestNetValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAD4E9080039DC9EDF4A2 /* DarkGravityWaveTestNetValidatorTests.swift */; }; - 58AAAE9A737EE5D91AD2884A /* InstantTransactionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA25F98854859029BE603 /* InstantTransactionState.swift */; }; - 58AAAEB8F73B3EBECD25F7B9 /* RequestLlmqInstantLocksTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA42CB13DB7A5BAB11FD8 /* RequestLlmqInstantLocksTask.swift */; }; - 58AAAEC46E9FC647D4132A66 /* SingleHasher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAFA578A209A7038893B5 /* SingleHasher.swift */; }; - 58AAAEEBE458C55215794E3A /* DashKitErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAC7FA40D103A60955173 /* DashKitErrors.swift */; }; - 58AAAF03C62698387C52FF5C /* MerkleRootCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA2C73B87C78CAB570A68 /* MerkleRootCreator.swift */; }; - 58AAAF53804CBB6C937DE062 /* PeerTaskFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAE28D7AB4A214D680314 /* PeerTaskFactory.swift */; }; - 58AAAFBB20809FB96678A994 /* DashExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAAAE6497AEB457771A718 /* DashExtensions.swift */; }; - 58AAAFCB1DBB74990D18CCC2 /* MasternodeSortedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AAA6EB559F918C1B3BB71F /* MasternodeSortedList.swift */; }; - 90C8B43708050F08868AD3A7 /* Pods_DashKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B85A704738CA3F5CD5564C /* Pods_DashKitTests.framework */; }; - F17777F01B0B02B0FE5727DC /* Pods_DashKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CBF9B9493B7FD18C59E784F /* Pods_DashKit.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 3A7A7D68226842DD0063D6AD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 3A7A7D54226842DD0063D6AD /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3A7A7D5C226842DD0063D6AD; - remoteInfo = DashKit; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 18CDEA7EA4DE77B7A030F991 /* Pods-DashKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashKitTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-DashKitTests/Pods-DashKitTests.debug.xcconfig"; sourceTree = ""; }; - 3A7A7D5D226842DD0063D6AD /* DashKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DashKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D60226842DD0063D6AD /* DashKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DashKit.h; sourceTree = ""; }; - 3A7A7D61226842DD0063D6AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7A7D66226842DD0063D6AD /* DashKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DashKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7A7D6D226842DD0063D6AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7A7D7E2268430D0063D6AD /* BitcoinCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3CBF9B9493B7FD18C59E784F /* Pods_DashKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DashKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 58AAA0373C0AAF259D678D34 /* MasternodeListSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListSyncer.swift; sourceTree = ""; }; - 58AAA0398DAC6811AAD8FF86 /* MasternodeSortedListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeSortedListTests.swift; sourceTree = ""; }; - 58AAA0399119E61DC3C34677 /* InstantSend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSend.swift; sourceTree = ""; }; - 58AAA0605DA659EF9FE6A8EA /* MasternodeListState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListState.swift; sourceTree = ""; }; - 58AAA0731BC44688D2072C72 /* TransactionLockVoteManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteManagerTests.swift; sourceTree = ""; }; - 58AAA135CAE12BD33A6D7D2E /* ConfirmedUnspentOutputProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmedUnspentOutputProvider.swift; sourceTree = ""; }; - 58AAA1776A1D0A594FC7FD18 /* TransactionLockVoteMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteMessage.swift; sourceTree = ""; }; - 58AAA1A86BE6A17469E53400 /* DashAddressSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashAddressSelector.swift; sourceTree = ""; }; - 58AAA1C1D2E2538CBC33B663 /* TransactionLockMessageParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockMessageParser.swift; sourceTree = ""; }; - 58AAA2017DB5CEA5C9B9D720 /* DarkGravityWaveTestNetValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkGravityWaveTestNetValidator.swift; sourceTree = ""; }; - 58AAA25F98854859029BE603 /* InstantTransactionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionState.swift; sourceTree = ""; }; - 58AAA26075017A2987200D87 /* DarkGravityWaveValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkGravityWaveValidator.swift; sourceTree = ""; }; - 58AAA2AF3D5C879040D2E7AE /* RequestMasternodeListDiffTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestMasternodeListDiffTask.swift; sourceTree = ""; }; - 58AAA2B4C33E32CF46D2D98B /* InstantTransactionSyncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionSyncer.swift; sourceTree = ""; }; - 58AAA2C73B87C78CAB570A68 /* MerkleRootCreator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleRootCreator.swift; sourceTree = ""; }; - 58AAA2DDA1326C29293D55B5 /* RequestTransactionLockRequestsTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTransactionLockRequestsTask.swift; sourceTree = ""; }; - 58AAA3EBFD8F2F023852F70B /* QuorumListManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuorumListManager.swift; sourceTree = ""; }; - 58AAA42CB13DB7A5BAB11FD8 /* RequestLlmqInstantLocksTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestLlmqInstantLocksTask.swift; sourceTree = ""; }; - 58AAA4AC566C9EC1630A1D83 /* QuorumListMerkleRootCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuorumListMerkleRootCalculator.swift; sourceTree = ""; }; - 58AAA5430DB3AED5094E6996 /* TransactionLockMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockMessage.swift; sourceTree = ""; }; - 58AAA5554604118DB100DAD3 /* MasternodeListManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListManagerTests.swift; sourceTree = ""; }; - 58AAA59886999B6239DD0E0A /* GetMasternodeListDiffMessageSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMasternodeListDiffMessageSerializer.swift; sourceTree = ""; }; - 58AAA5CCE40D60646530BDEC /* InstantSendLockManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockManager.swift; sourceTree = ""; }; - 58AAA5F935C9CEC4E2C143F3 /* QorumMasternode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QorumMasternode.swift; sourceTree = ""; }; - 58AAA60B71D68ABDACE2F8FA /* DarkGravityWaveValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkGravityWaveValidatorTests.swift; sourceTree = ""; }; - 58AAA64AC0729742A80FDCB5 /* InstantSendLockValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockValidator.swift; sourceTree = ""; }; - 58AAA67A09CB1D83F0DB016E /* MasternodeCbTxHashCalculatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeCbTxHashCalculatorTests.swift; sourceTree = ""; }; - 58AAA6C2797950C38AA3F125 /* X11Hasher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = X11Hasher.swift; sourceTree = ""; }; - 58AAA6EB559F918C1B3BB71F /* MasternodeSortedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeSortedList.swift; sourceTree = ""; }; - 58AAA6F46B5CED8D35657996 /* RequestTransactionLockVotesTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTransactionLockVotesTask.swift; sourceTree = ""; }; - 58AAA6FCD2841CF6180EBFB0 /* DashTransactionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashTransactionInfo.swift; sourceTree = ""; }; - 58AAA71B32E48969A3E1E52B /* MasternodeCbTxHashCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeCbTxHashCalculator.swift; sourceTree = ""; }; - 58AAA74142320578EB118DB0 /* TransactionLockVoteValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteValidator.swift; sourceTree = ""; }; - 58AAA7CBEEC5D08B11A7C6DB /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; - 58AAA7CEBD06DD533915741E /* DashGrdbStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashGrdbStorage.swift; sourceTree = ""; }; - 58AAA81B4D4D5458EE3FE964 /* BitcoinCoreCompatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCoreCompatibility.swift; sourceTree = ""; }; - 58AAA85F9F3918BB7E6A2C91 /* Outpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Outpoint.swift; sourceTree = ""; }; - 58AAA879961D8A283F21BB9D /* QuorumSortedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuorumSortedList.swift; sourceTree = ""; }; - 58AAA8B13589FA16E57D165C /* MasternodeListManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListManager.swift; sourceTree = ""; }; - 58AAA8F6E9EC26E29F4587B0 /* InstantSendTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendTests.swift; sourceTree = ""; }; - 58AAA96578DB9BE7A8E1AA65 /* DashExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashExtensions.swift; sourceTree = ""; }; - 58AAA9C4B7A2015BBE7EEF4B /* ISLockMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ISLockMessage.swift; sourceTree = ""; }; - 58AAA9D06F311DA7412688B5 /* ISLockParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ISLockParser.swift; sourceTree = ""; }; - 58AAAA110961D4988D83E86E /* TransactionLockVoteManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteManager.swift; sourceTree = ""; }; - 58AAAA294A297E74A7E6CE52 /* MasternodeListDiffMessageParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListDiffMessageParser.swift; sourceTree = ""; }; - 58AAAA2DA43B76925444C8F6 /* DashKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashKit.swift; sourceTree = ""; }; - 58AAAA839FB2C7EA770A036D /* MainNet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainNet.swift; sourceTree = ""; }; - 58AAAA9C78E24573D08995F0 /* MasternodeListMerkleRootCalculatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListMerkleRootCalculatorTests.swift; sourceTree = ""; }; - 58AAAAD1F0767070DBC89C8F /* InstantTransactionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionManager.swift; sourceTree = ""; }; - 58AAAAE0DB889BE114C53B2E /* MerkleRootCreatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleRootCreatorTests.swift; sourceTree = ""; }; - 58AAAAE6497AEB457771A718 /* DashExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashExtensions.swift; sourceTree = ""; }; - 58AAAB14B3DCDCBED15D0BE5 /* QuorumType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuorumType.swift; sourceTree = ""; }; - 58AAAB1C6D209CEB8839FABA /* TransactionLockVoteHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteHandler.swift; sourceTree = ""; }; - 58AAAB4372CC0F55CBBE60C9 /* MasternodeListDiffMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListDiffMessage.swift; sourceTree = ""; }; - 58AAAB4D9D73D00D4F098FB5 /* MasternodeListMerkleRootCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListMerkleRootCalculator.swift; sourceTree = ""; }; - 58AAAB557638C1D92ECCA51F /* MasternodeSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeSerializer.swift; sourceTree = ""; }; - 58AAAB949FDA9A6E33CA983E /* InstantSendLockValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockValidatorTests.swift; sourceTree = ""; }; - 58AAAC100B6F2FA333989819 /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; - 58AAAC14D905BB97DE6AD45B /* DashTestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashTestData.swift; sourceTree = ""; }; - 58AAAC1F302551317CABA7AC /* TransactionLockVoteMessageParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteMessageParser.swift; sourceTree = ""; }; - 58AAAC7FA40D103A60955173 /* DashKitErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashKitErrors.swift; sourceTree = ""; }; - 58AAACB2B535583527AD9294 /* InstantSendFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendFactory.swift; sourceTree = ""; }; - 58AAACB945F427DAB7C7783A /* QuorumParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuorumParser.swift; sourceTree = ""; }; - 58AAACFBF2C153954A53AE70 /* TransactionLockVoteValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteValidatorTests.swift; sourceTree = ""; }; - 58AAAD26E8F6F0D81E444A37 /* GetMasternodeListDiffMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMasternodeListDiffMessage.swift; sourceTree = ""; }; - 58AAAD4E2C46D1C1038E68FD /* DashTransactionInfoConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashTransactionInfoConverter.swift; sourceTree = ""; }; - 58AAAD4E9080039DC9EDF4A2 /* DarkGravityWaveTestNetValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkGravityWaveTestNetValidatorTests.swift; sourceTree = ""; }; - 58AAAD56C817AE1E7BB86FDC /* CoinbaseTransactionSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinbaseTransactionSerializer.swift; sourceTree = ""; }; - 58AAAD905E4A544A442269AF /* InstantSendLockHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockHandler.swift; sourceTree = ""; }; - 58AAADC512D5340724ADCE13 /* CoinbaseTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoinbaseTransaction.swift; sourceTree = ""; }; - 58AAADC9676645FAF1E6B185 /* InstantSendLockHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockHandlerTests.swift; sourceTree = ""; }; - 58AAAE28D7AB4A214D680314 /* PeerTaskFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerTaskFactory.swift; sourceTree = ""; }; - 58AAAE510575878A4AB340C1 /* TestNet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNet.swift; sourceTree = ""; }; - 58AAAE54B9246E927E19C091 /* Quorum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quorum.swift; sourceTree = ""; }; - 58AAAE8C1DD28970F1631067 /* InstantTransactionHash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionHash.swift; sourceTree = ""; }; - 58AAAEA87DCEBE51E9F79BEA /* InstantTransactionManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionManagerTests.swift; sourceTree = ""; }; - 58AAAEB5DE9557F4E41A7907 /* Masternode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Masternode.swift; sourceTree = ""; }; - 58AAAEB8362D398F7229AF3C /* TransactionLockVoteHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteHandlerTests.swift; sourceTree = ""; }; - 58AAAF1192518AD77095979C /* MasternodeParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeParser.swift; sourceTree = ""; }; - 58AAAF60A1E9BD745D645A7D /* InstantTransactionInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionInput.swift; sourceTree = ""; }; - 58AAAFA578A209A7038893B5 /* SingleHasher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleHasher.swift; sourceTree = ""; }; - 58AAAFDB68C6C388232EE26B /* MasternodeListSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListSyncerTests.swift; sourceTree = ""; }; - 5E70D9F39ED77288F419EFE4 /* Pods-DashKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashKit.release.xcconfig"; path = "../Pods/Target Support Files/Pods-DashKit/Pods-DashKit.release.xcconfig"; sourceTree = ""; }; - A870AB8888E53BA2DFCB89CE /* Pods-DashKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashKitTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-DashKitTests/Pods-DashKitTests.release.xcconfig"; sourceTree = ""; }; - B4B85A704738CA3F5CD5564C /* Pods_DashKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DashKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D6B8EA717B10F8DDAEBBFB5C /* Pods-DashKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashKit.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-DashKit/Pods-DashKit.debug.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 3A7A7D5A226842DD0063D6AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D7F2268430D0063D6AD /* BitcoinCore.framework in Frameworks */, - F17777F01B0B02B0FE5727DC /* Pods_DashKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D63226842DD0063D6AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D67226842DD0063D6AD /* DashKit.framework in Frameworks */, - 90C8B43708050F08868AD3A7 /* Pods_DashKitTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3A7A7D53226842DD0063D6AD = { - isa = PBXGroup; - children = ( - 3A7A7D5F226842DD0063D6AD /* DashKit */, - 3A7A7D6A226842DD0063D6AD /* DashKitTests */, - 3A7A7D5E226842DD0063D6AD /* Products */, - 3A7A7D7D2268430D0063D6AD /* Frameworks */, - A4D4B79F97B391375F9FB23B /* Pods */, - ); - sourceTree = ""; - }; - 3A7A7D5E226842DD0063D6AD /* Products */ = { - isa = PBXGroup; - children = ( - 3A7A7D5D226842DD0063D6AD /* DashKit.framework */, - 3A7A7D66226842DD0063D6AD /* DashKitTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 3A7A7D5F226842DD0063D6AD /* DashKit */ = { - isa = PBXGroup; - children = ( - 58AAACA62C52B48F7CC48DA0 /* Blocks */, - 58AAABC927C8D8C2A868934B /* Core */, - 58AAA6F49F58BB3826F6AE0A /* InstantSend */, - 58AAAE34B8A303B12C0AECBC /* MasternodeList */, - 58AAAEAEA95D4491C5E271B7 /* Models */, - 58AAAB3ABD5FBE6562B9BDDF /* Network */, - 58AAA07E0767C45516BDD9E6 /* Storage */, - 3A7A7D60226842DD0063D6AD /* DashKit.h */, - 3A7A7D61226842DD0063D6AD /* Info.plist */, - 58AAAC7FA40D103A60955173 /* DashKitErrors.swift */, - 58AAA940CCEC533F3402FB59 /* Managers */, - 58AAAAAF21CFB5722D34E03B /* QuorumList */, - ); - path = DashKit; - sourceTree = ""; - }; - 3A7A7D6A226842DD0063D6AD /* DashKitTests */ = { - isa = PBXGroup; - children = ( - 58AAA4BBF5A68076E07F528B /* Blocks */, - 58AAAF89DCB42FDB96B749D7 /* InstantSend */, - 58AAA92767A3243553B6C57B /* MasternodeList */, - 3A7A7D6D226842DD0063D6AD /* Info.plist */, - 58AAAC14D905BB97DE6AD45B /* DashTestData.swift */, - 58AAA96578DB9BE7A8E1AA65 /* DashExtensions.swift */, - 58AAA7CBEEC5D08B11A7C6DB /* GeneratedMocks.swift */, - ); - path = DashKitTests; - sourceTree = ""; - }; - 3A7A7D7D2268430D0063D6AD /* Frameworks */ = { - isa = PBXGroup; - children = ( - 3A7A7D7E2268430D0063D6AD /* BitcoinCore.framework */, - 3CBF9B9493B7FD18C59E784F /* Pods_DashKit.framework */, - B4B85A704738CA3F5CD5564C /* Pods_DashKitTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 3C7B97DDBAAB290971492CBC /* InitialSync */ = { - isa = PBXGroup; - children = ( - ); - path = InitialSync; - sourceTree = ""; - }; - 58AAA07E0767C45516BDD9E6 /* Storage */ = { - isa = PBXGroup; - children = ( - 58AAA7CEBD06DD533915741E /* DashGrdbStorage.swift */, - ); - path = Storage; - sourceTree = ""; - }; - 58AAA1E0D3534E3D0A89135D /* TransactionLockVote */ = { - isa = PBXGroup; - children = ( - 58AAAEB8362D398F7229AF3C /* TransactionLockVoteHandlerTests.swift */, - ); - path = TransactionLockVote; - sourceTree = ""; - }; - 58AAA23018F169F97F40BFE4 /* InstantSendLock */ = { - isa = PBXGroup; - children = ( - 58AAA64AC0729742A80FDCB5 /* InstantSendLockValidator.swift */, - 58AAA5CCE40D60646530BDEC /* InstantSendLockManager.swift */, - 58AAAD905E4A544A442269AF /* InstantSendLockHandler.swift */, - ); - path = InstantSendLock; - sourceTree = ""; - }; - 58AAA4BBF5A68076E07F528B /* Blocks */ = { - isa = PBXGroup; - children = ( - 58AAAF77CC4512D2CE905DA5 /* Validators */, - ); - path = Blocks; - sourceTree = ""; - }; - 58AAA6F49F58BB3826F6AE0A /* InstantSend */ = { - isa = PBXGroup; - children = ( - 58AAACB2B535583527AD9294 /* InstantSendFactory.swift */, - 58AAA2B4C33E32CF46D2D98B /* InstantTransactionSyncer.swift */, - 58AAA0399119E61DC3C34677 /* InstantSend.swift */, - 58AAAAD1F0767070DBC89C8F /* InstantTransactionManager.swift */, - 58AAAF9337042FF679E9FB04 /* TransactionLockVote */, - 58AAA23018F169F97F40BFE4 /* InstantSendLock */, - ); - path = InstantSend; - sourceTree = ""; - }; - 58AAA748C98B66E5CF2B3584 /* Parsers */ = { - isa = PBXGroup; - children = ( - 58AAAA294A297E74A7E6CE52 /* MasternodeListDiffMessageParser.swift */, - 58AAAF1192518AD77095979C /* MasternodeParser.swift */, - 58AAA1C1D2E2538CBC33B663 /* TransactionLockMessageParser.swift */, - 58AAAC1F302551317CABA7AC /* TransactionLockVoteMessageParser.swift */, - 58AAA9D06F311DA7412688B5 /* ISLockParser.swift */, - 58AAACB945F427DAB7C7783A /* QuorumParser.swift */, - ); - path = Parsers; - sourceTree = ""; - }; - 58AAA8633619543F49E0BF5F /* PeerTask */ = { - isa = PBXGroup; - children = ( - 58AAAE28D7AB4A214D680314 /* PeerTaskFactory.swift */, - 58AAA2AF3D5C879040D2E7AE /* RequestMasternodeListDiffTask.swift */, - 58AAA2DDA1326C29293D55B5 /* RequestTransactionLockRequestsTask.swift */, - 58AAA6F46B5CED8D35657996 /* RequestTransactionLockVotesTask.swift */, - 58AAA42CB13DB7A5BAB11FD8 /* RequestLlmqInstantLocksTask.swift */, - ); - path = PeerTask; - sourceTree = ""; - }; - 58AAA92767A3243553B6C57B /* MasternodeList */ = { - isa = PBXGroup; - children = ( - 58AAAFDB68C6C388232EE26B /* MasternodeListSyncerTests.swift */, - 58AAA5554604118DB100DAD3 /* MasternodeListManagerTests.swift */, - 58AAA0398DAC6811AAD8FF86 /* MasternodeSortedListTests.swift */, - 58AAAA9C78E24573D08995F0 /* MasternodeListMerkleRootCalculatorTests.swift */, - 58AAAAE0DB889BE114C53B2E /* MerkleRootCreatorTests.swift */, - 58AAA67A09CB1D83F0DB016E /* MasternodeCbTxHashCalculatorTests.swift */, - ); - path = MasternodeList; - sourceTree = ""; - }; - 58AAA940CCEC533F3402FB59 /* Managers */ = { - isa = PBXGroup; - children = ( - 3C7B97DDBAAB290971492CBC /* InitialSync */, - 58AAA1A86BE6A17469E53400 /* DashAddressSelector.swift */, - 58AAA135CAE12BD33A6D7D2E /* ConfirmedUnspentOutputProvider.swift */, - ); - path = Managers; - sourceTree = ""; - }; - 58AAAA859864D4132B38E6F4 /* Serializers */ = { - isa = PBXGroup; - children = ( - 58AAA59886999B6239DD0E0A /* GetMasternodeListDiffMessageSerializer.swift */, - ); - path = Serializers; - sourceTree = ""; - }; - 58AAAAAF21CFB5722D34E03B /* QuorumList */ = { - isa = PBXGroup; - children = ( - 58AAA879961D8A283F21BB9D /* QuorumSortedList.swift */, - 58AAA3EBFD8F2F023852F70B /* QuorumListManager.swift */, - 58AAA4AC566C9EC1630A1D83 /* QuorumListMerkleRootCalculator.swift */, - 58AAAB14B3DCDCBED15D0BE5 /* QuorumType.swift */, - ); - path = QuorumList; - sourceTree = ""; - }; - 58AAAB3ABD5FBE6562B9BDDF /* Network */ = { - isa = PBXGroup; - children = ( - 58AAAC85CAE24742ADAC3DDC /* Messages */, - 58AAA748C98B66E5CF2B3584 /* Parsers */, - 58AAADC9F8588F3C97FEB37D /* Peer */, - 58AAAA859864D4132B38E6F4 /* Serializers */, - 58AAA6C2797950C38AA3F125 /* X11Hasher.swift */, - 58AAAE510575878A4AB340C1 /* TestNet.swift */, - 58AAAA839FB2C7EA770A036D /* MainNet.swift */, - ); - path = Network; - sourceTree = ""; - }; - 58AAABC927C8D8C2A868934B /* Core */ = { - isa = PBXGroup; - children = ( - 58AAA81B4D4D5458EE3FE964 /* BitcoinCoreCompatibility.swift */, - 58AAAAE6497AEB457771A718 /* DashExtensions.swift */, - 58AAAA2DA43B76925444C8F6 /* DashKit.swift */, - 58AAAC100B6F2FA333989819 /* Protocols.swift */, - 58AAAD4E2C46D1C1038E68FD /* DashTransactionInfoConverter.swift */, - ); - path = Core; - sourceTree = ""; - }; - 58AAAC85CAE24742ADAC3DDC /* Messages */ = { - isa = PBXGroup; - children = ( - 58AAAD26E8F6F0D81E444A37 /* GetMasternodeListDiffMessage.swift */, - 58AAAB4372CC0F55CBBE60C9 /* MasternodeListDiffMessage.swift */, - 58AAA85F9F3918BB7E6A2C91 /* Outpoint.swift */, - 58AAA5430DB3AED5094E6996 /* TransactionLockMessage.swift */, - 58AAA1776A1D0A594FC7FD18 /* TransactionLockVoteMessage.swift */, - 58AAA9C4B7A2015BBE7EEF4B /* ISLockMessage.swift */, - ); - path = Messages; - sourceTree = ""; - }; - 58AAACA62C52B48F7CC48DA0 /* Blocks */ = { - isa = PBXGroup; - children = ( - 58AAAD10C611B30027BB28A2 /* Validators */, - ); - path = Blocks; - sourceTree = ""; - }; - 58AAAD10C611B30027BB28A2 /* Validators */ = { - isa = PBXGroup; - children = ( - 58AAA2017DB5CEA5C9B9D720 /* DarkGravityWaveTestNetValidator.swift */, - 58AAA26075017A2987200D87 /* DarkGravityWaveValidator.swift */, - ); - path = Validators; - sourceTree = ""; - }; - 58AAADC9F8588F3C97FEB37D /* Peer */ = { - isa = PBXGroup; - children = ( - 58AAA8633619543F49E0BF5F /* PeerTask */, - ); - path = Peer; - sourceTree = ""; - }; - 58AAAE34B8A303B12C0AECBC /* MasternodeList */ = { - isa = PBXGroup; - children = ( - 58AAA71B32E48969A3E1E52B /* MasternodeCbTxHashCalculator.swift */, - 58AAAB4D9D73D00D4F098FB5 /* MasternodeListMerkleRootCalculator.swift */, - 58AAA0373C0AAF259D678D34 /* MasternodeListSyncer.swift */, - 58AAA6EB559F918C1B3BB71F /* MasternodeSortedList.swift */, - 58AAA2C73B87C78CAB570A68 /* MerkleRootCreator.swift */, - 58AAA8B13589FA16E57D165C /* MasternodeListManager.swift */, - ); - path = MasternodeList; - sourceTree = ""; - }; - 58AAAE8722177D0BF537A48A /* ModelSerializers */ = { - isa = PBXGroup; - children = ( - 58AAAD56C817AE1E7BB86FDC /* CoinbaseTransactionSerializer.swift */, - 58AAAB557638C1D92ECCA51F /* MasternodeSerializer.swift */, - ); - path = ModelSerializers; - sourceTree = ""; - }; - 58AAAEAEA95D4491C5E271B7 /* Models */ = { - isa = PBXGroup; - children = ( - 58AAAE8722177D0BF537A48A /* ModelSerializers */, - 58AAADC512D5340724ADCE13 /* CoinbaseTransaction.swift */, - 58AAAF60A1E9BD745D645A7D /* InstantTransactionInput.swift */, - 58AAAEB5DE9557F4E41A7907 /* Masternode.swift */, - 58AAA0605DA659EF9FE6A8EA /* MasternodeListState.swift */, - 58AAAE8C1DD28970F1631067 /* InstantTransactionHash.swift */, - 58AAA25F98854859029BE603 /* InstantTransactionState.swift */, - 58AAA6FCD2841CF6180EBFB0 /* DashTransactionInfo.swift */, - 58AAAE54B9246E927E19C091 /* Quorum.swift */, - ); - path = Models; - sourceTree = ""; - }; - 58AAAF2D981354787830392C /* InstantSendLock */ = { - isa = PBXGroup; - children = ( - 58AAAB949FDA9A6E33CA983E /* InstantSendLockValidatorTests.swift */, - 58AAADC9676645FAF1E6B185 /* InstantSendLockHandlerTests.swift */, - ); - path = InstantSendLock; - sourceTree = ""; - }; - 58AAAF77CC4512D2CE905DA5 /* Validators */ = { - isa = PBXGroup; - children = ( - 58AAA60B71D68ABDACE2F8FA /* DarkGravityWaveValidatorTests.swift */, - 58AAAD4E9080039DC9EDF4A2 /* DarkGravityWaveTestNetValidatorTests.swift */, - ); - path = Validators; - sourceTree = ""; - }; - 58AAAF89DCB42FDB96B749D7 /* InstantSend */ = { - isa = PBXGroup; - children = ( - 58AAACFBF2C153954A53AE70 /* TransactionLockVoteValidatorTests.swift */, - 58AAA8F6E9EC26E29F4587B0 /* InstantSendTests.swift */, - 58AAAEA87DCEBE51E9F79BEA /* InstantTransactionManagerTests.swift */, - 58AAA0731BC44688D2072C72 /* TransactionLockVoteManagerTests.swift */, - 58AAAF2D981354787830392C /* InstantSendLock */, - 58AAA1E0D3534E3D0A89135D /* TransactionLockVote */, - ); - path = InstantSend; - sourceTree = ""; - }; - 58AAAF9337042FF679E9FB04 /* TransactionLockVote */ = { - isa = PBXGroup; - children = ( - 58AAA5F935C9CEC4E2C143F3 /* QorumMasternode.swift */, - 58AAAFA578A209A7038893B5 /* SingleHasher.swift */, - 58AAA74142320578EB118DB0 /* TransactionLockVoteValidator.swift */, - 58AAAA110961D4988D83E86E /* TransactionLockVoteManager.swift */, - 58AAAB1C6D209CEB8839FABA /* TransactionLockVoteHandler.swift */, - ); - path = TransactionLockVote; - sourceTree = ""; - }; - A4D4B79F97B391375F9FB23B /* Pods */ = { - isa = PBXGroup; - children = ( - D6B8EA717B10F8DDAEBBFB5C /* Pods-DashKit.debug.xcconfig */, - 5E70D9F39ED77288F419EFE4 /* Pods-DashKit.release.xcconfig */, - 18CDEA7EA4DE77B7A030F991 /* Pods-DashKitTests.debug.xcconfig */, - A870AB8888E53BA2DFCB89CE /* Pods-DashKitTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 3A7A7D58226842DD0063D6AD /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7A7D6E226842DD0063D6AD /* DashKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 3A7A7D5C226842DD0063D6AD /* DashKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7A7D71226842DD0063D6AD /* Build configuration list for PBXNativeTarget "DashKit" */; - buildPhases = ( - 64CEC5063FFB74BC2FE4C8C2 /* [CP] Check Pods Manifest.lock */, - 3A7A7D58226842DD0063D6AD /* Headers */, - 3A7A7D59226842DD0063D6AD /* Sources */, - 3A7A7D5A226842DD0063D6AD /* Frameworks */, - 3A7A7D5B226842DD0063D6AD /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = DashKit; - productName = DashKit; - productReference = 3A7A7D5D226842DD0063D6AD /* DashKit.framework */; - productType = "com.apple.product-type.framework"; - }; - 3A7A7D65226842DD0063D6AD /* DashKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3A7A7D74226842DD0063D6AD /* Build configuration list for PBXNativeTarget "DashKitTests" */; - buildPhases = ( - 68CE8C4BD3B872647B1E85BF /* [CP] Check Pods Manifest.lock */, - 3A17EE5122688EC90058F825 /* Cuckoo */, - 3A7A7D62226842DD0063D6AD /* Sources */, - 3A7A7D63226842DD0063D6AD /* Frameworks */, - 3A7A7D64226842DD0063D6AD /* Resources */, - 8086CC4CA91B62874843B634 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 3A7A7D69226842DD0063D6AD /* PBXTargetDependency */, - ); - name = DashKitTests; - productName = DashKitTests; - productReference = 3A7A7D66226842DD0063D6AD /* DashKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 3A7A7D54226842DD0063D6AD /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1010; - LastUpgradeCheck = 1010; - ORGANIZATIONNAME = "Horizontal Systems"; - TargetAttributes = { - 3A7A7D5C226842DD0063D6AD = { - CreatedOnToolsVersion = 10.1; - LastSwiftMigration = 1020; - }; - 3A7A7D65226842DD0063D6AD = { - CreatedOnToolsVersion = 10.1; - }; - }; - }; - buildConfigurationList = 3A7A7D57226842DD0063D6AD /* Build configuration list for PBXProject "DashKit" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 3A7A7D53226842DD0063D6AD; - productRefGroup = 3A7A7D5E226842DD0063D6AD /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 3A7A7D5C226842DD0063D6AD /* DashKit */, - 3A7A7D65226842DD0063D6AD /* DashKitTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 3A7A7D5B226842DD0063D6AD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D64226842DD0063D6AD /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3A17EE5122688EC90058F825 /* Cuckoo */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/${PROJECT_NAME}/Core/Protocols.swift", - ); - name = Cuckoo; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Define output file. Change \"$PROJECT_DIR/${PROJECT_NAME}Tests\" to your test's root source folder, if it's not the default name.\nOUTPUT_FILE=\"$PROJECT_DIR/${PROJECT_NAME}Tests/GeneratedMocks.swift\"\necho \"Generated Mocks File = $OUTPUT_FILE\"\n\n# Define input directory. Change \"${PROJECT_DIR}/${PROJECT_NAME}\" to your project's root source folder, if it's not the default name.\nINPUT_DIR=\"${PROJECT_DIR}/${PROJECT_NAME}\"\necho \"Mocks Input Directory = $INPUT_DIR\"\n\n# Generate mock files, include as many input files as you'd like to create mocks for.\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"$PROJECT_NAME\" \\\n--output \"${OUTPUT_FILE}\"\n"; - }; - 64CEC5063FFB74BC2FE4C8C2 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-DashKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 68CE8C4BD3B872647B1E85BF /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-DashKitTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 8086CC4CA91B62874843B634 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DashKitTests/Pods-DashKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DashKitTests/Pods-DashKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DashKitTests/Pods-DashKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 3A7A7D59226842DD0063D6AD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 58AAAAC5B216571E16B02AB9 /* DashKit.swift in Sources */, - 58AAAFBB20809FB96678A994 /* DashExtensions.swift in Sources */, - 58AAAAF33AF064F43E1A3129 /* CoinbaseTransaction.swift in Sources */, - 58AAA15491096368AD69A2D5 /* Masternode.swift in Sources */, - 58AAA497CB4425796D178DFF /* MasternodeListState.swift in Sources */, - 58AAA95A5CD91D6EC2364416 /* MasternodeSerializer.swift in Sources */, - 58AAA5DC4702D36F1C7A05B7 /* CoinbaseTransactionSerializer.swift in Sources */, - 58AAA1FC05748154358748CE /* InstantTransactionInput.swift in Sources */, - 58AAAEEBE458C55215794E3A /* DashKitErrors.swift in Sources */, - 58AAA7FC48828C89D56C1110 /* MasternodeListSyncer.swift in Sources */, - 58AAAFCB1DBB74990D18CCC2 /* MasternodeSortedList.swift in Sources */, - 58AAA445C4B1CF142B33C236 /* MasternodeListMerkleRootCalculator.swift in Sources */, - 58AAAF03C62698387C52FF5C /* MerkleRootCreator.swift in Sources */, - 58AAA64303E7973D3D8D2CD0 /* MasternodeCbTxHashCalculator.swift in Sources */, - 58AAA104320FC00205E999D2 /* InstantSendFactory.swift in Sources */, - 58AAAD0C7E66CD27B52CE667 /* Protocols.swift in Sources */, - 58AAA96D0CEBDA919FE95A8D /* DashGrdbStorage.swift in Sources */, - 58AAADFD826478EDA93605D5 /* TransactionLockMessage.swift in Sources */, - 58AAA04D4CDB79715654E978 /* TransactionLockVoteMessage.swift in Sources */, - 58AAA8CF8CB14661F3757639 /* Outpoint.swift in Sources */, - 58AAAC7FAFF35B1182E6CA45 /* GetMasternodeListDiffMessage.swift in Sources */, - 58AAAA59995E41B1661BBBAB /* MasternodeListDiffMessage.swift in Sources */, - 58AAA88B80E723DCE1D3DA15 /* TransactionLockMessageParser.swift in Sources */, - 58AAAC7B6DD2F0BE59353036 /* TransactionLockVoteMessageParser.swift in Sources */, - 58AAA41C7BA7F4AE78A99578 /* MasternodeListDiffMessageParser.swift in Sources */, - 58AAA7C359F5952EE5807FDB /* MasternodeParser.swift in Sources */, - 58AAABDCC27012ACCBF0CE45 /* RequestTransactionLockRequestsTask.swift in Sources */, - 58AAA050B036789F4EB37F6E /* RequestTransactionLockVotesTask.swift in Sources */, - 58AAAF53804CBB6C937DE062 /* PeerTaskFactory.swift in Sources */, - 58AAA59BBD76BAB11AAC061A /* RequestMasternodeListDiffTask.swift in Sources */, - 58AAA831BD89687D9A128590 /* GetMasternodeListDiffMessageSerializer.swift in Sources */, - 58AAAC4E69BC0FF9EE882D49 /* X11Hasher.swift in Sources */, - 58AAA656BDEBDEE092B93C5F /* DarkGravityWaveValidator.swift in Sources */, - 58AAABEA6F0463A71A950334 /* DarkGravityWaveTestNetValidator.swift in Sources */, - 58AAA0581D001F2AA294802B /* TestNet.swift in Sources */, - 58AAAA8392E527FFED910B01 /* MainNet.swift in Sources */, - 58AAA0C13ECAF509575AC590 /* BitcoinCoreCompatibility.swift in Sources */, - 58AAA2EBAFA03520E5C8F1F0 /* InstantTransactionSyncer.swift in Sources */, - 58AAAA9B99A71DC2D87BBCDA /* InstantSend.swift in Sources */, - 58AAA51780FC3C29D1DEC96B /* InstantTransactionManager.swift in Sources */, - 58AAA8A7E766E534A92D1A42 /* DashTransactionInfoConverter.swift in Sources */, - 58AAA5F7AF7CF8E3A909BD56 /* InstantTransactionHash.swift in Sources */, - 58AAAE9A737EE5D91AD2884A /* InstantTransactionState.swift in Sources */, - 58AAA1992857F107D48947E9 /* DashTransactionInfo.swift in Sources */, - 58AAAA292D3A413E0E9F44A7 /* DashAddressSelector.swift in Sources */, - 58AAA5020E3111ADB26B2261 /* ConfirmedUnspentOutputProvider.swift in Sources */, - 58AAAA13B65A94F50CDE64C1 /* ISLockMessage.swift in Sources */, - 58AAA65979EDF654C84E786F /* ISLockParser.swift in Sources */, - 58AAAE24A6D0888B5549F1C8 /* QorumMasternode.swift in Sources */, - 58AAAEC46E9FC647D4132A66 /* SingleHasher.swift in Sources */, - 58AAA7D71380E4555B03B662 /* TransactionLockVoteValidator.swift in Sources */, - 58AAA741B1FD041F7B434CD5 /* TransactionLockVoteManager.swift in Sources */, - 58AAAE764318B3B62C9D7F76 /* InstantSendLockValidator.swift in Sources */, - 58AAAEB8F73B3EBECD25F7B9 /* RequestLlmqInstantLocksTask.swift in Sources */, - 58AAA2BA0BE15B3B5212A3E3 /* InstantSendLockManager.swift in Sources */, - 58AAA67497C667DD6794E85B /* TransactionLockVoteHandler.swift in Sources */, - 58AAA15A8BE49BEF4F6FA045 /* InstantSendLockHandler.swift in Sources */, - 58AAA58B34781990B616307A /* Quorum.swift in Sources */, - 58AAA2287611FC00ADEEB601 /* QuorumParser.swift in Sources */, - 58AAA9A24E2E28955B893151 /* QuorumSortedList.swift in Sources */, - 58AAACBDFF8204102BA20514 /* MasternodeListManager.swift in Sources */, - 58AAAB768B1B3D5501958643 /* QuorumListManager.swift in Sources */, - 58AAA48909AC29CFA2761F65 /* QuorumListMerkleRootCalculator.swift in Sources */, - 58AAA6C601A18EA4FF399D05 /* QuorumType.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3A7A7D62226842DD0063D6AD /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 58AAA95B92844E2FC089DBB2 /* DashTestData.swift in Sources */, - 58AAA99C55992149EBC964E0 /* DashExtensions.swift in Sources */, - 58AAAB37BB32AC64A697BB91 /* MasternodeListSyncerTests.swift in Sources */, - 58AAAB6554710D83D6C2F372 /* MasternodeListManagerTests.swift in Sources */, - 58AAA2515EBD6650E0C005AA /* MasternodeSortedListTests.swift in Sources */, - 58AAA2C9BDA51E4733F3AC0C /* MasternodeListMerkleRootCalculatorTests.swift in Sources */, - 58AAA6966A02C430A3B46BE6 /* MerkleRootCreatorTests.swift in Sources */, - 58AAAB7ECD59374B52739E52 /* MasternodeCbTxHashCalculatorTests.swift in Sources */, - 58AAA1D98DEB1F6230E4EB27 /* TransactionLockVoteValidatorTests.swift in Sources */, - 58AAA4484F1C34B6B4E1F2E4 /* DarkGravityWaveValidatorTests.swift in Sources */, - 58AAAE8B13F1DD739661FB19 /* DarkGravityWaveTestNetValidatorTests.swift in Sources */, - 58AAA8A21156727FB34BD469 /* GeneratedMocks.swift in Sources */, - 58AAA51AE7098469E57E970F /* InstantSendTests.swift in Sources */, - 58AAA59182DEFEC059D60216 /* InstantTransactionManagerTests.swift in Sources */, - 58AAA055180E96AA33EF2C16 /* TransactionLockVoteManagerTests.swift in Sources */, - 58AAAC7E3D2B161ACFEB3276 /* InstantSendLockValidatorTests.swift in Sources */, - 58AAA2E6B0E12CC4C67E294C /* TransactionLockVoteHandlerTests.swift in Sources */, - 58AAAC5CC9A83D7C280E1ACF /* InstantSendLockHandlerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 3A7A7D69226842DD0063D6AD /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3A7A7D5C226842DD0063D6AD /* DashKit */; - targetProxy = 3A7A7D68226842DD0063D6AD /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 3A7A7D6F226842DD0063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 3A7A7D70226842DD0063D6AD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 3A7A7D72226842DD0063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D6B8EA717B10F8DDAEBBFB5C /* Pods-DashKit.debug.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = HC4MCAXJ66; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = DashKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.DashKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3A7A7D73226842DD0063D6AD /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5E70D9F39ED77288F419EFE4 /* Pods-DashKit.release.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = HC4MCAXJ66; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = DashKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.DashKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 3A7A7D75226842DD0063D6AD /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 18CDEA7EA4DE77B7A030F991 /* Pods-DashKitTests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HC4MCAXJ66; - INFOPLIST_FILE = DashKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.DashKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 3A7A7D76226842DD0063D6AD /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A870AB8888E53BA2DFCB89CE /* Pods-DashKitTests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HC4MCAXJ66; - INFOPLIST_FILE = DashKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.DashKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 3A7A7D57226842DD0063D6AD /* Build configuration list for PBXProject "DashKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D6F226842DD0063D6AD /* Debug */, - 3A7A7D70226842DD0063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3A7A7D71226842DD0063D6AD /* Build configuration list for PBXNativeTarget "DashKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D72226842DD0063D6AD /* Debug */, - 3A7A7D73226842DD0063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 3A7A7D74226842DD0063D6AD /* Build configuration list for PBXNativeTarget "DashKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3A7A7D75226842DD0063D6AD /* Debug */, - 3A7A7D76226842DD0063D6AD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 3A7A7D54226842DD0063D6AD /* Project object */; -} diff --git a/DashKit/DashKit/DashKit.h b/DashKit/DashKit/DashKit.h deleted file mode 100644 index 7da4c370..00000000 --- a/DashKit/DashKit/DashKit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DashKit.h -// DashKit -// -// Created by Anton Stavnichiy on 4/18/19. -// Copyright © 2019 Horizontal Systems. All rights reserved. -// - -#import - -//! Project version number for DashKit. -FOUNDATION_EXPORT double DashKitVersionNumber; - -//! Project version string for DashKit. -FOUNDATION_EXPORT const unsigned char DashKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/DashKit/DashKit/Info.plist b/DashKit/DashKit/Info.plist deleted file mode 100644 index e1fe4cfb..00000000 --- a/DashKit/DashKit/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/DashKit/DashKit/Managers/DashAddressSelector.swift b/DashKit/DashKit/Managers/DashAddressSelector.swift deleted file mode 100644 index 8a0ee781..00000000 --- a/DashKit/DashKit/Managers/DashAddressSelector.swift +++ /dev/null @@ -1,12 +0,0 @@ -import BitcoinCore - -public class DashAddressSelector: IAddressSelector { - - public init() {} - - public func getAddressVariants(addressConverter: IAddressConverter, publicKey: PublicKey) -> [String] { - let address = try? addressConverter.convert(keyHash: publicKey.keyHash, type: .p2pkh).stringValue - return [address].compactMap { $0 } - } - -} diff --git a/DashKit/DashKit/Models/DashTransactionInfo.swift b/DashKit/DashKit/Models/DashTransactionInfo.swift deleted file mode 100644 index bed7a784..00000000 --- a/DashKit/DashKit/Models/DashTransactionInfo.swift +++ /dev/null @@ -1,10 +0,0 @@ -import BitcoinCore - -public class DashTransactionInfo: TransactionInfo { - public var instantTx: Bool = false - - public required init(transactionHash: String, transactionIndex: Int, from: [TransactionAddressInfo], to: [TransactionAddressInfo], amount: Int, blockHeight: Int?, timestamp: Int) { - super.init(transactionHash: transactionHash, transactionIndex: transactionIndex, from: from, to: to, amount: amount, blockHeight: blockHeight, timestamp: timestamp) - } - -} \ No newline at end of file diff --git a/DashKit/DashKit/Network/MainNet.swift b/DashKit/DashKit/Network/MainNet.swift deleted file mode 100644 index 634116c3..00000000 --- a/DashKit/DashKit/Network/MainNet.swift +++ /dev/null @@ -1,55 +0,0 @@ -import BitcoinCore - -class MainNet: INetwork { - - let protocolVersion: Int32 = 70214 - - let name = "dash-main-net" - - let maxBlockSize: UInt32 = 2_000_000_000 - let pubKeyHash: UInt8 = 0x4c - let privateKey: UInt8 = 0x80 - let scriptHash: UInt8 = 0x10 - let bech32PrefixPattern: String = "bc" - let xPubKey: UInt32 = 0x0488b21e - let xPrivKey: UInt32 = 0x0488ade4 - let magic: UInt32 = 0xbf0c6bbd - let port: UInt32 = 9999 - let coinType: UInt32 = 5 - let sigHash: SigHashType = .bitcoinAll - var syncableFromApi: Bool = true - - let dnsSeeds = [ - "dnsseed.dash.org", - "dnsseed.dashdot.io", - "dnsseed.masternode.io", - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 1, - headerHash: "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6".reversedData!, - previousBlockHeaderHash: "0000000000000000000000000000000000000000000000000000000000000000".reversedData!, - merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".reversedData!, - timestamp: 1231006505, - bits: 486604799, - nonce: 2083236893 - ), - height: 0) - } - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 536870928, - headerHash: "00000000000000011ce58a8bb55333277640b015e97689f9277d582a4c1f9999".reversedData!, - previousBlockHeaderHash: "000000000000000fcbac491b68a0774d1b9f82edeae8742eb492815e8fa76ca5".reversedData!, - merkleRoot: "91e15e6045c20d06abc41eb5feb17813ccc723f6f018ca8fd01485e8837bc761".reversedData!, - timestamp: 1559624664, - bits: 0x191a414a, - nonce: 838341360 - ), - height: 1081358) - } - -} diff --git a/DashKit/DashKit/Network/TestNet.swift b/DashKit/DashKit/Network/TestNet.swift deleted file mode 100644 index d7f92f92..00000000 --- a/DashKit/DashKit/Network/TestNet.swift +++ /dev/null @@ -1,54 +0,0 @@ -import BitcoinCore - -class TestNet: INetwork { - let protocolVersion: Int32 = 70214 - - let name = "dash-main-net" - - let maxBlockSize: UInt32 = 1_000_000_000 - let pubKeyHash: UInt8 = 0x8c - let privateKey: UInt8 = 0x80 - let scriptHash: UInt8 = 0x13 - let bech32PrefixPattern: String = "bc" - let xPubKey: UInt32 = 0x0488b21e - let xPrivKey: UInt32 = 0x0488ade4 - let magic: UInt32 = 0xcee2caff - let port: UInt32 = 19999 - let coinType: UInt32 = 1 - let sigHash: SigHashType = .bitcoinAll - var syncableFromApi: Bool = true - - let dnsSeeds = [ - "testnet-seed.dashdot.io", - "test.dnsseed.masternode.io" - ] - - var bip44CheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 1, - headerHash: "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c".reversedData!, - previousBlockHeaderHash: "0000000000000000000000000000000000000000000000000000000000000000".reversedData!, - merkleRoot: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".reversedData!, - timestamp: 1231006505, - bits: 486604799, - nonce: 2083236893 - ), - height: 0) - } - - var lastCheckpointBlock: Block { - return Block( - withHeader: BlockHeader( - version: 536870912, - headerHash: "000000000cf1ebc27139b55559f2a0e312e566e1fd7dcac7ccf4e58d973794f5".reversedData!, - previousBlockHeaderHash: "000000001099bd5d3c903f2ab865b2c49c8bd29bddc9c990db43acd99617362c".reversedData!, - merkleRoot: "e58aeda83f17834baedb488c5276a37376c61c375848761f9a02c1981fe0d507".reversedData!, - timestamp: 1559651035, - bits: 0x1c0f8fa9, - nonce: 1118140024 - ), - height: 111324) - } - -} diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj deleted file mode 100644 index 9db8b657..00000000 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ /dev/null @@ -1,557 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXBuildFile section */ - 11B350DE67C4CEC69C29479B /* SendController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 11B35C941C13998329F0AA0D /* SendController.xib */; }; - 11B3510EF645DF798D9D2FA0 /* BitcoinAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3505C6F38384F4F2FD2B4 /* BitcoinAdapter.swift */; }; - 11B35180ECF8EAD0B8120131 /* WordsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 11B35443946C6294AB6583F9 /* WordsController.xib */; }; - 11B352CA92725B1EBB64DF15 /* BalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3543A6EEC0E9AB3FFB77B /* BalanceCell.swift */; }; - 11B352F9D77DA876E485F52F /* TransactionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 11B35AEEAA717AE99DA6335E /* TransactionCell.xib */; }; - 11B3542A2F5C149B4C13C406 /* TransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35D7C401FF37990688882 /* TransactionRecord.swift */; }; - 11B35595F5817B675ADF904B /* BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FF4A84349CC48FD0E25 /* BaseAdapter.swift */; }; - 11B35620585B19DAB2270920 /* BitcoinCashAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B354B50AB3D75876ED77D2 /* BitcoinCashAdapter.swift */; }; - 11B3563C0A6AC13C0BA2397B /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3547BDD5913989BF964B6 /* Signal.swift */; }; - 11B357558CF6B339F1E538F0 /* WordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B350888FB2882A1BE736F2 /* WordsController.swift */; }; - 11B3577C1321BF736D0365D3 /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35EDAC6FA494A2EAAF8A6 /* Manager.swift */; }; - 11B3581C287ED1A82A7F877B /* BalanceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 11B35B7251CBDF40D2AEAAEA /* BalanceCell.xib */; }; - 11B358489B9BE7705B93B7A7 /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35A4740CD0C67BE8A5D65 /* MainController.swift */; }; - 11B3587F6407CF3E3592A8CE /* TransactionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35DA8993F32EC0310B8CD /* TransactionCell.swift */; }; - 11B3595A55A9C05DFB163726 /* DashAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B3519FEDDCB5416C900040 /* DashAdapter.swift */; }; - 11B35BDCDD502878CD175475 /* TransactionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35D1E24456F624461933B /* TransactionsController.swift */; }; - 11B35C871CB1AF4786C269FF /* BalanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35FB21D99F485E98E7AEE /* BalanceController.swift */; }; - 11B35D7F57E14D63AA095514 /* SendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35E1DAF3C9EEEBE02F7C5 /* SendController.swift */; }; - 11B35E1839CAFDB222A0C0F9 /* ReceiveController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 11B358E91CF03C12866AD4AE /* ReceiveController.xib */; }; - 11B35F8D7C73F41FD9794117 /* ReceiveController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B353F536D54E9ACBB7D575 /* ReceiveController.swift */; }; - 11B35FF483B731F3ECFCB582 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B355861438272F37EB7213 /* Configuration.swift */; }; - D3285F4620BD158E00644076 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3285F4520BD158E00644076 /* AppDelegate.swift */; }; - D3373DB220C52F640082BC4A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D3373DB120C52F640082BC4A /* LaunchScreen.xib */; }; - D34BDCBA213E8CD5009D0AAC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D34BDCB9213E8CD5009D0AAC /* Assets.xcassets */; }; - D3927BAA227AB3C9005094C4 /* Pods_Demo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA9227AB3C9005094C4 /* Pods_Demo.framework */; }; - D3927BAB227AE928005094C4 /* BitcoinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA5227AB3C9005094C4 /* BitcoinKit.framework */; }; - D3927BAC227AE928005094C4 /* BitcoinKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA5227AB3C9005094C4 /* BitcoinKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D3927BAD227AE928005094C4 /* BitcoinCashKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA3227AB3C9005094C4 /* BitcoinCashKit.framework */; }; - D3927BAE227AE928005094C4 /* BitcoinCashKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA3227AB3C9005094C4 /* BitcoinCashKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D3927BAF227AE928005094C4 /* DashKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA7227AB3C9005094C4 /* DashKit.framework */; }; - D3927BB0227AE928005094C4 /* DashKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BA7227AB3C9005094C4 /* DashKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D3927BB6227AE94E005094C4 /* BitcoinCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BB5227AE94E005094C4 /* BitcoinCore.framework */; }; - D3927BB7227AE94E005094C4 /* BitcoinCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D3927BB5227AE94E005094C4 /* BitcoinCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - D3927BB1227AE928005094C4 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - D3927BAC227AE928005094C4 /* BitcoinKit.framework in Embed Frameworks */, - D3927BAE227AE928005094C4 /* BitcoinCashKit.framework in Embed Frameworks */, - D3927BB7227AE94E005094C4 /* BitcoinCore.framework in Embed Frameworks */, - D3927BB0227AE928005094C4 /* DashKit.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 11B3505C6F38384F4F2FD2B4 /* BitcoinAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinAdapter.swift; sourceTree = ""; }; - 11B350888FB2882A1BE736F2 /* WordsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordsController.swift; sourceTree = ""; }; - 11B3519FEDDCB5416C900040 /* DashAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashAdapter.swift; sourceTree = ""; }; - 11B353F536D54E9ACBB7D575 /* ReceiveController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveController.swift; sourceTree = ""; }; - 11B3543A6EEC0E9AB3FFB77B /* BalanceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceCell.swift; sourceTree = ""; }; - 11B35443946C6294AB6583F9 /* WordsController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WordsController.xib; sourceTree = ""; }; - 11B3547BDD5913989BF964B6 /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; - 11B354B50AB3D75876ED77D2 /* BitcoinCashAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashAdapter.swift; sourceTree = ""; }; - 11B355861438272F37EB7213 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - 11B358E91CF03C12866AD4AE /* ReceiveController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReceiveController.xib; sourceTree = ""; }; - 11B35A4740CD0C67BE8A5D65 /* MainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = ""; }; - 11B35AEEAA717AE99DA6335E /* TransactionCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransactionCell.xib; sourceTree = ""; }; - 11B35B7251CBDF40D2AEAAEA /* BalanceCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BalanceCell.xib; sourceTree = ""; }; - 11B35C941C13998329F0AA0D /* SendController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SendController.xib; sourceTree = ""; }; - 11B35D1E24456F624461933B /* TransactionsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsController.swift; sourceTree = ""; }; - 11B35D7C401FF37990688882 /* TransactionRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionRecord.swift; sourceTree = ""; }; - 11B35DA8993F32EC0310B8CD /* TransactionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionCell.swift; sourceTree = ""; }; - 11B35E1DAF3C9EEEBE02F7C5 /* SendController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendController.swift; sourceTree = ""; }; - 11B35EDAC6FA494A2EAAF8A6 /* Manager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Manager.swift; sourceTree = ""; }; - 11B35FB21D99F485E98E7AEE /* BalanceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceController.swift; sourceTree = ""; }; - 11B35FF4A84349CC48FD0E25 /* BaseAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseAdapter.swift; sourceTree = ""; }; - 74FB4D641711D9479B8D4823 /* Pods-Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Demo/Pods-Demo.debug.xcconfig"; sourceTree = ""; }; - 9C192BE97FB56FE2283F4EA9 /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig"; sourceTree = ""; }; - D3285F4220BD158E00644076 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; - D3285F4520BD158E00644076 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D3285F5120BD158F00644076 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; - D3373DB120C52F640082BC4A /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; - D34BDCB9213E8CD5009D0AAC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - D3927BA3227AB3C9005094C4 /* BitcoinCashKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinCashKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D3927BA5227AB3C9005094C4 /* BitcoinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D3927BA7227AB3C9005094C4 /* DashKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DashKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D3927BA9227AB3C9005094C4 /* Pods_Demo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_Demo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D3927BB5227AE94E005094C4 /* BitcoinCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BitcoinCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - D3285F3F20BD158E00644076 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D3927BAB227AE928005094C4 /* BitcoinKit.framework in Frameworks */, - D3927BAF227AE928005094C4 /* DashKit.framework in Frameworks */, - D3927BAD227AE928005094C4 /* BitcoinCashKit.framework in Frameworks */, - D3927BB6227AE94E005094C4 /* BitcoinCore.framework in Frameworks */, - D3927BAA227AB3C9005094C4 /* Pods_Demo.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 11B351C7581E81C500EA5DA6 /* Controllers */ = { - isa = PBXGroup; - children = ( - 11B359F3FE00A979544283F0 /* Cells */, - 11B350888FB2882A1BE736F2 /* WordsController.swift */, - 11B35443946C6294AB6583F9 /* WordsController.xib */, - 11B35A4740CD0C67BE8A5D65 /* MainController.swift */, - 11B35FB21D99F485E98E7AEE /* BalanceController.swift */, - 11B35D1E24456F624461933B /* TransactionsController.swift */, - 11B35E1DAF3C9EEEBE02F7C5 /* SendController.swift */, - 11B35C941C13998329F0AA0D /* SendController.xib */, - 11B353F536D54E9ACBB7D575 /* ReceiveController.swift */, - 11B358E91CF03C12866AD4AE /* ReceiveController.xib */, - ); - path = Controllers; - sourceTree = ""; - }; - 11B359F3FE00A979544283F0 /* Cells */ = { - isa = PBXGroup; - children = ( - 11B3543A6EEC0E9AB3FFB77B /* BalanceCell.swift */, - 11B35B7251CBDF40D2AEAAEA /* BalanceCell.xib */, - 11B35DA8993F32EC0310B8CD /* TransactionCell.swift */, - 11B35AEEAA717AE99DA6335E /* TransactionCell.xib */, - ); - path = Cells; - sourceTree = ""; - }; - 11B35E1CAA06B03B838B9D25 /* Core */ = { - isa = PBXGroup; - children = ( - 11B35EDAC6FA494A2EAAF8A6 /* Manager.swift */, - 11B35D7C401FF37990688882 /* TransactionRecord.swift */, - 11B3547BDD5913989BF964B6 /* Signal.swift */, - ); - path = Core; - sourceTree = ""; - }; - 11B35F658D316568E84D0896 /* Adapters */ = { - isa = PBXGroup; - children = ( - 11B35FF4A84349CC48FD0E25 /* BaseAdapter.swift */, - 11B3505C6F38384F4F2FD2B4 /* BitcoinAdapter.swift */, - 11B354B50AB3D75876ED77D2 /* BitcoinCashAdapter.swift */, - 11B3519FEDDCB5416C900040 /* DashAdapter.swift */, - ); - path = Adapters; - sourceTree = ""; - }; - 5758CFFC60270FA8C9931CD7 /* Pods */ = { - isa = PBXGroup; - children = ( - 74FB4D641711D9479B8D4823 /* Pods-Demo.debug.xcconfig */, - 9C192BE97FB56FE2283F4EA9 /* Pods-Demo.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - 95AD93BB0A2D5F5C4A435252 /* Frameworks */ = { - isa = PBXGroup; - children = ( - D3927BA3227AB3C9005094C4 /* BitcoinCashKit.framework */, - D3927BA5227AB3C9005094C4 /* BitcoinKit.framework */, - D3927BA7227AB3C9005094C4 /* DashKit.framework */, - D3927BA9227AB3C9005094C4 /* Pods_Demo.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - D3285F3920BD158E00644076 = { - isa = PBXGroup; - children = ( - D3927BB5227AE94E005094C4 /* BitcoinCore.framework */, - D3285F4420BD158E00644076 /* Demo */, - D3285F4320BD158E00644076 /* Products */, - 95AD93BB0A2D5F5C4A435252 /* Frameworks */, - 5758CFFC60270FA8C9931CD7 /* Pods */, - ); - sourceTree = ""; - }; - D3285F4320BD158E00644076 /* Products */ = { - isa = PBXGroup; - children = ( - D3285F4220BD158E00644076 /* Demo.app */, - ); - name = Products; - sourceTree = ""; - }; - D3285F4420BD158E00644076 /* Demo */ = { - isa = PBXGroup; - children = ( - 11B35E1CAA06B03B838B9D25 /* Core */, - 11B35F658D316568E84D0896 /* Adapters */, - 11B351C7581E81C500EA5DA6 /* Controllers */, - D34BDCB9213E8CD5009D0AAC /* Assets.xcassets */, - D3285F4520BD158E00644076 /* AppDelegate.swift */, - 11B355861438272F37EB7213 /* Configuration.swift */, - D3373DB120C52F640082BC4A /* LaunchScreen.xib */, - D3285F5120BD158F00644076 /* Info.plist */, - ); - path = Demo; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - D3285F4120BD158E00644076 /* Demo */ = { - isa = PBXNativeTarget; - buildConfigurationList = D3285F5420BD158F00644076 /* Build configuration list for PBXNativeTarget "Demo" */; - buildPhases = ( - 7692671597CE471BE02C713F /* [CP] Check Pods Manifest.lock */, - D3285F3E20BD158E00644076 /* Sources */, - D3285F3F20BD158E00644076 /* Frameworks */, - D3285F4020BD158E00644076 /* Resources */, - 47C39EA1800EB6F68E049C4E /* [CP] Embed Pods Frameworks */, - D3927BB1227AE928005094C4 /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Demo; - productName = Wallet; - productReference = D3285F4220BD158E00644076 /* Demo.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - D3285F3A20BD158E00644076 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0930; - LastUpgradeCheck = 0930; - ORGANIZATIONNAME = Grouvi; - TargetAttributes = { - D3285F4120BD158E00644076 = { - CreatedOnToolsVersion = 9.3.1; - LastSwiftMigration = 1020; - }; - }; - }; - buildConfigurationList = D3285F3D20BD158E00644076 /* Build configuration list for PBXProject "Demo" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = D3285F3920BD158E00644076; - productRefGroup = D3285F4320BD158E00644076 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - D3285F4120BD158E00644076 /* Demo */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - D3285F4020BD158E00644076 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D3373DB220C52F640082BC4A /* LaunchScreen.xib in Resources */, - D34BDCBA213E8CD5009D0AAC /* Assets.xcassets in Resources */, - 11B3581C287ED1A82A7F877B /* BalanceCell.xib in Resources */, - 11B352F9D77DA876E485F52F /* TransactionCell.xib in Resources */, - 11B35E1839CAFDB222A0C0F9 /* ReceiveController.xib in Resources */, - 11B350DE67C4CEC69C29479B /* SendController.xib in Resources */, - 11B35180ECF8EAD0B8120131 /* WordsController.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 47C39EA1800EB6F68E049C4E /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Demo/Pods-Demo-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 7692671597CE471BE02C713F /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Demo-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - D3285F3E20BD158E00644076 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D3285F4620BD158E00644076 /* AppDelegate.swift in Sources */, - 11B35FF483B731F3ECFCB582 /* Configuration.swift in Sources */, - 11B35C871CB1AF4786C269FF /* BalanceController.swift in Sources */, - 11B35BDCDD502878CD175475 /* TransactionsController.swift in Sources */, - 11B358489B9BE7705B93B7A7 /* MainController.swift in Sources */, - 11B3577C1321BF736D0365D3 /* Manager.swift in Sources */, - 11B35595F5817B675ADF904B /* BaseAdapter.swift in Sources */, - 11B3542A2F5C149B4C13C406 /* TransactionRecord.swift in Sources */, - 11B3510EF645DF798D9D2FA0 /* BitcoinAdapter.swift in Sources */, - 11B3563C0A6AC13C0BA2397B /* Signal.swift in Sources */, - 11B352CA92725B1EBB64DF15 /* BalanceCell.swift in Sources */, - 11B3587F6407CF3E3592A8CE /* TransactionCell.swift in Sources */, - 11B35F8D7C73F41FD9794117 /* ReceiveController.swift in Sources */, - 11B35D7F57E14D63AA095514 /* SendController.swift in Sources */, - 11B35620585B19DAB2270920 /* BitcoinCashAdapter.swift in Sources */, - 11B357558CF6B339F1E538F0 /* WordsController.swift in Sources */, - 11B3595A55A9C05DFB163726 /* DashAdapter.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - D3285F5220BD158F00644076 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_BITCODE = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; - }; - name = Debug; - }; - D3285F5320BD158F00644076 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_BITCODE = NO; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 4.2; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - D3285F5520BD158F00644076 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 74FB4D641711D9479B8D4823 /* Pods-Demo.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 72234W6E3D; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)", - ); - INFOPLIST_FILE = "$(SRCROOT)/Demo/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.Demo; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - D3285F5620BD158F00644076 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9C192BE97FB56FE2283F4EA9 /* Pods-Demo.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 72234W6E3D; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)", - ); - INFOPLIST_FILE = "$(SRCROOT)/Demo/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = io.horizontalsystems.Demo; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - D3285F3D20BD158E00644076 /* Build configuration list for PBXProject "Demo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D3285F5220BD158F00644076 /* Debug */, - D3285F5320BD158F00644076 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D3285F5420BD158F00644076 /* Build configuration list for PBXNativeTarget "Demo" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D3285F5520BD158F00644076 /* Debug */, - D3285F5620BD158F00644076 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = D3285F3A20BD158E00644076 /* Project object */; -} diff --git a/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme b/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme deleted file mode 100644 index 246786eb..00000000 --- a/Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Demo/Demo/Adapters/BaseAdapter.swift b/Demo/Demo/Adapters/BaseAdapter.swift deleted file mode 100644 index 587f5477..00000000 --- a/Demo/Demo/Adapters/BaseAdapter.swift +++ /dev/null @@ -1,154 +0,0 @@ -import BitcoinCore -import RxSwift - -class BaseAdapter { - var feeRate: Int { return 10 } - private let coinRate: Decimal = pow(10, 8) - - let name: String - let coinCode: String - - var changeableAddressType: Bool { return false } - - private let abstractKit: AbstractKit - - let lastBlockSignal = Signal() - let syncStateSignal = Signal() - let balanceSignal = Signal() - let transactionsSignal = Signal() - - var debugInfo: String { - return abstractKit.debugInfo - } - - init(name: String, coinCode: String, abstractKit: AbstractKit) { - self.name = name - self.coinCode = coinCode - self.abstractKit = abstractKit - } - - func transactionRecord(fromTransaction transaction: TransactionInfo) -> TransactionRecord { - let fromAddresses = transaction.from.map { - TransactionAddress(address: $0.address, mine: $0.mine) - } - - let toAddresses = transaction.to.map { - TransactionAddress(address: $0.address, mine: $0.mine) - } - - return TransactionRecord( - transactionHash: transaction.transactionHash, - transactionIndex: transaction.transactionIndex, - amount: Decimal(transaction.amount) / coinRate, - timestamp: Double(transaction.timestamp), - from: fromAddresses, - to: toAddresses, - blockHeight: transaction.blockHeight, - transactionExtraType: nil - ) - } - - private func convertToSatoshi(value: Decimal) -> Int { - let coinValue: Decimal = value * coinRate - - let handler = NSDecimalNumberHandler(roundingMode: .plain, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) - return NSDecimalNumber(decimal: coinValue).rounding(accordingToBehavior: handler).intValue - } - - func transactionsSingle(fromHash: String?, limit: Int) -> Single<[TransactionRecord]> { - return abstractKit.transactions(fromHash: fromHash, limit: limit) - .map { [weak self] transactions -> [TransactionRecord] in - return transactions.compactMap { - self?.transactionRecord(fromTransaction: $0) - } - } - } - -} - -extension BaseAdapter { - - var lastBlockObservable: Observable { - return lastBlockSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) - } - - var syncStateObservable: Observable { - return syncStateSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) - } - - var balanceObservable: Observable { - return balanceSignal.asObservable() - } - - var transactionsObservable: Observable { - return transactionsSignal.asObservable() - } - - func start() { - DispatchQueue.global(qos: .userInitiated).async { - self.abstractKit.start() - } - } - - var balance: Decimal { - return Decimal(abstractKit.balance) / coinRate - } - - var lastBlockInfo: BlockInfo? { - return abstractKit.lastBlockInfo - } - - var syncState: BitcoinCore.KitState { - return abstractKit.syncState - } - - func receiveAddress(for type: ScriptType) -> String { - return abstractKit.receiveAddress(for: type) - } - - func validate(address: String) throws { - try abstractKit.validate(address: address) - } - - func validate(amount: Decimal, address: String?) throws { - guard amount <= availableBalance(for: address) else { - throw SendError.insufficientAmount - } - } - - func sendSingle(to address: String, amount: Decimal) -> Single { - let satoshiAmount = convertToSatoshi(value: amount) - - return Single.create { [unowned self] observer in - do { - try self.abstractKit.send(to: address, value: satoshiAmount, feeRate: self.feeRate) - observer(.success(())) - } catch { - observer(.error(error)) - } - - return Disposables.create() - } - } - - func availableBalance(for address: String?) -> Decimal { - return max(0, balance - fee(for: balance, address: address)) - } - - func fee(for value: Decimal, address: String?) -> Decimal { - do { - let amount = convertToSatoshi(value: value) - let fee = try abstractKit.fee(for: amount, toAddress: address, senderPay: true, feeRate: feeRate) - return Decimal(fee) / coinRate - } catch BitcoinCoreErrors.UnspentOutputSelection.notEnough(let maxFee) { - return Decimal(maxFee) / coinRate - } catch { - return 0 - } - } - -} - -enum SendError: Error { - case insufficientAmount -} diff --git a/Demo/Demo/Configuration.swift b/Demo/Demo/Configuration.swift deleted file mode 100644 index d9d7fa6d..00000000 --- a/Demo/Demo/Configuration.swift +++ /dev/null @@ -1,16 +0,0 @@ -import BitcoinCore - -class Configuration { - static let shared = Configuration() - - let minLogLevel: Logger.Level = .error - let testNet = true - let mainNet = false - let defaultWords = [ - "used ugly meat glad balance divorce inner artwork hire invest already piano", - "razor noodle horse vital dilemma drum civil account grow turn genre turtle", - "current force clump paper shrug extra zebra employ prefer upon mobile hire", - "popular game latin harvest silly excess much valid elegant illness edge silk", - ] - -} diff --git a/Demo/Demo/Controllers/SendController.swift b/Demo/Demo/Controllers/SendController.swift deleted file mode 100644 index f6a012bc..00000000 --- a/Demo/Demo/Controllers/SendController.swift +++ /dev/null @@ -1,106 +0,0 @@ -import UIKit -import RxSwift - -class SendController: UIViewController { - private let disposeBag = DisposeBag() - - @IBOutlet weak var addressTextField: UITextField? - @IBOutlet weak var amountTextField: UITextField? - @IBOutlet weak var coinLabel: UILabel? - - private var adapters = [BaseAdapter]() - private let segmentedControl = UISegmentedControl() - - override func viewDidLoad() { - super.viewDidLoad() - - segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) - - Manager.shared.adapterSignal - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) - .observeOn(MainScheduler.instance) - .subscribe(onNext: { [weak self] in - self?.updateAdapters() - }) - .disposed(by: disposeBag) - - updateAdapters() - } - - private func updateAdapters() { - segmentedControl.removeAllSegments() - - adapters = Manager.shared.adapters - - for (index, adapter) in adapters.enumerated() { - segmentedControl.insertSegment(withTitle: adapter.coinCode, at: index, animated: false) - } - - navigationItem.titleView = segmentedControl - - segmentedControl.selectedSegmentIndex = 0 - segmentedControl.sendActions(for: .valueChanged) - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - super.touchesEnded(touches, with: event) - - view.endEditing(true) - } - - @objc func onSegmentChanged() { - coinLabel?.text = currentAdapter?.coinCode - } - - @IBAction func send() { - guard let address = addressTextField?.text else { - return - } - - do { - try currentAdapter?.validate(address: address) - } catch { - show(error: "Invalid address") - return - } - - guard let amountString = amountTextField?.text, let amount = Decimal(string: amountString) else { - show(error: "Invalid amount") - return - } - - currentAdapter?.sendSingle(to: address, amount: amount) - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) - .observeOn(MainScheduler.instance) - .subscribe(onSuccess: { [weak self] _ in - self?.addressTextField?.text = "" - self?.amountTextField?.text = "" - - self?.showSuccess(address: address, amount: amount) - }, onError: { [weak self] error in - self?.show(error: "Send failed: \(error)") - }) - .disposed(by: disposeBag) - } - - private func show(error: String) { - let alert = UIAlertController(title: "Send Error", message: error, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - present(alert, animated: true) - } - - private func showSuccess(address: String, amount: Decimal) { - let alert = UIAlertController(title: "Success", message: "\(amount.description) sent to \(address)", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .cancel)) - present(alert, animated: true) - } - - private var currentAdapter: BaseAdapter? { - guard segmentedControl.selectedSegmentIndex != -1, adapters.count > segmentedControl.selectedSegmentIndex else { - return nil - } - - return adapters[segmentedControl.selectedSegmentIndex] - } - -} diff --git a/Demo/Demo/Controllers/SendController.xib b/Demo/Demo/Controllers/SendController.xib deleted file mode 100644 index 4bd4477b..00000000 --- a/Demo/Demo/Controllers/SendController.xib +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Demo/Demo/Core/TransactionRecord.swift b/Demo/Demo/Core/TransactionRecord.swift deleted file mode 100644 index fe683b95..00000000 --- a/Demo/Demo/Core/TransactionRecord.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -struct TransactionRecord { - let transactionHash: String - let transactionIndex: Int - - let amount: Decimal - let timestamp: Double - - let from: [TransactionAddress] - let to: [TransactionAddress] - - let blockHeight: Int? - - var transactionExtraType: String? -} - -struct TransactionAddress { - let address: String - let mine: Bool -} diff --git a/Example/BitcoinKit.xcodeproj/project.pbxproj b/Example/BitcoinKit.xcodeproj/project.pbxproj new file mode 100644 index 00000000..670149f7 --- /dev/null +++ b/Example/BitcoinKit.xcodeproj/project.pbxproj @@ -0,0 +1,1998 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 11B35F729A8303254F2CC2A7 /* LitecoinAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35784074A8A389B0C3A6F /* LitecoinAdapter.swift */; }; + 1A564D56C3A051FF606AC76B /* Bip69Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A56412D624E0604F48AEECE /* Bip69Tests.swift */; }; + 1A564F07A30BCD9D357CB960 /* OutputSetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A56436E5E862A9A35F6FE41 /* OutputSetterTests.swift */; }; + 2FA5D47CFAEB8DB143F27645 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DACF4DE83AC5FE512D28 /* Extensions.swift */; }; + 2FA5D7CBDF994AEB18C185DD /* TransactionSenderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D6678DE19DE48939B822 /* TransactionSenderTests.swift */; }; + 3A713DE3E57894A08D0D7BCA /* libPods-BitcoinKitExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8533447FA6137FF0AD1A8653 /* libPods-BitcoinKitExample.a */; }; + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; + 6BADE51A1F50100CE1EA2E77 /* libPods-DashKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 68064D100A6B89360F422ACD /* libPods-DashKitTests.a */; }; + CCEE7A41B8D1161D30ECF667 /* libPods-BitcoinKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 61760DF10CCBAC95608DF73F /* libPods-BitcoinKitTests.a */; }; + CED3DF3255F84A325E377749 /* libPods-HodlerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AEBF785A9D87D74223787CA0 /* libPods-HodlerTests.a */; }; + D369A2F3240E51AB00D6FE1E /* SegWitBech32AddressConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B35B0FC1EF59DDBB427A25 /* SegWitBech32AddressConverterTests.swift */; }; + D36AAA9523A2125D0065B32B /* BitcoinCashAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA7C23A2125D0065B32B /* BitcoinCashAdapter.swift */; }; + D36AAA9623A2125D0065B32B /* DashAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA7D23A2125D0065B32B /* DashAdapter.swift */; }; + D36AAA9723A2125E0065B32B /* BitcoinAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA7E23A2125D0065B32B /* BitcoinAdapter.swift */; }; + D36AAA9823A2125E0065B32B /* BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA7F23A2125D0065B32B /* BaseAdapter.swift */; }; + D36AAA9923A2125E0065B32B /* SendController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D36AAA8123A2125D0065B32B /* SendController.xib */; }; + D36AAA9A23A2125E0065B32B /* BalanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8223A2125D0065B32B /* BalanceController.swift */; }; + D36AAA9B23A2125E0065B32B /* TransactionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D36AAA8423A2125D0065B32B /* TransactionCell.xib */; }; + D36AAA9C23A2125E0065B32B /* TransactionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8523A2125D0065B32B /* TransactionCell.swift */; }; + D36AAA9D23A2125E0065B32B /* BalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8623A2125D0065B32B /* BalanceCell.swift */; }; + D36AAA9E23A2125E0065B32B /* BalanceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D36AAA8723A2125D0065B32B /* BalanceCell.xib */; }; + D36AAA9F23A2125E0065B32B /* ReceiveController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8823A2125D0065B32B /* ReceiveController.swift */; }; + D36AAAA023A2125E0065B32B /* WordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8923A2125D0065B32B /* WordsController.swift */; }; + D36AAAA123A2125E0065B32B /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8A23A2125D0065B32B /* MainController.swift */; }; + D36AAAA223A2125E0065B32B /* WordsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D36AAA8B23A2125D0065B32B /* WordsController.xib */; }; + D36AAAA323A2125E0065B32B /* TransactionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8C23A2125D0065B32B /* TransactionsController.swift */; }; + D36AAAA423A2125E0065B32B /* SendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA8D23A2125D0065B32B /* SendController.swift */; }; + D36AAAA523A2125E0065B32B /* ReceiveController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D36AAA8E23A2125D0065B32B /* ReceiveController.xib */; }; + D36AAAA623A2125E0065B32B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D36AAA8F23A2125D0065B32B /* Assets.xcassets */; }; + D36AAAA723A2125E0065B32B /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA9023A2125D0065B32B /* Configuration.swift */; }; + D36AAAA823A2125E0065B32B /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA9223A2125D0065B32B /* Manager.swift */; }; + D36AAAA923A2125E0065B32B /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA9323A2125D0065B32B /* Signal.swift */; }; + D36AAAAA23A2125E0065B32B /* TransactionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36AAA9423A2125D0065B32B /* TransactionRecord.swift */; }; + D36AAAAC23A212E30065B32B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D36AAAAB23A212E30065B32B /* LaunchScreen.xib */; }; + D39EA3D623A2053700FF23B7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3D523A2053700FF23B7 /* GeneratedMocks.swift */; }; + D39EA42823A2094800FF23B7 /* KitStateProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3D923A2094700FF23B7 /* KitStateProviderTests.swift */; }; + D39EA42923A2094800FF23B7 /* BlockSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3DA23A2094700FF23B7 /* BlockSyncerTests.swift */; }; + D39EA42A23A2094800FF23B7 /* ProofOfWorkValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3DC23A2094700FF23B7 /* ProofOfWorkValidatorTests.swift */; }; + D39EA42B23A2094800FF23B7 /* LegacyTestNetDifficultyValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3DD23A2094700FF23B7 /* LegacyTestNetDifficultyValidatorTests.swift */; }; + D39EA42C23A2094800FF23B7 /* LegacyDifficultyAdjustmentValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3DE23A2094700FF23B7 /* LegacyDifficultyAdjustmentValidatorTests.swift */; }; + D39EA42D23A2094800FF23B7 /* BitsValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3DF23A2094700FF23B7 /* BitsValidatorTests.swift */; }; + D39EA42E23A2094800FF23B7 /* BlockchainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E023A2094700FF23B7 /* BlockchainTests.swift */; }; + D39EA42F23A2094800FF23B7 /* MerkleBlockValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E123A2094700FF23B7 /* MerkleBlockValidatorTests.swift */; }; + D39EA43023A2094800FF23B7 /* BitcoinTestNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E323A2094700FF23B7 /* BitcoinTestNetTests.swift */; }; + D39EA43123A2094800FF23B7 /* BitcoinMainNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E423A2094700FF23B7 /* BitcoinMainNetTests.swift */; }; + D39EA43223A2094800FF23B7 /* BitcoinRegTestNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E523A2094700FF23B7 /* BitcoinRegTestNetTests.swift */; }; + D39EA43323A2094800FF23B7 /* ConnectionTimeoutManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E723A2094700FF23B7 /* ConnectionTimeoutManagerTests.swift */; }; + D39EA43423A2094800FF23B7 /* PeerManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E823A2094700FF23B7 /* PeerManagerTests.swift */; }; + D39EA43523A2094800FF23B7 /* PeerAddressManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3E923A2094700FF23B7 /* PeerAddressManagerTests.swift */; }; + D39EA43623A2094800FF23B7 /* GetBlockHashesTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3EB23A2094700FF23B7 /* GetBlockHashesTaskTests.swift */; }; + D39EA43723A2094800FF23B7 /* SendTransactionTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3EC23A2094700FF23B7 /* SendTransactionTaskTests.swift */; }; + D39EA43823A2094800FF23B7 /* RequestTransactionTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3ED23A2094700FF23B7 /* RequestTransactionTaskTests.swift */; }; + D39EA43923A2094800FF23B7 /* GetMerkleBlocksTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3EE23A2094700FF23B7 /* GetMerkleBlocksTaskTests.swift */; }; + D39EA43A23A2094800FF23B7 /* PeerHostManagerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F023A2094700FF23B7 /* PeerHostManagerDelegateTests.swift */; }; + D39EA43B23A2094800FF23B7 /* BloomFilterManagerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F123A2094700FF23B7 /* BloomFilterManagerDelegateTests.swift */; }; + D39EA43C23A2094800FF23B7 /* IPeerGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F223A2094700FF23B7 /* IPeerGroupTests.swift */; }; + D39EA43D23A2094800FF23B7 /* PeerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F323A2094700FF23B7 /* PeerDelegateTests.swift */; }; + D39EA43E23A2094800FF23B7 /* PeerGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F423A2094700FF23B7 /* PeerGroupTests.swift */; }; + D39EA43F23A2094800FF23B7 /* IPeerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F623A2094700FF23B7 /* IPeerTests.swift */; }; + D39EA44023A2094800FF23B7 /* IPeerTaskDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F723A2094700FF23B7 /* IPeerTaskDelegateTests.swift */; }; + D39EA44123A2094800FF23B7 /* PeerConnectionDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F823A2094700FF23B7 /* PeerConnectionDelegateTests.swift */; }; + D39EA44223A2094800FF23B7 /* IPeerTaskRequesterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3F923A2094700FF23B7 /* IPeerTaskRequesterTests.swift */; }; + D39EA44323A2094800FF23B7 /* BitcoinCashMainNetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3FA23A2094700FF23B7 /* BitcoinCashMainNetTests.swift */; }; + D39EA44423A2094800FF23B7 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3FB23A2094700FF23B7 /* Extensions.swift */; }; + D39EA44523A2094800FF23B7 /* DataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3FD23A2094700FF23B7 /* DataProviderTests.swift */; }; + D39EA44623A2094800FF23B7 /* UnspentOutputSelectorSingleNoChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA3FF23A2094700FF23B7 /* UnspentOutputSelectorSingleNoChangeTests.swift */; }; + D39EA44723A2094800FF23B7 /* BloomFilterManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40023A2094700FF23B7 /* BloomFilterManagerTests.swift */; }; + D39EA44823A2094800FF23B7 /* UnspentOutputProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40123A2094700FF23B7 /* UnspentOutputProviderTests.swift */; }; + D39EA44923A2094800FF23B7 /* StateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40223A2094700FF23B7 /* StateManagerTests.swift */; }; + D39EA44A23A2094800FF23B7 /* WatchedTransactionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40323A2094700FF23B7 /* WatchedTransactionManagerTests.swift */; }; + D39EA44B23A2094800FF23B7 /* UnspentOutputSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40423A2094700FF23B7 /* UnspentOutputSelectorTests.swift */; }; + D39EA44C23A2094800FF23B7 /* PublicKeyManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40523A2094700FF23B7 /* PublicKeyManagerTests.swift */; }; + D39EA44D23A2094800FF23B7 /* InitialSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40623A2094700FF23B7 /* InitialSyncerTests.swift */; }; + D39EA44E23A2094800FF23B7 /* UnspentOutputSelectorOldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40723A2094700FF23B7 /* UnspentOutputSelectorOldTests.swift */; }; + D39EA44F23A2094800FF23B7 /* IrregularOutputFinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40823A2094700FF23B7 /* IrregularOutputFinderTests.swift */; }; + D39EA45023A2094800FF23B7 /* BlockHashFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40A23A2094700FF23B7 /* BlockHashFetcherTests.swift */; }; + D39EA45123A2094800FF23B7 /* BlockDiscoveryBatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40B23A2094700FF23B7 /* BlockDiscoveryBatchTest.swift */; }; + D39EA45223A2094800FF23B7 /* BlockHashFetcherHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40C23A2094700FF23B7 /* BlockHashFetcherHelperTests.swift */; }; + D39EA45323A2094800FF23B7 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40D23A2094700FF23B7 /* TestData.swift */; }; + D39EA45423A2094800FF23B7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA40E23A2094700FF23B7 /* GeneratedMocks.swift */; }; + D39EA45523A2094800FF23B7 /* DifficultyEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41023A2094700FF23B7 /* DifficultyEncoderTests.swift */; }; + D39EA45623A2094800FF23B7 /* BlockValidatorHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41223A2094800FF23B7 /* BlockValidatorHelperTests.swift */; }; + D39EA45723A2094800FF23B7 /* AddressConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41323A2094800FF23B7 /* AddressConverterTests.swift */; }; + D39EA45823A2094800FF23B7 /* PaymentAddressParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41423A2094800FF23B7 /* PaymentAddressParserTests.swift */; }; + D39EA45923A2094800FF23B7 /* MerkleBranchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41523A2094800FF23B7 /* MerkleBranchTests.swift */; }; + D39EA45A23A2094800FF23B7 /* HDPrivateKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41723A2094800FF23B7 /* HDPrivateKeyTests.swift */; }; + D39EA45B23A2094800FF23B7 /* TransactionOutputExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41923A2094800FF23B7 /* TransactionOutputExtractorTests.swift */; }; + D39EA45C23A2094800FF23B7 /* TransactionCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41A23A2094800FF23B7 /* TransactionCreatorTests.swift */; }; + D39EA45D23A2094800FF23B7 /* TransactionPublicKeySetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41B23A2094800FF23B7 /* TransactionPublicKeySetterTests.swift */; }; + D39EA45E23A2094800FF23B7 /* TransactionProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41C23A2094800FF23B7 /* TransactionProcessorTests.swift */; }; + D39EA45F23A2094800FF23B7 /* TransactionOutputAddressExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41D23A2094800FF23B7 /* TransactionOutputAddressExtractorTests.swift */; }; + D39EA46023A2094800FF23B7 /* TransactionSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA41E23A2094800FF23B7 /* TransactionSyncerTests.swift */; }; + D39EA46123A2094800FF23B7 /* ScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42023A2094800FF23B7 /* ScriptTests.swift */; }; + D39EA46223A2094800FF23B7 /* ChunkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42123A2094800FF23B7 /* ChunkTests.swift */; }; + D39EA46323A2094800FF23B7 /* ScriptConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42223A2094800FF23B7 /* ScriptConverterTests.swift */; }; + D39EA46423A2094800FF23B7 /* TransactionInputExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42323A2094800FF23B7 /* TransactionInputExtractorTests.swift */; }; + D39EA46523A2094800FF23B7 /* SegWitAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42523A2094800FF23B7 /* SegWitAddress.swift */; }; + D39EA46623A2094800FF23B7 /* InputSignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42623A2094800FF23B7 /* InputSignerTests.swift */; }; + D39EA46723A2094800FF23B7 /* TransactionSizeCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA42723A2094800FF23B7 /* TransactionSizeCalculatorTests.swift */; }; + D39EA47623A20C5E00FF23B7 /* BitcoinCashValidatorHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA46C23A20C5E00FF23B7 /* BitcoinCashValidatorHelperTests.swift */; }; + D39EA47723A20C5E00FF23B7 /* ForkValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA46E23A20C5E00FF23B7 /* ForkValidatorTests.swift */; }; + D39EA47823A20C5E00FF23B7 /* EDAValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA46F23A20C5E00FF23B7 /* EDAValidatorTests.swift */; }; + D39EA47923A20C5E00FF23B7 /* DAAValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA47023A20C5E00FF23B7 /* DAAValidatorTests.swift */; }; + D39EA47A23A20C5E00FF23B7 /* CashBech32AddressConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA47223A20C5E00FF23B7 /* CashBech32AddressConverterTests.swift */; }; + D39EA47B23A20C5E00FF23B7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA47323A20C5E00FF23B7 /* GeneratedMocks.swift */; }; + D39EA47C23A20C5E00FF23B7 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA47423A20C5E00FF23B7 /* Extensions.swift */; }; + D39EA47D23A20C5E00FF23B7 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA47523A20C5E00FF23B7 /* TestData.swift */; }; + D39EA49623A20C7000FF23B7 /* DashExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA47E23A20C6F00FF23B7 /* DashExtensions.swift */; }; + D39EA49723A20C7000FF23B7 /* MasternodeSortedListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48023A20C7000FF23B7 /* MasternodeSortedListTests.swift */; }; + D39EA49823A20C7000FF23B7 /* MerkleRootCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48123A20C7000FF23B7 /* MerkleRootCreatorTests.swift */; }; + D39EA49923A20C7000FF23B7 /* MasternodeListManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48223A20C7000FF23B7 /* MasternodeListManagerTests.swift */; }; + D39EA49A23A20C7000FF23B7 /* MasternodeListSyncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48323A20C7000FF23B7 /* MasternodeListSyncerTests.swift */; }; + D39EA49B23A20C7000FF23B7 /* MasternodeCbTxHashCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48423A20C7000FF23B7 /* MasternodeCbTxHashCalculatorTests.swift */; }; + D39EA49C23A20C7000FF23B7 /* MasternodeListMerkleRootCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48523A20C7000FF23B7 /* MasternodeListMerkleRootCalculatorTests.swift */; }; + D39EA49D23A20C7000FF23B7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48623A20C7000FF23B7 /* GeneratedMocks.swift */; }; + D39EA49E23A20C7000FF23B7 /* DarkGravityWaveTestNetValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48923A20C7000FF23B7 /* DarkGravityWaveTestNetValidatorTests.swift */; }; + D39EA49F23A20C7000FF23B7 /* DarkGravityWaveValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48A23A20C7000FF23B7 /* DarkGravityWaveValidatorTests.swift */; }; + D39EA4A023A20C7000FF23B7 /* TransactionLockVoteValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48C23A20C7000FF23B7 /* TransactionLockVoteValidatorTests.swift */; }; + D39EA4A123A20C7000FF23B7 /* InstantSendLockValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48E23A20C7000FF23B7 /* InstantSendLockValidatorTests.swift */; }; + D39EA4A223A20C7000FF23B7 /* InstantSendLockHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA48F23A20C7000FF23B7 /* InstantSendLockHandlerTests.swift */; }; + D39EA4A323A20C7000FF23B7 /* TransactionLockVoteManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA49023A20C7000FF23B7 /* TransactionLockVoteManagerTests.swift */; }; + D39EA4A423A20C7000FF23B7 /* InstantSendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA49123A20C7000FF23B7 /* InstantSendTests.swift */; }; + D39EA4A523A20C7000FF23B7 /* TransactionLockVoteHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA49323A20C7000FF23B7 /* TransactionLockVoteHandlerTests.swift */; }; + D39EA4A623A20C7000FF23B7 /* InstantTransactionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA49423A20C7000FF23B7 /* InstantTransactionManagerTests.swift */; }; + D39EA4A723A20C7000FF23B7 /* DashTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA49523A20C7000FF23B7 /* DashTestData.swift */; }; + D39EA4AA23A20C8500FF23B7 /* GeneratedMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA4A823A20C8500FF23B7 /* GeneratedMocks.swift */; }; + D39EA4AB23A20C8500FF23B7 /* HodlerPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39EA4A923A20C8500FF23B7 /* HodlerPluginTests.swift */; }; + EE39228BA69CD5E423918C4E /* libPods-BitcoinCoreTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F2017BF6B8CE56A1B7B7DF7 /* libPods-BitcoinCoreTests.a */; }; + F58031596DC7395E2A4ADB3D /* libPods-BitcoinCashKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 752AEC72EEED42E4B36FB86A /* libPods-BitcoinCashKitTests.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1029C270FEFAD935F7997EBE /* Pods-BitcoinKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKit_Example.release.xcconfig"; path = "Target Support Files/Pods-BitcoinKit_Example/Pods-BitcoinKit_Example.release.xcconfig"; sourceTree = ""; }; + 11B35784074A8A389B0C3A6F /* LitecoinAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LitecoinAdapter.swift; sourceTree = ""; }; + 11B35B0FC1EF59DDBB427A25 /* SegWitBech32AddressConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitBech32AddressConverterTests.swift; sourceTree = ""; }; + 1A56412D624E0604F48AEECE /* Bip69Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bip69Tests.swift; sourceTree = ""; }; + 1A56436E5E862A9A35F6FE41 /* OutputSetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutputSetterTests.swift; sourceTree = ""; }; + 2FA5D6678DE19DE48939B822 /* TransactionSenderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSenderTests.swift; sourceTree = ""; }; + 2FA5DACF4DE83AC5FE512D28 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 5ABDFB9CD579008DADA21D5E /* Pods-BitcoinKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKit_Example.debug.xcconfig"; path = "Target Support Files/Pods-BitcoinKit_Example/Pods-BitcoinKit_Example.debug.xcconfig"; sourceTree = ""; }; + 5F2017BF6B8CE56A1B7B7DF7 /* libPods-BitcoinCoreTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BitcoinCoreTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5F8F16D600F4DF6CD20E0BAF /* Pods-BitcoinKitExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKitExample.debug.xcconfig"; path = "Target Support Files/Pods-BitcoinKitExample/Pods-BitcoinKitExample.debug.xcconfig"; sourceTree = ""; }; + 607FACD01AFB9204008FA782 /* BitcoinKitExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitcoinKitExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 607FACE51AFB9204008FA782 /* BitcoinKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitcoinKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 61760DF10CCBAC95608DF73F /* libPods-BitcoinKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BitcoinKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 62C1FEEDB0512E77E56FACEC /* Pods-BitcoinCoreTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCoreTests.debug.xcconfig"; path = "Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests.debug.xcconfig"; sourceTree = ""; }; + 68064D100A6B89360F422ACD /* libPods-DashKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DashKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 752AEC72EEED42E4B36FB86A /* libPods-BitcoinCashKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BitcoinCashKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8533447FA6137FF0AD1A8653 /* libPods-BitcoinKitExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BitcoinKitExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8A7E6415B5F8A8DC1879CFA8 /* Pods-DashKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashKitTests.debug.xcconfig"; path = "Target Support Files/Pods-DashKitTests/Pods-DashKitTests.debug.xcconfig"; sourceTree = ""; }; + 9D42C5E0E663DB7B00733723 /* Pods-HodlerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HodlerTests.debug.xcconfig"; path = "Target Support Files/Pods-HodlerTests/Pods-HodlerTests.debug.xcconfig"; sourceTree = ""; }; + A4262CF9BB9B657C6BD94DDA /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + A7DBA76475D067CD41CF0EAA /* Pods-BitcoinKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKit_Tests.release.xcconfig"; path = "Target Support Files/Pods-BitcoinKit_Tests/Pods-BitcoinKit_Tests.release.xcconfig"; sourceTree = ""; }; + ABCB45B4E8EBC7D5B2EB2632 /* Pods-BitcoinKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKit_Tests.debug.xcconfig"; path = "Target Support Files/Pods-BitcoinKit_Tests/Pods-BitcoinKit_Tests.debug.xcconfig"; sourceTree = ""; }; + ACD6E6BDE7C346289FAB2499 /* Pods-BitcoinKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKitTests.debug.xcconfig"; path = "Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests.debug.xcconfig"; sourceTree = ""; }; + AEBF785A9D87D74223787CA0 /* libPods-HodlerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HodlerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + BBB9FFCB21BB700523232782 /* Pods-HodlerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HodlerTests.release.xcconfig"; path = "Target Support Files/Pods-HodlerTests/Pods-HodlerTests.release.xcconfig"; sourceTree = ""; }; + C45B20346E834C6B8A93D7EC /* Pods-BitcoinKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKitTests.release.xcconfig"; path = "Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests.release.xcconfig"; sourceTree = ""; }; + CA5C17E35989902FCC31411F /* BitcoinKit.swift.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.podspec; name = BitcoinKit.swift.podspec; path = ../BitcoinKit.swift.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + CC223B703634B83592B29EE5 /* Pods-BitcoinCashKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCashKitTests.debug.xcconfig"; path = "Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests.debug.xcconfig"; sourceTree = ""; }; + D369A2F22409238F00D6FE1E /* LitecoinKit.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = LitecoinKit.swift.podspec; path = ../LitecoinKit.swift.podspec; sourceTree = ""; }; + D36AAA7C23A2125D0065B32B /* BitcoinCashAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashAdapter.swift; sourceTree = ""; }; + D36AAA7D23A2125D0065B32B /* DashAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashAdapter.swift; sourceTree = ""; }; + D36AAA7E23A2125D0065B32B /* BitcoinAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinAdapter.swift; sourceTree = ""; }; + D36AAA7F23A2125D0065B32B /* BaseAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseAdapter.swift; sourceTree = ""; }; + D36AAA8123A2125D0065B32B /* SendController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SendController.xib; sourceTree = ""; }; + D36AAA8223A2125D0065B32B /* BalanceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceController.swift; sourceTree = ""; }; + D36AAA8423A2125D0065B32B /* TransactionCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TransactionCell.xib; sourceTree = ""; }; + D36AAA8523A2125D0065B32B /* TransactionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionCell.swift; sourceTree = ""; }; + D36AAA8623A2125D0065B32B /* BalanceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceCell.swift; sourceTree = ""; }; + D36AAA8723A2125D0065B32B /* BalanceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BalanceCell.xib; sourceTree = ""; }; + D36AAA8823A2125D0065B32B /* ReceiveController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveController.swift; sourceTree = ""; }; + D36AAA8923A2125D0065B32B /* WordsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordsController.swift; sourceTree = ""; }; + D36AAA8A23A2125D0065B32B /* MainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = ""; }; + D36AAA8B23A2125D0065B32B /* WordsController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WordsController.xib; sourceTree = ""; }; + D36AAA8C23A2125D0065B32B /* TransactionsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsController.swift; sourceTree = ""; }; + D36AAA8D23A2125D0065B32B /* SendController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendController.swift; sourceTree = ""; }; + D36AAA8E23A2125D0065B32B /* ReceiveController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReceiveController.xib; sourceTree = ""; }; + D36AAA8F23A2125D0065B32B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D36AAA9023A2125D0065B32B /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; + D36AAA9223A2125D0065B32B /* Manager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Manager.swift; sourceTree = ""; }; + D36AAA9323A2125D0065B32B /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; + D36AAA9423A2125D0065B32B /* TransactionRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionRecord.swift; sourceTree = ""; }; + D36AAAAB23A212E30065B32B /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; + D39EA37F23A113EF00FF23B7 /* Hodler.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = file.podspec; name = Hodler.swift.podspec; path = ../Hodler.swift.podspec; sourceTree = ""; }; + D39EA38023A113EF00FF23B7 /* DashKit.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = file.podspec; name = DashKit.swift.podspec; path = ../DashKit.swift.podspec; sourceTree = ""; }; + D39EA38123A113EF00FF23B7 /* BitcoinCore.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = file.podspec; name = BitcoinCore.swift.podspec; path = ../BitcoinCore.swift.podspec; sourceTree = ""; }; + D39EA38223A113EF00FF23B7 /* BitcoinCashKit.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = file.podspec; name = BitcoinCashKit.swift.podspec; path = ../BitcoinCashKit.swift.podspec; sourceTree = ""; }; + D39EA39923A1230000FF23B7 /* BitcoinCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitcoinCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D39EA3A823A123FF00FF23B7 /* BitcoinCashKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitcoinCashKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D39EA3B723A1240C00FF23B7 /* DashKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DashKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D39EA3C623A1241600FF23B7 /* HodlerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HodlerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D39EA3C823A124A600FF23B7 /* BitcoinCoreTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = BitcoinCoreTests.plist; sourceTree = ""; }; + D39EA3CA23A124B000FF23B7 /* BitcoinCashKitTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = BitcoinCashKitTests.plist; sourceTree = ""; }; + D39EA3CC23A124B900FF23B7 /* BitcoinKitTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = BitcoinKitTests.plist; sourceTree = ""; }; + D39EA3CE23A124C000FF23B7 /* HodlerTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = HodlerTests.plist; sourceTree = ""; }; + D39EA3D023A124CA00FF23B7 /* DashKitTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = DashKitTests.plist; sourceTree = ""; }; + D39EA3D523A2053700FF23B7 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; + D39EA3D923A2094700FF23B7 /* KitStateProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KitStateProviderTests.swift; sourceTree = ""; }; + D39EA3DA23A2094700FF23B7 /* BlockSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockSyncerTests.swift; sourceTree = ""; }; + D39EA3DC23A2094700FF23B7 /* ProofOfWorkValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProofOfWorkValidatorTests.swift; sourceTree = ""; }; + D39EA3DD23A2094700FF23B7 /* LegacyTestNetDifficultyValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyTestNetDifficultyValidatorTests.swift; sourceTree = ""; }; + D39EA3DE23A2094700FF23B7 /* LegacyDifficultyAdjustmentValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyDifficultyAdjustmentValidatorTests.swift; sourceTree = ""; }; + D39EA3DF23A2094700FF23B7 /* BitsValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitsValidatorTests.swift; sourceTree = ""; }; + D39EA3E023A2094700FF23B7 /* BlockchainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockchainTests.swift; sourceTree = ""; }; + D39EA3E123A2094700FF23B7 /* MerkleBlockValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBlockValidatorTests.swift; sourceTree = ""; }; + D39EA3E323A2094700FF23B7 /* BitcoinTestNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinTestNetTests.swift; sourceTree = ""; }; + D39EA3E423A2094700FF23B7 /* BitcoinMainNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinMainNetTests.swift; sourceTree = ""; }; + D39EA3E523A2094700FF23B7 /* BitcoinRegTestNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinRegTestNetTests.swift; sourceTree = ""; }; + D39EA3E723A2094700FF23B7 /* ConnectionTimeoutManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTimeoutManagerTests.swift; sourceTree = ""; }; + D39EA3E823A2094700FF23B7 /* PeerManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerManagerTests.swift; sourceTree = ""; }; + D39EA3E923A2094700FF23B7 /* PeerAddressManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAddressManagerTests.swift; sourceTree = ""; }; + D39EA3EB23A2094700FF23B7 /* GetBlockHashesTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBlockHashesTaskTests.swift; sourceTree = ""; }; + D39EA3EC23A2094700FF23B7 /* SendTransactionTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendTransactionTaskTests.swift; sourceTree = ""; }; + D39EA3ED23A2094700FF23B7 /* RequestTransactionTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTransactionTaskTests.swift; sourceTree = ""; }; + D39EA3EE23A2094700FF23B7 /* GetMerkleBlocksTaskTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMerkleBlocksTaskTests.swift; sourceTree = ""; }; + D39EA3F023A2094700FF23B7 /* PeerHostManagerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerHostManagerDelegateTests.swift; sourceTree = ""; }; + D39EA3F123A2094700FF23B7 /* BloomFilterManagerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilterManagerDelegateTests.swift; sourceTree = ""; }; + D39EA3F223A2094700FF23B7 /* IPeerGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerGroupTests.swift; sourceTree = ""; }; + D39EA3F323A2094700FF23B7 /* PeerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerDelegateTests.swift; sourceTree = ""; }; + D39EA3F423A2094700FF23B7 /* PeerGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerGroupTests.swift; sourceTree = ""; }; + D39EA3F623A2094700FF23B7 /* IPeerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTests.swift; sourceTree = ""; }; + D39EA3F723A2094700FF23B7 /* IPeerTaskDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTaskDelegateTests.swift; sourceTree = ""; }; + D39EA3F823A2094700FF23B7 /* PeerConnectionDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerConnectionDelegateTests.swift; sourceTree = ""; }; + D39EA3F923A2094700FF23B7 /* IPeerTaskRequesterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTaskRequesterTests.swift; sourceTree = ""; }; + D39EA3FA23A2094700FF23B7 /* BitcoinCashMainNetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashMainNetTests.swift; sourceTree = ""; }; + D39EA3FB23A2094700FF23B7 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + D39EA3FD23A2094700FF23B7 /* DataProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataProviderTests.swift; sourceTree = ""; }; + D39EA3FF23A2094700FF23B7 /* UnspentOutputSelectorSingleNoChangeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorSingleNoChangeTests.swift; sourceTree = ""; }; + D39EA40023A2094700FF23B7 /* BloomFilterManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomFilterManagerTests.swift; sourceTree = ""; }; + D39EA40123A2094700FF23B7 /* UnspentOutputProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputProviderTests.swift; sourceTree = ""; }; + D39EA40223A2094700FF23B7 /* StateManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateManagerTests.swift; sourceTree = ""; }; + D39EA40323A2094700FF23B7 /* WatchedTransactionManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchedTransactionManagerTests.swift; sourceTree = ""; }; + D39EA40423A2094700FF23B7 /* UnspentOutputSelectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorTests.swift; sourceTree = ""; }; + D39EA40523A2094700FF23B7 /* PublicKeyManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKeyManagerTests.swift; sourceTree = ""; }; + D39EA40623A2094700FF23B7 /* InitialSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialSyncerTests.swift; sourceTree = ""; }; + D39EA40723A2094700FF23B7 /* UnspentOutputSelectorOldTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnspentOutputSelectorOldTests.swift; sourceTree = ""; }; + D39EA40823A2094700FF23B7 /* IrregularOutputFinderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IrregularOutputFinderTests.swift; sourceTree = ""; }; + D39EA40A23A2094700FF23B7 /* BlockHashFetcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHashFetcherTests.swift; sourceTree = ""; }; + D39EA40B23A2094700FF23B7 /* BlockDiscoveryBatchTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockDiscoveryBatchTest.swift; sourceTree = ""; }; + D39EA40C23A2094700FF23B7 /* BlockHashFetcherHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockHashFetcherHelperTests.swift; sourceTree = ""; }; + D39EA40D23A2094700FF23B7 /* TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = ""; }; + D39EA40E23A2094700FF23B7 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; + D39EA41023A2094700FF23B7 /* DifficultyEncoderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DifficultyEncoderTests.swift; sourceTree = ""; }; + D39EA41223A2094800FF23B7 /* BlockValidatorHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockValidatorHelperTests.swift; sourceTree = ""; }; + D39EA41323A2094800FF23B7 /* AddressConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressConverterTests.swift; sourceTree = ""; }; + D39EA41423A2094800FF23B7 /* PaymentAddressParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentAddressParserTests.swift; sourceTree = ""; }; + D39EA41523A2094800FF23B7 /* MerkleBranchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleBranchTests.swift; sourceTree = ""; }; + D39EA41723A2094800FF23B7 /* HDPrivateKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HDPrivateKeyTests.swift; sourceTree = ""; }; + D39EA41923A2094800FF23B7 /* TransactionOutputExtractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputExtractorTests.swift; sourceTree = ""; }; + D39EA41A23A2094800FF23B7 /* TransactionCreatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionCreatorTests.swift; sourceTree = ""; }; + D39EA41B23A2094800FF23B7 /* TransactionPublicKeySetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionPublicKeySetterTests.swift; sourceTree = ""; }; + D39EA41C23A2094800FF23B7 /* TransactionProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionProcessorTests.swift; sourceTree = ""; }; + D39EA41D23A2094800FF23B7 /* TransactionOutputAddressExtractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionOutputAddressExtractorTests.swift; sourceTree = ""; }; + D39EA41E23A2094800FF23B7 /* TransactionSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSyncerTests.swift; sourceTree = ""; }; + D39EA42023A2094800FF23B7 /* ScriptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptTests.swift; sourceTree = ""; }; + D39EA42123A2094800FF23B7 /* ChunkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChunkTests.swift; sourceTree = ""; }; + D39EA42223A2094800FF23B7 /* ScriptConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptConverterTests.swift; sourceTree = ""; }; + D39EA42323A2094800FF23B7 /* TransactionInputExtractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInputExtractorTests.swift; sourceTree = ""; }; + D39EA42523A2094800FF23B7 /* SegWitAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegWitAddress.swift; sourceTree = ""; }; + D39EA42623A2094800FF23B7 /* InputSignerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSignerTests.swift; sourceTree = ""; }; + D39EA42723A2094800FF23B7 /* TransactionSizeCalculatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSizeCalculatorTests.swift; sourceTree = ""; }; + D39EA46C23A20C5E00FF23B7 /* BitcoinCashValidatorHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoinCashValidatorHelperTests.swift; sourceTree = ""; }; + D39EA46E23A20C5E00FF23B7 /* ForkValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForkValidatorTests.swift; sourceTree = ""; }; + D39EA46F23A20C5E00FF23B7 /* EDAValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EDAValidatorTests.swift; sourceTree = ""; }; + D39EA47023A20C5E00FF23B7 /* DAAValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAAValidatorTests.swift; sourceTree = ""; }; + D39EA47223A20C5E00FF23B7 /* CashBech32AddressConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CashBech32AddressConverterTests.swift; sourceTree = ""; }; + D39EA47323A20C5E00FF23B7 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; + D39EA47423A20C5E00FF23B7 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + D39EA47523A20C5E00FF23B7 /* TestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = ""; }; + D39EA47E23A20C6F00FF23B7 /* DashExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashExtensions.swift; sourceTree = ""; }; + D39EA48023A20C7000FF23B7 /* MasternodeSortedListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeSortedListTests.swift; sourceTree = ""; }; + D39EA48123A20C7000FF23B7 /* MerkleRootCreatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MerkleRootCreatorTests.swift; sourceTree = ""; }; + D39EA48223A20C7000FF23B7 /* MasternodeListManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListManagerTests.swift; sourceTree = ""; }; + D39EA48323A20C7000FF23B7 /* MasternodeListSyncerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListSyncerTests.swift; sourceTree = ""; }; + D39EA48423A20C7000FF23B7 /* MasternodeCbTxHashCalculatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeCbTxHashCalculatorTests.swift; sourceTree = ""; }; + D39EA48523A20C7000FF23B7 /* MasternodeListMerkleRootCalculatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasternodeListMerkleRootCalculatorTests.swift; sourceTree = ""; }; + D39EA48623A20C7000FF23B7 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; + D39EA48923A20C7000FF23B7 /* DarkGravityWaveTestNetValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkGravityWaveTestNetValidatorTests.swift; sourceTree = ""; }; + D39EA48A23A20C7000FF23B7 /* DarkGravityWaveValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkGravityWaveValidatorTests.swift; sourceTree = ""; }; + D39EA48C23A20C7000FF23B7 /* TransactionLockVoteValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteValidatorTests.swift; sourceTree = ""; }; + D39EA48E23A20C7000FF23B7 /* InstantSendLockValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockValidatorTests.swift; sourceTree = ""; }; + D39EA48F23A20C7000FF23B7 /* InstantSendLockHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendLockHandlerTests.swift; sourceTree = ""; }; + D39EA49023A20C7000FF23B7 /* TransactionLockVoteManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteManagerTests.swift; sourceTree = ""; }; + D39EA49123A20C7000FF23B7 /* InstantSendTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantSendTests.swift; sourceTree = ""; }; + D39EA49323A20C7000FF23B7 /* TransactionLockVoteHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionLockVoteHandlerTests.swift; sourceTree = ""; }; + D39EA49423A20C7000FF23B7 /* InstantTransactionManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantTransactionManagerTests.swift; sourceTree = ""; }; + D39EA49523A20C7000FF23B7 /* DashTestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashTestData.swift; sourceTree = ""; }; + D39EA4A823A20C8500FF23B7 /* GeneratedMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedMocks.swift; sourceTree = ""; }; + D39EA4A923A20C8500FF23B7 /* HodlerPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HodlerPluginTests.swift; sourceTree = ""; }; + D63B3F32063845F0AC9AD815 /* Pods-BitcoinCoreTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCoreTests.release.xcconfig"; path = "Target Support Files/Pods-BitcoinCoreTests/Pods-BitcoinCoreTests.release.xcconfig"; sourceTree = ""; }; + D9ABDE71F0DEF9D85F4A4A80 /* Pods-BitcoinCashKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinCashKitTests.release.xcconfig"; path = "Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests.release.xcconfig"; sourceTree = ""; }; + F7D3B3F76026512382C0D30D /* Pods-BitcoinKitExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitcoinKitExample.release.xcconfig"; path = "Target Support Files/Pods-BitcoinKitExample/Pods-BitcoinKitExample.release.xcconfig"; sourceTree = ""; }; + F9F3077F37128BE5E33206CB /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + FA329D8E228BBD9936928CD6 /* Pods-DashKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DashKitTests.release.xcconfig"; path = "Target Support Files/Pods-DashKitTests/Pods-DashKitTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACCD1AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A713DE3E57894A08D0D7BCA /* libPods-BitcoinKitExample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE21AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CCEE7A41B8D1161D30ECF667 /* libPods-BitcoinKitTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA39223A1230000FF23B7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE39228BA69CD5E423918C4E /* libPods-BitcoinCoreTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3A123A123FF00FF23B7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F58031596DC7395E2A4ADB3D /* libPods-BitcoinCashKitTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3B023A1240C00FF23B7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BADE51A1F50100CE1EA2E77 /* libPods-DashKitTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3BF23A1241600FF23B7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CED3DF3255F84A325E377749 /* libPods-HodlerTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 11B3516617F1FFA536053E9B /* DashKit */ = { + isa = PBXGroup; + children = ( + D39EA48723A20C7000FF23B7 /* Blocks */, + D39EA48B23A20C7000FF23B7 /* InstantSend */, + D39EA47F23A20C7000FF23B7 /* MasternodeList */, + D39EA47E23A20C6F00FF23B7 /* DashExtensions.swift */, + D39EA49523A20C7000FF23B7 /* DashTestData.swift */, + D39EA48623A20C7000FF23B7 /* GeneratedMocks.swift */, + ); + path = DashKit; + sourceTree = ""; + }; + 11B353BEEBD2124FD00AFA60 /* Hodler */ = { + isa = PBXGroup; + children = ( + D39EA4A923A20C8500FF23B7 /* HodlerPluginTests.swift */, + D39EA4A823A20C8500FF23B7 /* GeneratedMocks.swift */, + ); + path = Hodler; + sourceTree = ""; + }; + 11B35549713764C672AF8747 /* BitcoinCashKit */ = { + isa = PBXGroup; + children = ( + D39EA47123A20C5E00FF23B7 /* Bech32 */, + D39EA46B23A20C5E00FF23B7 /* Blocks */, + D39EA47423A20C5E00FF23B7 /* Extensions.swift */, + D39EA47523A20C5E00FF23B7 /* TestData.swift */, + D39EA47323A20C5E00FF23B7 /* GeneratedMocks.swift */, + ); + path = BitcoinCashKit; + sourceTree = ""; + }; + 11B355C4845959AD2F3A465A /* BitcoinCore */ = { + isa = PBXGroup; + children = ( + D39EA40F23A2094700FF23B7 /* BlockHeaders */, + D39EA3D823A2094700FF23B7 /* Blocks */, + D39EA3FC23A2094700FF23B7 /* Core */, + D39EA41623A2094800FF23B7 /* HDWallet */, + D39EA41123A2094700FF23B7 /* Helpers */, + D39EA3FE23A2094700FF23B7 /* Managers */, + D39EA3E223A2094700FF23B7 /* Network */, + D39EA41823A2094800FF23B7 /* Transactions */, + 11B35EB8AB33382E0F0DE3C5 /* SegWit */, + D39EA3FB23A2094700FF23B7 /* Extensions.swift */, + D39EA40D23A2094700FF23B7 /* TestData.swift */, + D39EA40E23A2094700FF23B7 /* GeneratedMocks.swift */, + ); + path = BitcoinCore; + sourceTree = ""; + }; + 11B35B706250BEAB6C3A01AD /* BitcoinKit */ = { + isa = PBXGroup; + children = ( + D39EA3D523A2053700FF23B7 /* GeneratedMocks.swift */, + ); + path = BitcoinKit; + sourceTree = ""; + }; + 11B35EB8AB33382E0F0DE3C5 /* SegWit */ = { + isa = PBXGroup; + children = ( + 11B35B0FC1EF59DDBB427A25 /* SegWitBech32AddressConverterTests.swift */, + ); + path = SegWit; + sourceTree = ""; + }; + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + 607FACD21AFB9204008FA782 /* Example for BitcoinKit */, + 607FACE81AFB9204008FA782 /* Tests */, + 607FACD11AFB9204008FA782 /* Products */, + 77152A17349C0FD70A530B43 /* Pods */, + C2139571C0F6E4D2D26ED0AD /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACD01AFB9204008FA782 /* BitcoinKitExample.app */, + 607FACE51AFB9204008FA782 /* BitcoinKitTests.xctest */, + D39EA39923A1230000FF23B7 /* BitcoinCoreTests.xctest */, + D39EA3A823A123FF00FF23B7 /* BitcoinCashKitTests.xctest */, + D39EA3B723A1240C00FF23B7 /* DashKitTests.xctest */, + D39EA3C623A1241600FF23B7 /* HodlerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACD21AFB9204008FA782 /* Example for BitcoinKit */ = { + isa = PBXGroup; + children = ( + D36AAA9123A2125D0065B32B /* Core */, + D36AAA7B23A2125D0065B32B /* Adapters */, + D36AAA8023A2125D0065B32B /* Controllers */, + D36AAA8F23A2125D0065B32B /* Assets.xcassets */, + D36AAA9023A2125D0065B32B /* Configuration.swift */, + 607FACD51AFB9204008FA782 /* AppDelegate.swift */, + D36AAAAB23A212E30065B32B /* LaunchScreen.xib */, + 607FACD31AFB9204008FA782 /* Supporting Files */, + ); + name = "Example for BitcoinKit"; + path = BitcoinKit; + sourceTree = ""; + }; + 607FACD31AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACD41AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACE81AFB9204008FA782 /* Tests */ = { + isa = PBXGroup; + children = ( + 607FACE91AFB9204008FA782 /* Supporting Files */, + 11B355C4845959AD2F3A465A /* BitcoinCore */, + 11B35B706250BEAB6C3A01AD /* BitcoinKit */, + 11B35549713764C672AF8747 /* BitcoinCashKit */, + 11B3516617F1FFA536053E9B /* DashKit */, + 11B353BEEBD2124FD00AFA60 /* Hodler */, + ); + path = Tests; + sourceTree = ""; + }; + 607FACE91AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D39EA3D023A124CA00FF23B7 /* DashKitTests.plist */, + D39EA3CE23A124C000FF23B7 /* HodlerTests.plist */, + D39EA3CC23A124B900FF23B7 /* BitcoinKitTests.plist */, + D39EA3CA23A124B000FF23B7 /* BitcoinCashKitTests.plist */, + D39EA3C823A124A600FF23B7 /* BitcoinCoreTests.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + D39EA38123A113EF00FF23B7 /* BitcoinCore.swift.podspec */, + CA5C17E35989902FCC31411F /* BitcoinKit.swift.podspec */, + D39EA38223A113EF00FF23B7 /* BitcoinCashKit.swift.podspec */, + D39EA38023A113EF00FF23B7 /* DashKit.swift.podspec */, + D369A2F22409238F00D6FE1E /* LitecoinKit.swift.podspec */, + D39EA37F23A113EF00FF23B7 /* Hodler.swift.podspec */, + F9F3077F37128BE5E33206CB /* README.md */, + A4262CF9BB9B657C6BD94DDA /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = ""; + }; + 77152A17349C0FD70A530B43 /* Pods */ = { + isa = PBXGroup; + children = ( + 5ABDFB9CD579008DADA21D5E /* Pods-BitcoinKit_Example.debug.xcconfig */, + 1029C270FEFAD935F7997EBE /* Pods-BitcoinKit_Example.release.xcconfig */, + ABCB45B4E8EBC7D5B2EB2632 /* Pods-BitcoinKit_Tests.debug.xcconfig */, + A7DBA76475D067CD41CF0EAA /* Pods-BitcoinKit_Tests.release.xcconfig */, + 5F8F16D600F4DF6CD20E0BAF /* Pods-BitcoinKitExample.debug.xcconfig */, + F7D3B3F76026512382C0D30D /* Pods-BitcoinKitExample.release.xcconfig */, + ACD6E6BDE7C346289FAB2499 /* Pods-BitcoinKitTests.debug.xcconfig */, + C45B20346E834C6B8A93D7EC /* Pods-BitcoinKitTests.release.xcconfig */, + 62C1FEEDB0512E77E56FACEC /* Pods-BitcoinCoreTests.debug.xcconfig */, + D63B3F32063845F0AC9AD815 /* Pods-BitcoinCoreTests.release.xcconfig */, + CC223B703634B83592B29EE5 /* Pods-BitcoinCashKitTests.debug.xcconfig */, + D9ABDE71F0DEF9D85F4A4A80 /* Pods-BitcoinCashKitTests.release.xcconfig */, + 8A7E6415B5F8A8DC1879CFA8 /* Pods-DashKitTests.debug.xcconfig */, + FA329D8E228BBD9936928CD6 /* Pods-DashKitTests.release.xcconfig */, + 9D42C5E0E663DB7B00733723 /* Pods-HodlerTests.debug.xcconfig */, + BBB9FFCB21BB700523232782 /* Pods-HodlerTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + C2139571C0F6E4D2D26ED0AD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 752AEC72EEED42E4B36FB86A /* libPods-BitcoinCashKitTests.a */, + 5F2017BF6B8CE56A1B7B7DF7 /* libPods-BitcoinCoreTests.a */, + 8533447FA6137FF0AD1A8653 /* libPods-BitcoinKitExample.a */, + 61760DF10CCBAC95608DF73F /* libPods-BitcoinKitTests.a */, + 68064D100A6B89360F422ACD /* libPods-DashKitTests.a */, + AEBF785A9D87D74223787CA0 /* libPods-HodlerTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + D36AAA7B23A2125D0065B32B /* Adapters */ = { + isa = PBXGroup; + children = ( + D36AAA7C23A2125D0065B32B /* BitcoinCashAdapter.swift */, + D36AAA7D23A2125D0065B32B /* DashAdapter.swift */, + D36AAA7E23A2125D0065B32B /* BitcoinAdapter.swift */, + D36AAA7F23A2125D0065B32B /* BaseAdapter.swift */, + 11B35784074A8A389B0C3A6F /* LitecoinAdapter.swift */, + ); + path = Adapters; + sourceTree = ""; + }; + D36AAA8023A2125D0065B32B /* Controllers */ = { + isa = PBXGroup; + children = ( + D36AAA8123A2125D0065B32B /* SendController.xib */, + D36AAA8223A2125D0065B32B /* BalanceController.swift */, + D36AAA8323A2125D0065B32B /* Cells */, + D36AAA8823A2125D0065B32B /* ReceiveController.swift */, + D36AAA8923A2125D0065B32B /* WordsController.swift */, + D36AAA8A23A2125D0065B32B /* MainController.swift */, + D36AAA8B23A2125D0065B32B /* WordsController.xib */, + D36AAA8C23A2125D0065B32B /* TransactionsController.swift */, + D36AAA8D23A2125D0065B32B /* SendController.swift */, + D36AAA8E23A2125D0065B32B /* ReceiveController.xib */, + ); + path = Controllers; + sourceTree = ""; + }; + D36AAA8323A2125D0065B32B /* Cells */ = { + isa = PBXGroup; + children = ( + D36AAA8423A2125D0065B32B /* TransactionCell.xib */, + D36AAA8523A2125D0065B32B /* TransactionCell.swift */, + D36AAA8623A2125D0065B32B /* BalanceCell.swift */, + D36AAA8723A2125D0065B32B /* BalanceCell.xib */, + ); + path = Cells; + sourceTree = ""; + }; + D36AAA9123A2125D0065B32B /* Core */ = { + isa = PBXGroup; + children = ( + D36AAA9223A2125D0065B32B /* Manager.swift */, + D36AAA9323A2125D0065B32B /* Signal.swift */, + D36AAA9423A2125D0065B32B /* TransactionRecord.swift */, + 2FA5DACF4DE83AC5FE512D28 /* Extensions.swift */, + ); + path = Core; + sourceTree = ""; + }; + D39EA3D823A2094700FF23B7 /* Blocks */ = { + isa = PBXGroup; + children = ( + D39EA3D923A2094700FF23B7 /* KitStateProviderTests.swift */, + D39EA3DA23A2094700FF23B7 /* BlockSyncerTests.swift */, + D39EA3DB23A2094700FF23B7 /* Validators */, + D39EA3E023A2094700FF23B7 /* BlockchainTests.swift */, + D39EA3E123A2094700FF23B7 /* MerkleBlockValidatorTests.swift */, + ); + path = Blocks; + sourceTree = ""; + }; + D39EA3DB23A2094700FF23B7 /* Validators */ = { + isa = PBXGroup; + children = ( + D39EA3DC23A2094700FF23B7 /* ProofOfWorkValidatorTests.swift */, + D39EA3DD23A2094700FF23B7 /* LegacyTestNetDifficultyValidatorTests.swift */, + D39EA3DE23A2094700FF23B7 /* LegacyDifficultyAdjustmentValidatorTests.swift */, + D39EA3DF23A2094700FF23B7 /* BitsValidatorTests.swift */, + ); + path = Validators; + sourceTree = ""; + }; + D39EA3E223A2094700FF23B7 /* Network */ = { + isa = PBXGroup; + children = ( + D39EA3E323A2094700FF23B7 /* BitcoinTestNetTests.swift */, + D39EA3E423A2094700FF23B7 /* BitcoinMainNetTests.swift */, + D39EA3E523A2094700FF23B7 /* BitcoinRegTestNetTests.swift */, + D39EA3E623A2094700FF23B7 /* Peer */, + D39EA3FA23A2094700FF23B7 /* BitcoinCashMainNetTests.swift */, + 2FA5D6678DE19DE48939B822 /* TransactionSenderTests.swift */, + ); + path = Network; + sourceTree = ""; + }; + D39EA3E623A2094700FF23B7 /* Peer */ = { + isa = PBXGroup; + children = ( + D39EA3E723A2094700FF23B7 /* ConnectionTimeoutManagerTests.swift */, + D39EA3E823A2094700FF23B7 /* PeerManagerTests.swift */, + D39EA3E923A2094700FF23B7 /* PeerAddressManagerTests.swift */, + D39EA3EA23A2094700FF23B7 /* PeerTask */, + D39EA3EF23A2094700FF23B7 /* PeerGroupTests */, + D39EA3F523A2094700FF23B7 /* PeerTests */, + ); + path = Peer; + sourceTree = ""; + }; + D39EA3EA23A2094700FF23B7 /* PeerTask */ = { + isa = PBXGroup; + children = ( + D39EA3EB23A2094700FF23B7 /* GetBlockHashesTaskTests.swift */, + D39EA3EC23A2094700FF23B7 /* SendTransactionTaskTests.swift */, + D39EA3ED23A2094700FF23B7 /* RequestTransactionTaskTests.swift */, + D39EA3EE23A2094700FF23B7 /* GetMerkleBlocksTaskTests.swift */, + ); + path = PeerTask; + sourceTree = ""; + }; + D39EA3EF23A2094700FF23B7 /* PeerGroupTests */ = { + isa = PBXGroup; + children = ( + D39EA3F023A2094700FF23B7 /* PeerHostManagerDelegateTests.swift */, + D39EA3F123A2094700FF23B7 /* BloomFilterManagerDelegateTests.swift */, + D39EA3F223A2094700FF23B7 /* IPeerGroupTests.swift */, + D39EA3F323A2094700FF23B7 /* PeerDelegateTests.swift */, + D39EA3F423A2094700FF23B7 /* PeerGroupTests.swift */, + ); + path = PeerGroupTests; + sourceTree = ""; + }; + D39EA3F523A2094700FF23B7 /* PeerTests */ = { + isa = PBXGroup; + children = ( + D39EA3F623A2094700FF23B7 /* IPeerTests.swift */, + D39EA3F723A2094700FF23B7 /* IPeerTaskDelegateTests.swift */, + D39EA3F823A2094700FF23B7 /* PeerConnectionDelegateTests.swift */, + D39EA3F923A2094700FF23B7 /* IPeerTaskRequesterTests.swift */, + ); + path = PeerTests; + sourceTree = ""; + }; + D39EA3FC23A2094700FF23B7 /* Core */ = { + isa = PBXGroup; + children = ( + D39EA3FD23A2094700FF23B7 /* DataProviderTests.swift */, + ); + path = Core; + sourceTree = ""; + }; + D39EA3FE23A2094700FF23B7 /* Managers */ = { + isa = PBXGroup; + children = ( + D39EA3FF23A2094700FF23B7 /* UnspentOutputSelectorSingleNoChangeTests.swift */, + D39EA40023A2094700FF23B7 /* BloomFilterManagerTests.swift */, + D39EA40123A2094700FF23B7 /* UnspentOutputProviderTests.swift */, + D39EA40223A2094700FF23B7 /* StateManagerTests.swift */, + D39EA40323A2094700FF23B7 /* WatchedTransactionManagerTests.swift */, + D39EA40423A2094700FF23B7 /* UnspentOutputSelectorTests.swift */, + D39EA40523A2094700FF23B7 /* PublicKeyManagerTests.swift */, + D39EA40623A2094700FF23B7 /* InitialSyncerTests.swift */, + D39EA40723A2094700FF23B7 /* UnspentOutputSelectorOldTests.swift */, + D39EA40823A2094700FF23B7 /* IrregularOutputFinderTests.swift */, + D39EA40923A2094700FF23B7 /* InitialSync */, + ); + path = Managers; + sourceTree = ""; + }; + D39EA40923A2094700FF23B7 /* InitialSync */ = { + isa = PBXGroup; + children = ( + D39EA40A23A2094700FF23B7 /* BlockHashFetcherTests.swift */, + D39EA40B23A2094700FF23B7 /* BlockDiscoveryBatchTest.swift */, + D39EA40C23A2094700FF23B7 /* BlockHashFetcherHelperTests.swift */, + ); + path = InitialSync; + sourceTree = ""; + }; + D39EA40F23A2094700FF23B7 /* BlockHeaders */ = { + isa = PBXGroup; + children = ( + D39EA41023A2094700FF23B7 /* DifficultyEncoderTests.swift */, + ); + path = BlockHeaders; + sourceTree = ""; + }; + D39EA41123A2094700FF23B7 /* Helpers */ = { + isa = PBXGroup; + children = ( + D39EA41223A2094800FF23B7 /* BlockValidatorHelperTests.swift */, + D39EA41323A2094800FF23B7 /* AddressConverterTests.swift */, + D39EA41423A2094800FF23B7 /* PaymentAddressParserTests.swift */, + D39EA41523A2094800FF23B7 /* MerkleBranchTests.swift */, + 1A56412D624E0604F48AEECE /* Bip69Tests.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + D39EA41623A2094800FF23B7 /* HDWallet */ = { + isa = PBXGroup; + children = ( + D39EA41723A2094800FF23B7 /* HDPrivateKeyTests.swift */, + ); + path = HDWallet; + sourceTree = ""; + }; + D39EA41823A2094800FF23B7 /* Transactions */ = { + isa = PBXGroup; + children = ( + D39EA41923A2094800FF23B7 /* TransactionOutputExtractorTests.swift */, + D39EA41A23A2094800FF23B7 /* TransactionCreatorTests.swift */, + D39EA41B23A2094800FF23B7 /* TransactionPublicKeySetterTests.swift */, + D39EA41C23A2094800FF23B7 /* TransactionProcessorTests.swift */, + D39EA41D23A2094800FF23B7 /* TransactionOutputAddressExtractorTests.swift */, + D39EA41E23A2094800FF23B7 /* TransactionSyncerTests.swift */, + D39EA41F23A2094800FF23B7 /* Scripts */, + D39EA42323A2094800FF23B7 /* TransactionInputExtractorTests.swift */, + D39EA42423A2094800FF23B7 /* Builder */, + D39EA42723A2094800FF23B7 /* TransactionSizeCalculatorTests.swift */, + 1A56436E5E862A9A35F6FE41 /* OutputSetterTests.swift */, + ); + path = Transactions; + sourceTree = ""; + }; + D39EA41F23A2094800FF23B7 /* Scripts */ = { + isa = PBXGroup; + children = ( + D39EA42023A2094800FF23B7 /* ScriptTests.swift */, + D39EA42123A2094800FF23B7 /* ChunkTests.swift */, + D39EA42223A2094800FF23B7 /* ScriptConverterTests.swift */, + ); + path = Scripts; + sourceTree = ""; + }; + D39EA42423A2094800FF23B7 /* Builder */ = { + isa = PBXGroup; + children = ( + D39EA42523A2094800FF23B7 /* SegWitAddress.swift */, + D39EA42623A2094800FF23B7 /* InputSignerTests.swift */, + ); + path = Builder; + sourceTree = ""; + }; + D39EA46B23A20C5E00FF23B7 /* Blocks */ = { + isa = PBXGroup; + children = ( + D39EA46C23A20C5E00FF23B7 /* BitcoinCashValidatorHelperTests.swift */, + D39EA46D23A20C5E00FF23B7 /* Validators */, + ); + path = Blocks; + sourceTree = ""; + }; + D39EA46D23A20C5E00FF23B7 /* Validators */ = { + isa = PBXGroup; + children = ( + D39EA46E23A20C5E00FF23B7 /* ForkValidatorTests.swift */, + D39EA46F23A20C5E00FF23B7 /* EDAValidatorTests.swift */, + D39EA47023A20C5E00FF23B7 /* DAAValidatorTests.swift */, + ); + path = Validators; + sourceTree = ""; + }; + D39EA47123A20C5E00FF23B7 /* Bech32 */ = { + isa = PBXGroup; + children = ( + D39EA47223A20C5E00FF23B7 /* CashBech32AddressConverterTests.swift */, + ); + path = Bech32; + sourceTree = ""; + }; + D39EA47F23A20C7000FF23B7 /* MasternodeList */ = { + isa = PBXGroup; + children = ( + D39EA48023A20C7000FF23B7 /* MasternodeSortedListTests.swift */, + D39EA48123A20C7000FF23B7 /* MerkleRootCreatorTests.swift */, + D39EA48223A20C7000FF23B7 /* MasternodeListManagerTests.swift */, + D39EA48323A20C7000FF23B7 /* MasternodeListSyncerTests.swift */, + D39EA48423A20C7000FF23B7 /* MasternodeCbTxHashCalculatorTests.swift */, + D39EA48523A20C7000FF23B7 /* MasternodeListMerkleRootCalculatorTests.swift */, + ); + path = MasternodeList; + sourceTree = ""; + }; + D39EA48723A20C7000FF23B7 /* Blocks */ = { + isa = PBXGroup; + children = ( + D39EA48823A20C7000FF23B7 /* Validators */, + ); + path = Blocks; + sourceTree = ""; + }; + D39EA48823A20C7000FF23B7 /* Validators */ = { + isa = PBXGroup; + children = ( + D39EA48923A20C7000FF23B7 /* DarkGravityWaveTestNetValidatorTests.swift */, + D39EA48A23A20C7000FF23B7 /* DarkGravityWaveValidatorTests.swift */, + ); + path = Validators; + sourceTree = ""; + }; + D39EA48B23A20C7000FF23B7 /* InstantSend */ = { + isa = PBXGroup; + children = ( + D39EA48C23A20C7000FF23B7 /* TransactionLockVoteValidatorTests.swift */, + D39EA48D23A20C7000FF23B7 /* InstantSendLock */, + D39EA49023A20C7000FF23B7 /* TransactionLockVoteManagerTests.swift */, + D39EA49123A20C7000FF23B7 /* InstantSendTests.swift */, + D39EA49223A20C7000FF23B7 /* TransactionLockVote */, + D39EA49423A20C7000FF23B7 /* InstantTransactionManagerTests.swift */, + ); + path = InstantSend; + sourceTree = ""; + }; + D39EA48D23A20C7000FF23B7 /* InstantSendLock */ = { + isa = PBXGroup; + children = ( + D39EA48E23A20C7000FF23B7 /* InstantSendLockValidatorTests.swift */, + D39EA48F23A20C7000FF23B7 /* InstantSendLockHandlerTests.swift */, + ); + path = InstantSendLock; + sourceTree = ""; + }; + D39EA49223A20C7000FF23B7 /* TransactionLockVote */ = { + isa = PBXGroup; + children = ( + D39EA49323A20C7000FF23B7 /* TransactionLockVoteHandlerTests.swift */, + ); + path = TransactionLockVote; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACCF1AFB9204008FA782 /* BitcoinKitExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "BitcoinKitExample" */; + buildPhases = ( + 4D2F1739ADC1B8FF2F30DEC6 /* [CP] Check Pods Manifest.lock */, + 607FACCC1AFB9204008FA782 /* Sources */, + 607FACCD1AFB9204008FA782 /* Frameworks */, + 607FACCE1AFB9204008FA782 /* Resources */, + CBACB7D2806000B2E6B7D92D /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitcoinKitExample; + productName = BitcoinKit; + productReference = 607FACD01AFB9204008FA782 /* BitcoinKitExample.app */; + productType = "com.apple.product-type.application"; + }; + 607FACE41AFB9204008FA782 /* BitcoinKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "BitcoinKitTests" */; + buildPhases = ( + 393739D2DB22DC4B9F04D4C3 /* [CP] Check Pods Manifest.lock */, + D39EA3D423A2021100FF23B7 /* Cuckoo */, + 607FACE11AFB9204008FA782 /* Sources */, + 607FACE21AFB9204008FA782 /* Frameworks */, + 607FACE31AFB9204008FA782 /* Resources */, + 73F6CD24266C8DEFDA100A12 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitcoinKitTests; + productName = Tests; + productReference = 607FACE51AFB9204008FA782 /* BitcoinKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + D39EA38C23A1230000FF23B7 /* BitcoinCoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D39EA39623A1230000FF23B7 /* Build configuration list for PBXNativeTarget "BitcoinCoreTests" */; + buildPhases = ( + BA9D76541EFC6174DADECCB8 /* [CP] Check Pods Manifest.lock */, + D39EA3D723A2089100FF23B7 /* Cuckoo */, + D39EA39023A1230000FF23B7 /* Sources */, + D39EA39223A1230000FF23B7 /* Frameworks */, + D39EA39423A1230000FF23B7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitcoinCoreTests; + productName = Tests; + productReference = D39EA39923A1230000FF23B7 /* BitcoinCoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + D39EA39B23A123FF00FF23B7 /* BitcoinCashKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D39EA3A523A123FF00FF23B7 /* Build configuration list for PBXNativeTarget "BitcoinCashKitTests" */; + buildPhases = ( + 0C5DFCC8EAC8A6516BFBCDA4 /* [CP] Check Pods Manifest.lock */, + D39EA46823A20B4300FF23B7 /* Cuckoo */, + D39EA39F23A123FF00FF23B7 /* Sources */, + D39EA3A123A123FF00FF23B7 /* Frameworks */, + D39EA3A323A123FF00FF23B7 /* Resources */, + 2F8AFF29D42B269D1BE10D3E /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitcoinCashKitTests; + productName = Tests; + productReference = D39EA3A823A123FF00FF23B7 /* BitcoinCashKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + D39EA3AA23A1240C00FF23B7 /* DashKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D39EA3B423A1240C00FF23B7 /* Build configuration list for PBXNativeTarget "DashKitTests" */; + buildPhases = ( + F9F696295B738796CD994D53 /* [CP] Check Pods Manifest.lock */, + D39EA46923A20B8C00FF23B7 /* Cuckoo */, + D39EA3AE23A1240C00FF23B7 /* Sources */, + D39EA3B023A1240C00FF23B7 /* Frameworks */, + D39EA3B223A1240C00FF23B7 /* Resources */, + CA02FDE0341145E1B5A15696 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DashKitTests; + productName = Tests; + productReference = D39EA3B723A1240C00FF23B7 /* DashKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + D39EA3B923A1241600FF23B7 /* HodlerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D39EA3C323A1241600FF23B7 /* Build configuration list for PBXNativeTarget "HodlerTests" */; + buildPhases = ( + AA3C1258195C13A83DF8BDFE /* [CP] Check Pods Manifest.lock */, + D39EA46A23A20BBF00FF23B7 /* Cuckoo */, + D39EA3BD23A1241600FF23B7 /* Sources */, + D39EA3BF23A1241600FF23B7 /* Frameworks */, + D39EA3C123A1241600FF23B7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = HodlerTests; + productName = Tests; + productReference = D39EA3C623A1241600FF23B7 /* HodlerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 1120; + ORGANIZATIONNAME = CocoaPods; + TargetAttributes = { + 607FACCF1AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + DevelopmentTeam = HC4MCAXJ66; + LastSwiftMigration = 0900; + }; + 607FACE41AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + DevelopmentTeam = HC4MCAXJ66; + LastSwiftMigration = 1120; + }; + D39EA38C23A1230000FF23B7 = { + DevelopmentTeam = HC4MCAXJ66; + LastSwiftMigration = 1120; + }; + D39EA39B23A123FF00FF23B7 = { + DevelopmentTeam = HC4MCAXJ66; + LastSwiftMigration = 1120; + }; + D39EA3AA23A1240C00FF23B7 = { + DevelopmentTeam = HC4MCAXJ66; + LastSwiftMigration = 1120; + }; + D39EA3B923A1241600FF23B7 = { + DevelopmentTeam = HC4MCAXJ66; + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "BitcoinKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACCF1AFB9204008FA782 /* BitcoinKitExample */, + 607FACE41AFB9204008FA782 /* BitcoinKitTests */, + D39EA38C23A1230000FF23B7 /* BitcoinCoreTests */, + D39EA39B23A123FF00FF23B7 /* BitcoinCashKitTests */, + D39EA3AA23A1240C00FF23B7 /* DashKitTests */, + D39EA3B923A1241600FF23B7 /* HodlerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACCE1AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D36AAA9923A2125E0065B32B /* SendController.xib in Resources */, + D36AAA9E23A2125E0065B32B /* BalanceCell.xib in Resources */, + D36AAAA223A2125E0065B32B /* WordsController.xib in Resources */, + D36AAAAC23A212E30065B32B /* LaunchScreen.xib in Resources */, + D36AAAA623A2125E0065B32B /* Assets.xcassets in Resources */, + D36AAA9B23A2125E0065B32B /* TransactionCell.xib in Resources */, + D36AAAA523A2125E0065B32B /* ReceiveController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE31AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA39423A1230000FF23B7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3A323A123FF00FF23B7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3B223A1240C00FF23B7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3C123A1241600FF23B7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0C5DFCC8EAC8A6516BFBCDA4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-BitcoinCashKitTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2F8AFF29D42B269D1BE10D3E /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/BitcoinCashKit.swift/BitcoinCashKit.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BitcoinCashKit.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitcoinCashKitTests/Pods-BitcoinCashKitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 393739D2DB22DC4B9F04D4C3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-BitcoinKitTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4D2F1739ADC1B8FF2F30DEC6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-BitcoinKitExample-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 73F6CD24266C8DEFDA100A12 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/BitcoinKit.swift/BitcoinKit.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BitcoinKit.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitcoinKitTests/Pods-BitcoinKitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + AA3C1258195C13A83DF8BDFE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-HodlerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + BA9D76541EFC6174DADECCB8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-BitcoinCoreTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CA02FDE0341145E1B5A15696 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DashKitTests/Pods-DashKitTests-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/DashKit.swift/DashKit.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DashKit.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DashKitTests/Pods-DashKitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + CBACB7D2806000B2E6B7D92D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-BitcoinKitExample/Pods-BitcoinKitExample-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/BitcoinCashKit.swift/BitcoinCashKit.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/BitcoinKit.swift/BitcoinKit.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/DashKit.swift/DashKit.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/LitecoinKit.swift/LitecoinKit.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BitcoinCashKit.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BitcoinKit.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DashKit.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LitecoinKit.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitcoinKitExample/Pods-BitcoinKitExample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D39EA3D423A2021100FF23B7 /* Cuckoo */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Cuckoo; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "OUTPUT_FILE=\"${PROJECT_DIR}/Tests/BitcoinKit/GeneratedMocks.swift\"\necho \"Generated Mocks File = ${OUTPUT_FILE}\"\n\nINPUT_DIR=\"${PROJECT_DIR}/../BitcoinKit/Classes\"\necho \"Mocks Input Directory = ${INPUT_DIR}\"\n\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"BitcoinKit\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"${INPUT_DIR}/Core/Protocols.swift\"\n"; + }; + D39EA3D723A2089100FF23B7 /* Cuckoo */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Cuckoo; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "OUTPUT_FILE=\"${PROJECT_DIR}/Tests/BitcoinCore/GeneratedMocks.swift\"\necho \"Generated Mocks File = ${OUTPUT_FILE}\"\n\nINPUT_DIR=\"${PROJECT_DIR}/../BitcoinCore/Classes\"\necho \"Mocks Input Directory = ${INPUT_DIR}\"\n\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"BitcoinCore\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"${INPUT_DIR}/Core/Protocols.swift\" \\\n\"${INPUT_DIR}/Transactions/Scripts/Script.swift\" \\\n\"${INPUT_DIR}/Network/Peer/PeerTask/PeerTask.swift\" \\\n\"${INPUT_DIR}/Network/Peer/PeerAddressManagerState.swift\" \\\n\"${INPUT_DIR}/Blocks/BlockSyncerState.swift\"\n"; + }; + D39EA46823A20B4300FF23B7 /* Cuckoo */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Cuckoo; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "OUTPUT_FILE=\"${PROJECT_DIR}/Tests/BitcoinCashKit/GeneratedMocks.swift\"\necho \"Generated Mocks File = ${OUTPUT_FILE}\"\n\nINPUT_DIR=\"${PROJECT_DIR}/../BitcoinCashKit/Classes\"\necho \"Mocks Input Directory = ${INPUT_DIR}\"\n\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"BitcoinCashKit\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"${INPUT_DIR}/Core/Protocols.swift\"\n"; + }; + D39EA46923A20B8C00FF23B7 /* Cuckoo */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Cuckoo; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "OUTPUT_FILE=\"${PROJECT_DIR}/Tests/DashKit/GeneratedMocks.swift\"\necho \"Generated Mocks File = ${OUTPUT_FILE}\"\n\nINPUT_DIR=\"${PROJECT_DIR}/../DashKit/Classes\"\necho \"Mocks Input Directory = ${INPUT_DIR}\"\n\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"DashKit\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"${INPUT_DIR}/Core/Protocols.swift\"\n"; + }; + D39EA46A23A20BBF00FF23B7 /* Cuckoo */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Cuckoo; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "OUTPUT_FILE=\"${PROJECT_DIR}/Tests/Hodler/GeneratedMocks.swift\"\necho \"Generated Mocks File = ${OUTPUT_FILE}\"\n\nINPUT_DIR=\"${PROJECT_DIR}/../Hodler/Classes\"\necho \"Mocks Input Directory = ${INPUT_DIR}\"\n\n\"${PODS_ROOT}/Cuckoo/run\" generate --testable \"Hodler\" \\\n--output \"${OUTPUT_FILE}\" \\\n\"${INPUT_DIR}/Core/Protocols.swift\"\n"; + }; + F9F696295B738796CD994D53 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DashKitTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607FACCC1AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D36AAAA323A2125E0065B32B /* TransactionsController.swift in Sources */, + D36AAAAA23A2125E0065B32B /* TransactionRecord.swift in Sources */, + D36AAA9F23A2125E0065B32B /* ReceiveController.swift in Sources */, + D36AAAA723A2125E0065B32B /* Configuration.swift in Sources */, + D36AAAA023A2125E0065B32B /* WordsController.swift in Sources */, + D36AAAA823A2125E0065B32B /* Manager.swift in Sources */, + D36AAAA923A2125E0065B32B /* Signal.swift in Sources */, + D36AAA9C23A2125E0065B32B /* TransactionCell.swift in Sources */, + D36AAA9723A2125E0065B32B /* BitcoinAdapter.swift in Sources */, + D36AAAA423A2125E0065B32B /* SendController.swift in Sources */, + D36AAA9D23A2125E0065B32B /* BalanceCell.swift in Sources */, + D36AAA9823A2125E0065B32B /* BaseAdapter.swift in Sources */, + D36AAA9A23A2125E0065B32B /* BalanceController.swift in Sources */, + D36AAAA123A2125E0065B32B /* MainController.swift in Sources */, + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + D36AAA9623A2125D0065B32B /* DashAdapter.swift in Sources */, + D36AAA9523A2125D0065B32B /* BitcoinCashAdapter.swift in Sources */, + 2FA5D47CFAEB8DB143F27645 /* Extensions.swift in Sources */, + 11B35F729A8303254F2CC2A7 /* LitecoinAdapter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE11AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D39EA3D623A2053700FF23B7 /* GeneratedMocks.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA39023A1230000FF23B7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D39EA43223A2094800FF23B7 /* BitcoinRegTestNetTests.swift in Sources */, + D39EA46323A2094800FF23B7 /* ScriptConverterTests.swift in Sources */, + D39EA43923A2094800FF23B7 /* GetMerkleBlocksTaskTests.swift in Sources */, + D39EA42D23A2094800FF23B7 /* BitsValidatorTests.swift in Sources */, + D39EA45223A2094800FF23B7 /* BlockHashFetcherHelperTests.swift in Sources */, + D39EA45423A2094800FF23B7 /* GeneratedMocks.swift in Sources */, + D39EA43123A2094800FF23B7 /* BitcoinMainNetTests.swift in Sources */, + D39EA45B23A2094800FF23B7 /* TransactionOutputExtractorTests.swift in Sources */, + D39EA43B23A2094800FF23B7 /* BloomFilterManagerDelegateTests.swift in Sources */, + D39EA45723A2094800FF23B7 /* AddressConverterTests.swift in Sources */, + D39EA44723A2094800FF23B7 /* BloomFilterManagerTests.swift in Sources */, + D39EA44B23A2094800FF23B7 /* UnspentOutputSelectorTests.swift in Sources */, + D39EA43323A2094800FF23B7 /* ConnectionTimeoutManagerTests.swift in Sources */, + D39EA46723A2094800FF23B7 /* TransactionSizeCalculatorTests.swift in Sources */, + D39EA43A23A2094800FF23B7 /* PeerHostManagerDelegateTests.swift in Sources */, + D39EA44423A2094800FF23B7 /* Extensions.swift in Sources */, + D39EA44523A2094800FF23B7 /* DataProviderTests.swift in Sources */, + D39EA43023A2094800FF23B7 /* BitcoinTestNetTests.swift in Sources */, + D39EA43C23A2094800FF23B7 /* IPeerGroupTests.swift in Sources */, + D39EA43823A2094800FF23B7 /* RequestTransactionTaskTests.swift in Sources */, + D39EA45023A2094800FF23B7 /* BlockHashFetcherTests.swift in Sources */, + D39EA43423A2094800FF23B7 /* PeerManagerTests.swift in Sources */, + D39EA46523A2094800FF23B7 /* SegWitAddress.swift in Sources */, + D39EA45E23A2094800FF23B7 /* TransactionProcessorTests.swift in Sources */, + D39EA43F23A2094800FF23B7 /* IPeerTests.swift in Sources */, + D39EA42823A2094800FF23B7 /* KitStateProviderTests.swift in Sources */, + D39EA42923A2094800FF23B7 /* BlockSyncerTests.swift in Sources */, + D39EA45523A2094800FF23B7 /* DifficultyEncoderTests.swift in Sources */, + D39EA44123A2094800FF23B7 /* PeerConnectionDelegateTests.swift in Sources */, + D39EA44223A2094800FF23B7 /* IPeerTaskRequesterTests.swift in Sources */, + D39EA45D23A2094800FF23B7 /* TransactionPublicKeySetterTests.swift in Sources */, + D39EA44A23A2094800FF23B7 /* WatchedTransactionManagerTests.swift in Sources */, + D39EA45323A2094800FF23B7 /* TestData.swift in Sources */, + D39EA42C23A2094800FF23B7 /* LegacyDifficultyAdjustmentValidatorTests.swift in Sources */, + D39EA45C23A2094800FF23B7 /* TransactionCreatorTests.swift in Sources */, + D39EA45623A2094800FF23B7 /* BlockValidatorHelperTests.swift in Sources */, + D39EA44E23A2094800FF23B7 /* UnspentOutputSelectorOldTests.swift in Sources */, + D39EA44823A2094800FF23B7 /* UnspentOutputProviderTests.swift in Sources */, + D39EA45123A2094800FF23B7 /* BlockDiscoveryBatchTest.swift in Sources */, + D39EA46423A2094800FF23B7 /* TransactionInputExtractorTests.swift in Sources */, + D39EA45A23A2094800FF23B7 /* HDPrivateKeyTests.swift in Sources */, + D39EA44F23A2094800FF23B7 /* IrregularOutputFinderTests.swift in Sources */, + D39EA43623A2094800FF23B7 /* GetBlockHashesTaskTests.swift in Sources */, + D39EA44323A2094800FF23B7 /* BitcoinCashMainNetTests.swift in Sources */, + D39EA46223A2094800FF23B7 /* ChunkTests.swift in Sources */, + D39EA43E23A2094800FF23B7 /* PeerGroupTests.swift in Sources */, + D39EA42B23A2094800FF23B7 /* LegacyTestNetDifficultyValidatorTests.swift in Sources */, + D39EA46623A2094800FF23B7 /* InputSignerTests.swift in Sources */, + D39EA42A23A2094800FF23B7 /* ProofOfWorkValidatorTests.swift in Sources */, + D39EA45F23A2094800FF23B7 /* TransactionOutputAddressExtractorTests.swift in Sources */, + D39EA44023A2094800FF23B7 /* IPeerTaskDelegateTests.swift in Sources */, + D39EA44923A2094800FF23B7 /* StateManagerTests.swift in Sources */, + D39EA42F23A2094800FF23B7 /* MerkleBlockValidatorTests.swift in Sources */, + D39EA45923A2094800FF23B7 /* MerkleBranchTests.swift in Sources */, + D39EA46123A2094800FF23B7 /* ScriptTests.swift in Sources */, + D39EA43D23A2094800FF23B7 /* PeerDelegateTests.swift in Sources */, + D39EA44D23A2094800FF23B7 /* InitialSyncerTests.swift in Sources */, + D39EA44C23A2094800FF23B7 /* PublicKeyManagerTests.swift in Sources */, + D39EA43523A2094800FF23B7 /* PeerAddressManagerTests.swift in Sources */, + D39EA46023A2094800FF23B7 /* TransactionSyncerTests.swift in Sources */, + D39EA45823A2094800FF23B7 /* PaymentAddressParserTests.swift in Sources */, + D39EA44623A2094800FF23B7 /* UnspentOutputSelectorSingleNoChangeTests.swift in Sources */, + D39EA42E23A2094800FF23B7 /* BlockchainTests.swift in Sources */, + D369A2F3240E51AB00D6FE1E /* SegWitBech32AddressConverterTests.swift in Sources */, + D39EA43723A2094800FF23B7 /* SendTransactionTaskTests.swift in Sources */, + 1A564D56C3A051FF606AC76B /* Bip69Tests.swift in Sources */, + 1A564F07A30BCD9D357CB960 /* OutputSetterTests.swift in Sources */, + 2FA5D7CBDF994AEB18C185DD /* TransactionSenderTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA39F23A123FF00FF23B7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D39EA47823A20C5E00FF23B7 /* EDAValidatorTests.swift in Sources */, + D39EA47B23A20C5E00FF23B7 /* GeneratedMocks.swift in Sources */, + D39EA47923A20C5E00FF23B7 /* DAAValidatorTests.swift in Sources */, + D39EA47D23A20C5E00FF23B7 /* TestData.swift in Sources */, + D39EA47723A20C5E00FF23B7 /* ForkValidatorTests.swift in Sources */, + D39EA47A23A20C5E00FF23B7 /* CashBech32AddressConverterTests.swift in Sources */, + D39EA47623A20C5E00FF23B7 /* BitcoinCashValidatorHelperTests.swift in Sources */, + D39EA47C23A20C5E00FF23B7 /* Extensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3AE23A1240C00FF23B7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D39EA49F23A20C7000FF23B7 /* DarkGravityWaveValidatorTests.swift in Sources */, + D39EA4A023A20C7000FF23B7 /* TransactionLockVoteValidatorTests.swift in Sources */, + D39EA49B23A20C7000FF23B7 /* MasternodeCbTxHashCalculatorTests.swift in Sources */, + D39EA49723A20C7000FF23B7 /* MasternodeSortedListTests.swift in Sources */, + D39EA49C23A20C7000FF23B7 /* MasternodeListMerkleRootCalculatorTests.swift in Sources */, + D39EA49D23A20C7000FF23B7 /* GeneratedMocks.swift in Sources */, + D39EA49A23A20C7000FF23B7 /* MasternodeListSyncerTests.swift in Sources */, + D39EA4A123A20C7000FF23B7 /* InstantSendLockValidatorTests.swift in Sources */, + D39EA49E23A20C7000FF23B7 /* DarkGravityWaveTestNetValidatorTests.swift in Sources */, + D39EA4A423A20C7000FF23B7 /* InstantSendTests.swift in Sources */, + D39EA4A223A20C7000FF23B7 /* InstantSendLockHandlerTests.swift in Sources */, + D39EA4A723A20C7000FF23B7 /* DashTestData.swift in Sources */, + D39EA49823A20C7000FF23B7 /* MerkleRootCreatorTests.swift in Sources */, + D39EA4A623A20C7000FF23B7 /* InstantTransactionManagerTests.swift in Sources */, + D39EA4A323A20C7000FF23B7 /* TransactionLockVoteManagerTests.swift in Sources */, + D39EA49923A20C7000FF23B7 /* MasternodeListManagerTests.swift in Sources */, + D39EA4A523A20C7000FF23B7 /* TransactionLockVoteHandlerTests.swift in Sources */, + D39EA49623A20C7000FF23B7 /* DashExtensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D39EA3BD23A1241600FF23B7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D39EA4AB23A20C8500FF23B7 /* HodlerPluginTests.swift in Sources */, + D39EA4AA23A20C8500FF23B7 /* GeneratedMocks.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF01AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5F8F16D600F4DF6CD20E0BAF /* Pods-BitcoinKitExample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = HC4MCAXJ66; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = BitcoinKit/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF11AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F7D3B3F76026512382C0D30D /* Pods-BitcoinKitExample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = HC4MCAXJ66; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = BitcoinKit/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 607FACF31AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ACD6E6BDE7C346289FAB2499 /* Pods-BitcoinKitTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/BitcoinKitTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF41AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C45B20346E834C6B8A93D7EC /* Pods-BitcoinKitTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/BitcoinKitTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + D39EA39723A1230000FF23B7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 62C1FEEDB0512E77E56FACEC /* Pods-BitcoinCoreTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/BitcoinCoreTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D39EA39823A1230000FF23B7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D63B3F32063845F0AC9AD815 /* Pods-BitcoinCoreTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/BitcoinCoreTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + D39EA3A623A123FF00FF23B7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CC223B703634B83592B29EE5 /* Pods-BitcoinCashKitTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/BitcoinCashKitTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D39EA3A723A123FF00FF23B7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D9ABDE71F0DEF9D85F4A4A80 /* Pods-BitcoinCashKitTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/BitcoinCashKitTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + D39EA3B523A1240C00FF23B7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8A7E6415B5F8A8DC1879CFA8 /* Pods-DashKitTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/DashKitTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D39EA3B623A1240C00FF23B7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FA329D8E228BBD9936928CD6 /* Pods-DashKitTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/DashKitTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + D39EA3C423A1241600FF23B7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9D42C5E0E663DB7B00733723 /* Pods-HodlerTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/HodlerTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D39EA3C523A1241600FF23B7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BBB9FFCB21BB700523232782 /* Pods-HodlerTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = HC4MCAXJ66; + FRAMEWORK_SEARCH_PATHS = ( + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/HodlerTests.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "BitcoinKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "BitcoinKitExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF01AFB9204008FA782 /* Debug */, + 607FACF11AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "BitcoinKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF31AFB9204008FA782 /* Debug */, + 607FACF41AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D39EA39623A1230000FF23B7 /* Build configuration list for PBXNativeTarget "BitcoinCoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D39EA39723A1230000FF23B7 /* Debug */, + D39EA39823A1230000FF23B7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D39EA3A523A123FF00FF23B7 /* Build configuration list for PBXNativeTarget "BitcoinCashKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D39EA3A623A123FF00FF23B7 /* Debug */, + D39EA3A723A123FF00FF23B7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D39EA3B423A1240C00FF23B7 /* Build configuration list for PBXNativeTarget "DashKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D39EA3B523A1240C00FF23B7 /* Debug */, + D39EA3B623A1240C00FF23B7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D39EA3C323A1241600FF23B7 /* Build configuration list for PBXNativeTarget "HodlerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D39EA3C423A1241600FF23B7 /* Debug */, + D39EA3C523A1241600FF23B7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/BitcoinKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 70% rename from Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Example/BitcoinKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata index f5eefb13..9ce41b0e 100644 --- a/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Example/BitcoinKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:BitcoinKit.xcodeproj"> diff --git a/BitcoinCashKit/BitcoinCashKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKitTests.xcscheme b/Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKitTests.xcscheme similarity index 62% rename from BitcoinCashKit/BitcoinCashKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKitTests.xcscheme rename to Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKitTests.xcscheme index 302c16ba..0bab376d 100644 --- a/BitcoinCashKit/BitcoinCashKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKitTests.xcscheme +++ b/Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinCashKitTests.xcscheme @@ -1,23 +1,10 @@ - - - - - - + ReferencedContainer = "container:BitcoinKit.xcodeproj"> - - - - - - + LastUpgradeVersion = "1120" + version = "1.3"> - - - - - - + ReferencedContainer = "container:BitcoinKit.xcodeproj"> - - - - - - - - - - + + + + @@ -27,29 +41,27 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - - - - + - - - + - + - + diff --git a/BitcoinKit/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinKitTests.xcscheme b/Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinKitTests.xcscheme similarity index 54% rename from BitcoinKit/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinKitTests.xcscheme rename to Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinKitTests.xcscheme index a8884045..3f19b85b 100644 --- a/BitcoinKit/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinKitTests.xcscheme +++ b/Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/BitcoinKitTests.xcscheme @@ -1,26 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - diff --git a/BitcoinCore/BitcoinCore.xcodeproj/xcshareddata/xcschemes/BitcoinCore.xcscheme b/Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/HodlerTests.xcscheme similarity index 50% rename from BitcoinCore/BitcoinCore.xcodeproj/xcshareddata/xcschemes/BitcoinCore.xcscheme rename to Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/HodlerTests.xcscheme index 071989cf..421c4e48 100644 --- a/BitcoinCore/BitcoinCore.xcodeproj/xcshareddata/xcschemes/BitcoinCore.xcscheme +++ b/Example/BitcoinKit.xcodeproj/xcshareddata/xcschemes/HodlerTests.xcscheme @@ -1,26 +1,10 @@ - - - - - - + + + + - - - - - - - - - - - - diff --git a/Example/BitcoinKit.xcworkspace/contents.xcworkspacedata b/Example/BitcoinKit.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..86ec3a34 --- /dev/null +++ b/Example/BitcoinKit.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/BitcoinKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/BitcoinKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from BitcoinKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Example/BitcoinKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Example/BitcoinKit/Adapters/BaseAdapter.swift b/Example/BitcoinKit/Adapters/BaseAdapter.swift new file mode 100644 index 00000000..07fc825c --- /dev/null +++ b/Example/BitcoinKit/Adapters/BaseAdapter.swift @@ -0,0 +1,194 @@ +import BitcoinCore +import RxSwift +import Hodler + +class BaseAdapter { + var feeRate: Int { 3 } + private let coinRate: Decimal = pow(10, 8) + + let name: String + let coinCode: String + + private let abstractKit: AbstractKit + + let lastBlockSignal = Signal() + let syncStateSignal = Signal() + let balanceSignal = Signal() + let transactionsSignal = Signal() + + init(name: String, coinCode: String, abstractKit: AbstractKit) { + self.name = name + self.coinCode = coinCode + self.abstractKit = abstractKit + } + + func transactionRecord(fromTransaction transaction: TransactionInfo) -> TransactionRecord { + var from = [TransactionInputOutput]() + var to = [TransactionInputOutput]() + + for input in transaction.inputs { + from.append(TransactionInputOutput( + mine: input.mine, address: input.address, value: input.value, + changeOutput: false, pluginId: nil, pluginData: nil + )) + } + + for output in transaction.outputs { + guard output.value > 0 else { + continue + } + + to.append(TransactionInputOutput( + mine: output.mine, address: output.address, value: output.value, + changeOutput: output.changeOutput, pluginId: output.pluginId, pluginData: output.pluginData + )) + } + + return TransactionRecord( + uid: transaction.uid, + transactionHash: transaction.transactionHash, + transactionIndex: transaction.transactionIndex, + interTransactionIndex: 0, + status: TransactionStatus(rawValue: transaction.status.rawValue) ?? TransactionStatus.new, + type: transaction.type, + blockHeight: transaction.blockHeight, + amount: Decimal(transaction.amount) / coinRate, + fee: transaction.fee.map { Decimal($0) / coinRate }, + date: Date(timeIntervalSince1970: Double(transaction.timestamp)), + from: from, + to: to, + conflictingHash: transaction.conflictingHash + ) + } + + private func convertToSatoshi(value: Decimal) -> Int { + let coinValue: Decimal = value * coinRate + + let handler = NSDecimalNumberHandler(roundingMode: .plain, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false) + return NSDecimalNumber(decimal: coinValue).rounding(accordingToBehavior: handler).intValue + } + + func transactionsSingle(fromUid: String?, type: TransactionFilterType? = nil, limit: Int) -> Single<[TransactionRecord]> { + abstractKit.transactions(fromUid: fromUid, type: type, limit: limit) + .map { [weak self] transactions -> [TransactionRecord] in + transactions.compactMap { + self?.transactionRecord(fromTransaction: $0) + } + } + } + +} + +extension BaseAdapter { + + var lastBlockObservable: Observable { + lastBlockSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + } + + var syncStateObservable: Observable { + syncStateSignal.asObservable().throttle(DispatchTimeInterval.milliseconds(200), scheduler: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + } + + var balanceObservable: Observable { + balanceSignal.asObservable() + } + + var transactionsObservable: Observable { + transactionsSignal.asObservable() + } + + func start() { + self.abstractKit.start() + } + + func refresh() { + self.abstractKit.start() + } + + var spendableBalance: Decimal { + Decimal(abstractKit.balance.spendable) / coinRate + } + + var unspendableBalance: Decimal { + Decimal(abstractKit.balance.unspendable) / coinRate + } + + var lastBlockInfo: BlockInfo? { + abstractKit.lastBlockInfo + } + + var syncState: BitcoinCore.KitState { + abstractKit.syncState + } + + func receiveAddress() -> String { + abstractKit.receiveAddress() + } + + func validate(address: String) throws { + try abstractKit.validate(address: address) + } + + func validate(amount: Decimal, address: String?) throws { + guard amount <= availableBalance(for: address) else { + throw SendError.insufficientAmount + } + } + + func sendSingle(to address: String, amount: Decimal, sortType: TransactionDataSortType, pluginData: [UInt8: IPluginData] = [:]) -> Single { + let satoshiAmount = convertToSatoshi(value: amount) + + return Single.create { [unowned self] observer in + do { + _ = try self.abstractKit.send(to: address, value: satoshiAmount, feeRate: self.feeRate, sortType: sortType, pluginData: pluginData) + observer(.success(())) + } catch { + observer(.error(error)) + } + + return Disposables.create() + } + } + + func availableBalance(for address: String?, pluginData: [UInt8: IPluginData] = [:]) -> Decimal { + let amount = (try? abstractKit.maxSpendableValue(toAddress: address, feeRate: feeRate, pluginData: pluginData)) ?? 0 + return Decimal(amount) / coinRate + } + + func maxSpendLimit(pluginData: [UInt8: IPluginData]) -> Int? { + do { + return try abstractKit.maxSpendLimit(pluginData: pluginData) + } catch { + return 0 + } + } + + func minSpendableAmount(for address: String?) -> Decimal { + Decimal(abstractKit.minSpendableValue(toAddress: address)) / coinRate + } + + func fee(for value: Decimal, address: String?, pluginData: [UInt8: IPluginData] = [:]) -> Decimal { + do { + let amount = convertToSatoshi(value: value) + let fee = try abstractKit.fee(for: amount, toAddress: address, feeRate: feeRate, pluginData: pluginData) + return Decimal(fee) / coinRate + } catch { + return 0 + } + } + + func printDebugs() { + print(abstractKit.debugInfo) + print() + print(abstractKit.statusInfo) + } + + func rawTransaction(transactionHash: String) -> String? { + abstractKit.rawTransaction(transactionHash: transactionHash) + } + +} + +enum SendError: Error { + case insufficientAmount +} diff --git a/Demo/Demo/Adapters/BitcoinAdapter.swift b/Example/BitcoinKit/Adapters/BitcoinAdapter.swift similarity index 57% rename from Demo/Demo/Adapters/BitcoinAdapter.swift rename to Example/BitcoinKit/Adapters/BitcoinAdapter.swift index 09418e2f..364c1581 100644 --- a/Demo/Demo/Adapters/BitcoinAdapter.swift +++ b/Example/BitcoinKit/Adapters/BitcoinAdapter.swift @@ -1,21 +1,23 @@ import BitcoinKit import BitcoinCore +import HdWalletKit +import HsToolKit import RxSwift class BitcoinAdapter: BaseAdapter { - private let bitcoinKit: BitcoinKit - override var changeableAddressType: Bool { return true } + let bitcoinKit: Kit - init(words: [String], testMode: Bool, syncMode: BitcoinCore.SyncMode) { - let networkType: BitcoinKit.NetworkType = testMode ? .testNet : .mainNet - bitcoinKit = try! BitcoinKit(withWords: words, walletId: "walletId", syncMode: syncMode, networkType: networkType, minLogLevel: Configuration.shared.minLogLevel) + init(words: [String], bip: Bip, testMode: Bool, syncMode: BitcoinCore.SyncMode, logger: Logger) { + let networkType: Kit.NetworkType = testMode ? .testNet : .mainNet + let seed = Mnemonic.seed(mnemonic: words) + bitcoinKit = try! Kit(seed: seed, bip: bip, walletId: "walletId", syncMode: syncMode, networkType: networkType, confirmationsThreshold: 1, logger: logger.scoped(with: "BitcoinKit")) super.init(name: "Bitcoin", coinCode: "BTC", abstractKit: bitcoinKit) bitcoinKit.delegate = self } class func clear() { - try? BitcoinKit.clear() + try? Kit.clear() } } @@ -29,7 +31,7 @@ extension BitcoinAdapter: BitcoinCoreDelegate { transactionsSignal.notify() } - func balanceUpdated(balance: Int) { + func balanceUpdated(balance: BalanceInfo) { balanceSignal.notify() } diff --git a/Demo/Demo/Adapters/BitcoinCashAdapter.swift b/Example/BitcoinKit/Adapters/BitcoinCashAdapter.swift similarity index 64% rename from Demo/Demo/Adapters/BitcoinCashAdapter.swift rename to Example/BitcoinKit/Adapters/BitcoinCashAdapter.swift index c91a8f9f..c3751649 100644 --- a/Demo/Demo/Adapters/BitcoinCashAdapter.swift +++ b/Example/BitcoinKit/Adapters/BitcoinCashAdapter.swift @@ -1,20 +1,23 @@ import BitcoinCashKit import BitcoinCore +import HsToolKit import RxSwift +import HdWalletKit class BitcoinCashAdapter: BaseAdapter { - private let bitcoinCashKit: BitcoinCashKit + let bitcoinCashKit: Kit - init(words: [String], testMode: Bool, syncMode: BitcoinCore.SyncMode) { - let networkType: BitcoinCashKit.NetworkType = testMode ? .testNet : .mainNet - bitcoinCashKit = try! BitcoinCashKit(withWords: words, walletId: "walletId", syncMode: syncMode, networkType: networkType, minLogLevel: Configuration.shared.minLogLevel) + init(words: [String], testMode: Bool, syncMode: BitcoinCore.SyncMode, logger: Logger) { + let networkType: Kit.NetworkType = testMode ? .testNet : .mainNet(coinType: .type145) + let seed = Mnemonic.seed(mnemonic: words) + bitcoinCashKit = try! Kit(seed: seed, walletId: "walletId", syncMode: syncMode, networkType: networkType, logger: logger.scoped(with: "BitcoinCashKit")) super.init(name: "Bitcoin Cash", coinCode: "BCH", abstractKit: bitcoinCashKit) bitcoinCashKit.delegate = self } class func clear() { - try? BitcoinCashKit.clear() + try? Kit.clear() } } @@ -28,7 +31,7 @@ extension BitcoinCashAdapter: BitcoinCoreDelegate { transactionsSignal.notify() } - func balanceUpdated(balance: Int) { + func balanceUpdated(balance: BalanceInfo) { balanceSignal.notify() } diff --git a/Demo/Demo/Adapters/DashAdapter.swift b/Example/BitcoinKit/Adapters/DashAdapter.swift similarity index 66% rename from Demo/Demo/Adapters/DashAdapter.swift rename to Example/BitcoinKit/Adapters/DashAdapter.swift index d04cc01d..9f8b0c31 100644 --- a/Demo/Demo/Adapters/DashAdapter.swift +++ b/Example/BitcoinKit/Adapters/DashAdapter.swift @@ -1,24 +1,27 @@ import DashKit import BitcoinCore +import HsToolKit import DashKit import RxSwift +import HdWalletKit class DashAdapter: BaseAdapter { override var feeRate: Int { return 1 } - private let dashKit: DashKit + private let dashKit: Kit - init(words: [String], testMode: Bool, syncMode: BitcoinCore.SyncMode) { - let networkType: DashKit.NetworkType = testMode ? .testNet : .mainNet - dashKit = try! DashKit(withWords: words, walletId: "walletId", syncMode: syncMode, networkType: networkType, minLogLevel: Configuration.shared.minLogLevel) + init(words: [String], testMode: Bool, syncMode: BitcoinCore.SyncMode, logger: Logger) { + let networkType: Kit.NetworkType = testMode ? .testNet : .mainNet + let seed = Mnemonic.seed(mnemonic: words) + dashKit = try! Kit(seed: seed, walletId: "walletId", syncMode: syncMode, networkType: networkType, logger: logger.scoped(with: "DashKit")) super.init(name: "Dash", coinCode: "DASH", abstractKit: dashKit) dashKit.delegate = self } - override func transactionsSingle(fromHash: String?, limit: Int) -> Single<[TransactionRecord]> { - return dashKit.transactions(fromHash: fromHash, limit: limit) + override func transactionsSingle(fromUid: String?, type: TransactionFilterType?, limit: Int) -> Single<[TransactionRecord]> { + dashKit.transactions(fromUid: fromUid, type: type, limit: limit) .map { [weak self] transactions -> [TransactionRecord] in - return transactions.compactMap { + transactions.compactMap { self?.transactionRecord(fromTransaction: $0) } } @@ -34,7 +37,7 @@ class DashAdapter: BaseAdapter { } class func clear() { - try? DashKit.clear() + try? Kit.clear() } } @@ -48,7 +51,7 @@ extension DashAdapter: DashKitDelegate { transactionsSignal.notify() } - func balanceUpdated(balance: Int) { + func balanceUpdated(balance: BalanceInfo) { balanceSignal.notify() } diff --git a/Example/BitcoinKit/Adapters/LitecoinAdapter.swift b/Example/BitcoinKit/Adapters/LitecoinAdapter.swift new file mode 100644 index 00000000..24a21c51 --- /dev/null +++ b/Example/BitcoinKit/Adapters/LitecoinAdapter.swift @@ -0,0 +1,46 @@ +import LitecoinKit +import BitcoinCore +import HdWalletKit +import HsToolKit +import RxSwift + +class LitecoinAdapter: BaseAdapter { + let litecoinKit: Kit + + init(words: [String], bip: Bip, testMode: Bool, syncMode: BitcoinCore.SyncMode, logger: Logger) { + let networkType: Kit.NetworkType = testMode ? .testNet : .mainNet + let seed = Mnemonic.seed(mnemonic: words) + litecoinKit = try! Kit(seed: seed, bip: bip, walletId: "walletId", syncMode: syncMode, networkType: networkType, confirmationsThreshold: 1, logger: logger.scoped(with: "LitecoinKit")) + + super.init(name: "Litecoin", coinCode: "LTC", abstractKit: litecoinKit) + litecoinKit.delegate = self + } + + class func clear() { + try? Kit.clear() + } +} + +extension LitecoinAdapter: BitcoinCoreDelegate { + + func transactionsUpdated(inserted: [TransactionInfo], updated: [TransactionInfo]) { + transactionsSignal.notify() + } + + func transactionsDeleted(hashes: [String]) { + transactionsSignal.notify() + } + + func balanceUpdated(balance: BalanceInfo) { + balanceSignal.notify() + } + + func lastBlockInfoUpdated(lastBlockInfo: BlockInfo) { + lastBlockSignal.notify() + } + + public func kitStateUpdated(state: BitcoinCore.KitState) { + syncStateSignal.notify() + } + +} diff --git a/Demo/Demo/AppDelegate.swift b/Example/BitcoinKit/AppDelegate.swift similarity index 65% rename from Demo/Demo/AppDelegate.swift rename to Example/BitcoinKit/AppDelegate.swift index 31f53c4b..77a158e7 100644 --- a/Demo/Demo/AppDelegate.swift +++ b/Example/BitcoinKit/AppDelegate.swift @@ -5,6 +5,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? + private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let controller = Manager.shared.savedWords == nil ? UINavigationController(rootViewController: WordsController()) : MainController() @@ -20,9 +22,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func applicationDidEnterBackground(_ application: UIApplication) { + backgroundTask = UIApplication.shared.beginBackgroundTask { + UIApplication.shared.endBackgroundTask(self.backgroundTask) + self.backgroundTask = UIBackgroundTaskIdentifier.invalid + } } func applicationWillEnterForeground(_ application: UIApplication) { + if backgroundTask != UIBackgroundTaskIdentifier.invalid { + UIApplication.shared.endBackgroundTask(backgroundTask) + backgroundTask = UIBackgroundTaskIdentifier.invalid + } } func applicationDidBecomeActive(_ application: UIApplication) { diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon1024.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon1024.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon1024.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon1024.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon120-1.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon120-1.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon120-1.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon120-1.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon120.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon120.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon120.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon120.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon180.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon180.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon180.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon180.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon40.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon40.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon40.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon40.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon58.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon58.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon58.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon58.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon60.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon60.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon60.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon60.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon80.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon80.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon80.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon80.png diff --git a/Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon87.png b/Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon87.png similarity index 100% rename from Demo/Demo/Assets.xcassets/AppIcon.appiconset/wk_appicon87.png rename to Example/BitcoinKit/Assets.xcassets/AppIcon.appiconset/wk_appicon87.png diff --git a/Demo/Demo/Assets.xcassets/Balance Tab Bar Icon.imageset/Contents.json b/Example/BitcoinKit/Assets.xcassets/Balance Tab Bar Icon.imageset/Contents.json similarity index 100% rename from Demo/Demo/Assets.xcassets/Balance Tab Bar Icon.imageset/Contents.json rename to Example/BitcoinKit/Assets.xcassets/Balance Tab Bar Icon.imageset/Contents.json diff --git a/Demo/Demo/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@2x.png b/Example/BitcoinKit/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@2x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@2x.png rename to Example/BitcoinKit/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@2x.png diff --git a/Demo/Demo/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@3x.png b/Example/BitcoinKit/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@3x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@3x.png rename to Example/BitcoinKit/Assets.xcassets/Balance Tab Bar Icon.imageset/baseline_account_balance_wallet_black_24pt@3x.png diff --git a/Demo/Demo/Assets.xcassets/Contents.json b/Example/BitcoinKit/Assets.xcassets/Contents.json similarity index 100% rename from Demo/Demo/Assets.xcassets/Contents.json rename to Example/BitcoinKit/Assets.xcassets/Contents.json diff --git a/Demo/Demo/Assets.xcassets/Receive Tab Bar Icon.imageset/Contents.json b/Example/BitcoinKit/Assets.xcassets/Receive Tab Bar Icon.imageset/Contents.json similarity index 100% rename from Demo/Demo/Assets.xcassets/Receive Tab Bar Icon.imageset/Contents.json rename to Example/BitcoinKit/Assets.xcassets/Receive Tab Bar Icon.imageset/Contents.json diff --git a/Demo/Demo/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@2x.png b/Example/BitcoinKit/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@2x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@2x.png rename to Example/BitcoinKit/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@2x.png diff --git a/Demo/Demo/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@3x.png b/Example/BitcoinKit/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@3x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@3x.png rename to Example/BitcoinKit/Assets.xcassets/Receive Tab Bar Icon.imageset/baseline_save_alt_black_24pt@3x.png diff --git a/Demo/Demo/Assets.xcassets/Send Tab Bar Icon.imageset/Contents.json b/Example/BitcoinKit/Assets.xcassets/Send Tab Bar Icon.imageset/Contents.json similarity index 100% rename from Demo/Demo/Assets.xcassets/Send Tab Bar Icon.imageset/Contents.json rename to Example/BitcoinKit/Assets.xcassets/Send Tab Bar Icon.imageset/Contents.json diff --git a/Demo/Demo/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@2x.png b/Example/BitcoinKit/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@2x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@2x.png rename to Example/BitcoinKit/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@2x.png diff --git a/Demo/Demo/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@3x.png b/Example/BitcoinKit/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@3x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@3x.png rename to Example/BitcoinKit/Assets.xcassets/Send Tab Bar Icon.imageset/baseline_unarchive_black_24pt@3x.png diff --git a/Demo/Demo/Assets.xcassets/Transactions Tab Bar Icon.imageset/Contents.json b/Example/BitcoinKit/Assets.xcassets/Transactions Tab Bar Icon.imageset/Contents.json similarity index 100% rename from Demo/Demo/Assets.xcassets/Transactions Tab Bar Icon.imageset/Contents.json rename to Example/BitcoinKit/Assets.xcassets/Transactions Tab Bar Icon.imageset/Contents.json diff --git a/Demo/Demo/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@2x.png b/Example/BitcoinKit/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@2x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@2x.png rename to Example/BitcoinKit/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@2x.png diff --git a/Demo/Demo/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@3x.png b/Example/BitcoinKit/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@3x.png similarity index 100% rename from Demo/Demo/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@3x.png rename to Example/BitcoinKit/Assets.xcassets/Transactions Tab Bar Icon.imageset/baseline_compare_arrows_black_24pt@3x.png diff --git a/Example/BitcoinKit/Configuration.swift b/Example/BitcoinKit/Configuration.swift new file mode 100644 index 00000000..e887b970 --- /dev/null +++ b/Example/BitcoinKit/Configuration.swift @@ -0,0 +1,14 @@ +import BitcoinCore +import HsToolKit + +class Configuration { + static let shared = Configuration() + + let minLogLevel: Logger.Level = .verbose + let testNet = false + let defaultWords = [ +// "current force clump paper shrug extra zebra employ prefer upon mobile hire", + "popular game latin harvest silly excess much valid elegant illness edge silk", + ] + +} diff --git a/Demo/Demo/Controllers/BalanceController.swift b/Example/BitcoinKit/Controllers/BalanceController.swift similarity index 92% rename from Demo/Demo/Controllers/BalanceController.swift rename to Example/BitcoinKit/Controllers/BalanceController.swift index 68f359f5..fa17f33c 100644 --- a/Demo/Demo/Controllers/BalanceController.swift +++ b/Example/BitcoinKit/Controllers/BalanceController.swift @@ -59,6 +59,14 @@ class BalanceController: UITableViewController { @objc func start() { Manager.shared.adapters.forEach { $0.start() } + if let button = navigationItem.rightBarButtonItem { + button.title = "Refresh" + button.action = #selector(refresh) + } + } + + @objc func refresh() { + Manager.shared.adapters.forEach { $0.refresh() } } @IBAction func showDebugInfo() { @@ -70,7 +78,7 @@ class BalanceController: UITableViewController { } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 160 + return 220 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { diff --git a/Demo/Demo/Controllers/Cells/BalanceCell.swift b/Example/BitcoinKit/Controllers/Cells/BalanceCell.swift similarity index 77% rename from Demo/Demo/Controllers/Cells/BalanceCell.swift rename to Example/BitcoinKit/Controllers/Cells/BalanceCell.swift index 71c7db71..1b40ed9f 100644 --- a/Demo/Demo/Controllers/Cells/BalanceCell.swift +++ b/Example/BitcoinKit/Controllers/Cells/BalanceCell.swift @@ -12,17 +12,23 @@ class BalanceCell: UITableViewCell { @IBOutlet weak var nameLabel: UILabel? @IBOutlet weak var titleLabel: UILabel? @IBOutlet weak var valueLabel: UILabel? + @IBOutlet weak var errorLabel: UILabel? func bind(adapter: BaseAdapter) { let syncStateString: String + var errorString = "" switch adapter.syncState { case .synced: syncStateString = "Synced!" + case .apiSyncing(let transactionsFound): syncStateString = "API Syncing \(transactionsFound) txs" case .syncing(let progress): syncStateString = "Syncing \(Int(progress * 100))%" - case .notSynced: syncStateString = "Not Synced" + case .notSynced(let error): + syncStateString = "Not Synced" + errorString = "\(error)" } nameLabel?.text = adapter.name + errorLabel?.text = errorString var lastBlockHeightString = "n/a" var lastBlockDateString = "n/a" @@ -40,14 +46,16 @@ class BalanceCell: UITableViewCell { Sync state: Last block: - Balance: + Spendable balance: + Unspendable balance: """, alignment: .left, label: titleLabel) set(string: """ \(syncStateString) \(lastBlockHeightString) \(lastBlockDateString) - \(adapter.balance) \(adapter.coinCode) + \(adapter.spendableBalance.formattedAmount) \(adapter.coinCode) + \(adapter.unspendableBalance.formattedAmount) \(adapter.coinCode) """, alignment: .right, label: valueLabel) } diff --git a/Demo/Demo/Controllers/Cells/BalanceCell.xib b/Example/BitcoinKit/Controllers/Cells/BalanceCell.xib similarity index 70% rename from Demo/Demo/Controllers/Cells/BalanceCell.xib rename to Example/BitcoinKit/Controllers/Cells/BalanceCell.xib index 2086228a..0ba30a5a 100644 --- a/Demo/Demo/Controllers/Cells/BalanceCell.xib +++ b/Example/BitcoinKit/Controllers/Cells/BalanceCell.xib @@ -1,37 +1,36 @@ - - - - + + - + + - - + + - + + + + + + - + diff --git a/Demo/Demo/Controllers/Cells/TransactionCell.swift b/Example/BitcoinKit/Controllers/Cells/TransactionCell.swift similarity index 50% rename from Demo/Demo/Controllers/Cells/TransactionCell.swift rename to Example/BitcoinKit/Controllers/Cells/TransactionCell.swift index 9d4f15fc..09b5f59b 100644 --- a/Demo/Demo/Controllers/Cells/TransactionCell.swift +++ b/Example/BitcoinKit/Controllers/Cells/TransactionCell.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import Hodler class TransactionCell: UITableViewCell { static let dateFormatter: DateFormatter = { @@ -12,8 +13,9 @@ class TransactionCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel? @IBOutlet weak var valueLabel: UILabel? @IBOutlet weak var transactionTypeLabel: UILabel? + private let coinRate: Decimal = pow(10, 8) - func bind(transaction: TransactionRecord, coinCode: String, lastBlockHeight: Int?) { + func bind(index: Int, transaction: TransactionRecord, coinCode: String, lastBlockHeight: Int?) { var confirmations = "n/a" if let lastBlockHeight = lastBlockHeight, let blockHeight = transaction.blockHeight { @@ -21,38 +23,59 @@ class TransactionCell: UITableViewCell { } let from = transaction.from.map { from -> String in - var string = format(hash: from.address) + var string = from.address.flatMap { format(hash: $0) } ?? "Unknown address" if from.mine { - string += " (mine)" + string += "(mine)" + } + if let value = from.value { + string += "(\((Decimal(value) / coinRate).formattedAmount))" } return string } let to = transaction.to.map { to -> String in - var string = format(hash: to.address) + var string = to.address.flatMap { format(hash: $0) } ?? "Unknown address" if to.mine { - string += " (mine)" + string += "(mine)" + } + if to.changeOutput { + string += "(change)" + } + if let value = to.value { + string += "(\((Decimal(value) / coinRate).formattedAmount))" + } + if let pluginId = to.pluginId, let pluginData = to.pluginData, pluginId == HodlerPlugin.id, let hodlerData = pluginData as? HodlerOutputData { + string += "\nLocked Until: \(TransactionCell.dateFormatter.string(from: Date(timeIntervalSince1970: Double(hodlerData.approximateUnlockTime!)))) <-" + string += "\nOriginal: \(format(hash: hodlerData.addressString)) <-" } return string } set(string: """ - Tx Hash: + Tx Hash: \(index) + Tx Status: Tx Index: Date: + Type: Amount: + Fee: Block: + ConflictingHash: Confirmations: \(from.map { _ in "From:" }.joined(separator: "\n")) - \(to.map { _ in "To:" }.joined(separator: "\n")) + \(transaction.to.map { "To:\(String(repeating: "\n", count: TransactionCell.rowsCount(address: $0)))" }.joined(separator: "")) """, alignment: .left, label: titleLabel) set(string: """ \(format(hash: transaction.transactionHash)) + \(transaction.status) \(transaction.transactionIndex) - \(TransactionCell.dateFormatter.string(from: Date(timeIntervalSince1970: transaction.timestamp))) - \(transaction.amount) \(coinCode) + \(TransactionCell.dateFormatter.string(from: transaction.date)) + \(transaction.type.rawValue) + \(transaction.amount.formattedAmount) \(coinCode) + \(transaction.fee?.formattedAmount ?? "") \(coinCode) \(transaction.blockHeight.map { "# \($0)" } ?? "n/a") + \(format(hash: transaction.conflictingHash ?? "n/a")) \(confirmations) \(from.joined(separator: "\n")) \(to.joined(separator: "\n")) @@ -78,7 +101,32 @@ class TransactionCell: UITableViewCell { return hash } - return "\(hash[.. Int { + var rowsCount = 1 + + if let pluginId = address.pluginId, pluginId == HodlerPlugin.id { + rowsCount += 2 + } + + return rowsCount + } + + static func rowHeight(for transaction: TransactionRecord) -> Int { + let addressRowsCount = transaction.to.reduce(0) { $0 + rowsCount(address: $1) } + transaction.from.count + var height = (addressRowsCount + 10) * 18 + 30 + + if transaction.transactionExtraType != nil { + height += 18 + } + + return height } } diff --git a/Demo/Demo/Controllers/Cells/TransactionCell.xib b/Example/BitcoinKit/Controllers/Cells/TransactionCell.xib similarity index 85% rename from Demo/Demo/Controllers/Cells/TransactionCell.xib rename to Example/BitcoinKit/Controllers/Cells/TransactionCell.xib index 1106fb89..eb1e7615 100644 --- a/Demo/Demo/Controllers/Cells/TransactionCell.xib +++ b/Example/BitcoinKit/Controllers/Cells/TransactionCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -16,17 +14,17 @@ - + - - + - + diff --git a/Demo/Demo/Controllers/MainController.swift b/Example/BitcoinKit/Controllers/MainController.swift similarity index 100% rename from Demo/Demo/Controllers/MainController.swift rename to Example/BitcoinKit/Controllers/MainController.swift diff --git a/Demo/Demo/Controllers/ReceiveController.swift b/Example/BitcoinKit/Controllers/ReceiveController.swift similarity index 79% rename from Demo/Demo/Controllers/ReceiveController.swift rename to Example/BitcoinKit/Controllers/ReceiveController.swift index 01d93546..6ffcbce8 100644 --- a/Demo/Demo/Controllers/ReceiveController.swift +++ b/Example/BitcoinKit/Controllers/ReceiveController.swift @@ -6,8 +6,7 @@ class ReceiveController: UIViewController { private let disposeBag = DisposeBag() @IBOutlet weak var addressLabel: UILabel? - @IBOutlet weak var addressTypeControl: UISegmentedControl! - + private var adapters = [BaseAdapter]() private let segmentedControl = UISegmentedControl() @@ -19,8 +18,6 @@ class ReceiveController: UIViewController { addressLabel?.layer.cornerRadius = 8 addressLabel?.clipsToBounds = true - segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) - Manager.shared.adapterSignal .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .observeOn(MainScheduler.instance) @@ -30,6 +27,7 @@ class ReceiveController: UIViewController { .disposed(by: disposeBag) updateAdapters() + segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) } private func updateAdapters() { @@ -45,9 +43,6 @@ class ReceiveController: UIViewController { segmentedControl.selectedSegmentIndex = 0 segmentedControl.sendActions(for: .valueChanged) - - addressTypeControl.selectedSegmentIndex = 0 - addressTypeControl.isHidden = false } override func viewWillAppear(_ animated: Bool) { @@ -56,26 +51,13 @@ class ReceiveController: UIViewController { segmentedControl.sendActions(for: .valueChanged) } - func type(segment: Int) -> ScriptType { - switch segment { - case 1: return .p2wpkh - case 2: return .p2wpkhSh - default: return .p2pkh - } - } - @objc func onSegmentChanged() { - addressTypeControl.isHidden = segmentedControl.selectedSegmentIndex != 0 - addressTypeControl.selectedSegmentIndex = 0 updateAddress() - if let adapter = currentAdapter { - print(adapter.debugInfo) - } + currentAdapter?.printDebugs() } func updateAddress() { - let segment = addressTypeControl.selectedSegmentIndex - addressLabel?.text = " \(currentAdapter?.receiveAddress(for: type(segment: segment)) ?? "") " + addressLabel?.text = " \(currentAdapter?.receiveAddress() ?? "") " } @IBAction func onAddressTypeChanged(_ sender: Any) { diff --git a/Demo/Demo/Controllers/ReceiveController.xib b/Example/BitcoinKit/Controllers/ReceiveController.xib similarity index 69% rename from Demo/Demo/Controllers/ReceiveController.xib rename to Example/BitcoinKit/Controllers/ReceiveController.xib index 54928982..cda4d9de 100644 --- a/Demo/Demo/Controllers/ReceiveController.xib +++ b/Example/BitcoinKit/Controllers/ReceiveController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -13,7 +11,6 @@ - @@ -23,13 +20,13 @@ - - - - - - - - - - - - - - - + - - + diff --git a/Example/BitcoinKit/Controllers/SendController.swift b/Example/BitcoinKit/Controllers/SendController.swift new file mode 100644 index 00000000..c07a11ee --- /dev/null +++ b/Example/BitcoinKit/Controllers/SendController.swift @@ -0,0 +1,230 @@ +import UIKit +import RxSwift +import Hodler +import BitcoinCore + +class SendController: UIViewController { + private let disposeBag = DisposeBag() + + @IBOutlet weak var addressTextField: UITextField? + @IBOutlet weak var amountTextField: UITextField? + @IBOutlet weak var coinLabel: UILabel? + @IBOutlet weak var feeLabel: UILabel? + @IBOutlet weak var timeLockSwitch: UISwitch? + @IBOutlet weak var picker: UIPickerView? + + private var timeIntervalStrings = ["Hour", "Month", "Half Year", "Year"] + private var timeIntervals: [HodlerPlugin.LockTimeInterval] = [.hour, .month, .halfYear, .year] + private var selectedTimeInterval: HodlerPlugin.LockTimeInterval = .hour + + private var adapters = [BaseAdapter]() + private let segmentedControl = UISegmentedControl() + private var timeLockEnabled = false + + override func viewDidLoad() { + super.viewDidLoad() + + segmentedControl.addTarget(self, action: #selector(onSegmentChanged), for: .valueChanged) + picker?.dataSource = self + picker?.delegate = self + + Manager.shared.adapterSignal + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self] in + self?.updateAdapters() + }) + .disposed(by: disposeBag) + + updateAdapters() + } + + private func updateAdapters() { + segmentedControl.removeAllSegments() + + adapters = Manager.shared.adapters + + for (index, adapter) in adapters.enumerated() { + segmentedControl.insertSegment(withTitle: adapter.coinCode, at: index, animated: false) + } + + navigationItem.titleView = segmentedControl + + segmentedControl.selectedSegmentIndex = 0 + segmentedControl.sendActions(for: .valueChanged) + } + + private func updateFee() { + var address: String? = nil + + if let addressStr = addressTextField?.text { + do { + try currentAdapter?.validate(address: addressStr) + address = addressStr + } catch { + } + } + + guard let amountString = amountTextField?.text, let amount = Decimal(string: amountString) else { + feeLabel?.text = "Fee: " + return + } + + var pluginData = [UInt8: IPluginData]() + if timeLockEnabled { + pluginData[HodlerPlugin.id] = HodlerData(lockTimeInterval: self.selectedTimeInterval) + } + + if let fee = currentAdapter?.fee(for: amount, address: address, pluginData: pluginData) { + feeLabel?.text = "Fee: \(fee.formattedAmount)" + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + + view.endEditing(true) + } + + @objc func onSegmentChanged() { + coinLabel?.text = currentAdapter?.coinCode + updateFee() + } + + @IBAction func onAddressEditEnded(_ sender: Any) { + updateFee() + } + + @IBAction func onAmountEditEnded(_ sender: Any) { + updateFee() + } + + @IBAction func onTimeLockSwitchToggle(_ sender: Any) { + timeLockEnabled = !timeLockEnabled + updateFee() + } + + @IBAction func setMaxAmount() { + var address: String? = nil + + if let addressStr = addressTextField?.text { + do { + try currentAdapter?.validate(address: addressStr) + address = addressStr + } catch { + } + } + + var pluginData = [UInt8: IPluginData]() + if timeLockEnabled { + pluginData[HodlerPlugin.id] = HodlerData(lockTimeInterval: self.selectedTimeInterval) + } + + if let maxAmount = currentAdapter?.availableBalance(for: address, pluginData: pluginData) { + amountTextField?.text = "\(maxAmount)" + onAmountEditEnded(0) + } + } + + @IBAction func setMinAmount() { + var address: String? = nil + + if let addressStr = addressTextField?.text { + do { + try currentAdapter?.validate(address: addressStr) + address = addressStr + } catch { + } + } + + if let minAmount = currentAdapter?.minSpendableAmount(for: address) { + amountTextField?.text = "\(minAmount)" + onAmountEditEnded(0) + } + } + + @IBAction func send() { + guard let address = addressTextField?.text else { + return + } + + do { + try currentAdapter?.validate(address: address) + } catch { + show(error: "Invalid address") + return + } + + guard let amountString = amountTextField?.text, let amount = Decimal(string: amountString) else { + show(error: "Invalid amount") + return + } + + var pluginData = [UInt8: IPluginData]() + if timeLockEnabled { + pluginData[HodlerPlugin.id] = HodlerData(lockTimeInterval: self.selectedTimeInterval) + } + + currentAdapter?.sendSingle(to: address, amount: amount, sortType: .shuffle, pluginData: pluginData) + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + .observeOn(MainScheduler.instance) + .subscribe(onSuccess: { [weak self] _ in + self?.addressTextField?.text = "" + self?.amountTextField?.text = "" + + self?.showSuccess(address: address, amount: amount) + }, onError: { [weak self] error in + self?.show(error: "Send failed: \(error)") + }) + .disposed(by: disposeBag) + } + + private func show(error: String) { + let alert = UIAlertController(title: "Send Error", message: error, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .cancel)) + present(alert, animated: true) + } + + private func showSuccess(address: String, amount: Decimal) { + let alert = UIAlertController(title: "Success", message: "\(amount.formattedAmount) sent to \(address)", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .cancel)) + present(alert, animated: true) + } + + private var currentAdapter: BaseAdapter? { + guard segmentedControl.selectedSegmentIndex != -1, adapters.count > segmentedControl.selectedSegmentIndex else { + return nil + } + + return adapters[segmentedControl.selectedSegmentIndex] + } + +} + +extension SendController: UIPickerViewDataSource { + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + 1 + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + timeIntervals.count + } +} + +extension SendController: UIPickerViewDelegate { + public func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { + 130 + } + + public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { + 30 + } + + public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + timeIntervalStrings[row] + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + selectedTimeInterval = timeIntervals[row] + } +} diff --git a/Example/BitcoinKit/Controllers/SendController.xib b/Example/BitcoinKit/Controllers/SendController.xib new file mode 100644 index 00000000..760136fb --- /dev/null +++ b/Example/BitcoinKit/Controllers/SendController.xib @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/Demo/Controllers/TransactionsController.swift b/Example/BitcoinKit/Controllers/TransactionsController.swift similarity index 85% rename from Demo/Demo/Controllers/TransactionsController.swift rename to Example/BitcoinKit/Controllers/TransactionsController.swift index 144741b3..a7e54e1a 100644 --- a/Demo/Demo/Controllers/TransactionsController.swift +++ b/Example/BitcoinKit/Controllers/TransactionsController.swift @@ -76,15 +76,15 @@ class TransactionsController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count + transactions.count } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 220 + CGFloat(TransactionCell.rowHeight(for: transactions[indexPath.row])) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return tableView.dequeueReusableCell(withIdentifier: String(describing: TransactionCell.self), for: indexPath) + tableView.dequeueReusableCell(withIdentifier: String(describing: TransactionCell.self), for: indexPath) } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { @@ -93,7 +93,7 @@ class TransactionsController: UITableViewController { } if let cell = cell as? TransactionCell { - cell.bind(transaction: transactions[indexPath.row], coinCode: currentAdapter.coinCode, lastBlockHeight: currentAdapter.lastBlockInfo?.height) + cell.bind(index: indexPath.row, transaction: transactions[indexPath.row], coinCode: currentAdapter.coinCode, lastBlockHeight: currentAdapter.lastBlockInfo?.height) } if indexPath.row > transactions.count - 3 { @@ -102,7 +102,12 @@ class TransactionsController: UITableViewController { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - UIPasteboard.general.setValue(transactions[indexPath.row].transactionHash, forPasteboardType: "public.plain-text") + let transactionHash = transactions[indexPath.row].transactionHash + + UIPasteboard.general.setValue(transactionHash, forPasteboardType: "public.plain-text") + + print("Transaction Hash: \(transactionHash)") + print("Raw Transaction: \(currentAdapter?.rawTransaction(transactionHash: transactionHash) ?? "")") let alert = UIAlertController(title: "Success", message: "Transaction Hash copied", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .cancel)) @@ -126,9 +131,9 @@ class TransactionsController: UITableViewController { loading = true - let fromHash = transactions.last?.transactionHash + let fromUid = transactions.last?.uid - currentAdapter?.transactionsSingle(fromHash: fromHash, limit: limit) + currentAdapter?.transactionsSingle(fromUid: fromUid, limit: limit) .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .observeOn(MainScheduler.instance) .subscribe(onSuccess: { [weak self] transactions in diff --git a/Demo/Demo/Controllers/WordsController.swift b/Example/BitcoinKit/Controllers/WordsController.swift similarity index 99% rename from Demo/Demo/Controllers/WordsController.swift rename to Example/BitcoinKit/Controllers/WordsController.swift index 44ac946a..ebcabd17 100644 --- a/Demo/Demo/Controllers/WordsController.swift +++ b/Example/BitcoinKit/Controllers/WordsController.swift @@ -1,5 +1,5 @@ import UIKit -import HSHDWalletKit +import HdWalletKit class WordsController: UIViewController { diff --git a/Demo/Demo/Controllers/WordsController.xib b/Example/BitcoinKit/Controllers/WordsController.xib similarity index 100% rename from Demo/Demo/Controllers/WordsController.xib rename to Example/BitcoinKit/Controllers/WordsController.xib diff --git a/Example/BitcoinKit/Core/Extensions.swift b/Example/BitcoinKit/Core/Extensions.swift new file mode 100644 index 00000000..902b69e8 --- /dev/null +++ b/Example/BitcoinKit/Core/Extensions.swift @@ -0,0 +1,12 @@ +import Foundation + +extension Decimal { + var formattedAmount: String { + let formatter = NumberFormatter() + formatter.generatesDecimalNumbers = true + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 8 + formatter.maximumFractionDigits = 8 + return formatter.string(from: self as NSDecimalNumber)! + } +} \ No newline at end of file diff --git a/Demo/Demo/Core/Manager.swift b/Example/BitcoinKit/Core/Manager.swift similarity index 84% rename from Demo/Demo/Core/Manager.swift rename to Example/BitcoinKit/Core/Manager.swift index 34f847ec..c5018eb3 100644 --- a/Demo/Demo/Core/Manager.swift +++ b/Example/BitcoinKit/Core/Manager.swift @@ -1,5 +1,6 @@ import RxSwift import BitcoinCore +import HsToolKit class Manager { static let shared = Manager() @@ -36,11 +37,13 @@ class Manager { private func initAdapters(words: [String], syncMode: BitcoinCore.SyncMode) { let configuration = Configuration.shared + let logger = Logger(minLogLevel: Configuration.shared.minLogLevel) adapters = [ - BitcoinAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode), - BitcoinCashAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode), - DashAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode), + BitcoinAdapter(words: words, bip: .bip44, testMode: configuration.testNet, syncMode: syncMode, logger: logger), + LitecoinAdapter(words: words, bip: .bip44, testMode: configuration.testNet, syncMode: syncMode, logger: logger), + BitcoinCashAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode, logger: logger), + DashAdapter(words: words, testMode: configuration.testNet, syncMode: syncMode, logger: logger), ] adapterSignal.notify() diff --git a/Demo/Demo/Core/Signal.swift b/Example/BitcoinKit/Core/Signal.swift similarity index 100% rename from Demo/Demo/Core/Signal.swift rename to Example/BitcoinKit/Core/Signal.swift diff --git a/Example/BitcoinKit/Core/TransactionRecord.swift b/Example/BitcoinKit/Core/TransactionRecord.swift new file mode 100644 index 00000000..dbc202a6 --- /dev/null +++ b/Example/BitcoinKit/Core/TransactionRecord.swift @@ -0,0 +1,32 @@ +import Foundation +import BitcoinCore + +struct TransactionRecord { + let uid: String + let transactionHash: String + let transactionIndex: Int + let interTransactionIndex: Int + let status: TransactionStatus + let type: TransactionType + let blockHeight: Int? + let amount: Decimal + let fee: Decimal? + let date: Date + let from: [TransactionInputOutput] + let to: [TransactionInputOutput] + let conflictingHash: String? + var transactionExtraType: String? +} + +struct TransactionInputOutput { + let mine: Bool + let address: String? + let value: Int? + let changeOutput: Bool + let pluginId: UInt8? + let pluginData: Any? +} + +enum TransactionStatus: Int { + case new, relayed, invalid +} diff --git a/Demo/Demo/Info.plist b/Example/BitcoinKit/Info.plist similarity index 58% rename from Demo/Demo/Info.plist rename to Example/BitcoinKit/Info.plist index 431ba885..2d4451b6 100644 --- a/Demo/Demo/Info.plist +++ b/Example/BitcoinKit/Info.plist @@ -3,13 +3,9 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIcons - - CFBundleIcons~ipad - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -20,35 +16,26 @@ APPL CFBundleShortVersionString 1.0 + CFBundleSignature + ???? CFBundleVersion 1 LSRequiresIPhoneOS - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSCameraUsageDescription - We need access to camera to scan QR codes. UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 - UIStatusBarStyle - UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + diff --git a/Demo/Demo/LaunchScreen.xib b/Example/BitcoinKit/LaunchScreen.xib similarity index 100% rename from Demo/Demo/LaunchScreen.xib rename to Example/BitcoinKit/LaunchScreen.xib diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 00000000..08bfb67c --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,56 @@ +platform :ios, '13' +use_modular_headers! + +target 'BitcoinKitExample' do + pod 'BitcoinCore.swift', :path => '../' + pod 'BitcoinKit.swift', :path => '../' + pod 'BitcoinCashKit.swift', :path => '../' + pod 'DashKit.swift', :path => '../' + pod 'LitecoinKit.swift', :path => '../' + pod 'Hodler.swift', :path => '../' + pod 'HsToolKit.swift', git: 'https://github.com/horizontalsystems/hs-tool-kit-ios' + pod 'HdWalletKit.swift', git: 'https://github.com/horizontalsystems/hd-wallet-kit-ios' + pod 'UIExtensions.swift', git: 'https://github.com/horizontalsystems/gui-kit/' +end + +def testPods + pod 'Quick' + pod 'Nimble' + pod 'Cuckoo' + pod 'RxBlocking', '~> 5.0' +end + +target 'BitcoinCoreTests' do + pod 'BitcoinCore.swift', :path => '../' + testPods +end + +target 'BitcoinKitTests' do + pod 'BitcoinKit.swift', :path => '../' + testPods +end + +target 'BitcoinCashKitTests' do + pod 'BitcoinCashKit.swift', :path => '../' + testPods +end + +target 'DashKitTests' do + pod 'DashKit.swift', :path => '../' + testPods +end + +target 'HodlerTests' do + pod 'Hodler.swift', :path => '../' + testPods +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle" + target.build_configurations.each do |config| + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end + end + end +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 00000000..f8bd9e5d --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,314 @@ +PODS: + - _NIODataStructures (2.40.0) + - Alamofire (5.6.1) + - BigInt (5.2.0) + - BitcoinCashKit.swift (0.18): + - BigInt (~> 5.0) + - BitcoinCore.swift (~> 0.18) + - GRDB.swift (~> 5.0) + - HdWalletKit.swift (~> 1.5) + - ObjectMapper (~> 4.0) + - OpenSslKit.swift (~> 1.0) + - RxSwift (~> 5.0) + - Secp256k1Kit.swift (~> 1.0) + - BitcoinCore.swift (0.18): + - BigInt (~> 5.0) + - GRDB.swift (~> 5.0) + - HdWalletKit.swift (~> 1.5) + - HsToolKit.swift (~> 1.3) + - ObjectMapper (~> 4.0) + - OpenSslKit.swift (~> 1.0) + - RxSwift (~> 5.0) + - Secp256k1Kit.swift (~> 1.0) + - SwiftNIO (~> 2) + - UIExtensions.swift (~> 1.1.1) + - BitcoinKit.swift (0.18): + - BigInt (~> 5.0) + - BitcoinCore.swift (~> 0.18) + - GRDB.swift (~> 5.0) + - HdWalletKit.swift (~> 1.5) + - Hodler.swift (~> 0.18) + - ObjectMapper (~> 4.0) + - OpenSslKit.swift (~> 1.0) + - RxSwift (~> 5.0) + - Secp256k1Kit.swift (~> 1.0) + - BlsKit.swift (1.0.1) + - CNIOAtomics (2.40.0) + - CNIOBoringSSL (2.19.0) + - CNIOBoringSSLShims (2.19.0): + - CNIOBoringSSL (= 2.19.0) + - CNIODarwin (2.40.0) + - CNIOHTTPParser (2.40.0) + - CNIOLinux (2.40.0) + - CNIOSHA1 (2.40.0) + - CNIOWindows (2.40.0) + - Cuckoo (1.7.1): + - Cuckoo/Swift (= 1.7.1) + - Cuckoo/Swift (1.7.1) + - DashKit.swift (0.18): + - BigInt (~> 5.0) + - BitcoinCore.swift (~> 0.18) + - BlsKit.swift (~> 1.0) + - GRDB.swift (~> 5.0) + - HdWalletKit.swift (~> 1.5) + - ObjectMapper (~> 4.0) + - OpenSslKit.swift (~> 1.0) + - RxSwift (~> 5.0) + - Secp256k1Kit.swift (~> 1.0) + - X11Kit.swift (~> 1.0) + - GRDB.swift (5.25.0): + - GRDB.swift/standard (= 5.25.0) + - GRDB.swift/standard (5.25.0) + - HdWalletKit.swift (1.5.1): + - OpenSslKit.swift (~> 1.0) + - Secp256k1Kit.swift (~> 1.0) + - Hodler.swift (0.18): + - BitcoinCore.swift (~> 0.18) + - OpenSslKit.swift (~> 1.0) + - Secp256k1Kit.swift (~> 1.0) + - HsToolKit.swift (1.3.0): + - Alamofire (~> 5.0) + - ObjectMapper (~> 4.0) + - RxSwift (~> 5.0) + - SwiftNIOFoundationCompat (~> 2) + - SwiftNIOSSL (~> 2) + - SwiftNIOWebSocket + - LitecoinKit.swift (0.18): + - BigInt (~> 5.0) + - BitcoinCore.swift (~> 0.18) + - GRDB.swift (~> 5.0) + - HdWalletKit.swift (~> 1.5) + - ObjectMapper (~> 4.0) + - OpenSslKit.swift (~> 1.0) + - RxSwift (~> 5.0) + - Secp256k1Kit.swift (~> 1.0) + - Nimble (10.0.0) + - ObjectMapper (4.2.0) + - OpenSslKit.swift (1.2.2) + - Quick (5.0.1) + - RxBlocking (5.1.3): + - RxSwift (~> 5) + - RxSwift (5.1.3) + - Secp256k1Kit.swift (1.1) + - SwiftNIO (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIODarwin (= 2.40.0) + - CNIOLinux (= 2.40.0) + - CNIOWindows (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOEmbedded (= 2.40.0) + - SwiftNIOPosix (= 2.40.0) + - SwiftNIOConcurrencyHelpers (2.40.0): + - CNIOAtomics (= 2.40.0) + - SwiftNIOCore (2.40.0): + - CNIOAtomics (= 2.40.0) + - CNIOLinux (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOEmbedded (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIOLinux (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOFoundationCompat (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIODarwin (= 2.40.0) + - CNIOLinux (= 2.40.0) + - CNIOWindows (= 2.40.0) + - SwiftNIO (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOEmbedded (= 2.40.0) + - SwiftNIOPosix (= 2.40.0) + - SwiftNIOHTTP1 (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIODarwin (= 2.40.0) + - CNIOHTTPParser (= 2.40.0) + - CNIOLinux (= 2.40.0) + - CNIOWindows (= 2.40.0) + - SwiftNIO (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOEmbedded (= 2.40.0) + - SwiftNIOPosix (= 2.40.0) + - SwiftNIOPosix (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIODarwin (= 2.40.0) + - CNIOLinux (= 2.40.0) + - CNIOWindows (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOSSL (2.19.0): + - _NIODataStructures (< 3, >= 2.32.0) + - CNIOAtomics (< 3, >= 2.32.0) + - CNIOBoringSSL (= 2.19.0) + - CNIOBoringSSLShims (= 2.19.0) + - CNIODarwin (< 3, >= 2.32.0) + - CNIOLinux (< 3, >= 2.32.0) + - CNIOWindows (< 3, >= 2.32.0) + - SwiftNIO (< 3, >= 2.32.0) + - SwiftNIOConcurrencyHelpers (< 3, >= 2.32.0) + - SwiftNIOCore (< 3, >= 2.32.0) + - SwiftNIOEmbedded (< 3, >= 2.32.0) + - SwiftNIOPosix (< 3, >= 2.32.0) + - SwiftNIOTLS (< 3, >= 2.32.0) + - SwiftNIOTLS (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIODarwin (= 2.40.0) + - CNIOLinux (= 2.40.0) + - CNIOWindows (= 2.40.0) + - SwiftNIO (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOEmbedded (= 2.40.0) + - SwiftNIOPosix (= 2.40.0) + - SwiftNIOWebSocket (2.40.0): + - _NIODataStructures (= 2.40.0) + - CNIOAtomics (= 2.40.0) + - CNIODarwin (= 2.40.0) + - CNIOHTTPParser (= 2.40.0) + - CNIOLinux (= 2.40.0) + - CNIOSHA1 (= 2.40.0) + - CNIOWindows (= 2.40.0) + - SwiftNIO (= 2.40.0) + - SwiftNIOConcurrencyHelpers (= 2.40.0) + - SwiftNIOCore (= 2.40.0) + - SwiftNIOEmbedded (= 2.40.0) + - SwiftNIOHTTP1 (= 2.40.0) + - SwiftNIOPosix (= 2.40.0) + - UIExtensions.swift (1.1.1) + - X11Kit.swift (1.0) + +DEPENDENCIES: + - BitcoinCashKit.swift (from `../`) + - BitcoinCore.swift (from `../`) + - BitcoinKit.swift (from `../`) + - Cuckoo + - DashKit.swift (from `../`) + - HdWalletKit.swift (from `https://github.com/horizontalsystems/hd-wallet-kit-ios`) + - Hodler.swift (from `../`) + - HsToolKit.swift (from `https://github.com/horizontalsystems/hs-tool-kit-ios`) + - LitecoinKit.swift (from `../`) + - Nimble + - Quick + - RxBlocking (~> 5.0) + - UIExtensions.swift (from `https://github.com/horizontalsystems/gui-kit/`) + +SPEC REPOS: + trunk: + - _NIODataStructures + - Alamofire + - BigInt + - BlsKit.swift + - CNIOAtomics + - CNIOBoringSSL + - CNIOBoringSSLShims + - CNIODarwin + - CNIOHTTPParser + - CNIOLinux + - CNIOSHA1 + - CNIOWindows + - Cuckoo + - GRDB.swift + - Nimble + - ObjectMapper + - OpenSslKit.swift + - Quick + - RxBlocking + - RxSwift + - Secp256k1Kit.swift + - SwiftNIO + - SwiftNIOConcurrencyHelpers + - SwiftNIOCore + - SwiftNIOEmbedded + - SwiftNIOFoundationCompat + - SwiftNIOHTTP1 + - SwiftNIOPosix + - SwiftNIOSSL + - SwiftNIOTLS + - SwiftNIOWebSocket + - X11Kit.swift + +EXTERNAL SOURCES: + BitcoinCashKit.swift: + :path: "../" + BitcoinCore.swift: + :path: "../" + BitcoinKit.swift: + :path: "../" + DashKit.swift: + :path: "../" + HdWalletKit.swift: + :git: https://github.com/horizontalsystems/hd-wallet-kit-ios + Hodler.swift: + :path: "../" + HsToolKit.swift: + :git: https://github.com/horizontalsystems/hs-tool-kit-ios + LitecoinKit.swift: + :path: "../" + UIExtensions.swift: + :git: https://github.com/horizontalsystems/gui-kit/ + +CHECKOUT OPTIONS: + HdWalletKit.swift: + :commit: 35f6e0272d74699610df1583759d909148f24a12 + :git: https://github.com/horizontalsystems/hd-wallet-kit-ios + HsToolKit.swift: + :commit: 285e753a39b90a3b639e4ad1349d365d2187fd7c + :git: https://github.com/horizontalsystems/hs-tool-kit-ios + UIExtensions.swift: + :commit: bb4f7adaa7d0b341a3c535a28e2e8368fb0a32b2 + :git: https://github.com/horizontalsystems/gui-kit/ + +SPEC CHECKSUMS: + _NIODataStructures: 3d45d8e70a1d17a15b1dc59d102c63dbc0525ffd + Alamofire: 87bd8c952f9a4454320fce00d9cc3de57bcadaf5 + BigInt: f668a80089607f521586bbe29513d708491ef2f7 + BitcoinCashKit.swift: 9572927e82c0c846d64bb1a8c7c96d6edf1beb39 + BitcoinCore.swift: 406c2dff782edded357a43e6661aa8ae65b7cde3 + BitcoinKit.swift: f95bd4e3c375b9e1e93eb87d37c13bb05e4602f5 + BlsKit.swift: e73fb332ed6a577b0f34a0da30aa5ec6c09bee55 + CNIOAtomics: 8edf08644e5e6fa0f021c239be9e8beb1cd9ef18 + CNIOBoringSSL: 2c9c96c2e95f15e83fb8d26b9738d939cc39ae33 + CNIOBoringSSLShims: c5c9346e7bbd1040f4f8793a35441dda7487539a + CNIODarwin: 93850990d29f2626b05306c6c9309f9be0d74c2f + CNIOHTTPParser: 8ce395236fa1d09ac3b4f4bcfba79b849b2ac684 + CNIOLinux: 62e3505f50de558c393dc2f273dde71dcce518da + CNIOSHA1: 6df39ae8db5922d6fcdd94e15bc57118cbd6b104 + CNIOWindows: 3047f2d8165848a3936a0a755fee27c6b5ee479b + Cuckoo: 9e258d68137c411df47c6390f72901d5276b4f03 + DashKit.swift: c6f22c1b97409e06be1ae3998be680633f7467f4 + GRDB.swift: 39b3ff769afde87a840ced4e3a327d5c6f6a6e4f + HdWalletKit.swift: 30b1ab08c736422eeefe346369ced89574e2ec00 + Hodler.swift: b5b6bd01d7aae0ae37f2318198dabad88e18dee6 + HsToolKit.swift: 58ef3ef2c70b52a647ae3a35b7265b7274e697b5 + LitecoinKit.swift: 69783a07fba32347020be4c277aa89c09c3a3eba + Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 + ObjectMapper: 1eb41f610210777375fa806bf161dc39fb832b81 + OpenSslKit.swift: 0e2194853fd9ce42006cf18c89743331406a10a9 + Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 + RxBlocking: 5aed110d3996c0a4b07340810b9ba230fa70df5c + RxSwift: 915abbdfb62214aa89ccd0b194d44fb478019b27 + Secp256k1Kit.swift: 322a66c4b3e95e96a397d73d45ad4d79af991bab + SwiftNIO: 829958aab300642625091f82fc2f49cb7cf4ef24 + SwiftNIOConcurrencyHelpers: 697370136789b1074e4535eaae75cbd7f900370e + SwiftNIOCore: 473fdfe746534d7aa25766916459eeaf6f92ef49 + SwiftNIOEmbedded: ffcb5147db67d9686c8366b7f8427b36132f2c8a + SwiftNIOFoundationCompat: b9cdbea4806e4a12e9f66d9696fa3b98c4c3232b + SwiftNIOHTTP1: ef56706550a1dc135ea69d65215b9941e643c23b + SwiftNIOPosix: b49af4bdbecaadfadd5c93dfe28594d6722b75e4 + SwiftNIOSSL: d153c5a6fc5b2301b0519b4c4d037a9414212da6 + SwiftNIOTLS: 598af547490133e9aac52aed0c23c4a90c31dcfc + SwiftNIOWebSocket: cb67e4bf1b9f6895d2103b15d8369f8504ea42da + UIExtensions.swift: 3ed084c0343ddd0f86c7c558d37759e1fd7d5778 + X11Kit.swift: 6cfd05473e41c7b866c1a016300da02af63ad049 + +PODFILE CHECKSUM: ed15f4b6c3167431c13daf7f763b5a2fd6a5ebdb + +COCOAPODS: 1.11.3 diff --git a/BitcoinCashKit/BitcoinCashKitTests/Bech32/CashBech32AddressConverterTests.swift b/Example/Tests/BitcoinCashKit/Bech32/CashBech32AddressConverterTests.swift similarity index 100% rename from BitcoinCashKit/BitcoinCashKitTests/Bech32/CashBech32AddressConverterTests.swift rename to Example/Tests/BitcoinCashKit/Bech32/CashBech32AddressConverterTests.swift diff --git a/BitcoinCashKit/BitcoinCashKitTests/Blocks/BitcoinCashValidatorHelperTests.swift b/Example/Tests/BitcoinCashKit/Blocks/BitcoinCashValidatorHelperTests.swift similarity index 63% rename from BitcoinCashKit/BitcoinCashKitTests/Blocks/BitcoinCashValidatorHelperTests.swift rename to Example/Tests/BitcoinCashKit/Blocks/BitcoinCashValidatorHelperTests.swift index 33e14739..6ebffa59 100644 --- a/BitcoinCashKit/BitcoinCashKitTests/Blocks/BitcoinCashValidatorHelperTests.swift +++ b/Example/Tests/BitcoinCashKit/Blocks/BitcoinCashValidatorHelperTests.swift @@ -10,14 +10,12 @@ class BitcoinCashValidatorHelperTests: QuickSpec { override func spec() { var helper: BitcoinCashBlockValidatorHelper! - let mockStorage = MockIBitcoinCashStorage() beforeEach { - helper = BitcoinCashBlockValidatorHelper(storage: mockStorage, coreBlockValidatorHelper: MockIBlockValidatorHelperWrapper()) + helper = BitcoinCashBlockValidatorHelper(coreBlockValidatorHelper: MockIBlockValidatorHelperWrapper()) } afterEach { - reset(mockStorage) helper = nil } @@ -27,11 +25,6 @@ class BitcoinCashValidatorHelperTests: QuickSpec { let block2 = Block(withHeader: BlockHeader(version: 0, headerHash: Data(hex: "22")!, previousBlockHeaderHash: Data(hex: "11")!, merkleRoot: Data(), timestamp: 20, bits: 0, nonce: 0), height: 2) let block3 = Block(withHeader: BlockHeader(version: 0, headerHash: Data(hex: "33")!, previousBlockHeaderHash: Data(hex: "22")!, merkleRoot: Data(), timestamp: 30, bits: 0, nonce: 0), height: 3) - stub(mockStorage) { mock in - when(mock.block(byHash: equal(to: block3.previousBlockHash))).thenReturn(block2) - when(mock.block(byHash: equal(to: block2.previousBlockHash))).thenReturn(block1) - } - let blockIndex = helper.suitableBlockIndex(for: [block1, block2, block3]) expect(blockIndex).to(equal(1)) } @@ -40,11 +33,6 @@ class BitcoinCashValidatorHelperTests: QuickSpec { let block2 = Block(withHeader: BlockHeader(version: 0, headerHash: Data(hex: "22")!, previousBlockHeaderHash: Data(hex: "11")!, merkleRoot: Data(), timestamp: 20, bits: 0, nonce: 0), height: 2) let block3 = Block(withHeader: BlockHeader(version: 0, headerHash: Data(hex: "33")!, previousBlockHeaderHash: Data(hex: "22")!, merkleRoot: Data(), timestamp: 20, bits: 0, nonce: 0), height: 3) - stub(mockStorage) { mock in - when(mock.block(byHash: equal(to: block3.previousBlockHash))).thenReturn(block2) - when(mock.block(byHash: equal(to: block2.previousBlockHash))).thenReturn(block1) - } - let blockIndex = helper.suitableBlockIndex(for: [block1, block2, block3]) expect(blockIndex).to(equal(1)) } @@ -53,32 +41,10 @@ class BitcoinCashValidatorHelperTests: QuickSpec { let block2 = Block(withHeader: BlockHeader(version: 0, headerHash: Data(hex: "22")!, previousBlockHeaderHash: Data(hex: "11")!, merkleRoot: Data(), timestamp: 10, bits: 0, nonce: 0), height: 2) let block3 = Block(withHeader: BlockHeader(version: 0, headerHash: Data(hex: "33")!, previousBlockHeaderHash: Data(hex: "22")!, merkleRoot: Data(), timestamp: 20, bits: 0, nonce: 0), height: 3) - stub(mockStorage) { mock in - when(mock.block(byHash: equal(to: block3.previousBlockHash))).thenReturn(block2) - when(mock.block(byHash: equal(to: block2.previousBlockHash))).thenReturn(block1) - } - let blockIndex = helper.suitableBlockIndex(for: [block1, block2, block3]) expect(blockIndex).to(equal(0)) } } - describe("#medianTimePast") { - it("checks valid median time past") { - let block = TestData.firstBlock - block.height = 1000 - - var timestamps = [Int]() - for i in 0..<11 { - timestamps.append(100 * (i + 1)) - } - stub(mockStorage) { mock in - when(mock.timestamps(from: 990, to: 1000, ascending: true)).thenReturn(timestamps) - } - - let medianTime = helper.medianTimePast(block: block) - expect(medianTime).to(equal(600)) - } - } } } diff --git a/BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/DAAValidatorTests.swift b/Example/Tests/BitcoinCashKit/Blocks/Validators/DAAValidatorTests.swift similarity index 99% rename from BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/DAAValidatorTests.swift rename to Example/Tests/BitcoinCashKit/Blocks/Validators/DAAValidatorTests.swift index bc13e4f4..89651d1a 100644 --- a/BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/DAAValidatorTests.swift +++ b/Example/Tests/BitcoinCashKit/Blocks/Validators/DAAValidatorTests.swift @@ -62,7 +62,7 @@ class DAAValidatorTests: XCTestCase { when(mock.suitableBlockIndex(for: equal(to: [blocks[1], blocks[2], blocks[3]].reversed()))).thenReturn(1) } - validator = DAAValidator(encoder: DifficultyEncoder(), blockHelper: mockBlockHelper, targetSpacing: 600, heightInterval: 144, firstCheckpointHeight: 544320 - 1 - 148) // previous block height - 148 + validator = DAAValidator(encoder: DifficultyEncoder(), blockHelper: mockBlockHelper, targetSpacing: 600, heightInterval: 144) } override func tearDown() { diff --git a/BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/EDAValidatorTests.swift b/Example/Tests/BitcoinCashKit/Blocks/Validators/EDAValidatorTests.swift similarity index 67% rename from BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/EDAValidatorTests.swift rename to Example/Tests/BitcoinCashKit/Blocks/Validators/EDAValidatorTests.swift index 9aa4a128..b2562ae0 100644 --- a/BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/EDAValidatorTests.swift +++ b/Example/Tests/BitcoinCashKit/Blocks/Validators/EDAValidatorTests.swift @@ -8,6 +8,7 @@ class EDAValidatorTests: XCTestCase { private var validator: EDAValidator! private var mockDifficultyEncoder: MockIBitcoinCashDifficultyEncoder! private var mockBlockHelper: MockIBitcoinCashBlockValidatorHelper! + private var mockMedianTimeHelper: MockIBitcoinCashBlockMedianTimeHelper! private var block: Block! private var candidate: Block! @@ -17,8 +18,9 @@ class EDAValidatorTests: XCTestCase { mockDifficultyEncoder = MockIBitcoinCashDifficultyEncoder() mockBlockHelper = MockIBitcoinCashBlockValidatorHelper() + mockMedianTimeHelper = MockIBitcoinCashBlockMedianTimeHelper() - validator = EDAValidator(encoder: mockDifficultyEncoder, blockHelper: mockBlockHelper, maxTargetBits: 0x1d00ffff, firstCheckpointHeight: 0) + validator = EDAValidator(encoder: mockDifficultyEncoder, blockHelper: mockBlockHelper, blockMedianTimeHelper: mockMedianTimeHelper, maxTargetBits: 0x1d00ffff) block = TestData.firstBlock candidate = TestData.secondBlock @@ -28,6 +30,7 @@ class EDAValidatorTests: XCTestCase { validator = nil mockDifficultyEncoder = nil mockBlockHelper = nil + mockMedianTimeHelper = nil block = nil candidate = nil @@ -35,25 +38,6 @@ class EDAValidatorTests: XCTestCase { super.tearDown() } - func testIgnoreFirstBlocks() { - // we must ignore all blocks before firstCheckpoint + 6 - let ignoredPreviousBlock = Block(withHeader: BlockHeader( - version: 1, - headerHash: "11b10ccc".reversedData!, - previousBlockHeaderHash: Data(repeating: 0x01, count: 2), - merkleRoot: Data(), - timestamp: 1337966314, - bits: 386604799, - nonce: 1716024842 - ), height: 5) - do { - try validator.validate(block: block, previousBlock: ignoredPreviousBlock) - verifyNoMoreInteractions(mockBlockHelper) - verifyNoMoreInteractions(mockDifficultyEncoder) - } catch let error { - XCTFail("\(error) Exception Thrown") - } - } func testValidate() { do { try validator.validate(block: candidate, previousBlock: block) diff --git a/BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/ForkValidatorTests.swift b/Example/Tests/BitcoinCashKit/Blocks/Validators/ForkValidatorTests.swift similarity index 87% rename from BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/ForkValidatorTests.swift rename to Example/Tests/BitcoinCashKit/Blocks/Validators/ForkValidatorTests.swift index 73a8279f..9f4de568 100644 --- a/BitcoinCashKit/BitcoinCashKitTests/Blocks/Validators/ForkValidatorTests.swift +++ b/Example/Tests/BitcoinCashKit/Blocks/Validators/ForkValidatorTests.swift @@ -46,10 +46,14 @@ class ForkValidatorTests: QuickSpec { } describe("#isBlockValidatable") { - it("return always true") { + it("returns true when fork height is not equal") { let validatable = validator.isBlockValidatable(block: block, previousBlock: prevBlock) expect(validatable).to(beTrue()) } + it("returns false when fork height is equal") { + let validatable = validator.isBlockValidatable(block: prevBlock, previousBlock: prevBlock) + expect(validatable).to(beFalse()) + } } describe("#validate") { context("when block has fork height") { @@ -85,16 +89,6 @@ class ForkValidatorTests: QuickSpec { } } } - context("when block has another height") { - it("checks block hash and call concrete validate") { - do { - try validator.validate(block: prevBlock, previousBlock: prevBlock) - verify(mockBlockValidator).validate(block: equal(to: prevBlock), previousBlock: equal(to: prevBlock)) - } catch { - XCTFail("Must no throwing error") - } - } - } } } } diff --git a/Example/Tests/BitcoinCashKit/Extensions.swift b/Example/Tests/BitcoinCashKit/Extensions.swift new file mode 100644 index 00000000..b1a62dde --- /dev/null +++ b/Example/Tests/BitcoinCashKit/Extensions.swift @@ -0,0 +1,20 @@ +import BitcoinCore + +extension Block: Equatable { + + public static func ==(lhs: Block, rhs: Block) -> Bool { + return lhs.headerHash == rhs.headerHash + } + +} + +extension TransactionDataSortType: Equatable { + + public static func ==(lhs: TransactionDataSortType, rhs: TransactionDataSortType) -> Bool { + switch (lhs, rhs) { + case (.none, .none), (.shuffle, .shuffle), (.bip69, .bip69): return true + default: return false + } + } + +} \ No newline at end of file diff --git a/BitcoinCashKit/BitcoinCashKitTests/TestData.swift b/Example/Tests/BitcoinCashKit/TestData.swift similarity index 100% rename from BitcoinCashKit/BitcoinCashKitTests/TestData.swift rename to Example/Tests/BitcoinCashKit/TestData.swift diff --git a/BitcoinKit/BitcoinKitTests/Info.plist b/Example/Tests/BitcoinCashKitTests.plist similarity index 89% rename from BitcoinKit/BitcoinKitTests/Info.plist rename to Example/Tests/BitcoinCashKitTests.plist index 6c40a6cd..ba72822e 100644 --- a/BitcoinKit/BitcoinKitTests/Info.plist +++ b/Example/Tests/BitcoinCashKitTests.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -16,6 +16,8 @@ BNDL CFBundleShortVersionString 1.0 + CFBundleSignature + ???? CFBundleVersion 1 diff --git a/BitcoinCore/BitcoinCoreTests/BlockHeaders/DifficultyEncoderTests.swift b/Example/Tests/BitcoinCore/BlockHeaders/DifficultyEncoderTests.swift similarity index 57% rename from BitcoinCore/BitcoinCoreTests/BlockHeaders/DifficultyEncoderTests.swift rename to Example/Tests/BitcoinCore/BlockHeaders/DifficultyEncoderTests.swift index 30103411..707ceadc 100644 --- a/BitcoinCore/BitcoinCoreTests/BlockHeaders/DifficultyEncoderTests.swift +++ b/Example/Tests/BitcoinCore/BlockHeaders/DifficultyEncoderTests.swift @@ -19,6 +19,41 @@ class DifficultyEncoderTests: XCTestCase { super.tearDown() } + func testCompactFromMaxHash() { + let hash = Data(hex: "123456789012345678901234567890FFFF12345678901234567890123456FFFF")! + let representation: Int = 0x2100ffff + + XCTAssertEqual(difficultyEncoder.compactFrom(hash: hash), representation) + } + + func testCompactFromHashWithoutShift() { + let hash = Data(hex: "123456789012345678901234567890FFFF12345678901234567890123456FF7F")! + let representation: Int = 0x207fff56 + + XCTAssertEqual(difficultyEncoder.compactFrom(hash: hash), representation) + } + + func testCompactFromHashStandartWithoutShift() { + let hash = Data(hex: "123456789012345678901234567890FFFF123456789012345678000000000000")! + let representation: Int = 0x1a785634 + + XCTAssertEqual(difficultyEncoder.compactFrom(hash: hash), representation) + } + + func testCompactFromHashStandartWithShift() { + let hash = Data(hex: "123456789012345678901234567890FFFF123456789012345681000000000000")! + let representation: Int = 0x1b008156 + + XCTAssertEqual(difficultyEncoder.compactFrom(hash: hash), representation) + } + + func testCompactFromHashBiggest() { + let hash = Data(hex: "0100000000000000000000000000000000000000000000000000000000000000")! + let representation: Int = 0x03000001 + + XCTAssertEqual(difficultyEncoder.compactFrom(hash: hash), representation) + } + func testEncodeCompact() { let difficulty: BigInt = BigInt("1234560000", radix: 16)! let representation: Int = 0x05123456 diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/BlockSyncerTests.swift b/Example/Tests/BitcoinCore/Blocks/BlockSyncerTests.swift similarity index 80% rename from BitcoinCore/BitcoinCoreTests/Blocks/BlockSyncerTests.swift rename to Example/Tests/BitcoinCore/Blocks/BlockSyncerTests.swift index 5536c882..a9ee3115 100644 --- a/BitcoinCore/BitcoinCoreTests/Blocks/BlockSyncerTests.swift +++ b/Example/Tests/BitcoinCore/Blocks/BlockSyncerTests.swift @@ -8,14 +8,13 @@ class BlockSyncerTests: QuickSpec { override func spec() { let mockStorage = MockIStorage() let mockFactory = MockIFactory() - let mockListener = MockISyncStateListener() - let mockTransactionProcessor = MockITransactionProcessor() + let mockListener = MockIBlockSyncListener() + let mockTransactionProcessor = MockIBlockTransactionProcessor() let mockBlockchain = MockIBlockchain() - let mockAddressManager = MockIAddressManager() - let mockBloomFilterManager = MockIBloomFilterManager() + let mockAddressManager = MockIPublicKeyManager() let mockState = MockBlockSyncerState() - let checkpointBlock = TestData.checkpointBlock + let checkpoint = TestData.checkpoint var syncer: BlockSyncer! beforeEach { @@ -24,80 +23,108 @@ class BlockSyncerTests: QuickSpec { when(mock.lastBlock.get).thenReturn(nil) when(mock.deleteBlockchainBlockHashes()).thenDoNothing() } - stub(mockListener) { mock in - when(mock.initialBestBlockHeightUpdated(height: equal(to: 0))).thenDoNothing() - } stub(mockBlockchain) { mock in when(mock.handleFork()).thenDoNothing() } stub(mockAddressManager) { mock in when(mock.fillGap()).thenDoNothing() } - stub(mockBloomFilterManager) { mock in - when(mock.regenerateBloomFilter()).thenDoNothing() - } stub(mockState) { mock in when(mock.iteration(hasPartialBlocks: any())).thenDoNothing() } } afterEach { - reset(mockStorage, mockListener, mockTransactionProcessor, mockBlockchain, mockAddressManager, mockBloomFilterManager, mockState) + reset(mockStorage, mockListener, mockTransactionProcessor, mockBlockchain, mockAddressManager, mockState) syncer = nil } context("static methods") { - describe("#instance") { + describe("#resolveCheckpoint") { + let bip44Checkpoint = TestData.checkpoint + let lastCheckpoint = TestData.lastCheckpoint + let mockNetwork = MockINetwork() + + beforeEach { + stub(mockNetwork) { mock in + when(mock.bip44Checkpoint.get).thenReturn(bip44Checkpoint) + when(mock.lastCheckpoint.get).thenReturn(lastCheckpoint) + } + } + + afterEach { + reset(mockNetwork) + } + context("when there are some blocks in storage") { + let lastBlock = TestData.secondBlock + beforeEach { stub(mockStorage) { mock in - when(mock.blocksCount.get).thenReturn(1) - when(mock.lastBlock.get).thenReturn(checkpointBlock) - } - - stub(mockListener) { mock in - when(mock.initialBestBlockHeightUpdated(height: equal(to: Int32(checkpointBlock.height)))).thenDoNothing() + when(mock.lastBlock.get).thenReturn(lastBlock) } - - let _ = BlockSyncer.instance(storage: mockStorage, checkpointBlock: checkpointBlock, factory: mockFactory, listener: mockListener, transactionProcessor: mockTransactionProcessor, - blockchain: mockBlockchain, addressManager: mockAddressManager, bloomFilterManager: mockBloomFilterManager, hashCheckpointThreshold: 100) } it("doesn't save checkpointBlock to storage") { + _ = BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .api, storage: mockStorage) + verify(mockStorage, never()).save(block: any()) - verify(mockStorage).blocksCount.get() verify(mockStorage).lastBlock.get() verifyNoMoreInteractions(mockStorage) } - it("triggers #initialBestBlockHeightUpdated event on listener") { - verify(mockListener).initialBestBlockHeightUpdated(height: equal(to: Int32(checkpointBlock.height))) - verifyNoMoreInteractions(mockListener) + context("when syncMode is .full") { + it("returns bip44CheckpointBlock") { + expect(BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .full, storage: mockStorage)).to(equal(bip44Checkpoint)) + } + } + + context("when syncMode is not .full") { + context("when lastBlock's height is more than lastCheckpointBlock") { + it("returns lastCheckpointBlock") { + lastBlock.height = lastCheckpoint.block.height + 1 + expect(BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .api, storage: mockStorage)).to(equal(lastCheckpoint)) + } + } + + context("when lastBlock's height is less than lastCheckpointBlock") { + it("returns bip44CheckpointBlock") { + lastBlock.height = lastCheckpoint.block.height - 1 + expect(BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .api, storage: mockStorage)).to(equal(bip44Checkpoint)) + } + } } } context("when there's no block in storage") { beforeEach { stub(mockStorage) { mock in - when(mock.blocksCount.get).thenReturn(0) - when(mock.lastBlock.get).thenReturn(checkpointBlock) - when(mock.save(block: sameInstance(as: checkpointBlock))).thenDoNothing() + when(mock.lastBlock.get).thenReturn(nil) + when(mock.save(block: any())).thenDoNothing() } - stub(mockListener) { mock in - when(mock.initialBestBlockHeightUpdated(height: equal(to: Int32(checkpointBlock.height)))).thenDoNothing() + } + + context("when syncMode is .full") { + it("returns bip44Checkpoint") { + expect(BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .full, storage: mockStorage)).to(equal(bip44Checkpoint)) } - let _ = BlockSyncer.instance(storage: mockStorage, checkpointBlock: checkpointBlock, factory: mockFactory, listener: mockListener, transactionProcessor: mockTransactionProcessor, - blockchain: mockBlockchain, addressManager: mockAddressManager, bloomFilterManager: mockBloomFilterManager, hashCheckpointThreshold: 100) + it("saves bip44Checkpoint to storage") { + _ = BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .full, storage: mockStorage) + verify(mockStorage).save(block: sameInstance(as: bip44Checkpoint.block)) + } } - it("saves checkpointBlock to storage") { - verify(mockStorage).save(block: sameInstance(as: checkpointBlock)) - } + context("when syncMode is not .full") { + it("returns lastCheckpoint") { + expect(BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .api, storage: mockStorage)).to(equal(lastCheckpoint)) + } - it("triggers #initialBestBlockHeightUpdated event on listener") { - verify(mockListener).initialBestBlockHeightUpdated(height: equal(to: Int32(checkpointBlock.height))) + it("saves lastCheckpoint to storage") { + _ = BlockSyncer.resolveCheckpoint(network: mockNetwork, syncMode: .api, storage: mockStorage) + verify(mockStorage).save(block: sameInstance(as: lastCheckpoint.block)) + } } } } @@ -105,18 +132,20 @@ class BlockSyncerTests: QuickSpec { context("instance methods") { beforeEach { - syncer = BlockSyncer(storage: mockStorage, checkpointBlock: checkpointBlock, factory: mockFactory, listener: mockListener, transactionProcessor: mockTransactionProcessor, - blockchain: mockBlockchain, addressManager: mockAddressManager, bloomFilterManager: mockBloomFilterManager, + syncer = BlockSyncer(storage: mockStorage, checkpoint: checkpoint, factory: mockFactory, transactionProcessor: mockTransactionProcessor, + blockchain: mockBlockchain, publicKeyManager: mockAddressManager, hashCheckpointThreshold: 100, logger: nil, state: mockState) + + syncer.listener = mockListener } describe("#localDownloadedBestBlockHeight") { context("when there are some blocks in storage") { it("returns the height of the last block") { stub(mockStorage) { mock in - when(mock.lastBlock.get).thenReturn(checkpointBlock) + when(mock.lastBlock.get).thenReturn(checkpoint.block) } - expect(syncer.localDownloadedBestBlockHeight).to(equal(Int32(checkpointBlock.height))) + expect(syncer.localDownloadedBestBlockHeight).to(equal(Int32(checkpoint.block.height))) } } @@ -150,9 +179,9 @@ class BlockSyncerTests: QuickSpec { context("when there are some blocks") { it("returns last block's height + blocks count") { stub(mockStorage) { mock in - when(mock.lastBlock.get).thenReturn(checkpointBlock) + when(mock.lastBlock.get).thenReturn(checkpoint.block) } - expect(syncer.localKnownBestBlockHeight).to(equal(Int32(checkpointBlock.height))) + expect(syncer.localKnownBestBlockHeight).to(equal(Int32(checkpoint.block.height))) } } } @@ -168,9 +197,9 @@ class BlockSyncerTests: QuickSpec { it("returns lastBlock + blockHashes count") { expect(syncer.localKnownBestBlockHeight).to(equal(1)) stub(mockStorage) { mock in - when(mock.lastBlock.get).thenReturn(checkpointBlock) + when(mock.lastBlock.get).thenReturn(checkpoint.block) } - expect(syncer.localKnownBestBlockHeight).to(equal(Int32(checkpointBlock.height + 1))) + expect(syncer.localKnownBestBlockHeight).to(equal(Int32(checkpoint.block.height + 1))) } } @@ -185,9 +214,9 @@ class BlockSyncerTests: QuickSpec { it("returns lastBlock + count of blockHashes without downloaded blocks") { expect(syncer.localKnownBestBlockHeight).to(equal(0)) stub(mockStorage) { mock in - when(mock.lastBlock.get).thenReturn(checkpointBlock) + when(mock.lastBlock.get).thenReturn(checkpoint.block) } - expect(syncer.localKnownBestBlockHeight).to(equal(Int32(checkpointBlock.height))) + expect(syncer.localKnownBestBlockHeight).to(equal(Int32(checkpoint.block.height))) } } } @@ -197,7 +226,7 @@ class BlockSyncerTests: QuickSpec { beforeEach { stub(mockStorage) { mock in - when(mock.blockHashHeaderHashes(except: equal(to: checkpointBlock.headerHash))).thenReturn([]) + when(mock.blockHashHeaderHashes(except: equal(to: [checkpoint.block.headerHash]))).thenReturn([]) when(mock.blocks(byHexes: equal(to: []))).thenReturn(emptyBlocks) } stub(mockBlockchain) { mock in @@ -209,7 +238,6 @@ class BlockSyncerTests: QuickSpec { it("handles partial blocks") { verify(mockAddressManager).fillGap() - verify(mockBloomFilterManager).regenerateBloomFilter() verify(mockState).iteration(hasPartialBlocks: equal(to: false)) } @@ -218,7 +246,7 @@ class BlockSyncerTests: QuickSpec { } it("clears partialBlock blocks") { - verify(mockStorage).blockHashHeaderHashes(except: equal(to: checkpointBlock.headerHash)) + verify(mockStorage).blockHashHeaderHashes(except: equal(to: [checkpoint.block.headerHash])) verify(mockStorage).blocks(byHexes: equal(to: [])) verify(mockBlockchain).deleteBlocks(blocks: equal(to: emptyBlocks)) } @@ -237,7 +265,6 @@ class BlockSyncerTests: QuickSpec { syncer.downloadIterationCompleted() verify(mockAddressManager).fillGap() - verify(mockBloomFilterManager).regenerateBloomFilter() verify(mockState).iteration(hasPartialBlocks: equal(to: false)) } } @@ -250,7 +277,6 @@ class BlockSyncerTests: QuickSpec { syncer.downloadIterationCompleted() verify(mockAddressManager, never()).fillGap() - verify(mockBloomFilterManager, never()).regenerateBloomFilter() verify(mockState, never()).iteration(hasPartialBlocks: any()) } } @@ -268,7 +294,7 @@ class BlockSyncerTests: QuickSpec { beforeEach { stub(mockStorage) { mock in - when(mock.blockHashHeaderHashes(except: equal(to: checkpointBlock.headerHash))).thenReturn([]) + when(mock.blockHashHeaderHashes(except: equal(to: [checkpoint.block.headerHash]))).thenReturn([]) when(mock.blocks(byHexes: equal(to: []))).thenReturn(emptyBlocks) } stub(mockBlockchain) { mock in @@ -280,7 +306,6 @@ class BlockSyncerTests: QuickSpec { it("handles partial blocks") { verify(mockAddressManager).fillGap() - verify(mockBloomFilterManager).regenerateBloomFilter() verify(mockState).iteration(hasPartialBlocks: equal(to: false)) } @@ -289,7 +314,7 @@ class BlockSyncerTests: QuickSpec { } it("clears partialBlock blocks") { - verify(mockStorage).blockHashHeaderHashes(except: equal(to: checkpointBlock.headerHash)) + verify(mockStorage).blockHashHeaderHashes(except: equal(to: [checkpoint.block.headerHash])) verify(mockStorage).blocks(byHexes: equal(to: [])) verify(mockBlockchain).deleteBlocks(blocks: equal(to: emptyBlocks)) } @@ -319,14 +344,14 @@ class BlockSyncerTests: QuickSpec { beforeEach { stub(mockStorage) { mock in when(mock.lastBlockchainBlockHash.get).thenReturn(nil) - when(mock.blocks(heightGreaterThan: equal(to: checkpointBlock.height), sortedBy: equal(to: Block.Columns.height), limit: equal(to: 10))).thenReturn([Block]()) + when(mock.blocks(heightGreaterThan: equal(to: checkpoint.block.height), sortedBy: equal(to: Block.Columns.height), limit: equal(to: 10))).thenReturn([Block]()) when(mock.block(byHeight: Int(peerLastBlockHeight))).thenReturn(nil) } } context("when there's no blocks or blockhashes") { it("returns checkpointBlock's header hash") { - expect(syncer.getBlockLocatorHashes(peerLastBlockHeight: peerLastBlockHeight)).to(equal([checkpointBlock.headerHash])) + expect(syncer.getBlockLocatorHashes(peerLastBlockHeight: peerLastBlockHeight)).to(equal([checkpoint.block.headerHash])) } } @@ -335,11 +360,11 @@ class BlockSyncerTests: QuickSpec { let blockHash = BlockHash(headerHash: Data(repeating: 0, count: 0), height: 0, order: 0) stub(mockStorage) { mock in when(mock.lastBlockchainBlockHash.get).thenReturn(blockHash) - when(mock.blocks(heightGreaterThan: equal(to: checkpointBlock.height), sortedBy: equal(to: Block.Columns.height), limit: equal(to: 10))).thenReturn([firstBlock, secondBlock]) + when(mock.blocks(heightGreaterThan: equal(to: checkpoint.block.height), sortedBy: equal(to: Block.Columns.height), limit: equal(to: 10))).thenReturn([firstBlock, secondBlock]) } expect(syncer.getBlockLocatorHashes(peerLastBlockHeight: peerLastBlockHeight)).to(equal([ - blockHash.headerHash, checkpointBlock.headerHash + blockHash.headerHash, checkpoint.block.headerHash ])) } } @@ -347,11 +372,11 @@ class BlockSyncerTests: QuickSpec { context("when there's no blockhashes but there are blocks") { it("returns last 10 blocks' header hashes") { stub(mockStorage) { mock in - when(mock.blocks(heightGreaterThan: equal(to: checkpointBlock.height), sortedBy: equal(to: Block.Columns.height), limit: equal(to: 10))).thenReturn([secondBlock, firstBlock]) + when(mock.blocks(heightGreaterThan: equal(to: checkpoint.block.height), sortedBy: equal(to: Block.Columns.height), limit: equal(to: 10))).thenReturn([secondBlock, firstBlock]) } expect(syncer.getBlockLocatorHashes(peerLastBlockHeight: peerLastBlockHeight)).to(equal([ - secondBlock.headerHash, firstBlock.headerHash, checkpointBlock.headerHash + secondBlock.headerHash, firstBlock.headerHash, checkpoint.block.headerHash ])) } } diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/BlockchainTests.swift b/Example/Tests/BitcoinCore/Blocks/BlockchainTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/BlockchainTests.swift rename to Example/Tests/BitcoinCore/Blocks/BlockchainTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/KitStateProviderTests.swift b/Example/Tests/BitcoinCore/Blocks/KitStateProviderTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/KitStateProviderTests.swift rename to Example/Tests/BitcoinCore/Blocks/KitStateProviderTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/MerkleBlockValidatorTests.swift b/Example/Tests/BitcoinCore/Blocks/MerkleBlockValidatorTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/MerkleBlockValidatorTests.swift rename to Example/Tests/BitcoinCore/Blocks/MerkleBlockValidatorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/Validators/BitsValidatorTests.swift b/Example/Tests/BitcoinCore/Blocks/Validators/BitsValidatorTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/Validators/BitsValidatorTests.swift rename to Example/Tests/BitcoinCore/Blocks/Validators/BitsValidatorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/Validators/LegacyDifficultyAdjustmentValidatorTests.swift b/Example/Tests/BitcoinCore/Blocks/Validators/LegacyDifficultyAdjustmentValidatorTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/Validators/LegacyDifficultyAdjustmentValidatorTests.swift rename to Example/Tests/BitcoinCore/Blocks/Validators/LegacyDifficultyAdjustmentValidatorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/Validators/LegacyTestNetDifficultyValidatorTests.swift b/Example/Tests/BitcoinCore/Blocks/Validators/LegacyTestNetDifficultyValidatorTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/Validators/LegacyTestNetDifficultyValidatorTests.swift rename to Example/Tests/BitcoinCore/Blocks/Validators/LegacyTestNetDifficultyValidatorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Blocks/Validators/ProofOfWorkValidatorTests.swift b/Example/Tests/BitcoinCore/Blocks/Validators/ProofOfWorkValidatorTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Blocks/Validators/ProofOfWorkValidatorTests.swift rename to Example/Tests/BitcoinCore/Blocks/Validators/ProofOfWorkValidatorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Core/DataProviderTests.swift b/Example/Tests/BitcoinCore/Core/DataProviderTests.swift similarity index 99% rename from BitcoinCore/BitcoinCoreTests/Core/DataProviderTests.swift rename to Example/Tests/BitcoinCore/Core/DataProviderTests.swift index 1ba302b9..39804b05 100644 --- a/BitcoinCore/BitcoinCoreTests/Core/DataProviderTests.swift +++ b/Example/Tests/BitcoinCore/Core/DataProviderTests.swift @@ -5,7 +5,7 @@ // //class DataProviderTests: XCTestCase { // private var mockStorage: MockIStorage! -// private var mockAddressManager: MockIAddressManager! +// private var mockAddressManager: MockIPublicKeyManager! // private var mockAddressConverter: MockIAddressConverter! // private var mockPaymentAddressParser: MockIPaymentAddressParser! // private var mockUnspentOutputProvider: MockIUnspentOutputProvider! @@ -20,7 +20,7 @@ // super.setUp() // // mockStorage = MockIStorage() -// mockAddressManager = MockIAddressManager() +// mockAddressManager = MockIPublicKeyManager() // mockAddressConverter = MockIAddressConverter() // mockPaymentAddressParser = MockIPaymentAddressParser() // mockUnspentOutputProvider = MockIUnspentOutputProvider() diff --git a/BitcoinCore/BitcoinCoreTests/Extensions.swift b/Example/Tests/BitcoinCore/Extensions.swift similarity index 50% rename from BitcoinCore/BitcoinCoreTests/Extensions.swift rename to Example/Tests/BitcoinCore/Extensions.swift index 04181814..11cba1c2 100644 --- a/BitcoinCore/BitcoinCoreTests/Extensions.swift +++ b/Example/Tests/BitcoinCore/Extensions.swift @@ -1,4 +1,5 @@ import XCTest +import Cuckoo @testable import BitcoinCore extension XCTestCase { @@ -12,19 +13,19 @@ extension XCTestCase { } public func equalErrors(_ lhs: Error?, _ rhs: Error?) -> Bool { - return lhs?.reflectedString == rhs?.reflectedString + lhs?.reflectedString == rhs?.reflectedString } public extension Error { var reflectedString: String { // NOTE 1: We can just use the standard reflection for our case - return String(reflecting: self) + String(reflecting: self) } // Same typed Equality func isEqual(to: Self) -> Bool { - return self.reflectedString == to.reflectedString + self.reflectedString == to.reflectedString } } @@ -32,7 +33,7 @@ public extension Error { extension Block { var header: BlockHeader { - return BlockHeader( + BlockHeader( version: version, headerHash: headerHash, previousBlockHeaderHash: previousBlockHash, merkleRoot: merkleRoot, timestamp: timestamp, bits: bits, nonce: nonce ) @@ -44,38 +45,10 @@ extension Block { } -extension BitcoinCore.KitState: Equatable { - - public static func ==(lhs: BitcoinCore.KitState, rhs: BitcoinCore.KitState) -> Bool { - switch (lhs, rhs) { - case (.synced, .synced): return true - case let (.syncing(lProgress), .syncing(rProgress)): return lProgress == rProgress - case (.notSynced, .notSynced): return true - default: - return false - } - } - -} - -extension BitcoinCoreErrors.UnspentOutputSelection: Equatable { - - public static func ==(lhs: BitcoinCoreErrors.UnspentOutputSelection, rhs: BitcoinCoreErrors.UnspentOutputSelection) -> Bool { - switch (lhs, rhs) { - case (.wrongValue, .wrongValue): return true - case (.emptyOutputs, .emptyOutputs): return true - case let (.notEnough(lMaxFee), .notEnough(rMaxFee)): return lMaxFee == rMaxFee - default: - return false - } - } - -} - extension BlockInfo: Equatable { public static func ==(lhs: BlockInfo, rhs: BlockInfo) -> Bool { - return lhs.headerHash == rhs.headerHash && lhs.height == rhs.height && lhs.timestamp == rhs.timestamp + lhs.headerHash == rhs.headerHash && lhs.height == rhs.height && lhs.timestamp == rhs.timestamp } } @@ -83,7 +56,7 @@ extension BlockInfo: Equatable { extension TransactionInfo: Equatable { public static func ==(lhs: TransactionInfo, rhs: TransactionInfo) -> Bool { - return lhs.transactionHash == rhs.transactionHash + lhs.transactionHash == rhs.transactionHash } } @@ -91,7 +64,7 @@ extension TransactionInfo: Equatable { extension PeerAddress: Equatable { public static func ==(lhs: PeerAddress, rhs: PeerAddress) -> Bool { - return lhs.ip == rhs.ip + lhs.ip == rhs.ip } } @@ -99,7 +72,15 @@ extension PeerAddress: Equatable { extension PublicKey: Equatable { public static func ==(lhs: PublicKey, rhs: PublicKey) -> Bool { - return lhs.path == rhs.path + lhs.path == rhs.path + } + +} + +extension Checkpoint: Equatable { + + public static func ==(lhs: Checkpoint, rhs: Checkpoint) -> Bool { + lhs.block == rhs.block } } @@ -107,7 +88,7 @@ extension PublicKey: Equatable { extension Block: Equatable { public static func ==(lhs: Block, rhs: Block) -> Bool { - return lhs.headerHash == rhs.headerHash + lhs.headerHash == rhs.headerHash } } @@ -115,7 +96,7 @@ extension Block: Equatable { extension Transaction: Equatable { public static func ==(lhs: Transaction, rhs: Transaction) -> Bool { - return lhs.dataHash == rhs.dataHash + lhs.dataHash == rhs.dataHash } } @@ -123,7 +104,15 @@ extension Transaction: Equatable { extension Input: Equatable { public static func ==(lhs: Input, rhs: Input) -> Bool { - return lhs.previousOutputIndex == rhs.previousOutputIndex && lhs.previousOutputTxHash == rhs.previousOutputTxHash + lhs.previousOutputIndex == rhs.previousOutputIndex && lhs.previousOutputTxHash == rhs.previousOutputTxHash + } + +} + +extension Output: Equatable { + + public static func ==(lhs: Output, rhs: Output) -> Bool { + lhs.keyHash == rhs.keyHash && lhs.scriptType == rhs.scriptType && lhs.value == rhs.value && lhs.index == rhs.index } } @@ -131,7 +120,7 @@ extension Input: Equatable { extension BlockHeader: Equatable { public static func ==(lhs: BlockHeader, rhs: BlockHeader) -> Bool { - return lhs.previousBlockHeaderHash == rhs.previousBlockHeaderHash && lhs.headerHash == rhs.headerHash && lhs.merkleRoot == rhs.merkleRoot + lhs.previousBlockHeaderHash == rhs.previousBlockHeaderHash && lhs.headerHash == rhs.headerHash && lhs.merkleRoot == rhs.merkleRoot } } @@ -139,7 +128,7 @@ extension BlockHeader: Equatable { extension FullTransaction: Equatable { public static func ==(lhs: FullTransaction, rhs: FullTransaction) -> Bool { - return TransactionSerializer.serialize(transaction: lhs) == TransactionSerializer.serialize(transaction: rhs) + TransactionSerializer.serialize(transaction: lhs) == TransactionSerializer.serialize(transaction: rhs) } } @@ -147,7 +136,33 @@ extension FullTransaction: Equatable { extension UnspentOutput: Equatable { public static func ==(lhs: UnspentOutput, rhs: UnspentOutput) -> Bool { - return lhs.output.value == rhs.output.value + lhs.output.value == rhs.output.value + } + +} + +extension InputToSign: Equatable { + + public static func ==(lhs: InputToSign, rhs: InputToSign) -> Bool { + lhs.input == rhs.input && lhs.previousOutputPublicKey == rhs.previousOutputPublicKey } } + +func addressMatcher(_ address: Address) -> ParameterMatcher
{ + ParameterMatcher
{ address.stringValue == $0.stringValue } +} + +func addressMatcher(_ address: Address?) -> ParameterMatcher { + ParameterMatcher { tested in + if let a1 = address, let a2 = tested { + return addressMatcher(a1).matches(a2) + } else { + return address == nil && tested == nil + } + } +} + +func outputs(withScriptTypes scriptTypes: [ScriptType]) -> [Output] { + scriptTypes.map { Output(withValue: 0, index: 0, lockingScript: Data(), type: $0) } +} diff --git a/BitcoinCore/BitcoinCoreTests/HDWallet/HDPrivateKeyTests.swift b/Example/Tests/BitcoinCore/HDWallet/HDPrivateKeyTests.swift similarity index 98% rename from BitcoinCore/BitcoinCoreTests/HDWallet/HDPrivateKeyTests.swift rename to Example/Tests/BitcoinCore/HDWallet/HDPrivateKeyTests.swift index 3d9c0891..67a58fbb 100644 --- a/BitcoinCore/BitcoinCoreTests/HDWallet/HDPrivateKeyTests.swift +++ b/Example/Tests/BitcoinCore/HDWallet/HDPrivateKeyTests.swift @@ -1,6 +1,6 @@ import XCTest import Cuckoo -import HSHDWalletKit +import HdWalletKit @testable import BitcoinCore class HDPrivateKeyTests: XCTestCase { diff --git a/BitcoinCore/BitcoinCoreTests/Helpers/AddressConverterTests.swift b/Example/Tests/BitcoinCore/Helpers/AddressConverterTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Helpers/AddressConverterTests.swift rename to Example/Tests/BitcoinCore/Helpers/AddressConverterTests.swift diff --git a/Example/Tests/BitcoinCore/Helpers/Bip69Tests.swift b/Example/Tests/BitcoinCore/Helpers/Bip69Tests.swift new file mode 100644 index 00000000..c5ddf97d --- /dev/null +++ b/Example/Tests/BitcoinCore/Helpers/Bip69Tests.swift @@ -0,0 +1,144 @@ +import XCTest +import Quick +import Nimble +import Cuckoo +@testable import BitcoinCore + +class Bip69Tests: QuickSpec { + + override func spec() { + describe("sort two outputs") { + + it("sort by amount") { + let outputWithBigAmount = Output(withValue: 140, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8)) + let outputWithSmallAmount = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8)) + + let expected = [outputWithSmallAmount, outputWithBigAmount] + let array = [outputWithBigAmount, outputWithSmallAmount] + + expect(expected).to(equal(array.sorted(by: Bip69.outputComparator)) ) + } + + it("amount are equal, sort by hashes") { + let outputHashA = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8)) + let outputHashB = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "76a9145be32612930b8323add2212a4ec03c1562084f8488ac".data(using: .utf8)) + + let expected = [outputHashA, outputHashB] + let array = [outputHashA, outputHashB] + + expect(expected).to(equal(array.sorted(by: Bip69.outputComparator)) ) + } + + it("amount are equal, sort by hashes with different length") { + let outputHashA = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8)) + let outputHashB = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f7".data(using: .utf8)) + + let expected = [outputHashB, outputHashA] + let array = [outputHashB, outputHashA] + + expect(expected).to(equal(array.sorted(by: Bip69.outputComparator)) ) + } + + it("sort by hashes") { + let outputHashA = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "3d8ed454f4fcc03ba35568aa37528748e56c0142".data(using: .utf8)) + let outputHashB = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "e191794cbc83dfaabe399af396904dd22b721ce2".data(using: .utf8)) + + let expected = [outputHashA, outputHashB] + let array = [outputHashB, outputHashA] + + expect(expected).to(equal(array.sorted(by: Bip69.outputComparator)) ) + } + + } + + describe("sort two inputs") { + + it("sort by hash") { + let unspentOutput1 = UnspentOutput( + output: Output(withValue: 0, index: 0, lockingScript: Data(), transactionHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8) ?? Data()), + publicKey: PublicKey(withAccount: 0, index: 0, external: false, hdPublicKeyData: Data()), + transaction: Transaction()) + let unspentOutput2 = UnspentOutput( + output: Output(withValue: 0, index: 0, lockingScript: Data(), transactionHash: "76a9145be32612930b8323add2212a4ec03c1562084f8488ac".data(using: .utf8) ?? Data()), + publicKey: PublicKey(withAccount: 0, index: 0, external: false, hdPublicKeyData: Data()), + transaction: Transaction()) + + let expected = [unspentOutput1, unspentOutput2] + let array = [unspentOutput1, unspentOutput2] + + expect(expected).to(equal(array.sorted(by: Bip69.inputComparator)) ) + } + + it("sort by index") { + let unspentOutput1 = UnspentOutput( + output: Output(withValue: 0, index: 1, lockingScript: Data(), transactionHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8) ?? Data()), + publicKey: PublicKey(withAccount: 0, index: 0, external: false, hdPublicKeyData: Data()), + transaction: Transaction()) + let unspentOutput2 = UnspentOutput( + output: Output(withValue: 0, index: 1, lockingScript: Data(), transactionHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8) ?? Data()), + publicKey: PublicKey(withAccount: 0, index: 0, external: false, hdPublicKeyData: Data()), + transaction: Transaction()) + + let expected = [unspentOutput2, unspentOutput1] + let array = [unspentOutput1, unspentOutput2] + + expect(expected).to(equal(array.sorted(by: Bip69.inputComparator)) ) + } + + } + } + +// override func setUp() { +// super.setUp() +// } + +// override func tearDown() { +// super.tearDown() +// } + +// func testSortTwoOutputs() { +// let outputWithBigAmount = Output(withValue: 140, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8)) +// let outputWithSmallAmount = Output(withValue: 12, index: 0, lockingScript: Data(), keyHash: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac".data(using: .utf8)) +// +// let expected = [outputWithSmallAmount, outputWithBigAmount] +// let array = [outputWithBigAmount, outputWithSmallAmount] +// XCTAssertEqual(expected, array.sorted(by: Bip69.outputComparator)) +// } + +// func testParseBitcoinCashPaymentAddress() { +// addressParser = PaymentAddressParser(validScheme: "bitcoincash", removeScheme: false) +// +// var paymentData = BitcoinPaymentData(address: "address_data") +// checkPaymentData(addressParser: addressParser, paymentAddress: "address_data", paymentData: paymentData) +// +// // Check bitcoincash addresses parsing with keep scheme if it's valid +// paymentData = BitcoinPaymentData(address: "bitcoincash:address_data") +// checkPaymentData(addressParser: addressParser, paymentAddress: "bitcoincash:address_data", paymentData: paymentData) +// +// // invalid scheme - need to leave scheme +// paymentData = BitcoinPaymentData(address: "bitcoin:address_data") +// checkPaymentData(addressParser: addressParser, paymentAddress: "bitcoin:address_data", paymentData: paymentData) +// +// // check parameters +// paymentData = BitcoinPaymentData(address: "address_data", version: "1.0") +// checkPaymentData(addressParser: addressParser, paymentAddress: "address_data;version=1.0", paymentData: paymentData) +// +// paymentData = BitcoinPaymentData(address: "bitcoincash:address_data", version: "1.0", label: "test") +// checkPaymentData(addressParser: addressParser, paymentAddress: "bitcoincash:address_data;version=1.0?label=test", paymentData: paymentData) +// +// paymentData = BitcoinPaymentData(address: "bitcoincash:address_data", amount: 0.01) +// checkPaymentData(addressParser: addressParser, paymentAddress: "bitcoincash:address_data?amount=0.01", paymentData: paymentData) +// +// paymentData = BitcoinPaymentData(address: "bitcoincash:address_data", amount: 0.01, label: "test_sender") +// checkPaymentData(addressParser: addressParser, paymentAddress: "bitcoincash:address_data?amount=0.01&label=test_sender", paymentData: paymentData) +// +// paymentData = BitcoinPaymentData(address: "bitcoincash:address_data", parameters: ["custom":"any"]) +// checkPaymentData(addressParser: addressParser, paymentAddress: "bitcoincash:address_data?custom=any", paymentData: paymentData) +// } + +// private func checkPaymentData(addressParser: PaymentAddressParser, paymentAddress: String, paymentData: BitcoinPaymentData) { +// let bitcoinPaymentData = addressParser.parse(paymentAddress: paymentAddress) +// XCTAssertEqual(bitcoinPaymentData, paymentData) +// } + +} diff --git a/BitcoinCore/BitcoinCoreTests/Helpers/BlockValidatorHelperTests.swift b/Example/Tests/BitcoinCore/Helpers/BlockValidatorHelperTests.swift similarity index 76% rename from BitcoinCore/BitcoinCoreTests/Helpers/BlockValidatorHelperTests.swift rename to Example/Tests/BitcoinCore/Helpers/BlockValidatorHelperTests.swift index 0633a389..4868aed4 100644 --- a/BitcoinCore/BitcoinCoreTests/Helpers/BlockValidatorHelperTests.swift +++ b/Example/Tests/BitcoinCore/Helpers/BlockValidatorHelperTests.swift @@ -12,11 +12,11 @@ class BlockValidatorHelperTests: XCTestCase { mockStorage = MockIStorage() stub(mockStorage) { mock in - when(mock.block(byHeight: TestData.checkpointBlock.height - 1)).thenReturn(nil) - when(mock.block(byHeight: TestData.checkpointBlock.height)).thenReturn(TestData.checkpointBlock) - when(mock.block(byHeight: TestData.firstBlock.height)).thenReturn(TestData.firstBlock) - when(mock.block(byHeight: TestData.secondBlock.height)).thenReturn(TestData.secondBlock) - when(mock.block(byHeight: TestData.thirdBlock.height)).thenReturn(TestData.thirdBlock) + when(mock.blockByHeightStalePrioritized(height: TestData.checkpointBlock.height - 1)).thenReturn(nil) + when(mock.blockByHeightStalePrioritized(height: TestData.checkpointBlock.height)).thenReturn(TestData.checkpointBlock) + when(mock.blockByHeightStalePrioritized(height: TestData.firstBlock.height)).thenReturn(TestData.firstBlock) + when(mock.blockByHeightStalePrioritized(height: TestData.secondBlock.height)).thenReturn(TestData.secondBlock) + when(mock.blockByHeightStalePrioritized(height: TestData.thirdBlock.height)).thenReturn(TestData.thirdBlock) } firstBlock = TestData.thirdBlock diff --git a/BitcoinCore/BitcoinCoreTests/Helpers/MerkleBranchTests.swift b/Example/Tests/BitcoinCore/Helpers/MerkleBranchTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Helpers/MerkleBranchTests.swift rename to Example/Tests/BitcoinCore/Helpers/MerkleBranchTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Helpers/PaymentAddressParserTests.swift b/Example/Tests/BitcoinCore/Helpers/PaymentAddressParserTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Helpers/PaymentAddressParserTests.swift rename to Example/Tests/BitcoinCore/Helpers/PaymentAddressParserTests.swift diff --git a/Example/Tests/BitcoinCore/Managers/BloomFilterManagerTests.swift b/Example/Tests/BitcoinCore/Managers/BloomFilterManagerTests.swift new file mode 100644 index 00000000..b276ba8a --- /dev/null +++ b/Example/Tests/BitcoinCore/Managers/BloomFilterManagerTests.swift @@ -0,0 +1,85 @@ +import Quick +import Nimble +import XCTest +import Cuckoo +import HdWalletKit +@testable import BitcoinCore + +class BloomFilterManagerTests: QuickSpec { + override func spec() { + let mockStorage = MockIStorage() + let mockFactory = MockIFactory() + let mockBloomFilterManagerDelegate = MockIBloomFilterManagerDelegate() + let mockBloomFilterProvider = MockIBloomFilterProvider() + + let bloomFilter = BloomFilter(elements: [Data(from: 9999999)]) + let lastBlock = TestData.checkpointBlock + + var manager: BloomFilterManager! + + beforeEach { + stub(mockBloomFilterManagerDelegate) { mock in + when(mock.bloomFilterUpdated(bloomFilter: any())).thenDoNothing() + } + stub(mockFactory) { mock in + when(mock).bloomFilter(withElements: any()).thenReturn(bloomFilter) + } + stub(mockStorage) { mock in + when(mock.lastBlock.get).thenReturn(lastBlock) + } + + manager = BloomFilterManager(factory: mockFactory) + manager.delegate = mockBloomFilterManagerDelegate + } + + afterEach { + reset(mockStorage, mockFactory, mockBloomFilterManagerDelegate) + + manager = nil + } + + describe("#regenerateBloomFilter") { + context("when has providers") { + let elements = [Data(repeating: 0, count: 32), Data(repeating: 1, count: 20)] + + beforeEach { + stub(mockStorage) { mock in + when(mock.publicKeys()).thenReturn([]) + when(mock.outputsWithPublicKeys()).thenReturn([]) + } + stub(mockBloomFilterProvider) { mock in + when(mock.bloomFilterManager.set(_: any())).thenDoNothing() + when(mock.filterElements()).thenReturn(elements) + } + + manager.add(provider: mockBloomFilterProvider) + } + + afterEach { + reset(mockBloomFilterProvider) + } + + it("adds elements to bloom filter") { + manager.regenerateBloomFilter() + + verify(mockFactory).bloomFilter(withElements: equal(to: elements)) + verify(mockBloomFilterManagerDelegate).bloomFilterUpdated(bloomFilter: equal(to: bloomFilter, equalWhen: { $0.filter == $1.filter })) + } + } + + context("when no elements") { + it("doesn't trigger events") { + stub(mockStorage) { mock in + when(mock.publicKeys()).thenReturn([]) + when(mock.outputsWithPublicKeys()).thenReturn([]) + } + + manager.regenerateBloomFilter() + verify(mockFactory, never()).bloomFilter(withElements: any()) + verify(mockBloomFilterManagerDelegate, never()).bloomFilterUpdated(bloomFilter: any()) + } + } + } + } + +} diff --git a/Example/Tests/BitcoinCore/Managers/InitialSync/BlockDiscoveryBatchTest.swift b/Example/Tests/BitcoinCore/Managers/InitialSync/BlockDiscoveryBatchTest.swift new file mode 100644 index 00000000..5fa721bc --- /dev/null +++ b/Example/Tests/BitcoinCore/Managers/InitialSync/BlockDiscoveryBatchTest.swift @@ -0,0 +1,88 @@ +import XCTest +import Cuckoo +import RxSwift +import RxBlocking + +@testable import BitcoinCore + + +class BlockDiscoveryBatchTest: XCTestCase { + + private var mockWallet: MockIHDWallet! + private var mockBlockHashFetcher: MockIBlockHashFetcher! + + private let checkpoint = TestData.checkpoint + + private var blockDiscovery: BlockDiscoveryBatch! + + private let externalPublicKeys = [ + PublicKey(withAccount: 0, index: 0, external: true, hdPublicKeyData: Data()), + PublicKey(withAccount: 0, index: 1, external: true, hdPublicKeyData: Data()), + PublicKey(withAccount: 0, index: 2, external: true, hdPublicKeyData: Data()), + PublicKey(withAccount: 0, index: 3, external: true, hdPublicKeyData: Data()), + PublicKey(withAccount: 0, index: 4, external: true, hdPublicKeyData: Data()) + ] + + private let internalPublicKeys = [ + PublicKey(withAccount: 0, index: 0, external: false, hdPublicKeyData: Data()), + PublicKey(withAccount: 0, index: 1, external: false, hdPublicKeyData: Data()), + PublicKey(withAccount: 0, index: 2, external: false, hdPublicKeyData: Data()) + ] + + override func setUp() { + super.setUp() + + mockWallet = MockIHDWallet() + mockBlockHashFetcher = MockIBlockHashFetcher() + + stub(mockWallet) {mock in + when(mock.gapLimit.get).thenReturn(3) + } + + blockDiscovery = BlockDiscoveryBatch(checkpoint: checkpoint, wallet: mockWallet, blockHashFetcher: mockBlockHashFetcher, logger: nil) + } + + override func tearDown() { + mockWallet = nil + mockBlockHashFetcher = nil + + blockDiscovery = nil + + super.tearDown() + } + + func testFetchFromApi() { + stub(mockWallet) { mock in + when(mock.publicKeys(account: 0, indices: equal(to: UInt32(0).. PublicKey { + let hdWallet = HDWallet(seed: Data(), coinType: UInt32(1), xPrivKey: UInt32(0x04358394), xPubKey: UInt32(0x043587cf)) + let hdPrivKeyData = try! hdWallet.privateKeyData(account: 0, index: index, external: chain == .external) + return PublicKey(withAccount: 0, index: index, external: chain == .external, hdPublicKeyData: hdPrivKeyData) + } + + private func byteArrayLittleEndian(int: Int) -> [UInt8] { + return [ + UInt8(int & 0x000000FF), + UInt8((int & 0x0000FF00) >> 8), + UInt8((int & 0x00FF0000) >> 16), + UInt8((int & 0xFF000000) >> 24) + ] + } + +} diff --git a/BitcoinCore/BitcoinCoreTests/Managers/AddressManagerTests.swift b/Example/Tests/BitcoinCore/Managers/PublicKeyManagerTests.swift similarity index 64% rename from BitcoinCore/BitcoinCoreTests/Managers/AddressManagerTests.swift rename to Example/Tests/BitcoinCore/Managers/PublicKeyManagerTests.swift index 589f38f1..8575bc4b 100644 --- a/BitcoinCore/BitcoinCoreTests/Managers/AddressManagerTests.swift +++ b/Example/Tests/BitcoinCore/Managers/PublicKeyManagerTests.swift @@ -1,16 +1,18 @@ import XCTest import Cuckoo -import HSHDWalletKit +import HdWalletKit @testable import BitcoinCore -class AddressManagerTests: XCTestCase { +class PublicKeyManagerTests: XCTestCase { private var mockStorage: MockIStorage! private var mockHDWallet: MockIHDWallet! private var mockAddressConverter: MockIAddressConverter! + private var mockRestoreKeyConverter: MockIRestoreKeyConverter! + private var mockBloomFilterManager: MockIBloomFilterManager! private var hdWallet: IHDWallet! - private var manager: AddressManager! + private var manager: PublicKeyManager! override func setUp() { super.setUp() @@ -18,19 +20,27 @@ class AddressManagerTests: XCTestCase { mockStorage = MockIStorage() mockHDWallet = MockIHDWallet() mockAddressConverter = MockIAddressConverter() + mockRestoreKeyConverter = MockIRestoreKeyConverter() + mockBloomFilterManager = MockIBloomFilterManager() stub(mockStorage) { mock in when(mock.add(publicKeys: any())).thenDoNothing() } + stub(mockBloomFilterManager) { mock in + when(mock.regenerateBloomFilter()).thenDoNothing() + } hdWallet = HDWallet(seed: Data(), coinType: UInt32(1), xPrivKey: UInt32(0x04358394), xPubKey: UInt32(0x043587cf)) - manager = AddressManager(storage: mockStorage, hdWallet: mockHDWallet, addressConverter: mockAddressConverter) + manager = PublicKeyManager(storage: mockStorage, hdWallet: mockHDWallet, restoreKeyConverter: mockRestoreKeyConverter) + manager.bloomFilterManager = mockBloomFilterManager } override func tearDown() { mockStorage = nil mockHDWallet = nil mockAddressConverter = nil + mockRestoreKeyConverter = nil + mockBloomFilterManager = nil hdWallet = nil manager = nil @@ -66,37 +76,32 @@ class AddressManagerTests: XCTestCase { do { let _ = try manager.changePublicKey() XCTFail("Should throw exception") - } catch let error as AddressManager.AddressManagerError { - XCTAssertEqual(error, AddressManager.AddressManagerError.noUnusedPublicKey) + } catch let error as PublicKeyManager.PublicKeyManagerError { + XCTAssertEqual(error, PublicKeyManager.PublicKeyManagerError.noUnusedPublicKey) } catch { XCTFail("Unexpected exception thrown") } } - func testReceiveAddress() { + func testReceivePublicKey() { let publicKeys = [ PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 0, chain: .external), used: true), PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 0, chain: .internal), used: false), PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 3, chain: .external), used: false), PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 1, chain: .external), used: false), - PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 1, chain: .internal), used: false), - PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 2, chain: .external), used: false) + PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 2, chain: .external), used: false), + PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 1, chain: .internal), used: false) ] stub(mockStorage) { mock in when(mock.publicKeysWithUsedState()).thenReturn(publicKeys) } - let address = LegacyAddress(type: .pubKeyHash, keyHash: publicKeys[3].publicKey.keyHash, base58: "receiveAddress") - stub(mockAddressConverter) { mock in - when(mock.convert(keyHash: equal(to: publicKeys[3].publicKey.keyHash), type: equal(to: ScriptType.p2pkh))).thenReturn(address) - } - - XCTAssertEqual(try? manager.receiveAddress(for: .p2pkh), address.stringValue) - verify(mockAddressConverter).convert(keyHash: equal(to: publicKeys[3].publicKey.keyHash), type: equal(to: ScriptType.p2pkh)) + let changePublicKey = try! manager.receivePublicKey() + XCTAssertEqual(changePublicKey.keyHash, publicKeys[3].publicKey.keyHash) } - func testReceiveAddress_NoUnusedPublicKey() { + func testReceivePublicKey_NoUnusedPublicKey() { let publicKey = PublicKeyWithUsedState(publicKey: getPublicKey(withAccount: 0, index: 0, chain: .external), used: true) stub(mockStorage) { mock in @@ -104,10 +109,10 @@ class AddressManagerTests: XCTestCase { } do { - let _ = try manager.receiveAddress(for: .p2pkh) + let _ = try manager.receivePublicKey() XCTFail("Should throw exception") - } catch let error as AddressManager.AddressManagerError { - XCTAssertEqual(error, AddressManager.AddressManagerError.noUnusedPublicKey) + } catch let error as PublicKeyManager.PublicKeyManagerError { + XCTAssertEqual(error, PublicKeyManager.PublicKeyManagerError.noUnusedPublicKey) } catch { XCTFail("Unexpected exception thrown") } @@ -131,25 +136,26 @@ class AddressManagerTests: XCTestCase { } stub(mockHDWallet) { mock in when(mock.gapLimit.get).thenReturn(2) - when(mock.publicKey(account: equal(to: 0), index: equal(to: 2), external: equal(to: false))).thenReturn(keys[2].publicKey) - when(mock.publicKey(account: equal(to: 0), index: equal(to: 0), external: equal(to: true))).thenReturn(keys[3].publicKey) - when(mock.publicKey(account: equal(to: 0), index: equal(to: 1), external: equal(to: true))).thenReturn(keys[4].publicKey) - when(mock.publicKey(account: equal(to: 1), index: equal(to: 0), external: equal(to: false))).thenReturn(keys[5].publicKey) - when(mock.publicKey(account: equal(to: 1), index: equal(to: 1), external: equal(to: false))).thenReturn(keys[6].publicKey) - when(mock.publicKey(account: equal(to: 1), index: equal(to: 0), external: equal(to: true))).thenReturn(keys[7].publicKey) - when(mock.publicKey(account: equal(to: 1), index: equal(to: 1), external: equal(to: true))).thenReturn(keys[8].publicKey) + when(mock.publicKeys(account: 0, indices: equal(to: UInt32(2).. PublicKey { let hdPrivKeyData = try! hdWallet.privateKeyData(account: account, index: index, external: chain == .external) return PublicKey(withAccount: account, index: index, external: chain == .external, hdPublicKeyData: hdPrivKeyData) } + } diff --git a/BitcoinCore/BitcoinCoreTests/Managers/StateManagerTests.swift b/Example/Tests/BitcoinCore/Managers/StateManagerTests.swift similarity index 85% rename from BitcoinCore/BitcoinCoreTests/Managers/StateManagerTests.swift rename to Example/Tests/BitcoinCore/Managers/StateManagerTests.swift index 70471574..c2bb8348 100644 --- a/BitcoinCore/BitcoinCoreTests/Managers/StateManagerTests.swift +++ b/Example/Tests/BitcoinCore/Managers/StateManagerTests.swift @@ -6,7 +6,7 @@ import RxSwift class StateManagerTests: XCTestCase { private var mockStorage: MockIStorage! - private var manager: StateManager! + private var manager: ApiSyncStateManager! override func setUp() { super.setUp() @@ -17,7 +17,7 @@ class StateManagerTests: XCTestCase { when(mock.set(initialRestored: any())).thenDoNothing() } - manager = StateManager(storage: mockStorage, restoreFromApi: true) + manager = ApiSyncStateManager(storage: mockStorage, restoreFromApi: true) } override func tearDown() { @@ -28,7 +28,7 @@ class StateManagerTests: XCTestCase { } func testRestored_get_newWallet() { - let manager = StateManager(storage: mockStorage, restoreFromApi: false) + let manager = ApiSyncStateManager(storage: mockStorage, restoreFromApi: false) XCTAssertTrue(manager.restored) } diff --git a/BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputProviderTests.swift b/Example/Tests/BitcoinCore/Managers/UnspentOutputProviderTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputProviderTests.swift rename to Example/Tests/BitcoinCore/Managers/UnspentOutputProviderTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputSelectorTests.swift b/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorOldTests.swift similarity index 65% rename from BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputSelectorTests.swift rename to Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorOldTests.swift index 2d41c5b1..88e4ed2a 100644 --- a/BitcoinCore/BitcoinCoreTests/Managers/UnspentOutputSelectorTests.swift +++ b/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorOldTests.swift @@ -2,26 +2,30 @@ import XCTest import Cuckoo @testable import BitcoinCore -class UnspentOutputSelectorTests: XCTestCase { +class UnspentOutputSelectorOldTests: XCTestCase { + + private let dust = 12 private var unspentOutputSelector: UnspentOutputSelector! private var outputs: [UnspentOutput]! var mockTransactionSizeCalculator: MockITransactionSizeCalculator! var mockUnspentOutputProvider: MockIUnspentOutputProvider! + var mockDustCalculator: MockIDustCalculator! override func setUp() { super.setUp() mockTransactionSizeCalculator = MockITransactionSizeCalculator() mockUnspentOutputProvider = MockIUnspentOutputProvider() + mockDustCalculator = MockIDustCalculator() stub(mockTransactionSizeCalculator) { mock in when(mock.inputSize(type: any())).thenReturn(10) when(mock.outputSize(type: any())).thenReturn(2) - when(mock.transactionSize(inputs: any(), outputScriptTypes: any())).thenReturn(100) + when(mock.transactionSize(previousOutputs: any(), outputScriptTypes: any(), pluginDataOutputSize: 0)).thenReturn(100) } - unspentOutputSelector = UnspentOutputSelector(calculator: mockTransactionSizeCalculator, provider: mockUnspentOutputProvider) + unspentOutputSelector = UnspentOutputSelector(calculator: mockTransactionSizeCalculator, provider: mockUnspentOutputProvider, dustCalculator: mockDustCalculator) outputs = [TestData.unspentOutput(output: Output(withValue: 1000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), TestData.unspentOutput(output: Output(withValue: 2000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), @@ -30,7 +34,10 @@ class UnspentOutputSelectorTests: XCTestCase { TestData.unspentOutput(output: Output(withValue: 16000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())) ] stub(mockUnspentOutputProvider) { mock in - when(mock.allUnspentOutputs.get).thenReturn(outputs) + when(mock.spendableUtxo.get).thenReturn(outputs) + } + stub(mockDustCalculator) { mock in + when(mock.dust(type: any())).thenReturn(dust) } } @@ -45,11 +52,10 @@ class UnspentOutputSelectorTests: XCTestCase { func testSummaryValueReceiverPay() { do { - let selectedOutputs = try unspentOutputSelector.select(value: 7000, feeRate: 1, senderPay: false) + let selectedOutputs = try unspentOutputSelector.select(value: 7000, feeRate: 1, senderPay: false, pluginDataOutputSize: 0) XCTAssertEqual(selectedOutputs.unspentOutputs, [outputs[0], outputs[1], outputs[2]]) - XCTAssertEqual(selectedOutputs.totalValue, 7000) - XCTAssertEqual(selectedOutputs.fee, 100) - XCTAssertEqual(selectedOutputs.addChangeOutput, false) + XCTAssertEqual(selectedOutputs.recipientValue, 7000 - 100) + XCTAssertEqual(selectedOutputs.changeValue, nil) } catch { XCTFail("Unexpected error!") } @@ -58,22 +64,24 @@ class UnspentOutputSelectorTests: XCTestCase { func testSummaryValueSenderPay() { // with change output do { - let selectedOutputs = try unspentOutputSelector.select(value: 7000, feeRate: 1, senderPay: true) + let selectedOutputs = try unspentOutputSelector.select(value: 7000, feeRate: 1, senderPay: true, pluginDataOutputSize: 0) XCTAssertEqual(selectedOutputs.unspentOutputs, [outputs[0], outputs[1], outputs[2], outputs[3]]) - XCTAssertEqual(selectedOutputs.totalValue, 15000) - XCTAssertEqual(selectedOutputs.fee, 100) - XCTAssertEqual(selectedOutputs.addChangeOutput, true) + XCTAssertEqual(selectedOutputs.recipientValue, 7000) + XCTAssertEqual(selectedOutputs.changeValue, 15000 - 7000 - 100) } catch { XCTFail("Unexpected error!") } // without change output + stub(mockDustCalculator) { mock in + when(mock.dust(type: any())).thenReturn(dust + 1) + } + do { let expectedFee = 100 + 10 + 2 // fee for tx + fee for change input + fee for change output - let selectedOutputs = try unspentOutputSelector.select(value: 15000 - expectedFee, feeRate: 1, senderPay: true) + let selectedOutputs = try unspentOutputSelector.select(value: 15000 - expectedFee, feeRate: 1, senderPay: true, pluginDataOutputSize: 0) XCTAssertEqual(selectedOutputs.unspentOutputs, [outputs[0], outputs[1], outputs[2], outputs[3]]) - XCTAssertEqual(selectedOutputs.totalValue, 15000) - XCTAssertEqual(selectedOutputs.fee, expectedFee) - XCTAssertEqual(selectedOutputs.addChangeOutput, false) + XCTAssertEqual(selectedOutputs.recipientValue, 15000 - expectedFee) + XCTAssertEqual(selectedOutputs.changeValue, nil) } catch { XCTFail("Unexpected error!") } @@ -81,10 +89,10 @@ class UnspentOutputSelectorTests: XCTestCase { func testNotEnoughErrorReceiverPay() { do { - _ = try unspentOutputSelector.select(value: 31001, feeRate: 1, senderPay: false) + _ = try unspentOutputSelector.select(value: 31001, feeRate: 1, senderPay: false, pluginDataOutputSize: 0) XCTFail("Wrong value summary!") - } catch let error as BitcoinCoreErrors.UnspentOutputSelection { - XCTAssertEqual(error, BitcoinCoreErrors.UnspentOutputSelection.notEnough(maxFee: 0)) + } catch let error as BitcoinCoreErrors.SendValueErrors { + XCTAssertEqual(error, BitcoinCoreErrors.SendValueErrors.notEnough) } catch { XCTFail("Unexpected \(error) error!") } @@ -92,10 +100,10 @@ class UnspentOutputSelectorTests: XCTestCase { func testNotEnoughErrorSenderPay() { do { - _ = try unspentOutputSelector.select(value: 30901, feeRate: 1, senderPay: true) + _ = try unspentOutputSelector.select(value: 30901, feeRate: 1, senderPay: true, pluginDataOutputSize: 0) XCTFail("Wrong value summary!") - } catch let error as BitcoinCoreErrors.UnspentOutputSelection { - XCTAssertEqual(error, BitcoinCoreErrors.UnspentOutputSelection.notEnough(maxFee: 100)) + } catch let error as BitcoinCoreErrors.SendValueErrors { + XCTAssertEqual(error, BitcoinCoreErrors.SendValueErrors.notEnough) } catch { XCTFail("Unexpected \(error) error!") } @@ -103,13 +111,13 @@ class UnspentOutputSelectorTests: XCTestCase { func testEmptyOutputsError() { stub(mockUnspentOutputProvider) { mock in - when(mock.allUnspentOutputs.get).thenReturn([]) + when(mock.spendableUtxo.get).thenReturn([]) } do { - _ = try unspentOutputSelector.select(value: 100, feeRate: 1, senderPay: false) + _ = try unspentOutputSelector.select(value: 100, feeRate: 1, senderPay: false, pluginDataOutputSize: 0) XCTFail("Wrong value summary!") - } catch let error as BitcoinCoreErrors.UnspentOutputSelection { - XCTAssertEqual(error, BitcoinCoreErrors.UnspentOutputSelection.emptyOutputs) + } catch let error as BitcoinCoreErrors.SendValueErrors { + XCTAssertEqual(error, BitcoinCoreErrors.SendValueErrors.emptyOutputs) } catch { XCTFail("Unexpected \(error) error!") } diff --git a/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorSingleNoChangeTests.swift b/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorSingleNoChangeTests.swift new file mode 100644 index 00000000..a4dad231 --- /dev/null +++ b/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorSingleNoChangeTests.swift @@ -0,0 +1,146 @@ +import XCTest +import Quick +import Nimble +import Cuckoo +@testable import BitcoinCore + +class UnspentOutputSelectorSingleNoChangeTests: QuickSpec { + + override func spec() { + let feeRate = 1 + let fee = 100 + let dust = 12 + let value = 4000 + + let mockTransactionSizeCalculator = MockITransactionSizeCalculator() + let mockUnspentOutputProvider = MockIUnspentOutputProvider() + let mockDustCalculator = MockIDustCalculator() + var selector: UnspentOutputSelectorSingleNoChange! + + let outputs = [TestData.unspentOutput(output: Output(withValue: dust + fee, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 2000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 4000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 8000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 16000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())) + ] + + beforeEach { + stub(mockTransactionSizeCalculator) { mock in + when(mock.inputSize(type: any())).thenReturn(10) + when(mock.outputSize(type: any())).thenReturn(2) + when(mock.transactionSize(previousOutputs: any(), outputScriptTypes: any(), pluginDataOutputSize: any())).thenReturn(fee) + } + stub(mockUnspentOutputProvider) { mock in + when(mock.spendableUtxo.get).thenReturn(outputs) + } + stub(mockDustCalculator) { mock in + when(mock.dust(type: any())).thenReturn(dust) + } + selector = UnspentOutputSelectorSingleNoChange(calculator: mockTransactionSizeCalculator, provider: mockUnspentOutputProvider, dustCalculator: mockDustCalculator) + } + + afterEach { + reset(mockTransactionSizeCalculator, mockUnspentOutputProvider) + selector = nil + } + + context("when senderPay = true") { + it("selects it on exact match") { + var selectedOutputs = try! selector.select(value: value - fee, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[2]])) + expect(selectedOutputs.recipientValue).to(equal(value - fee)) + expect(selectedOutputs.changeValue).to(beNil()) + + selectedOutputs = try! selector.select(value: dust, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0]])) + expect(selectedOutputs.recipientValue).to(equal(dust)) + expect(selectedOutputs.changeValue).to(beNil()) + } + + it("selects it on match with allowable remainder") { + let selectedOutputs = try! selector.select(value: value - fee - dust + 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[2]])) + expect(selectedOutputs.recipientValue).to(equal(value - fee - dust + 1)) + expect(selectedOutputs.changeValue).to(beNil()) + } + + it("doesn't select it on match with remainder more than dust") { + do { + _ = try selector.select(value: value - fee - dust, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.singleNoChangeOutputNotFound)) + } catch { + fail("Unexpected error") + } + } + + it("throws exception on value less than dust") { + do { + _ = try selector.select(value: dust - 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.dust)) + } catch { + fail("Unexpected error") + } + } + } + + context("when senderPay = false") { + it("selects it on exact match") { + let selectedOutputs = try! selector.select(value: value, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[2]])) + expect(selectedOutputs.recipientValue).to(equal(value - fee)) + expect(selectedOutputs.changeValue).to(beNil()) + } + + it("selects it on match with allowable remainder") { + let selectedOutputs = try! selector.select(value: value - dust + 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[2]])) + expect(selectedOutputs.recipientValue).to(equal(value - fee - dust + 1)) + expect(selectedOutputs.changeValue).to(beNil()) + } + + it("doesn't select it on match with remainder more than dust") { + do { + _ = try selector.select(value: value - dust, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.singleNoChangeOutputNotFound)) + } catch { + fail("Unexpected error") + } + } + + it("doesn't select it on recipientValue less than dust") { + do { + _ = try selector.select(value: dust + fee - 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.singleNoChangeOutputNotFound)) + } catch { + fail("Unexpected error") + } + } + + it("doesn't select it if there's an output failed to spend before") { + defer { + outputs.first?.output.failedToSpend = false + } + + do { + outputs.first?.output.failedToSpend = true + _ = try selector.select(value: value, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.singleNoChangeOutputNotFound)) + } catch { + fail("Unexpected error") + } + } + } + } + +} + diff --git a/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorTests.swift b/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorTests.swift new file mode 100644 index 00000000..f322760e --- /dev/null +++ b/Example/Tests/BitcoinCore/Managers/UnspentOutputSelectorTests.swift @@ -0,0 +1,219 @@ +import XCTest +import Quick +import Nimble +import Cuckoo +@testable import BitcoinCore + +// value: Given value +// totalValue: Total value of selected UTXOs +// recipientValue: Actual value receiver gets. A value in "TO" output +// sentValue: Value that sender actually loses. (recipientValue + fee) +// changeValue: Value in "CHANGE" output +// +// +// +// INPUTS OUTPUTS +// +// totalValue - - fee - -> recipientValue +// \ +// -> changeValue +// +// +// * when senderPay = true: recipientValue = value; sentValue = value + fee +// when senderPay = false: recipientValue = value - fee; sentValue = value + + +class UnspentOutputSelectorTests: QuickSpec { + + override func spec() { + let feeRate = 1 + let fee = 100 + let feeWithChangeOutput = 110 + let dust = 12 + let totalValue = 1000 + 2000 + + let mockTransactionSizeCalculator = MockITransactionSizeCalculator() + let mockUnspentOutputProvider = MockIUnspentOutputProvider() + let mockDustCalculator = MockIDustCalculator() + var selector: UnspentOutputSelector! + + let outputs = [TestData.unspentOutput(output: Output(withValue: 1000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 2000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 3000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 8000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())), + TestData.unspentOutput(output: Output(withValue: 16000, index: 0, lockingScript: Data(), type: .p2pkh, keyHash: Data())) + ] + + beforeEach { + stub(mockTransactionSizeCalculator) { mock in + when(mock.inputSize(type: any())).thenReturn(10) + when(mock.outputSize(type: any())).thenReturn(2) + when(mock.transactionSize(previousOutputs: any(), outputScriptTypes: equal(to: [.p2pkh]), pluginDataOutputSize: any())).thenReturn(fee) + when(mock.transactionSize(previousOutputs: any(), outputScriptTypes: equal(to: [.p2pkh, .p2pkh]), pluginDataOutputSize: any())).thenReturn(feeWithChangeOutput) + } + stub(mockUnspentOutputProvider) { mock in + when(mock.spendableUtxo.get).thenReturn(outputs) + } + stub(mockDustCalculator) { mock in + when(mock.dust(type: any())).thenReturn(dust) + } + selector = UnspentOutputSelector(calculator: mockTransactionSizeCalculator, provider: mockUnspentOutputProvider, dustCalculator: mockDustCalculator) + } + + afterEach { + reset(mockTransactionSizeCalculator, mockUnspentOutputProvider) + selector = nil + } + + context("when senderPay = true") { + context("when totalValue exactly matches recipientValue + fee") { + it("selects without changeValue") { + // CONDITION: totalValue == recipientValue + fee + // recipientValue = givenValue + // givenValue = totalValue - fee + // CONDITION CHECK: totalValue == (totalValue - fee) + fee + let selectedOutputs = try! selector.select(value: totalValue - fee, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0], outputs[1]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - fee)) + expect(selectedOutputs.changeValue).to(beNil()) + } + } + + context("when totalValue matches value with remainder less than dust") { + it("selects without changeValue") { + // CONDITION: totalValue == recipientValue + fee + (dust - 1) + // recipientValue = givenValue + // givenValue = totalValue - fee - dust + 1 + // CONDITION CHECK: totalValue == (totalValue - fee - dust + 1) + fee + (dust - 1) + let selectedOutputs = try! selector.select(value: totalValue - fee - dust + 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0], outputs[1]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - fee - dust + 1)) + expect(selectedOutputs.changeValue).to(beNil()) + } + } + + context("when totalValue matches value with remainder more than or equal to dust") { + it("selects with changeValue") { + // CONDITION: totalValue == recipientValue + fee + dust + // recipientValue = givenValue + // givenValue = totalValue - feeWithChangeOutput - dust + // CONDITION CHECK: totalValue == (totalValue - feeWithChangeOutput - dust) + fee + dust + let selectedOutputs = try! selector.select(value: totalValue - feeWithChangeOutput - dust, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0], outputs[1]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - feeWithChangeOutput - dust)) + expect(selectedOutputs.changeValue).to(equal(dust)) + } + } + + context("when value is less than dust") { + it("throws dust exception") { + do { + _ = try selector.select(value: dust - 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.dust)) + } catch { + fail("Unexpected error") + } + } + } + + context("when value is less than allAmount") { + it("throws notEnough exception") { + do { + _ = try selector.select(value: outputs.reduce(0) { $0 + $1.output.value } - fee + 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.notEnough)) + } catch { + fail("Unexpected error") + } + } + } + + context("when there is an output failed to spend before") { + beforeEach { + outputs[2].output.failedToSpend = true + } + afterEach { + outputs[2].output.failedToSpend = false + } + + it("selects output failed to spend") { + let selectedOutputs = try! selector.select(value: totalValue - fee, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: true, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[2]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - fee)) + expect(selectedOutputs.changeValue).to(beNil()) + } + } + } + + context("when senderPay = false") { + context("when totalValue exactly matches recipientValue + fee") { + it("selects without changeValue") { + // CONDITION: totalValue == recipientValue + fee + // recipientValue = givenValue - fee + // givenValue = totalValue + // CONDITION CHECK: totalValue == (totalValue - fee) + fee + let selectedOutputs = try! selector.select(value: totalValue, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0], outputs[1]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - fee)) + expect(selectedOutputs.changeValue).to(beNil()) + } + } + + context("when totalValue matches value with remainder less than dust") { + it("selects without changeValue") { + // CONDITION: totalValue == recipientValue + fee + (dust - 1) + // recipientValue = givenValue - fee + // givenValue = totalValue - dust + 1 + // CONDITION CHECK: totalValue == ((totalValue - dust + 1) - fee) + fee + (dust - 1) + let selectedOutputs = try! selector.select(value: totalValue - dust + 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0], outputs[1]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - dust + 1 - fee)) + expect(selectedOutputs.changeValue).to(beNil()) + } + } + + context("when totalValue matches value with remainder more than or equal to dust") { + it("selects with changeValue") { + // CONDITION: totalValue == recipientValue + feeWithChangeOutput + dust + // recipientValue = givenValue - feeWithChangeOutput + // givenValue = totalValue - dust + // CONDITION CHECK: totalValue == ((totalValue - dust) - feeWithChangeOutput) + feeWithChangeOutput + dust + let selectedOutputs = try! selector.select(value: totalValue - dust, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + expect(selectedOutputs.unspentOutputs).to(equal([outputs[0], outputs[1]])) + expect(selectedOutputs.recipientValue).to(equal(totalValue - dust - feeWithChangeOutput)) + expect(selectedOutputs.changeValue).to(equal(dust)) + } + } + + context("when value is less than dust") { + it("throws dust exception") { + do { + _ = try selector.select(value: dust + fee - 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.dust)) + } catch { + fail("Unexpected error") + } + } + } + + context("when value is less than allAmount") { + it("throws notEnough exception") { + do { + _ = try selector.select(value: outputs.reduce(0) { $0 + $1.output.value } + 1, feeRate: feeRate, outputScriptType: .p2pkh, changeType: .p2pkh, senderPay: false, pluginDataOutputSize: 0) + fail("Exception expected") + } catch let error as BitcoinCoreErrors.SendValueErrors { + expect(error).to(equal(BitcoinCoreErrors.SendValueErrors.notEnough)) + } catch { + fail("Unexpected error") + } + } + } + } + } + +} diff --git a/Example/Tests/BitcoinCore/Managers/WatchedTransactionManagerTests.swift b/Example/Tests/BitcoinCore/Managers/WatchedTransactionManagerTests.swift new file mode 100644 index 00000000..baa44958 --- /dev/null +++ b/Example/Tests/BitcoinCore/Managers/WatchedTransactionManagerTests.swift @@ -0,0 +1,87 @@ +import Quick +import Nimble +import XCTest +import Cuckoo +import HdWalletKit +@testable import BitcoinCore + +class WatchedTransactionManagerTests: QuickSpec { + override func spec() { + let mockBloomFilterManager = MockIBloomFilterManager() + let mockP2ShFilterDelegate = MockIWatchedTransactionDelegate() + let mockOutpointFilterDelegate = MockIWatchedTransactionDelegate() + var manager: WatchedTransactionManager! + + let scriptHash = Data(repeating: 0, count: 32) + let transactionHash = Data(repeating: 1, count: 32) + let outputIndex = 1 + + beforeEach { + stub(mockBloomFilterManager) { mock in + when(mock.regenerateBloomFilter()).thenDoNothing() + } + stub(mockP2ShFilterDelegate) { mock in + when(mock.transactionReceived(transaction: any(), outputIndex: any())).thenDoNothing() + } + stub(mockOutpointFilterDelegate) { mock in + when(mock.transactionReceived(transaction: any(), inputIndex: any())).thenDoNothing() + } + + manager = WatchedTransactionManager(queue: DispatchQueue.main) + manager.bloomFilterManager = mockBloomFilterManager + manager.add(transactionFilter: .p2shOutput(scriptHash: scriptHash), delegatedTo: mockP2ShFilterDelegate) + manager.add(transactionFilter: .outpoint(transactionHash: transactionHash, outputIndex: outputIndex), delegatedTo: mockOutpointFilterDelegate) + } + + afterEach { + reset(mockBloomFilterManager, mockP2ShFilterDelegate, mockOutpointFilterDelegate) + + manager = nil + } + + describe("#add(transactionFilter:delegatedTo:)") { + it("calls regenerateBloomFilter") { + manager.add(transactionFilter: .p2shOutput(scriptHash: scriptHash), delegatedTo: mockP2ShFilterDelegate) + verify(mockBloomFilterManager, times(3)).regenerateBloomFilter() + } + } + + describe("#onReceive") { + it("matches against p2shOutput filters") { + let transaction = TestData.p2shTransaction + transaction.outputs[0].keyHash = scriptHash + + manager.onReceive(transaction: transaction) + self.waitForMainQueue() + + verify(mockP2ShFilterDelegate).transactionReceived(transaction: equal(to: transaction), outputIndex: transaction.outputs[0].index) + verify(mockOutpointFilterDelegate, never()).transactionReceived(transaction: any(), inputIndex: any()) + } + + it("matches against outpoint filters") { + let transaction = TestData.p2shTransaction + transaction.inputs[0].previousOutputTxHash = transactionHash + transaction.inputs[0].previousOutputIndex = outputIndex + + manager.onReceive(transaction: transaction) + self.waitForMainQueue() + + verify(mockP2ShFilterDelegate, never()).transactionReceived(transaction: any(), outputIndex: any()) + verify(mockOutpointFilterDelegate).transactionReceived(transaction: equal(to: transaction), inputIndex: 0) + } + } + + describe("#getFilterElements") { + it("returns p2shOutput filters") { + let elements = manager.filterElements() + expect(elements).to(contain(scriptHash)) + } + + it("returns outpoint filters") { + let elements = manager.filterElements() + expect(elements).to(contain(transactionHash + byteArrayLittleEndian(int: outputIndex))) + } + } + } + +} diff --git a/BitcoinCore/BitcoinCoreTests/Network/BitcoinCashMainNetTests.swift b/Example/Tests/BitcoinCore/Network/BitcoinCashMainNetTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/BitcoinCashMainNetTests.swift rename to Example/Tests/BitcoinCore/Network/BitcoinCashMainNetTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/BitcoinMainNetTests.swift b/Example/Tests/BitcoinCore/Network/BitcoinMainNetTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/BitcoinMainNetTests.swift rename to Example/Tests/BitcoinCore/Network/BitcoinMainNetTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/BitcoinRegTestNetTests.swift b/Example/Tests/BitcoinCore/Network/BitcoinRegTestNetTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/BitcoinRegTestNetTests.swift rename to Example/Tests/BitcoinCore/Network/BitcoinRegTestNetTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/BitcoinTestNetTests.swift b/Example/Tests/BitcoinCore/Network/BitcoinTestNetTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/BitcoinTestNetTests.swift rename to Example/Tests/BitcoinCore/Network/BitcoinTestNetTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/ConnectionTimeoutManagerTests.swift b/Example/Tests/BitcoinCore/Network/Peer/ConnectionTimeoutManagerTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/ConnectionTimeoutManagerTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/ConnectionTimeoutManagerTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerAddressManagerTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerAddressManagerTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerAddressManagerTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerAddressManagerTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/BloomFilterManagerDelegateTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/BloomFilterManagerDelegateTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/BloomFilterManagerDelegateTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/BloomFilterManagerDelegateTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/IPeerGroupTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/IPeerGroupTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/IPeerGroupTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/IPeerGroupTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/PeerDelegateTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/PeerDelegateTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/PeerDelegateTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/PeerDelegateTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/PeerGroupTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/PeerGroupTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/PeerGroupTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/PeerGroupTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/PeerHostManagerDelegateTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/PeerHostManagerDelegateTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerGroupTests/PeerHostManagerDelegateTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerGroupTests/PeerHostManagerDelegateTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerManagerTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerManagerTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerManagerTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerManagerTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/GetBlockHashesTaskTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTask/GetBlockHashesTaskTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/GetBlockHashesTaskTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTask/GetBlockHashesTaskTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/GetMerkleBlocksTaskTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTask/GetMerkleBlocksTaskTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/GetMerkleBlocksTaskTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTask/GetMerkleBlocksTaskTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/RequestTransactionTaskTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTask/RequestTransactionTaskTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/RequestTransactionTaskTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTask/RequestTransactionTaskTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/SendTransactionTaskTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTask/SendTransactionTaskTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTask/SendTransactionTaskTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTask/SendTransactionTaskTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/IPeerTaskDelegateTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTests/IPeerTaskDelegateTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/IPeerTaskDelegateTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTests/IPeerTaskDelegateTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/IPeerTaskRequesterTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTests/IPeerTaskRequesterTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/IPeerTaskRequesterTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTests/IPeerTaskRequesterTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/IPeerTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTests/IPeerTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/IPeerTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTests/IPeerTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/PeerConnectionDelegateTests.swift b/Example/Tests/BitcoinCore/Network/Peer/PeerTests/PeerConnectionDelegateTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Network/New Group/PeerTests/PeerConnectionDelegateTests.swift rename to Example/Tests/BitcoinCore/Network/Peer/PeerTests/PeerConnectionDelegateTests.swift diff --git a/Example/Tests/BitcoinCore/Network/TransactionSenderTests.swift b/Example/Tests/BitcoinCore/Network/TransactionSenderTests.swift new file mode 100644 index 00000000..e1c6e2db --- /dev/null +++ b/Example/Tests/BitcoinCore/Network/TransactionSenderTests.swift @@ -0,0 +1,233 @@ +import XCTest +import Quick +import Nimble +import Cuckoo +@testable import BitcoinCore + +class TransactionSenderTests: QuickSpec { + override func spec() { + let mockTransactionSyncer = MockITransactionSyncer() + let mockInitialBlockDownload = MockIInitialBlockDownload() + let mockPeerManager = MockIPeerManager() + let mockStorage = MockIStorage() + let mockTimer = MockITransactionSendTimer() + + var sender: TransactionSender! + + let readyPeer = MockIPeer() + let readyPeer2 = MockIPeer() + let readyPeer3 = MockIPeer() + let syncedPeer = MockIPeer() + let syncedPeer2 = MockIPeer() + let syncedReadyPeer = MockIPeer() + let transaction = TestData.p2pkhTransaction + + beforeEach { + stub(mockTimer) { mock in + when(mock.startIfNotRunning()).thenDoNothing() + } + stub(mockStorage) { mock in + when(mock.sentTransaction(byHash: any())).thenReturn(nil) + when(mock.add(sentTransaction: any())).thenDoNothing() + } + + stub(readyPeer) { mock in + when(mock.ready.get).thenReturn(true) + when(mock.add(task: any())).thenDoNothing() + } + stub(readyPeer2) { mock in + when(mock.ready.get).thenReturn(true) + when(mock.add(task: any())).thenDoNothing() + } + stub(readyPeer3) { mock in + when(mock.ready.get).thenReturn(true) + when(mock.add(task: any())).thenDoNothing() + } + stub(syncedPeer) { mock in + when(mock.ready.get).thenReturn(false) + when(mock.add(task: any())).thenDoNothing() + } + stub(syncedPeer2) { mock in + when(mock.ready.get).thenReturn(false) + when(mock.add(task: any())).thenDoNothing() + } + stub(syncedReadyPeer) { mock in + when(mock.ready.get).thenReturn(true) + when(mock.add(task: any())).thenDoNothing() + } + + sender = TransactionSender(transactionSyncer: mockTransactionSyncer, initialBlockDownload: mockInitialBlockDownload, peerManager: mockPeerManager, storage: mockStorage, timer: mockTimer, queue: DispatchQueue.main) + } + + afterEach { + reset(mockTransactionSyncer, mockInitialBlockDownload, mockPeerManager, mockStorage, mockTimer, readyPeer, readyPeer2, syncedPeer, syncedPeer2, syncedReadyPeer) + + sender = nil + } + + describe("#send(transaction)") { + context("when has 1 synced and 2 ready peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(3) + when(mock.readyPeers.get).thenReturn([readyPeer, readyPeer2]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("sends to 1 ready peers") { + verify(readyPeer).add(task: any()) + verify(readyPeer2, never()).add(task: any()) + verify(syncedPeer, never()).add(task: any()) + } + } + + context("when has 0 synced and 2 ready peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(2) + when(mock.readyPeers.get).thenReturn([readyPeer, readyPeer2]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("doesn't send to any peer") { + verify(readyPeer, never()).add(task: any()) + verify(readyPeer2, never()).add(task: any()) + } + } + + context("when has 1 synced and 0 ready peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(1) + when(mock.readyPeers.get).thenReturn([]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("doesn't send to any peer") { + verify(syncedPeer, never()).add(task: any()) + } + } + + context("when has 1 syncedReady peer") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedReadyPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(1) + when(mock.readyPeers.get).thenReturn([syncedReadyPeer]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("doesn't send to any peer") { + verify(syncedReadyPeer, never()).add(task: any()) + } + } + + context("when has 1 synced and 1 ready peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(2) + when(mock.readyPeers.get).thenReturn([readyPeer]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("sends to the ready peer") { + verify(readyPeer).add(task: any()) + verify(syncedPeer, never()).add(task: any()) + } + } + + context("when has 1 syncedAndReady and 1 synced peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedPeer, syncedReadyPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(2) + when(mock.readyPeers.get).thenReturn([syncedReadyPeer]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("sends to the syncedAndReady peer") { + verify(syncedReadyPeer).add(task: any()) + verify(syncedPeer, never()).add(task: any()) + } + } + + context("when has 1 syncedAndReady and 1 ready peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedReadyPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(2) + when(mock.readyPeers.get).thenReturn([readyPeer]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("sends to the ready peer") { + verify(readyPeer).add(task: any()) + verify(syncedReadyPeer, never()).add(task: any()) + } + } + + context("when has 2 synced 1 syncedAndReady and 3 ready peers") { + beforeEach { + stub(mockInitialBlockDownload) { mock in + when(mock.syncedPeers.get).thenReturn([syncedPeer, syncedPeer2, syncedReadyPeer]) + } + stub(mockPeerManager) { mock in + when(mock.totalPeersCount.get).thenReturn(6) + when(mock.readyPeers.get).thenReturn([readyPeer, readyPeer2, readyPeer3, syncedReadyPeer]) + } + + sender.send(pendingTransaction: transaction) + self.waitForMainQueue() + } + + it("sends to the ready peer") { + verify(readyPeer).add(task: any()) + verify(readyPeer2).add(task: any()) + verify(readyPeer3, never()).add(task: any()) + verify(syncedReadyPeer, never()).add(task: any()) + verify(syncedPeer, never()).add(task: any()) + verify(syncedPeer2, never()).add(task: any()) + } + } + } + } +} diff --git a/BitcoinKit/BitcoinKitTests/SegWit/SegWitBech32AddressConverterTests.swift b/Example/Tests/BitcoinCore/SegWit/SegWitBech32AddressConverterTests.swift similarity index 96% rename from BitcoinKit/BitcoinKitTests/SegWit/SegWitBech32AddressConverterTests.swift rename to Example/Tests/BitcoinCore/SegWit/SegWitBech32AddressConverterTests.swift index 4f0c48f2..50dda9fe 100644 --- a/BitcoinKit/BitcoinKitTests/SegWit/SegWitBech32AddressConverterTests.swift +++ b/Example/Tests/BitcoinCore/SegWit/SegWitBech32AddressConverterTests.swift @@ -1,17 +1,15 @@ import XCTest import Cuckoo @testable import BitcoinCore -@testable import BitcoinKit - class SegWitBech32AddressConverterTests: XCTestCase { private var segWitBech32Converter: SegWitBech32AddressConverter! - private var mockScriptConverter: MockIBitcoinScriptConverter! + private var mockScriptConverter: MockIScriptConverter! private let prefix = "tb1" override func setUp() { super.setUp() - mockScriptConverter = MockIBitcoinScriptConverter() + mockScriptConverter = MockIScriptConverter() segWitBech32Converter = SegWitBech32AddressConverter(prefix: "bc", scriptConverter: mockScriptConverter) } diff --git a/BitcoinCore/BitcoinCoreTests/TestData.swift b/Example/Tests/BitcoinCore/TestData.swift similarity index 90% rename from BitcoinCore/BitcoinCoreTests/TestData.swift rename to Example/Tests/BitcoinCore/TestData.swift index 5beeac41..e5b9ee9d 100644 --- a/BitcoinCore/BitcoinCoreTests/TestData.swift +++ b/Example/Tests/BitcoinCore/TestData.swift @@ -1,12 +1,19 @@ import Foundation import GRDB -import HSCryptoKit @testable import BitcoinCore class TestData { + static var checkpoint: Checkpoint { + Checkpoint(block: checkpointBlock, additionalBlocks: []) + } + + static var lastCheckpoint: Checkpoint { + Checkpoint(block: firstBlock, additionalBlocks: []) + } + static var checkpointBlock: Block { - return Block( + Block( withHeader: BlockHeader( version: 1, headerHash: "cec100cc".reversedData!, @@ -20,7 +27,7 @@ class TestData { } static var firstBlock: Block { - return Block( + Block( withHeader: BlockHeader( version: 1, headerHash: "11b10ccc".reversedData!, @@ -34,7 +41,7 @@ class TestData { } static var secondBlock: Block { - return Block( + Block( withHeader: BlockHeader( version: 1, headerHash: "22b10ccc".reversedData!, @@ -48,7 +55,7 @@ class TestData { } static var thirdBlock: Block { - return Block( + Block( withHeader: BlockHeader( version: 1, headerHash: "33b10ccc".reversedData!, @@ -62,7 +69,7 @@ class TestData { } static var forthBlock: Block { - return Block( + Block( withHeader: BlockHeader( version: 1, headerHash: "44b10ccc".reversedData!, @@ -76,7 +83,7 @@ class TestData { } static var oldBlock: Block { - return Block( + Block( withHeader: BlockHeader( version: 1, headerHash: "01db10cc".reversedData!, @@ -149,7 +156,7 @@ class TestData { ] let outputs = [ Output(withValue: 10792000, index: 0 , lockingScript: Data(hex: "76a9141ec865abcb88cec71c484d4dadec3d7dc0271a7b88ac")!, type: .p2pkh, keyHash: Data()), - Output(withValue: 0, index: 0, lockingScript: Data(hex: "6a4c500000b919000189658af37cd16dbd16e4186ea13c5d8e1f40c5b5a0958326067dd923b8fc8f0767f62eb9a7fd57df4f3e775a96ca5b5eabf5057dff98997a3bbd011366703f5e45075f397f7f3c8465da")!, type: .p2pk, keyHash: Data()), + Output(withValue: 0, index: 0, lockingScript: Data(hex: "76a9141ec865abcb88cec71c484d4dadec3d7dc0271a7b88ac76a9141ec865abcb88cec71c484d4dadec3d7dc0271a7b88ac")!, type: .p2pk, keyHash: Data()), ] return FullTransaction(header: transaction, inputs: inputs, outputs: outputs) @@ -190,15 +197,15 @@ class TestData { } static func pubKey(pubKeyHash: Data = Data(hex: "1ec865abcb88cec71c484d4dadec3d7dc0271a7b")!) -> PublicKey { - return PublicKey(withAccount: 0, index: 0, external: true, hdPublicKeyData: pubKeyHash) + PublicKey(withAccount: 0, index: 0, external: true, hdPublicKeyData: pubKeyHash) } static func input(previousTransaction: Transaction, previousOutput: Output, script: Data, sequence: Int) -> Input { - return Input(withPreviousOutputTxHash: previousTransaction.dataHash, previousOutputIndex: previousOutput.index, script: script, sequence: sequence) + Input(withPreviousOutputTxHash: previousTransaction.dataHash, previousOutputIndex: previousOutput.index, script: script, sequence: sequence) } static func unspentOutput(output: Output) -> UnspentOutput { - return UnspentOutput(output: output, publicKey: pubKey(), transaction: Transaction(), blockHeight: nil) + UnspentOutput(output: output, publicKey: pubKey(), transaction: Transaction(), blockHeight: nil) } private class func setRandomHash(to transaction: Transaction) { @@ -212,3 +219,12 @@ class TestData { } } + +func randomBytes(length: Int) -> Data { + var bytes = Data(count: length) + let _ = bytes.withUnsafeMutableBytes { mutableBytes -> Int32 in + SecRandomCopyBytes(kSecRandomDefault, length, mutableBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)) + } + + return bytes +} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/Builder/InputSignerTests.swift b/Example/Tests/BitcoinCore/Transactions/Builder/InputSignerTests.swift similarity index 97% rename from BitcoinCore/BitcoinCoreTests/Transactions/Builder/InputSignerTests.swift rename to Example/Tests/BitcoinCore/Transactions/Builder/InputSignerTests.swift index 101e22c3..8f092d2c 100644 --- a/BitcoinCore/BitcoinCoreTests/Transactions/Builder/InputSignerTests.swift +++ b/Example/Tests/BitcoinCore/Transactions/Builder/InputSignerTests.swift @@ -53,7 +53,7 @@ class InputSignerTests: XCTestCase { XCTFail("Unexpected error") } - let signature = Data(hex: "3045022100a635a44b9565e9d8141c62217cebd2bf80cd6fc1a63bc3007942ead85c567d0b022027aa6e7cb692cce1604ce624299c8981e0ed22286e47ef64d58c16436bcb9add01")! + let signature = Data(hex: "3045022100d845739e4f2355acf785e4f379d736cd4aa303e2613f168a9248e3147a5cf376022045b876d3274a8fb43a80c7866d0790d10537798a49709e035e6fbea054ff277b01")! XCTAssertEqual(resultSignature.count, 2) XCTAssertEqual(resultSignature[0], signature) XCTAssertEqual(resultSignature[1], publicKey.raw) diff --git a/Example/Tests/BitcoinCore/Transactions/Builder/SegWitAddress.swift b/Example/Tests/BitcoinCore/Transactions/Builder/SegWitAddress.swift new file mode 100644 index 00000000..8c78919d --- /dev/null +++ b/Example/Tests/BitcoinCore/Transactions/Builder/SegWitAddress.swift @@ -0,0 +1,34 @@ +@testable import BitcoinCore + +public class SegWitAddress: Address, Equatable { + public let type: AddressType + public let keyHash: Data + public let stringValue: String + public let version: UInt8 + + public var scriptType: ScriptType { + switch type { + case .pubKeyHash: return .p2wpkh + case .scriptHash: return .p2wsh + } + } + + public var lockingScript: Data { + // Data[0] - version byte, Data[1] - push keyHash + OpCode.push(Int(version)) + OpCode.push(keyHash) + } + + public init(type: AddressType, keyHash: Data, bech32: String, version: UInt8) { + self.type = type + self.keyHash = keyHash + self.stringValue = bech32 + self.version = version + } + + static public func ==(lhs: SegWitAddress, rhs: T) -> Bool { + guard let rhs = rhs as? SegWitAddress else { + return false + } + return lhs.type == rhs.type && lhs.keyHash == rhs.keyHash && lhs.version == rhs.version + } +} diff --git a/Example/Tests/BitcoinCore/Transactions/OutputSetterTests.swift b/Example/Tests/BitcoinCore/Transactions/OutputSetterTests.swift new file mode 100644 index 00000000..01e2769a --- /dev/null +++ b/Example/Tests/BitcoinCore/Transactions/OutputSetterTests.swift @@ -0,0 +1,56 @@ +import XCTest +import Quick +import Nimble +import Cuckoo +@testable import BitcoinCore + +class OutputSetterTests: QuickSpec { + + override func spec() { + describe("check set outputs") { + let mockFactory = MockIFactory() + let mockOutputSorterFactory = MockITransactionDataSorterFactory() + let mockOutputSorter = MockITransactionDataSorter() + + let recipient = LegacyAddress(type: .pubKeyHash, keyHash: Data(repeating: 0, count: 20), base58: "") + let change = LegacyAddress(type: .pubKeyHash, keyHash: Data(repeating: 0, count: 20), base58: "") + + var outputs = [Output]() + outputs.append(Output(withValue: 100, index: 0, lockingScript: recipient.lockingScript, type: recipient.scriptType, address: recipient.stringValue, keyHash: recipient.keyHash)) + outputs.append(Output(withValue: 10, index: 0, lockingScript: change.lockingScript, type: change.scriptType, address: change.stringValue, keyHash: change.keyHash)) + + beforeEach { + stub(mockOutputSorterFactory) { mock in + when(mock.sorter(for: any())).thenReturn(mockOutputSorter) + } + stub(mockFactory) { mock in + when(mock.output(withIndex: 0, address: addressMatcher(recipient), value: 100, publicKey: isNil())).thenReturn(outputs[0]) + when(mock.output(withIndex: 0, address: addressMatcher(change), value: 10, publicKey: isNil())).thenReturn(outputs[1]) + } + stub(mockOutputSorter) { mock in + when(mock.sort(outputs: any())).thenReturn(outputs) + } + } + it("calls outputSorter and set outputs to mutable tx") { + let outputSetter = OutputSetter(outputSorterFactory: mockOutputSorterFactory, factory: mockFactory) + let transaction = MutableTransaction() + + transaction.recipientAddress = recipient + transaction.recipientValue = 100 + + transaction.changeAddress = change + transaction.changeValue = 10 + + outputSetter.setOutputs(to: transaction, sortType: .none) + verify(mockOutputSorterFactory).sorter(for: equal(to: TransactionDataSortType.none)) + verify(mockOutputSorter).sort(outputs: equal(to: outputs)) + + expect(0).to(equal(outputs.first!.index)) + expect(1).to(equal(outputs.last!.index)) + } + + } + + } + +} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ChunkTests.swift b/Example/Tests/BitcoinCore/Transactions/Scripts/ChunkTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ChunkTests.swift rename to Example/Tests/BitcoinCore/Transactions/Scripts/ChunkTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ScriptConverterTests.swift b/Example/Tests/BitcoinCore/Transactions/Scripts/ScriptConverterTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ScriptConverterTests.swift rename to Example/Tests/BitcoinCore/Transactions/Scripts/ScriptConverterTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ScriptTests.swift b/Example/Tests/BitcoinCore/Transactions/Scripts/ScriptTests.swift similarity index 87% rename from BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ScriptTests.swift rename to Example/Tests/BitcoinCore/Transactions/Scripts/ScriptTests.swift index 9c69b255..133a7237 100644 --- a/BitcoinCore/BitcoinCoreTests/Transactions/Scripts/ScriptTests.swift +++ b/Example/Tests/BitcoinCore/Transactions/Scripts/ScriptTests.swift @@ -24,7 +24,7 @@ class ScriptTests: XCTestCase { let script = Script(with: data, chunks: [Chunk(scriptData: data, index: 0, payloadRange: 1..<2), Chunk(scriptData: data, index: 2), Chunk(scriptData: data, index: 3)]) do { - try script.validate(opCodes: Data(bytes: [0x01, 0x45, 0x67])) + try script.validate(opCodes: Data([0x01, 0x45, 0x67])) } catch let error { XCTFail("\(error) Exception Thrown") } @@ -34,7 +34,7 @@ class ScriptTests: XCTestCase { let script = Script(with: data, chunks: [Chunk(scriptData: data, index: 2), Chunk(scriptData: data, index: 3)]) do { - try script.validate(opCodes: Data(bytes: [0x01, 0x45, 0x67])) + try script.validate(opCodes: Data([0x01, 0x45, 0x67])) } catch let error as ScriptError { XCTAssertEqual(error, ScriptError.wrongScriptLength) } catch { @@ -46,7 +46,7 @@ class ScriptTests: XCTestCase { let script = Script(with: data, chunks: [Chunk(scriptData: data, index: 0, payloadRange: 1..<2), Chunk(scriptData: data, index: 2), Chunk(scriptData: data, index: 3)]) do { - try script.validate(opCodes: Data(bytes: [0x07, 0x33, 0x67])) + try script.validate(opCodes: Data([0x07, 0x33, 0x67])) } catch let error as ScriptError { XCTAssertEqual(error, ScriptError.wrongSequence) } catch { diff --git a/Example/Tests/BitcoinCore/Transactions/TransactionCreatorTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionCreatorTests.swift new file mode 100644 index 00000000..83cf34f7 --- /dev/null +++ b/Example/Tests/BitcoinCore/Transactions/TransactionCreatorTests.swift @@ -0,0 +1,183 @@ +import XCTest +import Cuckoo +import Nimble +import Quick +@testable import BitcoinCore + +class TransactionCreatorTests: QuickSpec { + override func spec() { + let mockTransactionBuilder = MockITransactionBuilder() + let mockTransactionProcessor = MockIPendingTransactionProcessor() + let mockTransactionSender = MockITransactionSender() + let mockBloomFilterManager = MockIBloomFilterManager() + + let transaction = TestData.p2pkhTransaction + + let toAddress = "toAddressPKH" + let sendingValue = 100_000_000 + let feeRate = 1000 + let senderPay = true + let sortType: TransactionDataSortType = .none + + var creator: TransactionCreator! + + beforeEach { + stub(mockTransactionBuilder) { mock in + when(mock.buildTransaction(toAddress: any(), value: any(), feeRate: any(), senderPay: any(), sortType: any(), pluginData: any())).thenReturn(transaction) + } + stub(mockTransactionProcessor) { mock in + when(mock.processCreated(transaction: any())).thenDoNothing() + } + stub(mockTransactionSender) { mock in + when(mock.send(pendingTransaction: any())).thenDoNothing() + } + stub(mockBloomFilterManager) { mock in + when(mock.regenerateBloomFilter()).thenDoNothing() + } + + creator = TransactionCreator(transactionBuilder: mockTransactionBuilder, transactionProcessor: mockTransactionProcessor, transactionSender: mockTransactionSender, bloomFilterManager: mockBloomFilterManager) + } + + afterEach { + reset(mockTransactionBuilder, mockTransactionProcessor, mockTransactionSender, mockBloomFilterManager) + creator = nil + } + + describe("#create(to:value:feeRate:senderPay:)") { + context("when all valid") { + beforeEach { + stub(mockTransactionSender) { mock in + when(mock.verifyCanSend()).thenDoNothing() + } + _ = try! creator.create(to: toAddress, value: sendingValue, feeRate: feeRate, senderPay: senderPay, sortType: sortType) + } + + it("creates transaction") { + verify(mockTransactionBuilder).buildTransaction(toAddress: toAddress, value: sendingValue, feeRate: feeRate, senderPay: senderPay, sortType: any(), pluginData: any()) + verify(mockTransactionProcessor).processCreated(transaction: equal(to: transaction)) + } + + it("sends transaction") { + verify(mockTransactionSender).send(pendingTransaction: equal(to: transaction)) + } + } + + context("when BloomFilterManager.BloomFilterExpired error") { + beforeEach { + stub(mockTransactionProcessor) { mock in + when(mock.processCreated(transaction: any())).thenThrow(BloomFilterManager.BloomFilterExpired()) + } + stub(mockTransactionSender) { mock in + when(mock.verifyCanSend()).thenDoNothing() + } + + _ = try? creator.create(to: toAddress, value: sendingValue, feeRate: feeRate, senderPay: senderPay, sortType: sortType) + } + + it("does create transaction") { + verify(mockTransactionBuilder).buildTransaction(toAddress: toAddress, value: sendingValue, feeRate: feeRate, senderPay: senderPay, sortType: equal(to: sortType), pluginData: any()) + verify(mockTransactionProcessor).processCreated(transaction: any()) + } + + it("does send transaction") { + verify(mockTransactionSender).send(pendingTransaction: equal(to: transaction)) + } + + it("regenerates bloomfilter") { + verify(mockBloomFilterManager).regenerateBloomFilter() + } + } + + context("when other error") { + beforeEach { + stub(mockTransactionSender) { mock in + when(mock.verifyCanSend()).thenThrow(BitcoinCoreErrors.TransactionSendError.noConnectedPeers) + } + + _ = try? creator.create(to: toAddress, value: sendingValue, feeRate: feeRate, senderPay: senderPay, sortType: sortType) + } + + it("doesn't create transaction") { + verify(mockTransactionProcessor, never()).processCreated(transaction: any()) + } + + it("doesn't regenerate bloomfilter") { + verify(mockBloomFilterManager, never()).regenerateBloomFilter() + } + } + } + + describe("#create(from:to:feeRate:)") { + let unspentOutput = UnspentOutput(output: TestData.p2shTransaction.outputs[0], publicKey: TestData.pubKey(), transaction: Transaction(), blockHeight: nil) + + beforeEach { + stub(mockTransactionBuilder) { mock in + when(mock).buildTransaction(from: any(), toAddress: any(), feeRate: any(), sortType: any()).thenReturn(transaction) + } + } + + context("when success") { + beforeEach { + stub(mockTransactionSender) { mock in + when(mock.verifyCanSend()).thenDoNothing() + } + + _ = try! creator.create(from: unspentOutput, to: toAddress, feeRate: feeRate, sortType: sortType) + } + + it("creates transaction") { + verify(mockTransactionBuilder).buildTransaction(from: equal(to: unspentOutput), toAddress: toAddress, feeRate: feeRate, sortType: equal(to: sortType)) + verify(mockTransactionProcessor).processCreated(transaction: equal(to: transaction)) + } + + it("sends transaction") { + verify(mockTransactionSender).send(pendingTransaction: equal(to: transaction)) + } + } + + context("when BloomFilterManager.BloomFilterExpired error") { + beforeEach { + stub(mockTransactionProcessor) { mock in + when(mock.processCreated(transaction: any())).thenThrow(BloomFilterManager.BloomFilterExpired()) + } + stub(mockTransactionSender) { mock in + when(mock.verifyCanSend()).thenDoNothing() + } + + _ = try! creator.create(from: unspentOutput, to: toAddress, feeRate: feeRate, sortType: sortType) + } + + it("does create transaction") { + verify(mockTransactionBuilder).buildTransaction(from: equal(to: unspentOutput), toAddress: toAddress, feeRate: feeRate, sortType: equal(to: sortType)) + verify(mockTransactionProcessor).processCreated(transaction: any()) + } + + it("does send transaction") { + verify(mockTransactionSender).send(pendingTransaction: equal(to: transaction)) + } + + it("regenerates bloomfilter") { + verify(mockBloomFilterManager).regenerateBloomFilter() + } + } + + context("when other error") { + beforeEach { + stub(mockTransactionSender) { mock in + when(mock.verifyCanSend()).thenThrow(BitcoinCoreErrors.TransactionSendError.noConnectedPeers) + } + + _ = try? creator.create(from: unspentOutput, to: toAddress, feeRate: feeRate, sortType: sortType) + } + + it("doesn't create transaction") { + verify(mockTransactionProcessor, never()).processCreated(transaction: any()) + } + + it("doesn't regenerate bloomfilter") { + verify(mockBloomFilterManager, never()).regenerateBloomFilter() + } + } + } + } +} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionInputExtractorTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionInputExtractorTests.swift similarity index 96% rename from BitcoinCore/BitcoinCoreTests/Transactions/TransactionInputExtractorTests.swift rename to Example/Tests/BitcoinCore/Transactions/TransactionInputExtractorTests.swift index 7b9e63ad..d46c4421 100644 --- a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionInputExtractorTests.swift +++ b/Example/Tests/BitcoinCore/Transactions/TransactionInputExtractorTests.swift @@ -78,7 +78,7 @@ class TransactionInputExtractorTests: XCTestCase { let signatureScript = Data(hex: "1600148749115073ad59a6f3587f1f9e468adedf01473f")! let script = Script(with: address.keyHash, chunks: [Chunk(scriptData: redeemData, index: 0, payloadRange: 0..<4)]) - let redeemScript = Script(with: address.keyHash, chunks: [Chunk(scriptData: Data(bytes: [OpCode.checkSig]), index: 0), Chunk(scriptData: Data(bytes: [OpCode.endIf]), index: 0)]) + let redeemScript = Script(with: address.keyHash, chunks: [Chunk(scriptData: Data([OpCode.checkSig]), index: 0), Chunk(scriptData: Data([OpCode.endIf]), index: 0)]) stub(scriptConverter) { mock in when(mock.decode(data: equal(to: signatureScript))).thenReturn(script) when(mock.decode(data: equal(to: redeemData))).thenReturn(redeemScript) diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionOutputAddressExtractorTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionOutputAddressExtractorTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Transactions/TransactionOutputAddressExtractorTests.swift rename to Example/Tests/BitcoinCore/Transactions/TransactionOutputAddressExtractorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionOutputExtractorTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionOutputExtractorTests.swift similarity index 77% rename from BitcoinCore/BitcoinCoreTests/Transactions/TransactionOutputExtractorTests.swift rename to Example/Tests/BitcoinCore/Transactions/TransactionOutputExtractorTests.swift index 63f033bc..3a971877 100644 --- a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionOutputExtractorTests.swift +++ b/Example/Tests/BitcoinCore/Transactions/TransactionOutputExtractorTests.swift @@ -6,6 +6,7 @@ class TransactionOutputExtractorTests: XCTestCase { private var extractor: TransactionOutputExtractor! private var mockPublicKeySetter: MockITransactionPublicKeySetter! + private var mockPluginManager: MockIPluginManager! private var transaction: FullTransaction! @@ -13,16 +14,21 @@ class TransactionOutputExtractorTests: XCTestCase { super.setUp() mockPublicKeySetter = MockITransactionPublicKeySetter() + mockPluginManager = MockIPluginManager() stub(mockPublicKeySetter) { mock in when(mock.set(output: any())).thenReturn(false) } + stub(mockPluginManager) { mock in + when(mock.processTransactionWithNullData(transaction: any(), nullDataOutput: any())).thenDoNothing() + } - extractor = TransactionOutputExtractor(transactionKeySetter: mockPublicKeySetter) + extractor = TransactionOutputExtractor(transactionKeySetter: mockPublicKeySetter, pluginManager: mockPluginManager) transaction = TestData.p2pkhTransaction } override func tearDown() { mockPublicKeySetter = nil + mockPluginManager = nil extractor = nil transaction = nil @@ -80,4 +86,14 @@ class TransactionOutputExtractorTests: XCTestCase { XCTAssertEqual(transaction.outputs[0].scriptType, ScriptType.p2wpkh) } + func testExtractNullData() { + let keyHash = Data(hex: "6a51020100147288e43af7997b486f5d2e4ac50bab99b9187807")! + transaction.outputs[0].lockingScript = keyHash + + extractor.extract(transaction: transaction) + XCTAssertEqual(transaction.outputs[0].keyHash, keyHash) + XCTAssertEqual(transaction.outputs[0].scriptType, ScriptType.nullData) + verify(mockPluginManager).processTransactionWithNullData(transaction: equal(to: transaction), nullDataOutput: equal(to: transaction.outputs[0])) + } + } diff --git a/Example/Tests/BitcoinCore/Transactions/TransactionProcessorTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionProcessorTests.swift new file mode 100644 index 00000000..a902fb71 --- /dev/null +++ b/Example/Tests/BitcoinCore/Transactions/TransactionProcessorTests.swift @@ -0,0 +1,552 @@ +//import XCTest +//import Cuckoo +//@testable import BitcoinCore +// +//class TransactionProcessorTests: XCTestCase { +// private var mockStorage: MockIStorage! +// private var mockOutputExtractor: MockITransactionExtractor! +// private var mockOutputAddressExtractor: MockITransactionOutputAddressExtractor! +// private var mockInputExtractor: MockITransactionExtractor! +// private var mockOutputsCache: MockIOutputsCache! +// private var mockAddressManager: MockIPublicKeyManager! +// private var mockBlockchainDataListener: MockIBlockchainDataListener! +// private var mockTransactionListener: MockITransactionListener! +// private var mockIrregularOutputFinder: MockIIrregularOutputFinder! +// private var mockTransactionInfoConverter: MockITransactionInfoConverter! +// +// private var generatedDate: Date! +// private var dateGenerator: (() -> Date)! +// +// private var transactionProcessor: TransactionProcessor! +// +// override func setUp() { +// super.setUp() +// +// generatedDate = Date() +// dateGenerator = { +// return self.generatedDate +// } +// +// mockStorage = MockIStorage() +// mockOutputExtractor = MockITransactionExtractor() +// mockOutputAddressExtractor = MockITransactionOutputAddressExtractor() +// mockInputExtractor = MockITransactionExtractor() +// mockOutputsCache = MockIOutputsCache() +// mockAddressManager = MockIPublicKeyManager() +// mockBlockchainDataListener = MockIBlockchainDataListener() +// mockTransactionListener = MockITransactionListener() +// mockIrregularOutputFinder = MockIIrregularOutputFinder() +// mockTransactionInfoConverter = MockITransactionInfoConverter() +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: any())).thenReturn(nil) +// when(mock.add(transaction: any())).thenDoNothing() +// when(mock.update(transaction: any())).thenDoNothing() +// when(mock.update(block: any())).thenDoNothing() +// } +// stub(mockOutputsCache) { mock in +// when(mock.add(fromOutputs: any())).thenDoNothing() +// when(mock.hasOutputs(forInputs: any())).thenReturn(false) +// } +// stub(mockOutputExtractor) { mock in +// when(mock.extract(transaction: any())).thenDoNothing() +// } +// stub(mockOutputAddressExtractor) { mock in +// when(mock.extractOutputAddresses(transaction: any())).thenDoNothing() +// } +// stub(mockInputExtractor) { mock in +// when(mock.extract(transaction: any())).thenDoNothing() +// } +// stub(mockAddressManager) { mock in +// when(mock.gapShifts()).thenReturn(false) +// } +// stub(mockBlockchainDataListener) { mock in +// when(mock.onUpdate(updated: any(), inserted: any(), inBlock: any())).thenDoNothing() +// when(mock.onDelete(transactionHashes: any())).thenDoNothing() +// when(mock.onInsert(block: any())).thenDoNothing() +// } +// stub(mockTransactionListener) { mock in +// when(mock.onReceive(transaction: any())).thenDoNothing() +// } +// stub(mockIrregularOutputFinder) { mock in +// when(mock.hasIrregularOutput(outputs: any())).thenReturn(false) +// } +// +// transactionProcessor = TransactionProcessor( +// storage: mockStorage, outputExtractor: mockOutputExtractor, inputExtractor: mockInputExtractor, outputsCache: mockOutputsCache, +// outputAddressExtractor: mockOutputAddressExtractor, addressManager: mockAddressManager, irregularOutputFinder: mockIrregularOutputFinder, +// transactionInfoConverter: mockTransactionInfoConverter, listener: mockBlockchainDataListener, dateGenerator: dateGenerator +// ) +// transactionProcessor.transactionListener = mockTransactionListener +// } +// +// override func tearDown() { +// mockStorage = nil +// mockOutputExtractor = nil +// mockInputExtractor = nil +// mockOutputsCache = nil +// transactionProcessor = nil +// mockBlockchainDataListener = nil +// mockIrregularOutputFinder = nil +// +// generatedDate = nil +// dateGenerator = nil +// +// super.tearDown() +// } +// +// func testProcessCreated() { +// let transaction = TestData.p2pkhTransaction +// +// try! transactionProcessor.processCreated(transaction: transaction) +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [Transaction]()), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) +// verify(mockStorage).add(transaction: equal(to: transaction)) +// verifyNoMoreInteractions(mockOutputAddressExtractor) +// verifyNoMoreInteractions(mockInputExtractor) +// } +// +// func testProcessCreated_isMine() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = true +// +// try! transactionProcessor.processCreated(transaction: transaction) +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) +// verify(mockStorage).add(transaction: equal(to: transaction)) +// verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) +// verify(mockInputExtractor).extract(transaction: equal(to: transaction)) +// } +// +// func testProcessCreated_TransactionExists() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = true +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) +// } +// +// do { +// try transactionProcessor.processCreated(transaction: transaction) +// XCTFail("Expecting error") +// } catch let error as TransactionCreator.CreationError { +// XCTAssertEqual(error, TransactionCreator.CreationError.transactionAlreadyExists) +// } catch { +// XCTFail("Unexpected error") +// } +// +// verify(mockOutputExtractor, never()).extract(transaction: any()) +// verify(mockOutputsCache, never()).hasOutputs(forInputs: any()) +// verify(mockBlockchainDataListener, never()).onUpdate(updated: any(), inserted: any(), inBlock: any()) +// verify(mockStorage, never()).add(transaction: any()) +// verify(mockOutputAddressExtractor, never()).extractOutputAddresses(transaction: any()) +// verify(mockInputExtractor, never()).extract(transaction: any()) +// } +// +// func testProcessCreated_HasIrregularOutput() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = true +// +// stub(mockIrregularOutputFinder) { mock in +// when(mock.hasIrregularOutput(outputs: equal(to: transaction.outputs))).thenReturn(true) +// } +// +// do { +// try transactionProcessor.processCreated(transaction: transaction) +// XCTFail("Expecting error") +// } catch _ as BloomFilterManager.BloomFilterExpired { +// } catch { +// XCTFail("Unexpected error") +// } +// } +// +// func testProcessReceived_TransactionExists() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.status = .new +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) +// } +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// +// verify(mockOutputExtractor, never()).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache, never()).hasOutputs(forInputs: any()) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transaction.header]), inserted: equal(to: []), inBlock: equal(to: nil)) +// verify(mockStorage).update(transaction: equal(to: transaction.header)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, nil) +// XCTAssertEqual(transaction.header.order, 0) +// } +// +// func testProcessReceived_SeveralMempoolTransactions() { +// let transactions = self.transactions() +// for transaction in transactions { +// transaction.header.isMine = true +// transaction.header.timestamp = 0 +// transaction.header.order = 0 +// } +// transactions[1].header.status = .new +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transactions[1].header.dataHash))).thenReturn(transactions[1].header) +// when(mock.transaction(byHash: equal(to: transactions[3].header.dataHash))).thenReturn(transactions[3].header) +// } +// +// try! transactionProcessor.processReceived(transactions: [transactions[3], transactions[1], transactions[2], transactions[0]], inBlock: nil, skipCheckBloomFilter: false) +// +// verify(mockStorage).add(transaction: equal(to: transactions[0])) +// verify(mockStorage).update(transaction: equal(to: transactions[1].header)) +// verify(mockStorage).add(transaction: equal(to: transactions[2])) +// verify(mockStorage).update(transaction: equal(to: transactions[3].header)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transactions[1].header, transactions[3].header]), inserted: equal(to: [transactions[0].header, transactions[2].header]), inBlock: equal(to: nil)) +// +// for (i, transaction) in transactions.enumerated() { +// XCTAssertEqual(transaction.header.blockHash, nil) +// XCTAssertEqual(transaction.header.status, .relayed) +// XCTAssertEqual(transaction.header.order, i) +// XCTAssertEqual(transaction.header.timestamp, Int(generatedDate.timeIntervalSince1970)) +// } +// } +// +// func testProcessReceivedMempool_After_Block_TransactionExists() { +// let transaction = TestData.p2pkhTransaction +// let block = TestData.firstBlock +// transaction.header.status = .new +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) +// } +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: block, skipCheckBloomFilter: false) +// verify(mockStorage).update(transaction: equal(to: transaction.header)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transaction.header]), inserted: equal(to: []), inBlock: equal(to: block)) +// +// reset(mockStorage, mockBlockchainDataListener) +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) +// } +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// +// verify(mockStorage, never()).update(transaction: equal(to: transaction.header)) +// verify(mockBlockchainDataListener, never()).onUpdate(updated: equal(to: [transaction.header]), inserted: equal(to: []), inBlock: equal(to: nil)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, block.headerHash) +// XCTAssertEqual(transaction.header.timestamp, block.timestamp) +// XCTAssertEqual(transaction.header.order, 0) +// +// } +// +// func testProcessReceivedBlock_After_Block_TransactionExists() { +// let transaction = TestData.p2pkhTransaction +// let block = TestData.firstBlock +// let nextBlock = TestData.secondBlock +// transaction.header.status = .new +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) +// } +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: block, skipCheckBloomFilter: false) +// verify(mockStorage).update(transaction: equal(to: transaction.header)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transaction.header]), inserted: equal(to: []), inBlock: equal(to: block)) +// +// reset(mockStorage, mockBlockchainDataListener) +// stub(mockStorage) { mock in +// when(mock.update(transaction: any())).thenDoNothing() +// when(mock.update(block: any())).thenDoNothing() +// when(mock.transaction(byHash: equal(to: transaction.header.dataHash))).thenReturn(transaction.header) +// } +// stub(mockBlockchainDataListener) { mock in +// when(mock.onUpdate(updated: any(), inserted: any(), inBlock: any())).thenDoNothing() +// } +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nextBlock, skipCheckBloomFilter: false) +// +// verify(mockStorage).update(transaction: equal(to: transaction.header)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transaction.header]), inserted: equal(to: []), inBlock: equal(to: nextBlock)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, nextBlock.headerHash) +// XCTAssertEqual(transaction.header.timestamp, nextBlock.timestamp) +// XCTAssertEqual(transaction.header.order, 0) +// } +// +// +// func testProcessReceived_SeveralTransactionsInBlock() { +// let transactions = self.transactions() +// let block = TestData.firstBlock +// +// for transaction in transactions { +// transaction.header.isMine = true +// transaction.header.timestamp = 0 +// transaction.header.order = 0 +// } +// transactions[1].header.status = .new +// +// stub(mockStorage) { mock in +// when(mock.transaction(byHash: equal(to: transactions[1].header.dataHash))).thenReturn(transactions[1].header) +// when(mock.transaction(byHash: equal(to: transactions[3].header.dataHash))).thenReturn(transactions[3].header) +// } +// +// try! transactionProcessor.processReceived(transactions: [transactions[3], transactions[1], transactions[2], transactions[0]], inBlock: block, skipCheckBloomFilter: false) +// +// verify(mockStorage).add(transaction: equal(to: transactions[0])) +// verify(mockStorage).update(transaction: equal(to: transactions[1].header)) +// verify(mockStorage).add(transaction: equal(to: transactions[2])) +// verify(mockStorage).update(transaction: equal(to: transactions[3].header)) +// verify(mockStorage).update(block: equal(to: block)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: [transactions[1].header, transactions[3].header]), inserted: equal(to: [transactions[0].header, transactions[2].header]), inBlock: equal(to: block)) +// +// for (i, transaction) in transactions.enumerated() { +// XCTAssertEqual(transaction.header.blockHash, block.headerHash) +// XCTAssertEqual(transaction.header.status, .relayed) +// XCTAssertEqual(transaction.header.order, i) +// XCTAssertEqual(transaction.header.timestamp, block.header.timestamp) +// } +// } +// +// func testProcessReceived_TransactionNotExists_Mine() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = true +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) +// verify(mockStorage).add(transaction: equal(to: transaction)) +// verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) +// verify(mockInputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockTransactionListener).onReceive(transaction: equal(to: transaction)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, nil) +// } +// +// func testProcessReceived_TransactionNotExists_NotMine() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = false +// +// try! transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockStorage, never()).add(transaction: any()) +// verify(mockTransactionListener).onReceive(transaction: equal(to: transaction)) +// verifyNoMoreInteractions(mockBlockchainDataListener) +// verifyNoMoreInteractions(mockOutputAddressExtractor) +// verifyNoMoreInteractions(mockInputExtractor) +// } +// +// func testProcessReceived_TransactionNotExists_Mine_GapShifts() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = true +// +// stub(mockAddressManager) { mock in +// when(mock.gapShifts()).thenReturn(true) +// } +// +// do { +// try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// XCTFail("Should throw exception") +// } catch _ as BloomFilterManager.BloomFilterExpired { +// } catch { +// XCTFail("Unknown error thrown") +// } +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) +// verify(mockStorage).add(transaction: equal(to: transaction)) +// verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) +// verify(mockInputExtractor).extract(transaction: equal(to: transaction)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, nil) +// } +// +// func testProcessReceived_TransactionNotExists_Mine_HasIrregularOutputs() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = true +// +// stub(mockIrregularOutputFinder) { mock in +// when(mock.hasIrregularOutput(outputs: equal(to: transaction.outputs))).thenReturn(true) +// } +// +// do { +// try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// XCTFail("Should throw exception") +// } catch _ as BloomFilterManager.BloomFilterExpired { +// } catch { +// XCTFail("Unknown error thrown") +// } +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) +// verify(mockStorage).add(transaction: equal(to: transaction)) +// verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) +// verify(mockInputExtractor).extract(transaction: equal(to: transaction)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, nil) +// } +// +// func testProcessReceived_TransactionNotExists_Mine_GapShifts_CheckBloomFilterFalse() { +// let transaction = TestData.p2wpkhTransaction +// transaction.header.isMine = true +// transaction.outputs[0].publicKeyPath = TestData.pubKey().path +// +// do { +// try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: true) +// } catch { +// XCTFail("Unknown error thrown") +// } +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockBlockchainDataListener).onUpdate(updated: equal(to: []), inserted: equal(to: [transaction.header]), inBlock: equal(to: nil)) +// verify(mockStorage).add(transaction: equal(to: transaction)) +// verify(mockOutputAddressExtractor).extractOutputAddresses(transaction: equal(to: transaction)) +// verify(mockInputExtractor).extract(transaction: equal(to: transaction)) +// +// XCTAssertEqual(transaction.header.status, TransactionStatus.relayed) +// XCTAssertEqual(transaction.header.blockHash, nil) +// } +// +// func testProcessReceived_TransactionNotExists_NotMine_GapShifts() { +// let transaction = TestData.p2pkhTransaction +// transaction.header.isMine = false +// +// stub(mockAddressManager) { mock in +// when(mock.gapShifts()).thenReturn(true) +// } +// +// do { +// try transactionProcessor.processReceived(transactions: [transaction], inBlock: nil, skipCheckBloomFilter: false) +// } catch { +// XCTFail("Shouldn't throw exception") +// } +// +// verify(mockOutputExtractor).extract(transaction: equal(to: transaction)) +// verify(mockOutputsCache).hasOutputs(forInputs: equal(to: transaction.inputs)) +// verify(mockStorage, never()).add(transaction: any()) +// verifyNoMoreInteractions(mockBlockchainDataListener) +// verifyNoMoreInteractions(mockOutputAddressExtractor) +// verifyNoMoreInteractions(mockInputExtractor) +// } +// +// func testProcessReceived_TransactionNotInTopologicalOrder() { +// let transactions = self.transactions() +// var calledTransactions = [FullTransaction]() +// +// stub(mockOutputExtractor) { mock in +// when(mock.extract(transaction: any())).then { transaction in +// calledTransactions.append(transaction) +// } +// } +// +// for i in 0..<4 { +// for j in 0..<4 { +// for k in 0..<4 { +// for l in 0..<4 { +// if [0, 1, 2, 3].contains(where: { $0 != i && $0 != j && $0 != k && $0 != l }) { +// continue +// } +// +// calledTransactions = [] +// +// try! transactionProcessor.processReceived(transactions: [transactions[i], transactions[j], transactions[k], transactions[l]], inBlock: nil, skipCheckBloomFilter: false) +// +// verifyNoMoreInteractions(mockBlockchainDataListener) +// +// for (m, transaction) in calledTransactions.enumerated() { +// XCTAssertEqual(transaction.header.dataHash, transactions[m].header.dataHash) +// } +// } +// } +// } +// } +// } +// +// +// private func transactions() -> [FullTransaction] { +// let transaction = FullTransaction( +// header: Transaction(version: 0, lockTime: 0), +// inputs: [ +// Input( +// withPreviousOutputTxHash: Data(from: 1), +// previousOutputIndex: 0, +// script: Data(from: 999999999999), +// sequence: 0 +// ) +// ], +// outputs: [ +// Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()) +// ] +// ) +// +// let transaction2 = FullTransaction( +// header: Transaction(version: 0, lockTime: 0), +// inputs: [ +// Input( +// withPreviousOutputTxHash: transaction.header.dataHash, +// previousOutputIndex: 0, +// script: Data(from: 999999999999), +// sequence: 0 +// ) +// ], +// outputs: [ +// Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()), +// Output(withValue: 0, index: 1, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()) +// ] +// ) +// +// let transaction3 = FullTransaction( +// header: Transaction(version: 0, lockTime: 0), +// inputs: [ +// Input( +// withPreviousOutputTxHash: transaction2.header.dataHash, +// previousOutputIndex: 0, +// script: Data(from: 999999999999), +// sequence: 0 +// ) +// ], +// outputs: [ +// Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()), +// ] +// ) +// +// let transaction4 = FullTransaction( +// header: Transaction(version: 0, lockTime: 0), +// inputs: [ +// Input( +// withPreviousOutputTxHash: transaction2.header.dataHash, +// previousOutputIndex: 1, +// script: Data(from: 999999999999), +// sequence: 0 +// ), +// Input( +// withPreviousOutputTxHash: transaction3.header.dataHash, +// previousOutputIndex: 0, +// script: Data(from: 999999999999), +// sequence: 0 +// ) +// ], +// outputs: [ +// Output(withValue: 0, index: 0, lockingScript: Data(hex: "9999999999")!, type: .p2pk, keyHash: Data()) +// ] +// ) +// +// return [transaction, transaction2, transaction3, transaction4] +// } +// +//} diff --git a/BitcoinCore/BitcoinCoreTests/Transactions/TransactionPublicKeySetterTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionPublicKeySetterTests.swift similarity index 100% rename from BitcoinCore/BitcoinCoreTests/Transactions/TransactionPublicKeySetterTests.swift rename to Example/Tests/BitcoinCore/Transactions/TransactionPublicKeySetterTests.swift diff --git a/Example/Tests/BitcoinCore/Transactions/TransactionSizeCalculatorTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionSizeCalculatorTests.swift new file mode 100644 index 00000000..965aef62 --- /dev/null +++ b/Example/Tests/BitcoinCore/Transactions/TransactionSizeCalculatorTests.swift @@ -0,0 +1,63 @@ +import XCTest +import Cuckoo +@testable import BitcoinCore + +class TransactionSizeCalculatorTests: XCTestCase { + var calculator: TransactionSizeCalculator! + + override func setUp() { + super.setUp() + + calculator = TransactionSizeCalculator() + } + + override func tearDown() { + calculator = nil + + super.tearDown() + } + + func testTransactionSize() { + XCTAssertEqual(calculator.transactionSize(previousOutputs: [], outputScriptTypes: []), 10) // empty legacy tx + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2pkh]), outputScriptTypes: [.p2pkh]), 192) // 1-in 1-out standard tx + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2pkh, .p2pk]), outputScriptTypes: [.p2pkh]), 306) // 2-in 1-out legacy tx + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2pkh, .p2pk]), outputScriptTypes: [.p2wpkh]), 303) // 2-in 1-out legacy tx with witness output + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2pkh, .p2pk]), outputScriptTypes: [.p2pkh, .p2pk]), 350) // 2-in 2-out legacy tx + + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2wpkh]), outputScriptTypes: [.p2pkh]), 113) // 1-in 1-out witness tx + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2wpkhSh]), outputScriptTypes: [.p2pkh]), 136) // 1-in 1-out (sh) witness tx + XCTAssertEqual(calculator.transactionSize(previousOutputs: outputs(withScriptTypes: [.p2wpkh, .p2pkh, .p2pkh, .p2pkh]), outputScriptTypes: [.p2pkh]), 558) // 4-in 1-out witness tx + } + + func testTransactionSizeShInputsStandard() { + let redeemScript = Data(repeating: 0, count: 45) + let shOutput = Output(withValue: 0, index: 0, lockingScript: Data(), type: .p2sh, redeemScript: redeemScript) + + XCTAssertEqual(calculator.transactionSize(previousOutputs: [shOutput], outputScriptTypes: [.p2pkh]), 238) + } + + func testTransactionSizeShInputsNonStandard() { + let shOutput = Output(withValue: 0, index: 0, lockingScript: Data(), type: .p2sh, redeemScript: Data()) + shOutput.signatureScriptFunction = { _ in + Data(repeating: 0, count: 100) + } + + XCTAssertEqual(calculator.transactionSize(previousOutputs: [shOutput], outputScriptTypes: [.p2pkh]), 185) + } + + func testInputSize() { + XCTAssertEqual(calculator.inputSize(type: .p2pkh), 148) + XCTAssertEqual(calculator.inputSize(type: .p2pk), 114) + XCTAssertEqual(calculator.inputSize(type: .p2wpkh), 41) + XCTAssertEqual(calculator.inputSize(type: .p2wpkhSh), 64) + } + + func testOutputSize() { + XCTAssertEqual(calculator.outputSize(type: .p2pkh), 34) + XCTAssertEqual(calculator.outputSize(type: .p2sh), 32) + XCTAssertEqual(calculator.outputSize(type: .p2pk), 44) + XCTAssertEqual(calculator.outputSize(type: .p2wpkh), 31) + XCTAssertEqual(calculator.outputSize(type: .p2wpkhSh), 32) + } + +} diff --git a/Example/Tests/BitcoinCore/Transactions/TransactionSyncerTests.swift b/Example/Tests/BitcoinCore/Transactions/TransactionSyncerTests.swift new file mode 100644 index 00000000..b795d631 --- /dev/null +++ b/Example/Tests/BitcoinCore/Transactions/TransactionSyncerTests.swift @@ -0,0 +1,142 @@ +//import XCTest +//import Quick +//import Nimble +//import Cuckoo +//@testable import BitcoinCore +// +//class TransactionSyncerTests: QuickSpec { +// override func spec() { +// let mockStorage = MockIStorage() +// let mockTransactionProcessor = MockITransactionProcessor() +// let mockAddressManager = MockIPublicKeyManager() +// +// var syncer: TransactionSyncer! +// +// beforeEach { +// stub(mockTransactionProcessor) { mock in +// when(mock.processReceived(transactions: any(), inBlock: any(), skipCheckBloomFilter: any())).thenDoNothing() +// } +// stub(mockAddressManager) { mock in +// when(mock.fillGap()).thenDoNothing() +// } +// +// syncer = TransactionSyncer(storage: mockStorage, processor: mockTransactionProcessor, publicKeyManager: mockAddressManager) +// } +// +// afterEach { +// reset(mockStorage, mockTransactionProcessor, mockAddressManager) +// +// syncer = nil +// } +// +// describe("#pendingTransactions") { +// let fullTransaction = TestData.p2pkTransaction +// +// context("when transaction is .new") { +// beforeEach { +// stub(mockStorage) { mock in +// when(mock.newTransactions()).thenReturn([fullTransaction.header]) +// } +// } +// +// it("returns transaction") { +// stub(mockStorage) { mock in +// when(mock.sentTransaction(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(nil) +// when(mock.inputs(transactionHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.inputs) +// when(mock.outputs(transactionHash: equal(to: fullTransaction.header.dataHash))).thenReturn(fullTransaction.outputs) +// } +// let transactions = syncer.newTransactions() +// +// expect(transactions.count).to(equal(1)) +// expect(transactions.first!.header.dataHash).to(equal(fullTransaction.header.dataHash)) +// } +// } +// +// context("when transaction is not new") { +// it("doesn't return transaction") { +// stub(mockStorage) { mock in +// when(mock.newTransactions()).thenReturn([]) +// } +// expect(syncer.newTransactions()).to(beEmpty()) +// } +// } +// } +// +// describe("#handleRelayed(transactions:)") { +// context("when empty array is given") { +// it("doesn't do anything") { +// syncer.handleRelayed(transactions: []) +// +// verify(mockTransactionProcessor, never()).processReceived(transactions: any(), inBlock: any(), skipCheckBloomFilter: any()) +// verify(mockAddressManager, never()).fillGap() +// } +// } +// +// context("when not empty array is given") { +// let transactions = [TestData.p2pkhTransaction] +// +// context("when need to update bloom filter") { +// it("fills addresses gap") { +// stub(mockTransactionProcessor) { mock in +// when(mock.processReceived(transactions: equal(to: transactions), inBlock: any(), skipCheckBloomFilter: any())).thenThrow(BloomFilterManager.BloomFilterExpired()) +// } +// +// syncer.handleRelayed(transactions: transactions) +// verify(mockTransactionProcessor).processReceived(transactions: equal(to: transactions), inBlock: equal(to: nil), skipCheckBloomFilter: equal(to: false)) +// verify(mockAddressManager).fillGap() +// } +// } +// +// context("when don't need to update bloom filter") { +// it("doesn't run address fillGap") { +// stub(mockTransactionProcessor) { mock in +// when(mock.processReceived(transactions: equal(to: transactions), inBlock: any(), skipCheckBloomFilter: equal(to: false))).thenDoNothing() +// } +// +// syncer.handleRelayed(transactions: transactions) +// verify(mockTransactionProcessor).processReceived(transactions: equal(to: transactions), inBlock: equal(to: nil), skipCheckBloomFilter: equal(to: false)) +// verify(mockAddressManager, never()).fillGap() +// } +// } +// +// } +// } +// +// describe("#handleInvalid(transactionWithHash:)") { +// it("calls processes invalid transaction") { +// let fullTransaction = TestData.p2pkhTransaction +// +// stub(mockTransactionProcessor) { mock in +// when(mock.processInvalid(transactionHash: equal(to: fullTransaction.header.dataHash))).thenDoNothing() +// } +// +// syncer.handleInvalid(fullTransaction: fullTransaction) +// verify(mockTransactionProcessor).processInvalid(transactionHash: equal(to: fullTransaction.header.dataHash)) +// } +// } +// +// describe("#shouldRequestTransaction") { +// let fullTransaction = TestData.p2wpkhTransaction +// +// context("when relayed transaction exists") { +// it("returns false") { +// stub(mockStorage) { mock in +// when(mock.relayedTransactionExists(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(true) +// } +// +// XCTAssertEqual(syncer.shouldRequestTransaction(hash: fullTransaction.header.dataHash), false) +// } +// } +// +// context("when relayed transaction doesn't exist") { +// it("returns true") { +// stub(mockStorage) { mock in +// when(mock.relayedTransactionExists(byHash: equal(to: fullTransaction.header.dataHash))).thenReturn(false) +// } +// +// XCTAssertEqual(syncer.shouldRequestTransaction(hash: fullTransaction.header.dataHash), true) +// } +// } +// } +// } +//} diff --git a/BitcoinCashKit/BitcoinCashKitTests/Info.plist b/Example/Tests/BitcoinCoreTests.plist similarity index 89% rename from BitcoinCashKit/BitcoinCashKitTests/Info.plist rename to Example/Tests/BitcoinCoreTests.plist index 6c40a6cd..ba72822e 100644 --- a/BitcoinCashKit/BitcoinCashKitTests/Info.plist +++ b/Example/Tests/BitcoinCoreTests.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -16,6 +16,8 @@ BNDL CFBundleShortVersionString 1.0 + CFBundleSignature + ???? CFBundleVersion 1 diff --git a/BitcoinCore/BitcoinCore/Models/BlockHeader.swift b/Example/Tests/BitcoinKit/.gitkeep similarity index 100% rename from BitcoinCore/BitcoinCore/Models/BlockHeader.swift rename to Example/Tests/BitcoinKit/.gitkeep diff --git a/DashKit/DashKitTests/Info.plist b/Example/Tests/BitcoinKitTests.plist similarity index 89% rename from DashKit/DashKitTests/Info.plist rename to Example/Tests/BitcoinKitTests.plist index 6c40a6cd..ba72822e 100644 --- a/DashKit/DashKitTests/Info.plist +++ b/Example/Tests/BitcoinKitTests.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -16,6 +16,8 @@ BNDL CFBundleShortVersionString 1.0 + CFBundleSignature + ???? CFBundleVersion 1 diff --git a/DashKit/DashKitTests/Blocks/Validators/DarkGravityWaveTestNetValidatorTests.swift b/Example/Tests/DashKit/Blocks/Validators/DarkGravityWaveTestNetValidatorTests.swift similarity index 100% rename from DashKit/DashKitTests/Blocks/Validators/DarkGravityWaveTestNetValidatorTests.swift rename to Example/Tests/DashKit/Blocks/Validators/DarkGravityWaveTestNetValidatorTests.swift diff --git a/DashKit/DashKitTests/Blocks/Validators/DarkGravityWaveValidatorTests.swift b/Example/Tests/DashKit/Blocks/Validators/DarkGravityWaveValidatorTests.swift similarity index 100% rename from DashKit/DashKitTests/Blocks/Validators/DarkGravityWaveValidatorTests.swift rename to Example/Tests/DashKit/Blocks/Validators/DarkGravityWaveValidatorTests.swift diff --git a/DashKit/DashKitTests/DashExtensions.swift b/Example/Tests/DashKit/DashExtensions.swift similarity index 100% rename from DashKit/DashKitTests/DashExtensions.swift rename to Example/Tests/DashKit/DashExtensions.swift diff --git a/DashKit/DashKitTests/DashTestData.swift b/Example/Tests/DashKit/DashTestData.swift similarity index 100% rename from DashKit/DashKitTests/DashTestData.swift rename to Example/Tests/DashKit/DashTestData.swift diff --git a/DashKit/DashKitTests/InstantSend/InstantSendLock/InstantSendLockHandlerTests.swift b/Example/Tests/DashKit/InstantSend/InstantSendLock/InstantSendLockHandlerTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/InstantSendLock/InstantSendLockHandlerTests.swift rename to Example/Tests/DashKit/InstantSend/InstantSendLock/InstantSendLockHandlerTests.swift diff --git a/DashKit/DashKitTests/InstantSend/InstantSendLock/InstantSendLockValidatorTests.swift b/Example/Tests/DashKit/InstantSend/InstantSendLock/InstantSendLockValidatorTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/InstantSendLock/InstantSendLockValidatorTests.swift rename to Example/Tests/DashKit/InstantSend/InstantSendLock/InstantSendLockValidatorTests.swift diff --git a/DashKit/DashKitTests/InstantSend/InstantSendTests.swift b/Example/Tests/DashKit/InstantSend/InstantSendTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/InstantSendTests.swift rename to Example/Tests/DashKit/InstantSend/InstantSendTests.swift diff --git a/DashKit/DashKitTests/InstantSend/InstantTransactionManagerTests.swift b/Example/Tests/DashKit/InstantSend/InstantTransactionManagerTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/InstantTransactionManagerTests.swift rename to Example/Tests/DashKit/InstantSend/InstantTransactionManagerTests.swift diff --git a/DashKit/DashKitTests/InstantSend/TransactionLockVote/TransactionLockVoteHandlerTests.swift b/Example/Tests/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteHandlerTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/TransactionLockVote/TransactionLockVoteHandlerTests.swift rename to Example/Tests/DashKit/InstantSend/TransactionLockVote/TransactionLockVoteHandlerTests.swift diff --git a/DashKit/DashKitTests/InstantSend/TransactionLockVoteManagerTests.swift b/Example/Tests/DashKit/InstantSend/TransactionLockVoteManagerTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/TransactionLockVoteManagerTests.swift rename to Example/Tests/DashKit/InstantSend/TransactionLockVoteManagerTests.swift diff --git a/DashKit/DashKitTests/InstantSend/TransactionLockVoteValidatorTests.swift b/Example/Tests/DashKit/InstantSend/TransactionLockVoteValidatorTests.swift similarity index 100% rename from DashKit/DashKitTests/InstantSend/TransactionLockVoteValidatorTests.swift rename to Example/Tests/DashKit/InstantSend/TransactionLockVoteValidatorTests.swift diff --git a/DashKit/DashKitTests/MasternodeList/MasternodeCbTxHashCalculatorTests.swift b/Example/Tests/DashKit/MasternodeList/MasternodeCbTxHashCalculatorTests.swift similarity index 100% rename from DashKit/DashKitTests/MasternodeList/MasternodeCbTxHashCalculatorTests.swift rename to Example/Tests/DashKit/MasternodeList/MasternodeCbTxHashCalculatorTests.swift diff --git a/DashKit/DashKitTests/MasternodeList/MasternodeListManagerTests.swift b/Example/Tests/DashKit/MasternodeList/MasternodeListManagerTests.swift similarity index 100% rename from DashKit/DashKitTests/MasternodeList/MasternodeListManagerTests.swift rename to Example/Tests/DashKit/MasternodeList/MasternodeListManagerTests.swift diff --git a/DashKit/DashKitTests/MasternodeList/MasternodeListMerkleRootCalculatorTests.swift b/Example/Tests/DashKit/MasternodeList/MasternodeListMerkleRootCalculatorTests.swift similarity index 100% rename from DashKit/DashKitTests/MasternodeList/MasternodeListMerkleRootCalculatorTests.swift rename to Example/Tests/DashKit/MasternodeList/MasternodeListMerkleRootCalculatorTests.swift diff --git a/DashKit/DashKitTests/MasternodeList/MasternodeListSyncerTests.swift b/Example/Tests/DashKit/MasternodeList/MasternodeListSyncerTests.swift similarity index 100% rename from DashKit/DashKitTests/MasternodeList/MasternodeListSyncerTests.swift rename to Example/Tests/DashKit/MasternodeList/MasternodeListSyncerTests.swift diff --git a/DashKit/DashKitTests/MasternodeList/MasternodeSortedListTests.swift b/Example/Tests/DashKit/MasternodeList/MasternodeSortedListTests.swift similarity index 100% rename from DashKit/DashKitTests/MasternodeList/MasternodeSortedListTests.swift rename to Example/Tests/DashKit/MasternodeList/MasternodeSortedListTests.swift diff --git a/DashKit/DashKitTests/MasternodeList/MerkleRootCreatorTests.swift b/Example/Tests/DashKit/MasternodeList/MerkleRootCreatorTests.swift similarity index 100% rename from DashKit/DashKitTests/MasternodeList/MerkleRootCreatorTests.swift rename to Example/Tests/DashKit/MasternodeList/MerkleRootCreatorTests.swift diff --git a/BitcoinCore/BitcoinCoreTests/Info.plist b/Example/Tests/DashKitTests.plist similarity index 89% rename from BitcoinCore/BitcoinCoreTests/Info.plist rename to Example/Tests/DashKitTests.plist index 6c40a6cd..ba72822e 100644 --- a/BitcoinCore/BitcoinCoreTests/Info.plist +++ b/Example/Tests/DashKitTests.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -16,6 +16,8 @@ BNDL CFBundleShortVersionString 1.0 + CFBundleSignature + ???? CFBundleVersion 1 diff --git a/Example/Tests/Hodler/HodlerPluginTests.swift b/Example/Tests/Hodler/HodlerPluginTests.swift new file mode 100644 index 00000000..55cdc524 --- /dev/null +++ b/Example/Tests/Hodler/HodlerPluginTests.swift @@ -0,0 +1,248 @@ +import XCTest +import Cuckoo +import Nimble +import Quick +import OpenSslKit +@testable import BitcoinCore +@testable import Hodler + +class HodlerPluginTests: QuickSpec { + override func spec() { + let mockAddressConverter = MockIHodlerAddressConverter() + let mockStorage = MockIHodlerPublicKeyStorage() + let mockBlockMedianTimeHelper = MockIHodlerBlockMedianTimeHelper() + + let p2pkhAddress = LegacyAddress(type: .pubKeyHash, keyHash: Data(repeating: 0, count: 20), base58: "") + let p2shAddress = LegacyAddress(type: .scriptHash, keyHash: self.scriptHash(from: p2pkhAddress.keyHash), base58: "") + let publicKey = PublicKey(withAccount: 0, index: 0, external: true, hdPublicKeyData: Data()) + let currentTimestamp = Int(Date().timeIntervalSince1970) + + var hodler: HodlerPlugin! + var transaction: FullTransaction! + + beforeEach { + transaction = FullTransaction( + header: Transaction(lockTime: 0, timestamp: currentTimestamp), + inputs: [], outputs: [Output(withValue: 0, index: 0, lockingScript: Data(), keyHash: p2shAddress.keyHash)] + ) + + hodler = HodlerPlugin(addressConverter: mockAddressConverter, blockMedianTimeHelper: mockBlockMedianTimeHelper, publicKeyStorage: mockStorage) + } + + afterEach { + reset(mockAddressConverter, mockStorage, mockBlockMedianTimeHelper) + } + + describe("#processOutputs") { + var mutableTransaction: MutableTransaction! + + beforeEach { + mutableTransaction = MutableTransaction() + mutableTransaction.recipientAddress = p2pkhAddress + + stub(mockAddressConverter) { mock in + when(mock.convert(keyHash: any(), type: equal(to: ScriptType.p2sh))).thenReturn(p2shAddress) + } + } + + context("when pluginData is valid") { + beforeEach { + try! hodler.processOutputs(mutableTransaction: mutableTransaction, pluginData: HodlerData(lockTimeInterval: HodlerPlugin.LockTimeInterval.hour)) + } + + it("generates correct scriptHash") { + verify(mockAddressConverter).convert(keyHash: equal(to: p2shAddress.keyHash), type: equal(to: ScriptType.p2sh)) + } + + it("sets new P2SH address") { + expect(mutableTransaction.recipientAddress.stringValue).to(equal(p2shAddress.stringValue)) + } + + fit("sets pluginData") { + guard let pluginData = mutableTransaction.pluginData[HodlerPlugin.id] else { + fail("Must have pluginData output") + return + } + + let expectedLockingScript = Data(hex: "020700140000000000000000000000000000000000000000")! + expect(pluginData).to(equal(expectedLockingScript)) + } + } + + context("invalid hodler data") { + it("throws invalidData") { + do { + try hodler.processOutputs(mutableTransaction: mutableTransaction, pluginData: OtherPluginData()) + fail("Exception expected") + } catch let error as HodlerPluginError { + expect(error).to(equal(HodlerPluginError.invalidData)) + } catch { + fail("Unexpected exception") + } + } + } + + context("recipientAddress not p2pkh") { + context("skipChecks is false") { + it("throws unsupportedAddress") { + mutableTransaction.recipientAddress = p2shAddress + do { + try hodler.processOutputs(mutableTransaction: mutableTransaction, pluginData: HodlerData(lockTimeInterval: HodlerPlugin.LockTimeInterval.hour)) + fail("Exception expected") + } catch let error as HodlerPluginError { + expect(error).to(equal(HodlerPluginError.unsupportedAddress)) + } catch { + fail("Unexpected exception") + } + } + } + + context("skipChecks is true") { + it("doesn't throw unsupportedAddress") { + mutableTransaction.recipientAddress = p2shAddress + do { + try hodler.processOutputs(mutableTransaction: mutableTransaction, pluginData: HodlerData(lockTimeInterval: HodlerPlugin.LockTimeInterval.hour), skipChecks: true) + } catch { + fail("Unexpected exception") + } + } + } + } + } + + describe("#processTransactionWithNullData") { + let chunks = [ + Chunk(scriptData: Data([0x02, 0x07, 0x00]), index: 0, payloadRange: 1..<3), + Chunk(scriptData: p2pkhAddress.keyHash, index: 1, payloadRange: 0..<20) + ] + + context("when valid nullData output") { + beforeEach { + stub(mockAddressConverter) { mock in + when(mock.convert(keyHash: equal(to: p2pkhAddress.keyHash), type: equal(to: ScriptType.p2pkh))).thenReturn(p2pkhAddress) + } + } + + context("when publicKey is found") { + beforeEach { + stub(mockStorage) { mock in + when(mock.publicKey(byRawOrKeyHash: equal(to: p2pkhAddress.keyHash))).thenReturn(publicKey) + } + + var chunksIterator = chunks.makeIterator() + try! hodler.processTransactionWithNullData(transaction: transaction, nullDataChunks: &chunksIterator) + } + + it("sets pluginId and pluginData to output") { + expect(transaction.outputs[0].pluginId).to(equal(HodlerPlugin.id)) + expect(transaction.outputs[0].pluginData).to(equal("7|\(p2pkhAddress.stringValue)")) + } + + it("sets publicKey and redeemScript and transaction.isMine flag") { + expect(transaction.outputs[0].redeemScript).to(equal(self.redeemScript(from: p2pkhAddress.keyHash))) + expect(transaction.outputs[0].publicKeyPath).to(equal(publicKey.path)) + expect(transaction.header.isMine).to(beTrue()) + } + } + + context("when publicKey is not found") { + beforeEach { + stub(mockStorage) { mock in + when(mock.publicKey(byRawOrKeyHash: equal(to: p2pkhAddress.keyHash))).thenReturn(nil) + } + + var chunksIterator = chunks.makeIterator() + try! hodler.processTransactionWithNullData(transaction: transaction, nullDataChunks: &chunksIterator) + } + + it("sets pluginId and pluginData to output") { + expect(transaction.outputs[0].pluginId).to(equal(HodlerPlugin.id)) + expect(transaction.outputs[0].pluginData).to(equal("7|\(p2pkhAddress.stringValue)")) + } + + it("doesn't set publicKey and redeemScript and transaction.isMine flag") { + expect(transaction.outputs[0].redeemScript).to(beNil()) + expect(transaction.outputs[0].publicKeyPath).to(beNil()) + expect(transaction.header.isMine).to(beFalse()) + } + } + } + + context("when invalid nullData output") { + it("throws invalidData") { + var chunksIterator = [Chunk]().makeIterator() + + do { + try hodler.processTransactionWithNullData(transaction: transaction, nullDataChunks: &chunksIterator) + fail("Exception expected") + } catch let error as HodlerPluginError { + expect(error).to(equal(HodlerPluginError.invalidData)) + } catch { + fail("Unexpected error") + } + } + } + } + + describe("#isSpendable") { + let output = Output(withValue: 0, index: 0, lockingScript: Data()) + output.pluginData = "\(HodlerPlugin.LockTimeInterval.hour.rawValue)|someAddressString" + var unspentOutput: UnspentOutput! + + beforeEach { + unspentOutput = UnspentOutput(output: output, publicKey: publicKey, transaction: transaction.header) + } + + context("when lastBlockMedianTime is can't be retrieved") { + it("return false") { + stub(mockBlockMedianTimeHelper) { mock in + when(mock.medianTimePast.get).thenReturn(nil) + } + + expect(try! hodler.isSpendable(unspentOutput: unspentOutput)).to(beFalse()) + } + } + + context("when lastBlockMedianTime is more than unlock time") { + it("return false") { + stub(mockBlockMedianTimeHelper) { mock in + when(mock.medianTimePast.get).thenReturn(currentTimestamp + HodlerPlugin.LockTimeInterval.hour.valueInSeconds - 1) + } + + expect(try! hodler.isSpendable(unspentOutput: unspentOutput)).to(beFalse()) + } + } + + context("when lastBlockMedianTime is less than unlock time") { + it("return false") { + stub(mockBlockMedianTimeHelper) { mock in + when(mock.medianTimePast.get).thenReturn(currentTimestamp + HodlerPlugin.LockTimeInterval.hour.valueInSeconds + 1) + } + + expect(try! hodler.isSpendable(unspentOutput: unspentOutput)).to(beTrue()) + } + } + } + + describe("#inputSequenceNumber") { + let output = Output(withValue: 0, index: 0, lockingScript: Data()) + output.pluginData = "\(HodlerPlugin.LockTimeInterval.hour.rawValue)|someAddressString" + + it("returns UInt32 sequenceNumber") { + expect(try! hodler.inputSequenceNumber(output: output)).to(equal(Int(Int(HodlerPlugin.LockTimeInterval.hour.rawValue) + 0x400000))) + } + } + } + + private func scriptHash(from publicKeyHash: Data) -> Data { + Kit.sha256ripemd160(redeemScript(from: publicKeyHash)) + } + + private func redeemScript(from publicKeyHash: Data) -> Data { + OpCode.push(Data([0x07, 0x00, 0x40])) + Data([OpCode.checkSequenceVerify, OpCode.drop]) + OpCode.p2pkhStart + OpCode.push(publicKeyHash) + OpCode.p2pkhFinish + } + +} + +class OtherPluginData: IPluginData { +} diff --git a/Example/Tests/HodlerTests.plist b/Example/Tests/HodlerTests.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/Example/Tests/HodlerTests.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 00a6b3f8..00000000 --- a/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source 'https://rubygems.org' - -gem 'cocoapods' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index b72406dd..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,76 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.0) - activesupport (4.2.11.1) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - atomos (0.1.3) - claide (1.0.2) - cocoapods (1.7.0) - activesupport (>= 4.0.2, < 5) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.7.0) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.2.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.8.2, < 2.0) - cocoapods-core (1.7.0) - activesupport (>= 4.0.2, < 6) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) - cocoapods-trunk (1.3.1) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.1.0) - colored2 (3.1.2) - concurrent-ruby (1.1.5) - escape (0.0.4) - fourflusher (2.2.0) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - minitest (5.11.3) - molinillo (0.6.6) - nanaimo (0.2.6) - nap (1.1.0) - netrc (0.11.0) - ruby-macho (1.4.0) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - xcodeproj (1.9.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.2.6) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods - -BUNDLED WITH - 2.0.1 diff --git a/Hodler.swift.podspec b/Hodler.swift.podspec new file mode 100644 index 00000000..0e5d3f48 --- /dev/null +++ b/Hodler.swift.podspec @@ -0,0 +1,27 @@ +Pod::Spec.new do |s| + s.name = 'Hodler.swift' + s.module_name = 'Hodler' + s.version = '0.18' + s.summary = 'Hodler library for Swift.' + + s.description = <<-DESC +Hodler plugin enables to send/receive/spend time-locked transactions. + DESC + + s.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } + s.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "hodler-#{s.version}" } + s.social_media_url = 'http://horizontalsystems.io/' + + s.ios.deployment_target = '13.0' + s.swift_version = '5' + + s.source_files = 'Hodler/Classes/**/*' + + s.requires_arc = true + + s.dependency 'BitcoinCore.swift', '~> 0.18' + s.dependency 'OpenSslKit.swift', '~> 1.0' + s.dependency 'Secp256k1Kit.swift', '~> 1.0' +end diff --git a/Hodler/Classes/Core/BitcoinCoreCompatibility.swift b/Hodler/Classes/Core/BitcoinCoreCompatibility.swift new file mode 100644 index 00000000..091e737c --- /dev/null +++ b/Hodler/Classes/Core/BitcoinCoreCompatibility.swift @@ -0,0 +1,5 @@ +import BitcoinCore + +extension AddressConverterChain: IHodlerAddressConverter {} +extension GrdbStorage: IHodlerPublicKeyStorage {} +extension BlockMedianTimeHelper: IHodlerBlockMedianTimeHelper {} \ No newline at end of file diff --git a/Hodler/Classes/Core/HodlerOutputData.swift b/Hodler/Classes/Core/HodlerOutputData.swift new file mode 100644 index 00000000..5cd9ae8c --- /dev/null +++ b/Hodler/Classes/Core/HodlerOutputData.swift @@ -0,0 +1,47 @@ +import BitcoinCore + +public class HodlerOutputData: IPluginOutputData { + public let lockTimeInterval: HodlerPlugin.LockTimeInterval + public let addressString: String + public var approximateUnlockTime: Int? = nil + + static func parse(serialized: String?) throws -> HodlerOutputData { + guard let serialized = serialized else { + throw HodlerPluginError.invalidData + } + + let parts = serialized.split(separator: "|") + + guard parts.count == 2 else { + throw HodlerPluginError.invalidData + } + + let lockTimeIntervalStr = String(parts[0]) + let addressString = String(parts[1]) + + guard let int16 = UInt16(lockTimeIntervalStr), let lockTimeInterval = HodlerPlugin.LockTimeInterval(rawValue: int16) else { + throw HodlerPluginError.invalidData + } + + + return HodlerOutputData(lockTimeInterval: lockTimeInterval, addressString: addressString) + } + + init(lockTimeInterval: HodlerPlugin.LockTimeInterval, addressString: String) { + self.lockTimeInterval = lockTimeInterval + self.addressString = addressString + } + + func toString() -> String { + "\(lockTimeInterval.rawValue)|\(addressString)" + } + +} + +public class HodlerData: IPluginData { + let lockTimeInterval: HodlerPlugin.LockTimeInterval + + public init(lockTimeInterval: HodlerPlugin.LockTimeInterval) { + self.lockTimeInterval = lockTimeInterval + } +} diff --git a/Hodler/Classes/Core/HodlerPlugin.swift b/Hodler/Classes/Core/HodlerPlugin.swift new file mode 100644 index 00000000..10a7f104 --- /dev/null +++ b/Hodler/Classes/Core/HodlerPlugin.swift @@ -0,0 +1,176 @@ +import Foundation +import BitcoinCore +import OpenSslKit + +public enum HodlerPluginError: Error { + case unsupportedAddress + case addressNotGiven + case invalidData + case lockedValueLimitExceeded +} + +public class HodlerPlugin { + public static let lockedValueLimit = 50_000_000 // 0.5 BTC + + public enum LockTimeInterval: UInt16, CaseIterable, Codable { + case hour = 7 // 60 * 60 / 512 + case month = 5063 // 30 * 24 * 60 * 60 / 512 + case halfYear = 30881 // 183 * 24 * 60 * 60 / 512 + case year = 61593 // 365 * 24 * 60 * 60 / 512 + + private static let sequenceTimeSecondsGranularity = 512 + private static let relativeLockTimeLockMask: UInt32 = 0x400000 // (1 << 22) + + var sequenceNumber: UInt32 { + LockTimeInterval.relativeLockTimeLockMask | UInt32(self.rawValue) + } + + public var valueInSeconds: Int { + Int(self.rawValue) * LockTimeInterval.sequenceTimeSecondsGranularity + } + + var valueInTwoBytes: Data { + Data(from: self.rawValue) + } + + var valueInThreeBytes: Data { + Data(from: sequenceNumber).subdata(in: 0..<3) + } + } + + public static let id: UInt8 = OpCode.push(1)[0] + public var id: UInt8 { HodlerPlugin.id } + public var maxSpendLimit: Int? { HodlerPlugin.lockedValueLimit } + + private let addressConverter: IHodlerAddressConverter + private let blockMedianTimeHelper: IHodlerBlockMedianTimeHelper + private let publicKeyStorage: IHodlerPublicKeyStorage + + public init(addressConverter: IHodlerAddressConverter, blockMedianTimeHelper: IHodlerBlockMedianTimeHelper, publicKeyStorage: IHodlerPublicKeyStorage) { + self.addressConverter = addressConverter + self.blockMedianTimeHelper = blockMedianTimeHelper + self.publicKeyStorage = publicKeyStorage + } + + private func lockTimeIntervalFrom(data lockTimeIntervalData: Data) -> LockTimeInterval? { + guard lockTimeIntervalData.count == 2 else { + return nil + } + + let int16 = lockTimeIntervalData.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt16.self).pointee } + return LockTimeInterval(rawValue: int16) + } + + private func lockTimeIntervalFrom(output: Output) throws -> LockTimeInterval { + try HodlerOutputData.parse(serialized: output.pluginData).lockTimeInterval + } + + private func inputLockTime(unspentOutput: UnspentOutput) throws -> Int { + // Use (an approximate medianTimePast of a block in which given transaction is included) PLUS ~1 hour. + // This is not an accurate medianTimePast, it is always a timestamp nearly 7 blocks ahead. + // But this is quite enough in our case since we're setting relative time-locks for at least 1 month + let previousOutputMedianTime = unspentOutput.transaction.timestamp + + return previousOutputMedianTime + (try lockTimeIntervalFrom(output: unspentOutput.output)).valueInSeconds + } + + private func csvRedeemScript(lockTimeInterval: LockTimeInterval, publicKeyHash: Data) -> Data { + OpCode.push(lockTimeInterval.valueInThreeBytes) + Data([OpCode.checkSequenceVerify, OpCode.drop]) + OpCode.p2pkhStart + OpCode.push(publicKeyHash) + OpCode.p2pkhFinish + } + +} + + +extension HodlerPlugin: IPlugin { + + public func validate(address: Address) throws { + if address.scriptType != .p2pkh { + throw HodlerPluginError.unsupportedAddress + } + } + + public func processOutputs(mutableTransaction: MutableTransaction, pluginData: IPluginData, skipChecks: Bool = false) throws { + guard let hodlerData = pluginData as? HodlerData else { + throw HodlerPluginError.invalidData + } + + guard let recipientAddress = mutableTransaction.recipientAddress else { + throw HodlerPluginError.addressNotGiven + } + + if !skipChecks { + guard recipientAddress.scriptType == .p2pkh else { + throw HodlerPluginError.unsupportedAddress + } + + guard mutableTransaction.recipientValue <= HodlerPlugin.lockedValueLimit else { + throw HodlerPluginError.lockedValueLimitExceeded + } + } + + let redeemScript = csvRedeemScript(lockTimeInterval: hodlerData.lockTimeInterval, publicKeyHash: recipientAddress.keyHash) + let scriptHash = Kit.sha256ripemd160(redeemScript) + let newAddress = try addressConverter.convert(keyHash: scriptHash, type: .p2sh) + + mutableTransaction.recipientAddress = newAddress + mutableTransaction.add(pluginData: OpCode.push(hodlerData.lockTimeInterval.valueInTwoBytes) + OpCode.push(recipientAddress.keyHash), pluginId: id) + } + + public func processTransactionWithNullData(transaction: FullTransaction, nullDataChunks: inout IndexingIterator<[Chunk]>) throws { + guard let lockTimeIntervalData = nullDataChunks.next()?.data, let publicKeyHash = nullDataChunks.next()?.data, + let lockTimeInterval = lockTimeIntervalFrom(data: lockTimeIntervalData) else { + throw HodlerPluginError.invalidData + } + + let redeemScript = csvRedeemScript(lockTimeInterval: lockTimeInterval, publicKeyHash: publicKeyHash) + let redeemScriptHash = Kit.sha256ripemd160(redeemScript) + + guard let output = transaction.outputs.first(where: { $0.keyHash == redeemScriptHash }) else { + return + } + + output.pluginId = id + output.pluginData = HodlerOutputData( + lockTimeInterval: lockTimeInterval, + addressString: (try addressConverter.convert(keyHash: publicKeyHash, type: .p2pkh).stringValue) + ).toString() + + if let publicKey = publicKeyStorage.publicKey(byRawOrKeyHash: publicKeyHash) { + output.redeemScript = redeemScript + output.set(publicKey: publicKey) + } + } + + public func isSpendable(unspentOutput: UnspentOutput) throws -> Bool { + guard let lastBlockMedianTime = blockMedianTimeHelper.medianTimePast else { + return false + } + + return try inputLockTime(unspentOutput: unspentOutput) < lastBlockMedianTime + } + + public func inputSequenceNumber(output: Output) throws -> Int { + Int((try lockTimeIntervalFrom(output: output)).sequenceNumber) + } + + public func parsePluginData(from pluginData: String, transactionTimestamp: Int) throws -> IPluginOutputData { + let hodlerOutputData = try HodlerOutputData.parse(serialized: pluginData) + + // When checking if UTXO is spendable we use the best block median time. + // The median time is 6 blocks earlier which is approximately equal to 1 hour. + // Here we add 1 hour to show the time when this UTXO will be spendable + hodlerOutputData.approximateUnlockTime = transactionTimestamp + hodlerOutputData.lockTimeInterval.valueInSeconds + 3600 + + return hodlerOutputData + } + + public func keysForApiRestore(publicKey: PublicKey) throws -> [String] { + try LockTimeInterval.allCases.map { lockTimeInterval in + let redeemScript = csvRedeemScript(lockTimeInterval: lockTimeInterval, publicKeyHash: publicKey.keyHash) + let redeemScriptHash = Kit.sha256ripemd160(redeemScript) + + return try addressConverter.convert(keyHash: redeemScriptHash, type: .p2sh).stringValue + } + } + +} diff --git a/Hodler/Classes/Core/Protocols.swift b/Hodler/Classes/Core/Protocols.swift new file mode 100644 index 00000000..a1e126aa --- /dev/null +++ b/Hodler/Classes/Core/Protocols.swift @@ -0,0 +1,13 @@ +import BitcoinCore + +public protocol IHodlerAddressConverter { + func convert(keyHash: Data, type: ScriptType) throws -> Address +} + +public protocol IHodlerPublicKeyStorage { + func publicKey(byRawOrKeyHash hash: Data) -> PublicKey? +} + +public protocol IHodlerBlockMedianTimeHelper { + var medianTimePast: Int? { get } +} diff --git a/LICENSE b/LICENSE index 9d91565e..dbdbcd00 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,4 @@ -MIT License - -Copyright (c) 2018 Horizontal Systems +Copyright (c) 2019 Horizontal Systems Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +7,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LitecoinKit.swift.podspec b/LitecoinKit.swift.podspec new file mode 100644 index 00000000..cc31c71d --- /dev/null +++ b/LitecoinKit.swift.podspec @@ -0,0 +1,34 @@ +Pod::Spec.new do |s| + s.name = 'LitecoinKit.swift' + s.module_name = 'LitecoinKit' + s.version = '0.18' + s.summary = 'Litecoin library for Swift.' + + s.description = <<-DESC +LitecoinKit implements Litecoin protocol in Swift. + DESC + + s.homepage = 'https://github.com/horizontalsystems/bitcoin-kit-ios' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Horizontal Systems' => 'hsdao@protonmail.ch' } + s.source = { git: 'https://github.com/horizontalsystems/bitcoin-kit-ios.git', tag: "litecoin-#{s.version}" } + s.social_media_url = 'http://horizontalsystems.io/' + + s.ios.deployment_target = '13.0' + s.swift_version = '5' + + s.source_files = 'LitecoinKit/Classes/**/*' + s.resource_bundle = { 'LitecoinKit' => 'LitecoinKit/Assets/Checkpoints/*' } + + s.requires_arc = true + + s.dependency 'BitcoinCore.swift', '~> 0.18' + s.dependency 'OpenSslKit.swift', '~> 1.0' + s.dependency 'Secp256k1Kit.swift', '~> 1.0' + s.dependency 'HdWalletKit.swift', '~> 1.5' + + s.dependency 'ObjectMapper', '~> 4.0' + s.dependency 'RxSwift', '~> 5.0' + s.dependency 'BigInt', '~> 5.0' + s.dependency 'GRDB.swift', '~> 5.0' +end diff --git a/LitecoinKit/Assets/Checkpoints/MainNet-bip44.checkpoint b/LitecoinKit/Assets/Checkpoints/MainNet-bip44.checkpoint new file mode 100644 index 00000000..3dcb2231 --- /dev/null +++ b/LitecoinKit/Assets/Checkpoints/MainNet-bip44.checkpoint @@ -0,0 +1,2 @@ +02000000982f9bc89bf1a828f3472ec5b332b40c3e904336fdce82d39e881108f3106a639fec4363378ef3ace42b11af273bed070d6459f00025b249c1b448cd7e0486997b1f5853037a0c1b39bd90dda07508009f742a26e3a5c6deb6bbb7f73fd88ea21473ba6600bdb1863ead4f15e5056a25 +02000000a75c1531c307b047c759bc15b9988acb9b02d4c8d3071633de78dc98a373c47940eaaf37543281e9d64209d23f997313d218b1607bf2416e4cd42b6337f8ec17cd1e5853d0b10c1b069a92349f750800982f9bc89bf1a828f3472ec5b332b40c3e904336fdce82d39e881108f3106a63 diff --git a/LitecoinKit/Assets/Checkpoints/MainNet-last.checkpoint b/LitecoinKit/Assets/Checkpoints/MainNet-last.checkpoint new file mode 100644 index 00000000..3ff3bc8e --- /dev/null +++ b/LitecoinKit/Assets/Checkpoints/MainNet-last.checkpoint @@ -0,0 +1,2 @@ +0000002030464cc91e0817665b7aae775fe22ff6e7288310cab1607f7d17161c6c4166b6ed0f4fae49eca1f88c27793309dd6b80f989c1af60ff9b1a899fe86280398092e2012d635cf4001abcf3bd4f00af230059623dd0bf54b000b182e52824f69095bff5da615f93a06c7185c9dcae012da6 +14000020048115392c1c96c589d9e6de2eea54f5e63a3e5518b171320b954e5637b452ceef882ecde68830deaa09221113220fb39f08dfc89e33c5f8a8751a816534801b2c012d636106011adc11fd74ffae230030464cc91e0817665b7aae775fe22ff6e7288310cab1607f7d17161c6c4166b6 \ No newline at end of file diff --git a/LitecoinKit/Assets/Checkpoints/TestNet-bip44.checkpoint b/LitecoinKit/Assets/Checkpoints/TestNet-bip44.checkpoint new file mode 100644 index 00000000..578f1a44 --- /dev/null +++ b/LitecoinKit/Assets/Checkpoints/TestNet-bip44.checkpoint @@ -0,0 +1,2 @@ +03000020e5139533e5d19576f69c1ba6c2a3dcdb2f25d38a03b7b181ad9b9e463439080adddcf013d87d13dc5a945b7f3ccc7f35aff69b0164e0313e4b24257d0c90cb8a264ea2580257041e0006e356e007000015c0dcbb2e6b9280f7f93daff368f7b2243a3d04b3c18da574b103d500459a5f +00000020cf8fdf87e046e6320f77d07f0ee1bfc289d524691af67373e7e5a23a68c5f9c573b4c778d63094be9b8ab63bd958705b4814702deabb97ac409cc71b4bde086d5f4ca258f0ff0f1e000b5ac7df070000e5139533e5d19576f69c1ba6c2a3dcdb2f25d38a03b7b181ad9b9e463439080a diff --git a/LitecoinKit/Assets/Checkpoints/TestNet-last.checkpoint b/LitecoinKit/Assets/Checkpoints/TestNet-last.checkpoint new file mode 100644 index 00000000..a504233e --- /dev/null +++ b/LitecoinKit/Assets/Checkpoints/TestNet-last.checkpoint @@ -0,0 +1,2 @@ +00000020a3286986ed86d706592f4959ac1dab21dd0494931e04f8767cb556dac3164dabd7ad2b483529d03d6a02c172d53b4f42cdb9435e959d09d2f14415311633a0a8e5632c6337860d1e5f27000020642400e6b3d9b1e7e26ff79c12dcffe803f28343424e567be81cb454a1a908fb89cd62 +00000020bada336341d2569f1f93d19b2136462f14726004adf34d4312eddd6e5107346f47c0ffa2b7f29d0bae0f5b51b79a95731dfbac4e444effb8ac30d75cf9e3c0db8a612c630e980d1e640500001f642400a3286986ed86d706592f4959ac1dab21dd0494931e04f8767cb556dac3164dab \ No newline at end of file diff --git a/LitecoinKit/Classes/Core/Kit.swift b/LitecoinKit/Classes/Core/Kit.swift new file mode 100644 index 00000000..d264cb7d --- /dev/null +++ b/LitecoinKit/Classes/Core/Kit.swift @@ -0,0 +1,117 @@ +import BitcoinCore +import HdWalletKit +import BigInt +import RxSwift +import HsToolKit + +public class Kit: AbstractKit { + private static let heightInterval = 2016 // Default block count in difficulty change circle + private static let targetSpacing = Int(2.5 * 60) // Time to mining one block + private static let maxTargetBits = 0x1e0fffff // Initially and max. target difficulty for blocks + + private static let name = "LitecoinKit" + + public enum NetworkType: String, CaseIterable { case mainNet, testNet } + + public weak var delegate: BitcoinCoreDelegate? { + didSet { + bitcoinCore.delegate = delegate + } + } + + public init(seed: Data, bip: Bip, walletId: String, syncMode: BitcoinCore.SyncMode = .api, networkType: NetworkType = .mainNet, confirmationsThreshold: Int = 6, logger: Logger?) throws { + let network: INetwork + let initialSyncApiUrl: String + + switch networkType { + case .mainNet: + network = MainNet() + initialSyncApiUrl = "https://ltc.horizontalsystems.xyz/api" + case .testNet: + network = TestNet() + initialSyncApiUrl = "" + } + + let logger = logger ?? Logger(minLogLevel: .verbose) + + let initialSyncApi = BCoinApi(url: initialSyncApiUrl, logger: logger) + + let databaseFilePath = try DirectoryHelper.directoryURL(for: Kit.name).appendingPathComponent(Kit.databaseFileName(walletId: walletId, networkType: networkType, bip: bip, syncMode: syncMode)).path + let storage = GrdbStorage(databaseFilePath: databaseFilePath) + + let paymentAddressParser = PaymentAddressParser(validScheme: "litecoin", removeScheme: true) + let scriptConverter = ScriptConverter() + let bech32AddressConverter = SegWitBech32AddressConverter(prefix: network.bech32PrefixPattern, scriptConverter: scriptConverter) + let base58AddressConverter = Base58AddressConverter(addressVersion: network.pubKeyHash, addressScriptVersion: network.scriptHash) + + let difficultyEncoder = DifficultyEncoder() + + let blockValidatorSet = BlockValidatorSet() + let scryptHasher = ScryptHasher() + blockValidatorSet.add(blockValidator: ProofOfWorkValidator(hasher: scryptHasher, difficultyEncoder: difficultyEncoder)) + + let blockValidatorChain = BlockValidatorChain() + let blockHelper = BlockValidatorHelper(storage: storage) + + let difficultyAdjustmentValidator = LegacyDifficultyAdjustmentValidator( + encoder: difficultyEncoder, + blockValidatorHelper: blockHelper, + heightInterval: Kit.heightInterval, + targetTimespan: Kit.heightInterval * Kit.targetSpacing, + maxTargetBits: Kit.maxTargetBits + ) + + switch networkType { + case .mainNet: + blockValidatorChain.add(blockValidator: difficultyAdjustmentValidator) + blockValidatorChain.add(blockValidator: BitsValidator()) + case .testNet: + blockValidatorChain.add(blockValidator: difficultyAdjustmentValidator) + blockValidatorChain.add(blockValidator: LegacyTestNetDifficultyValidator(blockHelper: blockHelper, heightInterval: Kit.heightInterval, targetSpacing: Kit.targetSpacing, maxTargetBits: Kit.maxTargetBits)) + } + + blockValidatorSet.add(blockValidator: blockValidatorChain) + + let bitcoinCore = try BitcoinCoreBuilder(logger: logger) + .set(network: network) + .set(initialSyncApi: initialSyncApi) + .set(seed: seed) + .set(bip: bip) + .set(paymentAddressParser: paymentAddressParser) + .set(walletId: walletId) + .set(confirmationsThreshold: confirmationsThreshold) + .set(peerSize: 10) + .set(syncMode: syncMode) + .set(storage: storage) + .set(blockValidator: blockValidatorSet) + .build() + + super.init(bitcoinCore: bitcoinCore, network: network) + + // extending BitcoinCore + + bitcoinCore.prepend(addressConverter: bech32AddressConverter) + + switch bip { + case .bip44: + bitcoinCore.add(restoreKeyConverter: Bip44RestoreKeyConverter(addressConverter: base58AddressConverter)) + case .bip49: + bitcoinCore.add(restoreKeyConverter: Bip49RestoreKeyConverter(addressConverter: base58AddressConverter)) + case .bip84: + bitcoinCore.add(restoreKeyConverter: KeyHashRestoreKeyConverter()) + } + } + +} + +extension Kit { + + public static func clear(exceptFor walletIdsToExclude: [String] = []) throws { + try DirectoryHelper.removeAll(inDirectory: Kit.name, except: walletIdsToExclude) + } + + private static func databaseFileName(walletId: String, networkType: NetworkType, bip: Bip, syncMode: BitcoinCore.SyncMode) -> String { + "\(walletId)-\(networkType.rawValue)-\(bip.description)-\(syncMode)" + } + +} diff --git a/LitecoinKit/Classes/Core/LegacyDifficultyAdjustmentValidator.swift b/LitecoinKit/Classes/Core/LegacyDifficultyAdjustmentValidator.swift new file mode 100644 index 00000000..2dc70e32 --- /dev/null +++ b/LitecoinKit/Classes/Core/LegacyDifficultyAdjustmentValidator.swift @@ -0,0 +1,52 @@ +import BigInt +import BitcoinCore + +class LegacyDifficultyAdjustmentValidator: IBlockChainedValidator { + private let heightInterval: Int + private let targetTimespan: Int + private let maxTargetBits: Int + + let difficultyEncoder: IDifficultyEncoder + let blockValidatorHelper: IBlockValidatorHelper + + init(encoder: IDifficultyEncoder, blockValidatorHelper: IBlockValidatorHelper, heightInterval: Int, targetTimespan: Int, maxTargetBits: Int) { + difficultyEncoder = encoder + self.blockValidatorHelper = blockValidatorHelper + + self.heightInterval = heightInterval + self.targetTimespan = targetTimespan + self.maxTargetBits = maxTargetBits + } + + func validate(block: Block, previousBlock: Block) throws { + guard let beforeFirstBlock = blockValidatorHelper.previous(for: previousBlock, count: heightInterval) else { + throw BitcoinCoreErrors.BlockValidation.noPreviousBlock + } + + var timespan = previousBlock.timestamp - beforeFirstBlock.timestamp + if (timespan < targetTimespan / 4) { + timespan = targetTimespan / 4 + } else if (timespan > targetTimespan * 4) { + timespan = targetTimespan * 4 + } + + var bigIntDifficulty = difficultyEncoder.decodeCompact(bits: previousBlock.bits) + bigIntDifficulty *= BigInt(timespan) + bigIntDifficulty /= BigInt(targetTimespan) + var newDifficulty = difficultyEncoder.encodeCompact(from: bigIntDifficulty) + + // Difficulty hit proof of work limit: newTarget + if newDifficulty > maxTargetBits { + newDifficulty = maxTargetBits + } + + guard newDifficulty == block.bits else { + throw BitcoinCoreErrors.BlockValidation.notDifficultyTransitionEqualBits + } + } + + func isBlockValidatable(block: Block, previousBlock: Block) -> Bool { + block.height % heightInterval == 0 + } + +} diff --git a/LitecoinKit/Classes/Core/ProofOfWorkValidator.swift b/LitecoinKit/Classes/Core/ProofOfWorkValidator.swift new file mode 100644 index 00000000..4e08f79b --- /dev/null +++ b/LitecoinKit/Classes/Core/ProofOfWorkValidator.swift @@ -0,0 +1,36 @@ +import Foundation +import BitcoinCore +import BigInt + +class ProofOfWorkValidator: IBlockValidator { + private let hasher: IHasher + private let difficultyEncoder: IDifficultyEncoder + + init(hasher: IHasher, difficultyEncoder: IDifficultyEncoder) { + self.hasher = hasher + self.difficultyEncoder = difficultyEncoder + } + + private func serializeHeader(block: Block) -> Data { + var data = Data() + + data.append(Data(from: UInt32(block.version))) + data += block.previousBlockHash + data += block.merkleRoot + data.append(Data(from: UInt32(block.timestamp))) + data.append(Data(from: UInt32(block.bits))) + data.append(Data(from: UInt32(block.nonce))) + + return data + } + + func validate(block: Block, previousBlock: Block) throws { + let header = serializeHeader(block: block) + let hash = hasher.hash(data: header) + + guard (difficultyEncoder.compactFrom(hash: hash) < block.bits) else { + throw BitcoinCoreErrors.BlockValidation.invalidProofOfWork + } + } + +} diff --git a/LitecoinKit/Classes/Core/ScryptHasher.swift b/LitecoinKit/Classes/Core/ScryptHasher.swift new file mode 100644 index 00000000..e6008805 --- /dev/null +++ b/LitecoinKit/Classes/Core/ScryptHasher.swift @@ -0,0 +1,12 @@ +import OpenSslKit +import BitcoinCore + +class ScryptHasher: IHasher { + + init() {} + + func hash(data: Data) -> Data { + OpenSslKit.Kit.scrypt(pass: data) + } + +} diff --git a/LitecoinKit/Classes/Network/MainNet.swift b/LitecoinKit/Classes/Network/MainNet.swift new file mode 100644 index 00000000..a602304a --- /dev/null +++ b/LitecoinKit/Classes/Network/MainNet.swift @@ -0,0 +1,29 @@ +import BitcoinCore + +public class MainNet: INetwork { + public let bundleName = "LitecoinKit" + + public let pubKeyHash: UInt8 = 0x30 + public let privateKey: UInt8 = 0xb0 + public let scriptHash: UInt8 = 0x32 + public let bech32PrefixPattern: String = "ltc" + public let xPubKey: UInt32 = 0x0488b21e + public let xPrivKey: UInt32 = 0x0488ade4 + public let magic: UInt32 = 0xfbc0b6db + public let port = 9333 + public let coinType: UInt32 = 2 + public let sigHash: SigHashType = .bitcoinAll + public var syncableFromApi: Bool = true + + public let dnsSeeds = [ + "x5.dnsseed.thrasher.io", + "x5.dnsseed.litecointools.com", + "x5.dnsseed.litecoinpool.org", + "seed-a.litecoin.loshan.co.uk" + ] + + public let dustRelayTxFee = 3000 + + public init() {} + +} diff --git a/LitecoinKit/Classes/Network/TestNet.swift b/LitecoinKit/Classes/Network/TestNet.swift new file mode 100644 index 00000000..f3b5d85d --- /dev/null +++ b/LitecoinKit/Classes/Network/TestNet.swift @@ -0,0 +1,25 @@ +import BitcoinCore + +class TestNet: INetwork { + let bundleName = "LitecoinKit" + + let pubKeyHash: UInt8 = 0x6f + let privateKey: UInt8 = 0xef + let scriptHash: UInt8 = 0x3a + let bech32PrefixPattern: String = "tltc" + let xPubKey: UInt32 = 0x043587cf + let xPrivKey: UInt32 = 0x04358394 + let magic: UInt32 = 0xfdd2c8f1 + let port = 19335 + let coinType: UInt32 = 1 + let sigHash: SigHashType = .bitcoinAll + var syncableFromApi: Bool = false + + let dnsSeeds = [ + "testnet-seed.ltc.xurious.com", + "seed-b.litecoin.loshan.co.uk", + "dnsseed-testnet.thrasher.io", + ] + + let dustRelayTxFee = 3000 // https://github.com/bitcoin/bitcoin/blob/c536dfbcb00fb15963bf5d507b7017c241718bf6/src/policy/policy.h#L50 +} diff --git a/Podfile b/Podfile deleted file mode 100644 index dc803462..00000000 --- a/Podfile +++ /dev/null @@ -1,96 +0,0 @@ -platform :ios, '11.0' -use_frameworks! - -inhibit_all_warnings! - -workspace 'BitcoinKit' - -project 'Demo/Demo' -project 'BitcoinCore/BitcoinCore' -project 'BitcoinKit/BitcoinKit' -project 'BitcoinCashKit/BitcoinCashKit' -project 'DashKit/DashKit' - -def internal_pods - pod 'HSCryptoKit', '~> 1.4' - pod 'HSHDWalletKit', '~> 1.1' -end - -def kit_pods - internal_pods - - pod 'Alamofire', '~> 4.0' - pod 'ObjectMapper', '~> 3.0' - pod 'RxSwift', '~> 5.0' - pod 'BigInt', '~> 4.0' - pod 'GRDB.swift', '~> 4.0' -end - -target :BitcoinCore do - project 'BitcoinCore/BitcoinCore' - kit_pods -end - -target :BitcoinKit do - project 'BitcoinKit/BitcoinKit' - kit_pods -end - -target :BitcoinCashKit do - project 'BitcoinCashKit/BitcoinCashKit' - kit_pods -end - -target :DashKit do - project 'DashKit/DashKit' - kit_pods - - pod 'CryptoBLS.swift', '~> 1.1' - pod 'CryptoX11.swift', '~> 1.1' -end - -target :Demo do - project 'Demo/Demo' - kit_pods - - pod 'CryptoBLS.swift', '~> 1.1' - pod 'CryptoX11.swift', '~> 1.1' -end - -def test_pods - pod 'Quick' - pod 'Nimble' - pod 'Cuckoo' - pod 'RxBlocking', '~> 5.0' -end - -target :BitcoinCoreTests do - project 'BitcoinCore/BitcoinCore' - - internal_pods - test_pods -end - -target :BitcoinKitTests do - project 'BitcoinKit/BitcoinKit' - - internal_pods - test_pods -end - -target :BitcoinCashKitTests do - project 'BitcoinCashKit/BitcoinCashKit' - - internal_pods - test_pods -end - -target :DashKitTests do - project 'DashKit/DashKit' - - internal_pods - test_pods - - pod 'CryptoBLS.swift', '~> 1.1' - pod 'CryptoX11.swift', '~> 1.1' -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index cc6900a9..00000000 --- a/Podfile.lock +++ /dev/null @@ -1,68 +0,0 @@ -PODS: - - Alamofire (4.8.2) - - BigInt (4.0.0) - - CryptoBLS.swift (1.1) - - CryptoX11.swift (1.1) - - Cuckoo (1.0.5) - - GRDB.swift (4.0.1): - - GRDB.swift/standard (= 4.0.1) - - GRDB.swift/standard (4.0.1) - - HSCryptoKit (1.4.1) - - HSHDWalletKit (1.1): - - HSCryptoKit (~> 1.4) - - Nimble (8.0.1) - - ObjectMapper (3.4.2) - - Quick (2.1.0) - - RxBlocking (5.0.0): - - RxSwift (~> 5) - - RxSwift (5.0.0) - -DEPENDENCIES: - - Alamofire (~> 4.0) - - BigInt (~> 4.0) - - CryptoBLS.swift (~> 1.1) - - CryptoX11.swift (~> 1.1) - - Cuckoo - - GRDB.swift (~> 4.0) - - HSCryptoKit (~> 1.4) - - HSHDWalletKit (~> 1.1) - - Nimble - - ObjectMapper (~> 3.0) - - Quick - - RxBlocking (~> 5.0) - - RxSwift (~> 5.0) - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - Alamofire - - BigInt - - CryptoBLS.swift - - CryptoX11.swift - - Cuckoo - - GRDB.swift - - HSCryptoKit - - HSHDWalletKit - - Nimble - - ObjectMapper - - Quick - - RxBlocking - - RxSwift - -SPEC CHECKSUMS: - Alamofire: ae5c501addb7afdbb13687d7f2f722c78734c2d3 - BigInt: 2aad1a9942dc932ec8b84290d2c564a3d76f97ab - CryptoBLS.swift: 89828d8e3013e45ce4f913d26008b91fc5d0f3a0 - CryptoX11.swift: 8f0ca5fbfefbe49126220ce23b5de106928cf0f0 - Cuckoo: 56ceda7442986ad2aa0615f462b0e431253b97a5 - GRDB.swift: 106a830decf1d92a3fc63c6d6a2f6586f6187297 - HSCryptoKit: 639ab56381fa4c4b9da520e286c9f993b5b8b028 - HSHDWalletKit: 1d946bd24bd307cc494141f4e518d25db814f050 - Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0 - ObjectMapper: 0d4402610f4e468903ae64629eec4784531e5c51 - Quick: 4be43f6634acfa727dd106bdf3929ce125ffa79d - RxBlocking: c67185d26498ea3cbe3e121917c3c16739e43123 - RxSwift: 8b0671caa829a763bbce7271095859121cbd895f - -PODFILE CHECKSUM: b45f4c6b3e83ecd66793f2ae2479d2038bcfc8e9 - -COCOAPODS: 1.7.0 diff --git a/README.md b/README.md index 5951ebde..e95cba13 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Bitcoin, BitcoinCash(ABC) and Dash wallet toolkit for Swift. This is a full impl ### BitcoinCashKit.swift - bech32 cashaddr addresses -### DashKit.swfit +### DashKit.swift - Instant send - LLMQ lock, Masternodes validation @@ -56,10 +56,10 @@ let dashKit = DashKit(withWords: words, walletId: "dash-wallet-id", syncMode: .a All 3 *Kits* can be configured to work in `.mainNet` or `.testNet`. -##### `sycMode` paramater +##### `syncMode` parameter *Kits* can restore existing wallet or create a new one. When restoring, it generates addresses for given wallet according to bip44 protocol, then it pulls all historical transactions for each of those addresses. This is done only once on initial sync. `syncMode` parameter defines where it pulls historical transactions from. When they are pulled, it continues to sync according to [SPV](https://en.bitcoinwiki.org/wiki/Simplified_Payment_Verification) protocol no matter which syncMode was used for initial sync. There are 3 modes available: -- `.full`: Fully synchronizes from peer-to-peer network starting from the block when bip44 was introduced. This mode is the most private (since it fully complies with [SPV](https://en.bitcoinwiki.org/wiki/Simplified_Payment_Verification) protocol), but it takes approximately 2 hours to sync upto now (June 10, 2019). +- `.full`: Fully synchronizes from peer-to-peer network starting from the block when bip44 was introduced. This mode is the most private (since it fully complies with [SPV](https://en.bitcoinwiki.org/wiki/Simplified_Payment_Verification) protocol), but it takes approximately 2 hours to sync up to now (June 10, 2019). - `.api`: Transactions before checkpoint are pulled from API(currently [Insight API](https://github.com/bitpay/insight-api) or [BcoinAPI](http://bcoin.io/api-docs/)). Then the rest is synchronized from peer-to-peer network. This is the fastest one, but it's possible for an attacker to learn which addresses you own. Checkpoints are updated with each new release and hardcoded so the blocks validation is not broken. - `.newWallet`: No need to pull transactions. @@ -300,16 +300,18 @@ $ pod install All features of the library are used in example project. It can be referred as a starting point for usage of the library. -* [Example Project](https://github.com/horizontalsystems/bitcoin-kit-ios/tree/master/HSBitcoinKitDemo) +* [Example Project](https://github.com/horizontalsystems/bitcoin-kit-ios/tree/master/Example) ## Dependencies -* [HSHDWalletKit](https://github.com/horizontalsystems/hd-wallet-kit-ios) - HD Wallet related features, mnemonic phrase geneartion. -* [HSCryptoKit](https://github.com/horizontalsystems/crypto-kit-ios) - Crypto functions required for working with blockchain. +* [HSHDWalletKit](https://github.com/horizontalsystems/hd-wallet-kit-ios) - HD Wallet related features, mnemonic phrase generation. +* [OpenSslKit.swift](https://github.com/horizontalsystems/open-ssl-kit-ios) - Crypto functions required for working with blockchain. +* [Secp256k1Kit.swift](https://github.com/horizontalsystems/secp256k1-kit-ios) - Crypto functions required for working with blockchain. ### Dash dependencies -* [CryptoBLS.swift](https://github.com/horizontalsystems/crypto-bls-ios) -* [CryptoX11.swift](https://github.com/horizontalsystems/crypto-x11-ios) + +* [BlsKit.swift](https://github.com/horizontalsystems/bls-kit-ios) +* [X11Kit.swift](https://github.com/horizontalsystems/x11-kit-ios) ## License