From 1bbe8234fd5569530aaf901d130d14502806055a Mon Sep 17 00:00:00 2001 From: Victor Bodinaud Date: Mon, 5 May 2025 15:16:53 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Enhance=20blockchain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 8196 bytes SwiftChain.xcodeproj/project.pbxproj | 338 ++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 4 +- .../xcschemes/xcschememanagement.plist | 5 + SwiftChain/Models/Block.swift | 109 ------ SwiftChain/Models/Blockchain.swift | 182 ---------- SwiftChain/Models/Transaction.swift | 73 ---- SwiftChain/main.swift | 45 ++- .../Models/Account.swift | 6 +- .../Models/AccountManager.swift | 8 +- SwiftChainCore/Models/Block.swift | 116 ++++++ SwiftChainCore/Models/Blockchain.swift | 127 +++++++ .../Models/MemPool.swift | 8 +- .../Models/Node.swift | 227 +++++++++--- SwiftChainCore/Models/Transaction.swift | 96 +++++ .../Models/Wallet.swift | 14 +- .../SwiftChainCore.docc/SwiftChainCore.md | 13 + SwiftChainCore/SwiftChainCore.h | 18 + SwiftChainCoreTests/SwiftChainCoreTests.swift | 36 ++ 19 files changed, 978 insertions(+), 447 deletions(-) delete mode 100644 SwiftChain/Models/Block.swift delete mode 100644 SwiftChain/Models/Blockchain.swift delete mode 100644 SwiftChain/Models/Transaction.swift rename {SwiftChain => SwiftChainCore}/Models/Account.swift (89%) rename {SwiftChain => SwiftChainCore}/Models/AccountManager.swift (86%) create mode 100644 SwiftChainCore/Models/Block.swift create mode 100644 SwiftChainCore/Models/Blockchain.swift rename {SwiftChain => SwiftChainCore}/Models/MemPool.swift (94%) rename {SwiftChain => SwiftChainCore}/Models/Node.swift (63%) create mode 100644 SwiftChainCore/Models/Transaction.swift rename {SwiftChain => SwiftChainCore}/Models/Wallet.swift (81%) create mode 100644 SwiftChainCore/SwiftChainCore.docc/SwiftChainCore.md create mode 100644 SwiftChainCore/SwiftChainCore.h create mode 100644 SwiftChainCoreTests/SwiftChainCoreTests.swift diff --git a/.DS_Store b/.DS_Store index 383433838f6d72f0df1da3ebe4b08e2559b8f9e1..56dbff92d5edb58f86347656c1c763ef4293f2d0 100644 GIT binary patch literal 8196 zcmeHMOK;Oa5S~o~I4z(ciWDJ`EV!1E7F1DjAWg!7D=u&V6zn)nOdW3&I|LC0g#!`- zB)B2|1YG$62yx-Ufg6GoH+}+-*+*MD57*^@P}z}ozsc^*j^|r@*XsZPDR%4yfE)m5 zSQut!uo@$*pOq0w$(Eyt1pUFSFBnzYY>il|2E~A4Krx^gPz)#r{uKsr&t{@c*!M-R zsucr@f&Y>Lwm#@s7#1WB1oEW=D}Dq(Ok*<_)U!-M)Fl=q4g_Kk3K6b|!j;G)1`+Pq zu1mXu#DPHJ4n!V4h>T3+35D?JIKIx!ffNL)S}~v)7-fK+-P14+b#NevoZnLyu7vxV zv|P4aYGIppX#e`TQ%~QmMEmX7{#GOOZD5)|rPE+Q7vru$03~REiMn^mrBxY+8L=4i zzqp_VFxDq>-H?4W$u6A3hmS8#{JhU|7&~T@ZsoI`Z6r6baLta(4SBu`RiI!)4O-Af z?e{6yevr?9BB`nLp1u1tO`Fkf8V%Yt0yF6NMYDaK{aU5ewKjCq zy6V)rmF#SZdV%S9wKjKf>>8TfxaN2^?G|arvwiMshz>22$yBnrUT<-EX-+?z&-dr_ z-tuA|^)pNTenvZT^7Q%3m36o2(Z`q%3XxkK91y=Z>nEti<`}sb;Ni>d?8EbKzPh(2 z=RY<^`1wfc6PaiD$`Xo=amQ$TF7@hY$GA8gcTd17K2tufB>vFd!Pp8vQ3*$ha+v;{ zJbswX2VcG)lWZo88|HQA-cg3-yAWQw62-s>15=_U$Iky3Cx8DR@ly?o0mZ=XVSuE| zM!AUjY`ky`r^a5%bS$^9FkyBBf!qZv{?>-bkK>4&e;A^!qbc(#NE`^n7L>pKAt2#C OtoDCCJ*wKJ4EzS{jGwgt delta 170 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjdE!6q~50D9Q}tfW=A}oEb705*acX@+KxO zXJnrIKtN*h90B>ss|7VD_Xx@}@=tysB+41WkjhZZP{L3=`GAo8#xy&&#q1m$f*@mn rK!6)axPpw|nE0J}GQW(a0!SEW0TYyF0ntDX$RQxNF>H?KnZpbK6Al}? diff --git a/SwiftChain.xcodeproj/project.pbxproj b/SwiftChain.xcodeproj/project.pbxproj index 4b1a68a..8fa09e4 100644 --- a/SwiftChain.xcodeproj/project.pbxproj +++ b/SwiftChain.xcodeproj/project.pbxproj @@ -7,9 +7,28 @@ objects = { /* Begin PBXBuildFile section */ + 576693F62DC8EDCE0024463C /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 576693F52DC8EDCE0024463C /* CryptoSwift */; }; + 57B6CA1A2CFDFA39009F401F /* SwiftChainCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */; }; 57BA298C2CF77907009E4448 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 57BA298B2CF77907009E4448 /* CryptoSwift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 57B6CA1B2CFDFA39009F401F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 57BA29782CF778F5009E4448 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57B6CA0F2CFDFA39009F401F; + remoteInfo = SwiftChainCore; + }; + 57B6CA392CFDFB1F009F401F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 57BA29782CF778F5009E4448 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57B6CA0F2CFDFA39009F401F; + remoteInfo = SwiftChainCore; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 57BA297E2CF778F5009E4448 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -23,10 +42,35 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftChainCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftChainCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57BA29802CF778F5009E4448 /* SwiftChain */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwiftChain; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 57B6CA252CFDFA39009F401F /* Exceptions for "SwiftChainCore" folder in "SwiftChainCore" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + publicHeaders = ( + SwiftChainCore.h, + ); + target = 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ + 57B6CA112CFDFA39009F401F /* SwiftChainCore */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 57B6CA252CFDFA39009F401F /* Exceptions for "SwiftChainCore" folder in "SwiftChainCore" target */, + ); + path = SwiftChainCore; + sourceTree = ""; + }; + 57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = SwiftChainCoreTests; + sourceTree = ""; + }; 57BA29822CF778F5009E4448 /* SwiftChain */ = { isa = PBXFileSystemSynchronizedRootGroup; path = SwiftChain; @@ -35,6 +79,22 @@ /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ + 57B6CA0D2CFDFA39009F401F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 576693F62DC8EDCE0024463C /* CryptoSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57B6CA162CFDFA39009F401F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 57B6CA1A2CFDFA39009F401F /* SwiftChainCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 57BA297D2CF778F5009E4448 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -50,6 +110,8 @@ isa = PBXGroup; children = ( 57BA29822CF778F5009E4448 /* SwiftChain */, + 57B6CA112CFDFA39009F401F /* SwiftChainCore */, + 57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */, 57BA29812CF778F5009E4448 /* Products */, ); sourceTree = ""; @@ -58,13 +120,72 @@ isa = PBXGroup; children = ( 57BA29802CF778F5009E4448 /* SwiftChain */, + 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */, + 57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + 57B6CA0B2CFDFA39009F401F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ + 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57B6CA262CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCore" */; + buildPhases = ( + 57B6CA0B2CFDFA39009F401F /* Headers */, + 57B6CA0C2CFDFA39009F401F /* Sources */, + 57B6CA0D2CFDFA39009F401F /* Frameworks */, + 57B6CA0E2CFDFA39009F401F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 57B6CA112CFDFA39009F401F /* SwiftChainCore */, + ); + name = SwiftChainCore; + packageProductDependencies = ( + 576693F52DC8EDCE0024463C /* CryptoSwift */, + ); + productName = SwiftChainCore; + productReference = 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */; + productType = "com.apple.product-type.framework"; + }; + 57B6CA182CFDFA39009F401F /* SwiftChainCoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57B6CA272CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCoreTests" */; + buildPhases = ( + 57B6CA152CFDFA39009F401F /* Sources */, + 57B6CA162CFDFA39009F401F /* Frameworks */, + 57B6CA172CFDFA39009F401F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 57B6CA1C2CFDFA39009F401F /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */, + ); + name = SwiftChainCoreTests; + packageProductDependencies = ( + ); + productName = SwiftChainCoreTests; + productReference = 57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 57BA297F2CF778F5009E4448 /* SwiftChain */ = { isa = PBXNativeTarget; buildConfigurationList = 57BA29872CF778F5009E4448 /* Build configuration list for PBXNativeTarget "SwiftChain" */; @@ -76,6 +197,8 @@ buildRules = ( ); dependencies = ( + 576693F42DC8EDB60024463C /* PBXTargetDependency */, + 57B6CA3A2CFDFB1F009F401F /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 57BA29822CF778F5009E4448 /* SwiftChain */, @@ -98,6 +221,12 @@ LastSwiftUpdateCheck = 1610; LastUpgradeCheck = 1610; TargetAttributes = { + 57B6CA0F2CFDFA39009F401F = { + CreatedOnToolsVersion = 16.1; + }; + 57B6CA182CFDFA39009F401F = { + CreatedOnToolsVersion = 16.1; + }; 57BA297F2CF778F5009E4448 = { CreatedOnToolsVersion = 16.1; }; @@ -121,11 +250,44 @@ projectRoot = ""; targets = ( 57BA297F2CF778F5009E4448 /* SwiftChain */, + 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */, + 57B6CA182CFDFA39009F401F /* SwiftChainCoreTests */, ); }; /* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + 57B6CA0E2CFDFA39009F401F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57B6CA172CFDFA39009F401F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ + 57B6CA0C2CFDFA39009F401F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57B6CA152CFDFA39009F401F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 57BA297C2CF778F5009E4448 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -135,7 +297,156 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 576693F42DC8EDB60024463C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 576693F32DC8EDB60024463C /* CryptoSwift */; + }; + 57B6CA1C2CFDFA39009F401F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */; + targetProxy = 57B6CA1B2CFDFA39009F401F /* PBXContainerItemProxy */; + }; + 57B6CA3A2CFDFB1F009F401F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */; + targetProxy = 57B6CA392CFDFB1F009F401F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + 57B6CA212CFDFA39009F401F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = WVH3Y23X7X; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.1; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Debug; + }; + 57B6CA222CFDFA39009F401F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = WVH3Y23X7X; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.1; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Release; + }; + 57B6CA232CFDFA39009F401F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = WVH3Y23X7X; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + MACOSX_DEPLOYMENT_TARGET = 15.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCoreTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Debug; + }; + 57B6CA242CFDFA39009F401F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = WVH3Y23X7X; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + MACOSX_DEPLOYMENT_TARGET = 15.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCoreTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Release; + }; 57BA29852CF778F5009E4448 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -279,6 +590,24 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 57B6CA262CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57B6CA212CFDFA39009F401F /* Debug */, + 57B6CA222CFDFA39009F401F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57B6CA272CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57B6CA232CFDFA39009F401F /* Debug */, + 57B6CA242CFDFA39009F401F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 57BA297B2CF778F5009E4448 /* Build configuration list for PBXProject "SwiftChain" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -311,6 +640,15 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 576693F32DC8EDB60024463C /* CryptoSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */; + productName = CryptoSwift; + }; + 576693F52DC8EDCE0024463C /* CryptoSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = CryptoSwift; + }; 57BA298B2CF77907009E4448 /* CryptoSwift */ = { isa = XCSwiftPackageProductDependency; package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */; diff --git a/SwiftChain.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftChain.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 09d045a..51d8071 100644 --- a/SwiftChain.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftChain.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "state" : { - "revision" : "678d442c6f7828def400a70ae15968aef67ef52d", - "version" : "1.8.3" + "revision" : "729e01bc9b9dab466ac85f21fb9ee2bc1c61b258", + "version" : "1.8.4" } } ], diff --git a/SwiftChain.xcodeproj/xcuserdata/victor.xcuserdatad/xcschemes/xcschememanagement.plist b/SwiftChain.xcodeproj/xcuserdata/victor.xcuserdatad/xcschemes/xcschememanagement.plist index 5bb2053..95eed41 100644 --- a/SwiftChain.xcodeproj/xcuserdata/victor.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/SwiftChain.xcodeproj/xcuserdata/victor.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,11 @@ orderHint 0 + SwiftChainCore.xcscheme_^#shared#^_ + + orderHint + 1 + diff --git a/SwiftChain/Models/Block.swift b/SwiftChain/Models/Block.swift deleted file mode 100644 index 53abb02..0000000 --- a/SwiftChain/Models/Block.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Block.swift -// Blockchain -// -// Created by Victor BODINAUD on 27/02/2020. -// Copyright © 2020 Victor BODINAUD. All rights reserved. -// - -import Foundation -import CryptoSwift - -class Block: Codable { - var hash: String - var transactions: [Transaction] - var previousHash: String - var index: Int - var nonce: Int - var timestamp: Int - var difficulty: Int - var miner: String? // Adresse qui recevra la récompense - - enum CodingKeys: String, CodingKey { - case hash, transactions, previousHash, index, nonce, timestamp, difficulty, miner - } - - init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "", - index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) { - self.hash = hash - self.transactions = transactions - self.previousHash = previousHash - self.index = index - self.nonce = nonce - self.timestamp = timestamp - self.difficulty = difficulty - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - hash = try container.decode(String.self, forKey: .hash) - transactions = try container.decode([Transaction].self, forKey: .transactions) - previousHash = try container.decode(String.self, forKey: .previousHash) - index = try container.decode(Int.self, forKey: .index) - nonce = try container.decode(Int.self, forKey: .nonce) - timestamp = try container.decode(Int.self, forKey: .timestamp) - difficulty = try container.decode(Int.self, forKey: .difficulty) - miner = try container.decodeIfPresent(String.self, forKey: .miner) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(hash, forKey: .hash) - try container.encode(transactions, forKey: .transactions) - try container.encode(previousHash, forKey: .previousHash) - try container.encode(index, forKey: .index) - try container.encode(nonce, forKey: .nonce) - try container.encode(timestamp, forKey: .timestamp) - try container.encode(difficulty, forKey: .difficulty) - try container.encodeIfPresent(miner, forKey: .miner) - } - - func mineBlock() -> Double { - let startTime = Date() - let target = String(repeating: "0", count: difficulty) - - print("Mining block \(index) with difficulty \(difficulty)...") - - repeat { - nonce += 1 - hash = generateHash() - } while !hash.hasPrefix(target) - - let timeElapsed = Date().timeIntervalSince(startTime) - print("Block mined! Nonce: \(nonce), Hash: \(hash)") - - return timeElapsed - } - - func isValid() -> Bool { - let target = String(repeating: "0", count: difficulty) - return hash == generateHash() && hash.hasPrefix(target) - } - - /** - Génère le hash en incluant toutes les transactions - */ - func generateHash() -> String { - let txData = transactions.map { - "\($0.sender)\($0.receiver)\($0.amount)\($0.type)" - }.joined() - - return "\(previousHash)\(txData)\(String(timestamp))\(String(index))\(String(nonce))".sha256() - } - - /** - Calcule le montant total des transactions dans le bloc - */ - func getTotalAmount() -> Int { - return transactions.reduce(0) { $0 + $1.amount } - } - - /** - Vérifie si le bloc contient une transaction spécifique - */ - func containsTransaction(_ txId: String) -> Bool { - return transactions.contains { tx in - "\(tx.sender)\(tx.receiver)\(tx.amount)".sha256() == txId - } - } -} diff --git a/SwiftChain/Models/Blockchain.swift b/SwiftChain/Models/Blockchain.swift deleted file mode 100644 index d962e5f..0000000 --- a/SwiftChain/Models/Blockchain.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// Blockchain.swift -// Blockchain -// -// Created by Victor BODINAUD on 27/02/2020. -// Copyright © 2020 Victor BODINAUD. All rights reserved. -// - -import Foundation - -class Blockchain { - var chain = [Block]() - let memPool: MemPool - let accountManager: AccountManager - let minDifficulty = 2 - let maxDifficulty = 6 - let targetBlockTime = 10.0 // en secondes - - static let genesisBlock: Block = { - let block = Block() - block.previousHash = "0000genesis0000" - block.index = 0 - block.timestamp = 1701388800 - block.difficulty = 4 - block.nonce = 12345 - block.hash = "000088c1731bed4996680d2c50ea3d9b573c1507d2d61866c0deff33a7f8cf5e" - return block - }() - - init() { - self.accountManager = AccountManager() - self.memPool = MemPool(accountManager: accountManager) - } - - /** - Initialize the first block of the blockchain. - - - Parameters: - - data: The datas of the block - */ - func createGenesisBlock() { - chain.append(Blockchain.genesisBlock) - print("Genesis block initialized -- hash: \(Blockchain.genesisBlock.hash)") - } - - /** - Initialize a new block of the blockchain. - - - Parameters: - - data: The datas of the block - */ - func createBlock(minerAddress: String) -> Block? { - guard let lastBlock = chain.last else { return nil } - - // Récupérer les transactions en attente - var transactions = memPool.getTransactionsForBlock() - - // Ajouter la récompense de minage - let miningReward = Transaction(sender: "SYSTEM", receiver: minerAddress, amount: 50, type: "MINING_REWARD") - transactions.append(miningReward) - - let newBlock = Block( - transactions: transactions, - previousHash: lastBlock.hash, - index: chain.count, - difficulty: calculateNewDifficulty() - ) - - newBlock.miner = minerAddress - newBlock.timestamp = Int(Date().timeIntervalSince1970) - let miningTime = newBlock.mineBlock() - - // Valider et traiter les transactions - if accountManager.processBlock(newBlock) { - chain.append(newBlock) - print(""" - Block \(newBlock.index) created: - Hash: \(newBlock.hash) - Previous hash: \(newBlock.previousHash) - Transactions: \(newBlock.transactions.count) - Mining time: \(String(format: "%.2f", miningTime)) seconds - """) - return newBlock - } - - return nil - } - - func calculateNewDifficulty() -> Int { - guard chain.count >= 2 else { return minDifficulty } - - let lastBlock = chain.last! - let previousBlock = chain[chain.count - 2] - let timeSpent = Double(lastBlock.timestamp - previousBlock.timestamp) - - if timeSpent < targetBlockTime / 2 { - return min(lastBlock.difficulty + 1, maxDifficulty) - } else if timeSpent > targetBlockTime * 2 { - return max(lastBlock.difficulty - 1, minDifficulty) - } - - return lastBlock.difficulty - } - - func submitTransaction(_ transaction: Transaction) -> Bool { - return memPool.addTransaction(transaction) - } - - func getBalance(address: String) -> Int { - return accountManager.getBalance(address) - } - - func chainValidity() -> Bool { - for i in 1.. Bool { - // Vérifier que la chaîne commence par notre bloc genesis codé en dur - guard let firstBlock = chain.first, - firstBlock.hash == Blockchain.genesisBlock.hash else { - print("Genesis block mismatch") - return false - } - - // Vérifier les blocs suivants - for i in 1.. Bool { - // Vérifier que la nouvelle chaîne est plus longue - guard newChain.count > chain.count else { - print("Received chain is not longer than current chain") - return false - } - - // Vérifier la validité de la nouvelle chaîne - guard validateChain(newChain) else { - print("Received chain is invalid") - return false - } - - // Sauvegarder l'ancienne chaîne au cas où - let oldChain = chain - - // Essayer de traiter tous les blocs - for block in newChain { - if !accountManager.processBlock(block) { - print("Failed to process transactions in block \(block.index)") - chain = oldChain - return false - } - } - - chain = newChain - print("Chain replaced successfully") - return true - } -} diff --git a/SwiftChain/Models/Transaction.swift b/SwiftChain/Models/Transaction.swift deleted file mode 100644 index cfab5fd..0000000 --- a/SwiftChain/Models/Transaction.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// File.swift -// -// -// Created by Victor BODINAUD on 31/03/2021. -// - -import Foundation - -class Transaction: Codable { - let sender: String - let receiver: String - let amount: Int - let type: String - var signature: Data? - var senderPublicKey: Data? - - init(sender: String, receiver: String, amount: Int, type: String) { - self.sender = sender - self.receiver = receiver - self.amount = amount - self.type = type - } - - // Pour encoder/décoder les Data optionnels - enum CodingKeys: String, CodingKey { - case sender, receiver, amount, type, signature, senderPublicKey - } - - // Données à signer - func messageToSign() -> Data { - return "\(sender)\(receiver)\(amount)\(type)".data(using: .utf8)! - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - sender = try container.decode(String.self, forKey: .sender) - receiver = try container.decode(String.self, forKey: .receiver) - amount = try container.decode(Int.self, forKey: .amount) - type = try container.decode(String.self, forKey: .type) - signature = try container.decodeIfPresent(Data.self, forKey: .signature) - senderPublicKey = try container.decodeIfPresent(Data.self, forKey: .senderPublicKey) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(sender, forKey: .sender) - try container.encode(receiver, forKey: .receiver) - try container.encode(amount, forKey: .amount) - try container.encode(type, forKey: .type) - try container.encodeIfPresent(signature, forKey: .signature) - try container.encodeIfPresent(senderPublicKey, forKey: .senderPublicKey) - } - - // Vérifier la validité de la signature - func isSignatureValid() -> Bool { - guard let signature = signature, - let publicKeyData = senderPublicKey else { - return false - } - - // Pas besoin de vérifier la signature pour les récompenses de minage - if type == "MINING_REWARD" { - return true - } - - return Wallet.verifySignature( - for: self, - signature: signature, - publicKeyData: publicKeyData - ) - } -} diff --git a/SwiftChain/main.swift b/SwiftChain/main.swift index 9ee8477..e19ece5 100644 --- a/SwiftChain/main.swift +++ b/SwiftChain/main.swift @@ -7,6 +7,7 @@ // import Foundation +import SwiftChainCore let node = Node() var command: String? @@ -15,6 +16,8 @@ var wallets: [String: Wallet] = [:] print(""" Blockchain CLI - Commandes disponibles: +- createwallet : Créer un nouveau wallet +- listwallet : Liste des wallets - mine : Miner un nouveau bloc - send : Envoyer des tokens - balance : Voir le solde d'une adresse @@ -23,6 +26,7 @@ Blockchain CLI - Commandes disponibles: - setminer : Changer l'adresse du mineur - connect : Se connecter à un pair - peers : Liste des pairs connectés +- mempool : État du mempool - exit : Quitter """) @@ -41,7 +45,7 @@ repeat { node.listPeers() case "mine": - if let block = node.mineBlock(minerAddress: currentMinerAddress) { + if let _ = node.mineBlock(minerAddress: currentMinerAddress) { print("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)") print("Nouveau solde: \(node.getBalance(currentMinerAddress))") } else { @@ -81,27 +85,41 @@ repeat { case "exit": print("Au revoir!") - // Ajoutons une nouvelle commande pour créer un wallet case "createwallet": let wallet = Wallet() wallets[wallet.address] = wallet print("Nouveau wallet créé!") print("Adresse: \(wallet.address)") + case "listwallet": + print("\nWallets disponibles:") + for (address, _) in wallets { + print("- \(address) (Solde: \(node.getBalance(address)))") + } + case "send": print("Votre adresse (wallet):") guard let senderAddress = readLine(), - let wallet = wallets[senderAddress] else { + let wallet = wallets[senderAddress] + else { print("Wallet non trouvé") break } print("Adresse du destinataire:") - guard let receiverAddress = readLine() else { break } + guard let receiverAddress = readLine() else { + print("Adresse du destinataire invalide") + break + } print("Montant:") guard let amountStr = readLine(), - let amount = Int(amountStr) else { break } + let amount = Int(amountStr), + amount > 0 + else { + print("Montant invalide") + break + } let transaction = Transaction( sender: senderAddress, @@ -110,16 +128,21 @@ repeat { type: "TRANSFER" ) - // Signer la transaction transaction.senderPublicKey = wallet.getPublicKeyData() - transaction.signature = wallet.signTransaction(transaction) - - if node.submitTransaction(transaction) { - print("Transaction propagée au réseau!") + if let signature = wallet.signTransaction(transaction) { + transaction.signature = signature + if node.submitTransaction(transaction) { + print("Transaction signée et propagée au réseau!") + } else { + print("Erreur lors de l'envoi de la transaction") + } } else { - print("Erreur lors de l'envoi de la transaction") + print("Erreur lors de la signature de la transaction") } + case "mempool": + node.printMemPoolStatus() + default: print("Commande inconnue") } diff --git a/SwiftChain/Models/Account.swift b/SwiftChainCore/Models/Account.swift similarity index 89% rename from SwiftChain/Models/Account.swift rename to SwiftChainCore/Models/Account.swift index 5c49cac..f6b03e5 100644 --- a/SwiftChain/Models/Account.swift +++ b/SwiftChainCore/Models/Account.swift @@ -1,16 +1,16 @@ // // File.swift -// +// // // Created by Victor BODINAUD on 31/03/2021. // import Foundation -class Account { +public class Account { var id: String var balance: Int - + init(id: String, balance: Int) { self.id = id self.balance = balance diff --git a/SwiftChain/Models/AccountManager.swift b/SwiftChainCore/Models/AccountManager.swift similarity index 86% rename from SwiftChain/Models/AccountManager.swift rename to SwiftChainCore/Models/AccountManager.swift index 6ccab74..78e2bc7 100644 --- a/SwiftChain/Models/AccountManager.swift +++ b/SwiftChainCore/Models/AccountManager.swift @@ -5,10 +5,8 @@ // Created by Victor on 27/11/2024. // -class AccountManager { +public class AccountManager { private var accounts: [String: Account] = [:] - - // Mining reward amount private let miningReward = 50 func getAccount(_ address: String) -> Account { @@ -21,7 +19,6 @@ class AccountManager { } func canProcessTransaction(_ transaction: Transaction) -> Bool { - // Mining rewards are always valid if transaction.type == "MINING_REWARD" { return true } @@ -55,13 +52,10 @@ class AccountManager { } func processBlock(_ block: Block) -> Bool { - // Create temporary state to validate all transactions let tempAccounts = accounts - // Try to process all transactions for transaction in block.transactions { if !processTransaction(transaction) { - // Restore previous state if any transaction fails accounts = tempAccounts return false } diff --git a/SwiftChainCore/Models/Block.swift b/SwiftChainCore/Models/Block.swift new file mode 100644 index 0000000..6535fb7 --- /dev/null +++ b/SwiftChainCore/Models/Block.swift @@ -0,0 +1,116 @@ +// +// Block.swift +// Blockchain +// +// Created by Victor BODINAUD on 27/02/2020. +// Copyright © 2020 Victor BODINAUD. All rights reserved. +// + +internal import CryptoSwift +import Foundation + +public class Block: Codable { + var hash: String + var transactions: [Transaction] + var previousHash: String + var index: Int + var nonce: Int + var timestamp: Int + var difficulty: Int + var miner: String? + + enum CodingKeys: String, CodingKey { + case hash, transactions, previousHash, index, nonce, timestamp, difficulty, miner + } + + init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "", + index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) + { + self.hash = hash + self.transactions = transactions + self.previousHash = previousHash + self.index = index + self.nonce = nonce + self.timestamp = timestamp + self.difficulty = difficulty + } + + func mineBlock() -> Double { + let startTime = Date() + let target = String(repeating: "0", count: difficulty) + + print("Mining block \(index) with difficulty \(difficulty)...") + + repeat { + nonce += 1 + hash = generateHash() + } while !hash.hasPrefix(target) + + let timeElapsed = Date().timeIntervalSince(startTime) + print("Block mined! Nonce: \(nonce), Hash: \(hash)") + + return timeElapsed + } + + func isValid() -> Bool { + // Vérification de base du hash + let target = String(repeating: "0", count: difficulty) + if hash != generateHash() || !hash.hasPrefix(target) { + print("Block \(index): Invalid hash") + return false + } + + // Vérifier l'index + if index < 0 { + print("Block: Invalid index") + return false + } + + // Vérifier le timestamp + let currentTime = Int(Date().timeIntervalSince1970) + if timestamp > currentTime + 7200 || timestamp < 1701388800 { // 2h dans le futur max + print("Block: Invalid timestamp") + return false + } + + // Vérifier les transactions + for transaction in transactions { + if !transaction.isValid() { + print("Block: Invalid transaction found") + return false + } + + // Vérifier qu'il n'y a qu'une seule récompense de minage + if transaction.type == "MINING_REWARD" { + if transactions.filter({ $0.type == "MINING_REWARD" }).count > 1 { + print("Block: Multiple mining rewards found") + return false + } + } + } + + return true + } + + func generateHash() -> String { + let txData = transactions.map { + "\($0.sender)\($0.receiver)\($0.amount)\($0.type)" + }.joined() + + return "\(previousHash)\(txData)\(String(timestamp))\(String(index))\(String(nonce))".sha256() + } +} + +// Structure pour suivre la progression du mining +struct MiningProgress { + let hashesChecked: Int + let hashRate: Double + let elapsedTime: TimeInterval + let estimatedTimeRemaining: TimeInterval? +} + +// Erreurs possibles pendant le mining +enum MiningError: Error { + case cancelled + case failed(String) +} diff --git a/SwiftChainCore/Models/Blockchain.swift b/SwiftChainCore/Models/Blockchain.swift new file mode 100644 index 0000000..750fb81 --- /dev/null +++ b/SwiftChainCore/Models/Blockchain.swift @@ -0,0 +1,127 @@ +// +// Blockchain.swift +// Blockchain +// +// Created by Victor BODINAUD on 27/02/2020. +// Copyright © 2020 Victor BODINAUD. All rights reserved. +// + +import Foundation + +public class Blockchain { + var chain = [Block]() + let minDifficulty = 2 + let maxDifficulty = 6 + let targetBlockTime = 10.0 // en secondes + + static let genesisBlock: Block = { + let block = Block() + block.previousHash = "0000genesis0000" + block.index = 0 + block.timestamp = 1701388800 + block.difficulty = 4 + block.nonce = 12345 + block.hash = "000088c1731bed4996680d2c50ea3d9b573c1507d2d61866c0deff33a7f8cf5e" + return block + }() + + init() { + chain.append(Blockchain.genesisBlock) + print("Genesis block initialized -- hash: \(Blockchain.genesisBlock.hash)") + } + + func calculateNewDifficulty() -> Int { + guard chain.count >= 2 else { return minDifficulty } + + let lastBlock = chain.last! + let previousBlock = chain[chain.count - 2] + let timeSpent = Double(lastBlock.timestamp - previousBlock.timestamp) + + if timeSpent < targetBlockTime / 2 { + return min(lastBlock.difficulty + 1, maxDifficulty) + } else if timeSpent > targetBlockTime * 2 { + return max(lastBlock.difficulty - 1, minDifficulty) + } + + return lastBlock.difficulty + } + + func validateChain(_ chain: [Block]) -> Bool { + guard let firstBlock = chain.first, + firstBlock.hash == Blockchain.genesisBlock.hash + else { + print("Genesis block mismatch") + return false + } + + for i in 1.. Bool { + // Vérifier le genesis block + guard let firstBlock = chain.first, + firstBlock.hash == Blockchain.genesisBlock.hash + else { + print("Chain: Invalid genesis block") + return false + } + + // Vérifier la séquence des blocs + for i in 1.. Int { + return chain[index].difficulty + } +} diff --git a/SwiftChain/Models/MemPool.swift b/SwiftChainCore/Models/MemPool.swift similarity index 94% rename from SwiftChain/Models/MemPool.swift rename to SwiftChainCore/Models/MemPool.swift index ea92ccc..10b05ff 100644 --- a/SwiftChain/Models/MemPool.swift +++ b/SwiftChainCore/Models/MemPool.swift @@ -5,7 +5,7 @@ // Created by Victor on 27/11/2024. // -class MemPool { +public class MemPool { private var pendingTransactions: [Transaction] = [] private let maxTransactionsPerBlock: Int = 10 private let accountManager: AccountManager @@ -108,9 +108,9 @@ class MemPool { for block in chain { if block.transactions.contains(where: { chainTx in chainTx.sender == pendingTx.sender && - chainTx.receiver == pendingTx.receiver && - chainTx.amount == pendingTx.amount && - chainTx.type == pendingTx.type + chainTx.receiver == pendingTx.receiver && + chainTx.amount == pendingTx.amount && + chainTx.type == pendingTx.type }) { isInChain = true break diff --git a/SwiftChain/Models/Node.swift b/SwiftChainCore/Models/Node.swift similarity index 63% rename from SwiftChain/Models/Node.swift rename to SwiftChainCore/Models/Node.swift index e924e85..de4ae3c 100644 --- a/SwiftChain/Models/Node.swift +++ b/SwiftChainCore/Models/Node.swift @@ -8,21 +8,22 @@ import Foundation import Network -class Node { +public class Node { + // Network properties private var peers: [NWConnection] = [] private let port: NWEndpoint.Port = 8333 private var listener: NWListener? - - // Le node possède et gère la blockchain - private let blockchain = Blockchain() - private let memPool: MemPool - private let accountManager = AccountManager() - - // Queue pour gérer les connexions de manière asynchrone private let queue = DispatchQueue(label: "com.blockchain.node") - init() { - self.memPool = MemPool(accountManager: accountManager) + // Blockchain components + private let blockchain: Blockchain + private let accountManager: AccountManager + private var pendingTransactions: [Transaction] = [] + private let maxTransactionsPerBlock: Int = 10 + + public init() { + self.accountManager = AccountManager() + self.blockchain = Blockchain() setupListener() } @@ -65,13 +66,14 @@ class Node { } } - func mineBlock(minerAddress: String) -> Block? { - guard let lastBlock = blockchain.chain.last else { return nil } + public func mineBlock(minerAddress: String) -> Block? { + guard let lastBlock = blockchain.chain.last else { + print("No existing blockchain") + return nil + } - // Récupérer les transactions du mempool - var transactions = memPool.getTransactionsForBlock() - - // Créer la récompense de minage + // Utiliser la méthode locale au lieu de memPool + var transactions = getTransactionsForBlock() let miningReward = Transaction( sender: "SYSTEM", receiver: minerAddress, @@ -80,7 +82,6 @@ class Node { ) transactions.append(miningReward) - // Créer et miner le bloc let newBlock = Block( transactions: transactions, previousHash: lastBlock.hash, @@ -92,46 +93,49 @@ class Node { newBlock.timestamp = Int(Date().timeIntervalSince1970) let miningTime = newBlock.mineBlock() - // Valider et traiter les transactions if accountManager.processBlock(newBlock) { blockchain.chain.append(newBlock) broadcastBlock(newBlock) + // Nettoyer le mempool après avoir ajouté le bloc + cleanupMempool(chain: blockchain.chain) + print(""" - Block \(newBlock.index) created: - Hash: \(newBlock.hash) - Previous hash: \(newBlock.previousHash) - Transactions: \(newBlock.transactions.count) - Mining time: \(String(format: "%.2f", miningTime)) seconds - """) + Block \(newBlock.index) created: + Hash: \(newBlock.hash) + Previous hash: \(newBlock.previousHash) + Transactions: \(newBlock.transactions.count) + Mining time: \(String(format: "%.2f", miningTime)) seconds + """) return newBlock } return nil } - func submitTransaction(_ transaction: Transaction) -> Bool { - if memPool.addTransaction(transaction) { + // Public interface + public func submitTransaction(_ transaction: Transaction) -> Bool { + if addTransaction(transaction) { broadcastTransaction(transaction) return true } return false } - func getBalance(_ address: String) -> Int { + public func getBalance(_ address: String) -> Int { return accountManager.getBalance(address) } - func getPendingTransactions() -> [Transaction] { - return memPool.getAllPendingTransactions() + public func getPendingTransactions() -> [Transaction] { + return pendingTransactions } - func isChainValid() -> Bool { + public func isChainValid() -> Bool { return blockchain.validateChain(blockchain.chain) } // Connexion à un pair - func connectToPeer(host: String) { + public func connectToPeer(host: String) { let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: port) let connection = NWConnection(to: endpoint, using: .tcp) @@ -156,7 +160,7 @@ class Node { } // Méthode pour lister les pairs connectés - func listPeers() { + public func listPeers() { if peers.isEmpty { print("Aucun pair connecté") return @@ -202,20 +206,12 @@ class Node { print("Receive error: \(error)") } - if !isComplete && error == nil { + if !isComplete, error == nil { self?.startReceiving(connection) } } } - // Traitement d'une transaction reçue - private func handleReceivedTransaction(_ transaction: Transaction) { - if blockchain.submitTransaction(transaction) { - // Propager aux autres pairs si la transaction est valide - broadcastTransaction(transaction) - } - } - private func handleReceivedData(_ data: Data, from connection: NWConnection) { do { print("Received data of size: \(data.count) bytes") @@ -226,7 +222,7 @@ class Node { case .transaction: let transaction = try JSONDecoder().decode(Transaction.self, from: message.data) print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)") - if blockchain.submitTransaction(transaction) { + if addTransaction(transaction) { // Utiliser la méthode locale print("Transaction added to mempool successfully") } else { print("Failed to add transaction to mempool") @@ -299,15 +295,31 @@ class Node { private func handleReceivedBlockchain(_ receivedChain: [Block]) { print("Received blockchain with \(receivedChain.count) blocks") - // Vérifier si la chaîne reçue est plus longue et valide if receivedChain.count > blockchain.chain.count { print("Received chain is longer, validating...") - if blockchain.replaceChain(receivedChain) { - print("Blockchain updated successfully") - // Nettoyer le mempool des transactions qui sont maintenant dans la chaîne - blockchain.memPool.cleanupMempool(chain: receivedChain) + if blockchain.validateChain(receivedChain) { + // Sauvegarder l'état actuel + let oldChain = blockchain.chain + + // Essayer d'appliquer la nouvelle chaîne + blockchain.chain = receivedChain + + // Vérifier que toutes les transactions sont valides + var isValid = true + for block in receivedChain where !accountManager.processBlock(block) { + isValid = false + break + } + + if isValid { + print("Blockchain updated successfully") + cleanupMempool(chain: receivedChain) + } else { + blockchain.chain = oldChain + print("Failed to process transactions in received chain") + } } else { - print("Failed to update blockchain") + print("Received chain is invalid") } } else { print("Current chain is longer or equal, keeping it") @@ -334,11 +346,14 @@ class Node { } // Traiter les transactions du bloc - if !blockchain.accountManager.processBlock(block) { + if !accountManager.processBlock(block) { print("Failed to process block transactions") return } + // Nettoyer le mempool des transactions incluses dans le bloc + cleanupMempool(chain: [block]) + // Ajouter le bloc à la chaîne blockchain.chain.append(block) print("New block added to chain") @@ -369,3 +384,117 @@ class Node { } } } + +// MARK: - Mempool methods + +extension Node { + // MemPool functionality + private func addTransaction(_ transaction: Transaction) -> Bool { + if validateTransaction(transaction) { + pendingTransactions.append(transaction) + print("Transaction added to mempool successfully") + return true + } + return false + } + + private func getTransactionsForBlock() -> [Transaction] { + var validTransactions: [Transaction] = [] + var remainingTransactions: [Transaction] = [] + + for transaction in pendingTransactions { + if validTransactions.count >= maxTransactionsPerBlock { + break + } + + if validateTransaction(transaction) { + validTransactions.append(transaction) + } else { + remainingTransactions.append(transaction) + } + } + + // Update pending transactions + pendingTransactions = remainingTransactions + pendingTransactions.dropFirst(validTransactions.count) + + return validTransactions + } + + private func cleanupMempool(chain: [Block]) { + var remainingTransactions: [Transaction] = [] + + for pendingTx in pendingTransactions { + // On vérifie si la transaction est dans un des blocs + var isInChain = false + + for block in chain { + if block.transactions.contains(where: { chainTx in + chainTx.sender == pendingTx.sender && + chainTx.receiver == pendingTx.receiver && + chainTx.amount == pendingTx.amount && + chainTx.type == pendingTx.type + }) { + isInChain = true + break + } + } + + if !isInChain { + remainingTransactions.append(pendingTx) + } + } + + print("Mempool cleanup: removed \(pendingTransactions.count - remainingTransactions.count) transactions") + pendingTransactions = remainingTransactions + } + + var hasPendingTransactions: Bool { + return !pendingTransactions.isEmpty + } + + public func printMemPoolStatus() { + print(""" + MemPool Status: + - Pending transactions: \(pendingTransactions.count) + - Maximum transactions per block: \(maxTransactionsPerBlock) + """) + } + + private func checkDoubleSpending(_ transaction: Transaction) -> Bool { + let address = transaction.sender + let pendingAmount = pendingTransactions + .filter { $0.sender == address } + .reduce(0) { $0 + $1.amount } + + let currentBalance = accountManager.getBalance(address) + + if currentBalance < (pendingAmount + transaction.amount) { + print("MemPool: Transaction refusée - tentative de double dépense détectée") + return false + } + + return true + } + + private func validateTransaction(_ transaction: Transaction) -> Bool { + if !transaction.isValid() { + return false + } + + if transaction.type == "MINING_REWARD" { + return true + } + + if !checkDoubleSpending(transaction) { + return false + } + + if !accountManager.canProcessTransaction(transaction) { + print("MemPool: Transaction refusée - solde insuffisant") + return false + } + + print("MemPool: Transaction validée avec succès") + return true + } +} diff --git a/SwiftChainCore/Models/Transaction.swift b/SwiftChainCore/Models/Transaction.swift new file mode 100644 index 0000000..26c0c67 --- /dev/null +++ b/SwiftChainCore/Models/Transaction.swift @@ -0,0 +1,96 @@ +// +// File.swift +// +// +// Created by Victor BODINAUD on 31/03/2021. +// + +import Foundation + +public class Transaction: Codable { + public let sender: String + public let receiver: String + public let amount: Int + public let type: String + public var signature: Data? + public var senderPublicKey: Data? + + public init(sender: String, receiver: String, amount: Int, type: String) { + self.sender = sender + self.receiver = receiver + self.amount = amount + self.type = type + } + + // Pour encoder/décoder les Data optionnels + enum CodingKeys: String, CodingKey { + case sender, receiver, amount, type, signature, senderPublicKey + } + + func messageToSign() -> Data { + return "\(sender)\(receiver)\(amount)\(type)".data(using: .utf8)! + } + + func isSignatureValid() -> Bool { + guard let signature = signature, + let publicKeyData = senderPublicKey + else { + return false + } + + if type == "MINING_REWARD" { + return true + } + + return Wallet.verifySignature( + for: self, + signature: signature, + publicKeyData: publicKeyData + ) + } + + func isValid() -> Bool { + // Vérifications de base + if amount <= 0 { + print("Transaction: Amount must be positive") + return false + } + + if sender.isEmpty || receiver.isEmpty { + print("Transaction: Invalid addresses") + return false + } + + // Vérification du type + if !["TRANSFER", "MINING_REWARD"].contains(type) { + print("Transaction: Invalid type") + return false + } + + // Vérifications spécifiques selon le type + if type == "MINING_REWARD" { + if sender != "SYSTEM" { + print("Transaction: Mining reward must come from SYSTEM") + return false + } + if amount != 50 { // La récompense doit être exactement 50 + print("Transaction: Invalid mining reward amount") + return false + } + return true // Pas besoin de vérifier la signature pour les récompenses + } + + // Pour les transactions normales + if !isSignatureValid() { + print("Transaction: Invalid signature") + return false + } + + if sender == receiver { + print("Transaction: Sender cannot be receiver") + return false + } + + return true + } +} diff --git a/SwiftChain/Models/Wallet.swift b/SwiftChainCore/Models/Wallet.swift similarity index 81% rename from SwiftChain/Models/Wallet.swift rename to SwiftChainCore/Models/Wallet.swift index 8dff926..d1d2faf 100644 --- a/SwiftChain/Models/Wallet.swift +++ b/SwiftChainCore/Models/Wallet.swift @@ -5,15 +5,15 @@ // Created by Victor on 27/11/2024. // -import Foundation import CryptoKit +import Foundation -class Wallet { +public class Wallet { private let privateKey: Curve25519.Signing.PrivateKey let publicKey: Curve25519.Signing.PublicKey - let address: String + public let address: String - init() { + public init() { // Générer une nouvelle paire de clés privateKey = Curve25519.Signing.PrivateKey() publicKey = privateKey.publicKey @@ -26,7 +26,7 @@ class Wallet { } // Signer une transaction - func signTransaction(_ transaction: Transaction) -> Data? { + public func signTransaction(_ transaction: Transaction) -> Data? { let messageData = transaction.messageToSign() return try? privateKey.signature(for: messageData) } @@ -37,11 +37,11 @@ class Wallet { return false } - return (publicKey.isValidSignature(signature, for: transaction.messageToSign())) + return publicKey.isValidSignature(signature, for: transaction.messageToSign()) } // Obtenir la clé publique en format Data - func getPublicKeyData() -> Data { + public func getPublicKeyData() -> Data { return publicKey.rawRepresentation } } diff --git a/SwiftChainCore/SwiftChainCore.docc/SwiftChainCore.md b/SwiftChainCore/SwiftChainCore.docc/SwiftChainCore.md new file mode 100644 index 0000000..755d09e --- /dev/null +++ b/SwiftChainCore/SwiftChainCore.docc/SwiftChainCore.md @@ -0,0 +1,13 @@ +# ``SwiftChainCore`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/SwiftChainCore/SwiftChainCore.h b/SwiftChainCore/SwiftChainCore.h new file mode 100644 index 0000000..acf907f --- /dev/null +++ b/SwiftChainCore/SwiftChainCore.h @@ -0,0 +1,18 @@ +// +// SwiftChainCore.h +// SwiftChainCore +// +// Created by Victor on 02/12/2024. +// + +#import + +//! Project version number for SwiftChainCore. +FOUNDATION_EXPORT double SwiftChainCoreVersionNumber; + +//! Project version string for SwiftChainCore. +FOUNDATION_EXPORT const unsigned char SwiftChainCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/SwiftChainCoreTests/SwiftChainCoreTests.swift b/SwiftChainCoreTests/SwiftChainCoreTests.swift new file mode 100644 index 0000000..f93a923 --- /dev/null +++ b/SwiftChainCoreTests/SwiftChainCoreTests.swift @@ -0,0 +1,36 @@ +// +// SwiftChainCoreTests.swift +// SwiftChainCoreTests +// +// Created by Victor on 02/12/2024. +// + +import XCTest +@testable import SwiftChainCore + +final class SwiftChainCoreTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +}