10 Commits

Author SHA1 Message Date
Victor Bodinaud
cba2a778c3 🐛 Fix node connection & sync 2025-05-05 16:58:52 +02:00
Victor Bodinaud
7a71c6bbfe Launch 2 nodes 2025-05-05 16:12:59 +02:00
Victor Bodinaud
7fe82bde44 ⚠️ Fix warnings in console 2025-05-05 16:00:54 +02:00
Victor Bodinaud
1bbe8234fd Enhance blockchain 2025-05-05 15:16:53 +02:00
Victor Bodinaud
358bdda5a6 🐛 Change sync to be more consistent 2024-11-30 12:21:37 +01:00
Victor Bodinaud
22d8b22ea1 🐛 Fix blockchain sync 2024-11-30 12:03:03 +01:00
Victor Bodinaud
6d0777930d Add blockchain sync between nodes 2024-11-30 11:49:31 +01:00
Victor Bodinaud
3884a2a0ca 💬 Add logs to mempool 2024-11-30 11:38:26 +01:00
Victor Bodinaud
212b175b6b Add p2p 2024-11-27 20:41:51 +01:00
Victor Bodinaud
9905bf2964 Add mempool & transactions 2024-11-27 19:42:01 +01:00
20 changed files with 1726 additions and 203 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -7,9 +7,27 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
57BA298C2CF77907009E4448 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 57BA298B2CF77907009E4448 /* CryptoSwift */; }; 576693F62DC8EDCE0024463C /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 576693F52DC8EDCE0024463C /* CryptoSwift */; };
57B6CA1A2CFDFA39009F401F /* SwiftChainCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */; };
/* End PBXBuildFile section */ /* 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 */ /* Begin PBXCopyFilesBuildPhase section */
57BA297E2CF778F5009E4448 /* CopyFiles */ = { 57BA297E2CF778F5009E4448 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
@@ -23,10 +41,35 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference 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; }; 57BA29802CF778F5009E4448 /* SwiftChain */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwiftChain; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* 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 */ /* Begin PBXFileSystemSynchronizedRootGroup section */
57B6CA112CFDFA39009F401F /* SwiftChainCore */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
57B6CA252CFDFA39009F401F /* Exceptions for "SwiftChainCore" folder in "SwiftChainCore" target */,
);
path = SwiftChainCore;
sourceTree = "<group>";
};
57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = SwiftChainCoreTests;
sourceTree = "<group>";
};
57BA29822CF778F5009E4448 /* SwiftChain */ = { 57BA29822CF778F5009E4448 /* SwiftChain */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
path = SwiftChain; path = SwiftChain;
@@ -35,11 +78,26 @@
/* End PBXFileSystemSynchronizedRootGroup section */ /* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase 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 */ = { 57BA297D2CF778F5009E4448 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
57BA298C2CF77907009E4448 /* CryptoSwift in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -50,6 +108,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
57BA29822CF778F5009E4448 /* SwiftChain */, 57BA29822CF778F5009E4448 /* SwiftChain */,
57B6CA112CFDFA39009F401F /* SwiftChainCore */,
57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */,
57BA29812CF778F5009E4448 /* Products */, 57BA29812CF778F5009E4448 /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@@ -58,13 +118,73 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
57BA29802CF778F5009E4448 /* SwiftChain */, 57BA29802CF778F5009E4448 /* SwiftChain */,
57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */,
57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
57B6CA0B2CFDFA39009F401F /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget 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 = (
5766942C2DC8FBA30024463C /* PBXTargetDependency */,
);
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 */ = { 57BA297F2CF778F5009E4448 /* SwiftChain */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 57BA29872CF778F5009E4448 /* Build configuration list for PBXNativeTarget "SwiftChain" */; buildConfigurationList = 57BA29872CF778F5009E4448 /* Build configuration list for PBXNativeTarget "SwiftChain" */;
@@ -76,6 +196,7 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
57B6CA3A2CFDFB1F009F401F /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = ( fileSystemSynchronizedGroups = (
57BA29822CF778F5009E4448 /* SwiftChain */, 57BA29822CF778F5009E4448 /* SwiftChain */,
@@ -98,6 +219,12 @@
LastSwiftUpdateCheck = 1610; LastSwiftUpdateCheck = 1610;
LastUpgradeCheck = 1610; LastUpgradeCheck = 1610;
TargetAttributes = { TargetAttributes = {
57B6CA0F2CFDFA39009F401F = {
CreatedOnToolsVersion = 16.1;
};
57B6CA182CFDFA39009F401F = {
CreatedOnToolsVersion = 16.1;
};
57BA297F2CF778F5009E4448 = { 57BA297F2CF778F5009E4448 = {
CreatedOnToolsVersion = 16.1; CreatedOnToolsVersion = 16.1;
}; };
@@ -121,11 +248,44 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
57BA297F2CF778F5009E4448 /* SwiftChain */, 57BA297F2CF778F5009E4448 /* SwiftChain */,
57B6CA0F2CFDFA39009F401F /* SwiftChainCore */,
57B6CA182CFDFA39009F401F /* SwiftChainCoreTests */,
); );
}; };
/* End PBXProject section */ /* 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 */ /* Begin PBXSourcesBuildPhase section */
57B6CA0C2CFDFA39009F401F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
57B6CA152CFDFA39009F401F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
57BA297C2CF778F5009E4448 /* Sources */ = { 57BA297C2CF778F5009E4448 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -135,7 +295,156 @@
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
5766942C2DC8FBA30024463C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 5766942B2DC8FBA30024463C /* 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 */ /* 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 */ = { 57BA29852CF778F5009E4448 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@@ -279,6 +588,24 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList 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" */ = { 57BA297B2CF778F5009E4448 /* Build configuration list for PBXProject "SwiftChain" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@@ -311,6 +638,15 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
576693F52DC8EDCE0024463C /* CryptoSwift */ = {
isa = XCSwiftPackageProductDependency;
productName = CryptoSwift;
};
5766942B2DC8FBA30024463C /* CryptoSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */;
productName = CryptoSwift;
};
57BA298B2CF77907009E4448 /* CryptoSwift */ = { 57BA298B2CF77907009E4448 /* CryptoSwift */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */; package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */;

View File

@@ -6,8 +6,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git", "location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : { "state" : {
"revision" : "678d442c6f7828def400a70ae15968aef67ef52d", "revision" : "729e01bc9b9dab466ac85f21fb9ee2bc1c61b258",
"version" : "1.8.3" "version" : "1.8.4"
} }
} }
], ],

View File

@@ -5,6 +5,11 @@
<key>SchemeUserState</key> <key>SchemeUserState</key>
<dict> <dict>
<key>SwiftChain.xcscheme_^#shared#^_</key> <key>SwiftChain.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>SwiftChainCore.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>0</integer> <integer>0</integer>

View File

@@ -1,59 +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 {
var hash: String
var data: String
var previousHash: String
var index: Int
var nonce: Int
var timestamp: Int
/**
Initialize a block with the provided parts and specifications.
- parameters:
- hash: The hash of the block
- data: The data of the block
- previousHash: The hash of the previous block
- index: The index of the block
- nonce: The nonce of the block
- timestamp: The timestamp of the block
- returns: A beautiful new block for the blockchain.
*/
init(hash: String = "", data: String = "", previousHash: String = "", index: Int = 0, nonce: Int = 0, timestamp: Int = 0) {
self.data = data
self.previousHash = previousHash
self.index = index
self.nonce = nonce
self.timestamp = timestamp
self.hash = hash
}
/**
Generate the hash of the block.
- returns: The hash of the block
*/
func generateHash() -> String {
return data.sha256()
}
/**
Generate the timestamp of the block
- returns: The timestamp of the block
*/
func generateTimestamp() -> Int {
return Int(Date().timeIntervalSince1970)
}
}

View File

@@ -1,88 +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]()
/**
Initialize the first block of the blockchain.
- Parameters:
- data: The datas of the block
*/
func createGenesisBlock(data: String) {
let genesisBlock = Block()
genesisBlock.data = data
genesisBlock.previousHash = "0000"
genesisBlock.index = 0
genesisBlock.nonce = 0
genesisBlock.timestamp = genesisBlock.generateTimestamp()
genesisBlock.hash = genesisBlock.generateHash()
chain.append(genesisBlock)
print("Genesis block created -- hash: \(genesisBlock.hash)")
}
/**
Initialize a new block of the blockchain.
- Parameters:
- data: The datas of the block
*/
func createBlock(data: String) {
let newBlock = Block()
newBlock.data = data
newBlock.previousHash = chain.last!.hash
newBlock.index = chain.count
newBlock.nonce = 0
newBlock.timestamp = newBlock.generateTimestamp()
newBlock.hash = newBlock.generateHash()
chain.append(newBlock)
print("-- Block \(newBlock.index) created --\n hash: \(newBlock.hash)\n previous hash: \(newBlock.previousHash)\n data: \(newBlock.data)")
}
/**
Insert a corrupted block in the blockhain.
(for testing purpose)
*/
func insertCorruptedBlock() {
let newCorruptedBlock = Block()
newCorruptedBlock.data = "Corrupted block"
newCorruptedBlock.previousHash = "1234567890"
newCorruptedBlock.index = chain.count
newCorruptedBlock.nonce = 0
newCorruptedBlock.timestamp = newCorruptedBlock.generateTimestamp()
newCorruptedBlock.hash = newCorruptedBlock.generateHash()
chain.append(newCorruptedBlock)
print("-- Corrupted block \(newCorruptedBlock.index) created --\n hash: \(newCorruptedBlock.hash)\n previous hash: \(newCorruptedBlock.previousHash)\n data: \(newCorruptedBlock.data)")
}
/**
Check validity of the blockchain.
*/
func chainValidity() {
var isChainValid = true
var corruptedBlock = Block()
for i in 1...chain.count-1 {
if chain[i].previousHash != chain[i-1].hash {
isChainValid = false
corruptedBlock = chain[i]
}
}
print("Chain is valid : \(isChainValid)")
if !isChainValid {
print("Corrupted block is : \(corruptedBlock.index)")
}
}
}

View File

@@ -1,22 +0,0 @@
//
// File.swift
//
//
// Created by Victor BODINAUD on 31/03/2021.
//
import Foundation
class Transaction {
var sender: String
var receiver: String
var amount: Int
var type: String
init(sender: String, receiver: String, amount: Int, type: String) {
self.sender = sender
self.receiver = receiver
self.amount = amount
self.type = type
}
}

View File

@@ -7,38 +7,168 @@
// //
import Foundation import Foundation
import SwiftChainCore
// Create two nodes on different ports
let node1 = Node(port: 8333)
let node2 = Node(port: 8334)
// Track which node is active
var activeNode = node1
var activeNodeName = "Node 1"
let blockchain = Blockchain()
var command: String? var command: String?
var currentMinerAddress = "MINER1"
var wallets: [String: Wallet] = [:]
blockchain.createGenesisBlock(data: "") // Connect nodes to each other (add after node initialization)
node1.connectToPeer(host: "localhost", port: 8334) // Connect to node2
node2.connectToPeer(host: "localhost", port: 8333) // Connect to node1
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
- pending : Voir les transactions en attente
- validity : Vérifier la validité de la chaîne
- setminer : Changer l'adresse du mineur
- connect <host> : Se connecter à un pair
- peers : Liste des pairs connectés
- mempool : État du mempool
- switchnode : Basculer entre Node 1 et Node 2
- exit : Quitter
Node actif: \(activeNodeName)
""")
repeat { repeat {
print("Enter command:") LogManager.log("\nEntrer une commande:", level: .info)
command = readLine() command = readLine()?.lowercased()
// Create a new block switch command {
if command == "create" { case "connect":
print("data for the new block:") LogManager.log("Entrez l'adresse du pair (ex: 192.168.1.100):", level: .info)
let data = readLine() if let host = readLine() {
blockchain.createBlock(data: data ?? "") activeNode.connectToPeer(host: host)
} }
// Create a lot of blocks case "peers":
if command == "spam" { activeNode.listPeers()
for _ in 1...1000 {
blockchain.createBlock(data: "\((blockchain.chain.last?.index ?? 0)+1)") case "mine":
} if let _ = activeNode.mineBlock(minerAddress: currentMinerAddress) {
LogManager.log("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)", level: .success)
LogManager.log("Nouveau solde: \(activeNode.getBalance(currentMinerAddress))", level: .info)
} else {
LogManager.log("Échec du minage", level: .error)
} }
// Check validity of the blockchain case "balance":
if command == "validity" { LogManager.log("Entrer l'adresse:", level: .info)
blockchain.chainValidity() if let address = readLine() {
let balance = activeNode.getBalance(address)
LogManager.log("Solde de \(address): \(balance)", level: .info)
} }
// Insert a corrupted block case "setminer":
if command == "corrupt" { LogManager.log("Nouvelle adresse du mineur:", level: .info)
blockchain.insertCorruptedBlock() if let address = readLine() {
currentMinerAddress = address
LogManager.log("Adresse du mineur mise à jour: \(currentMinerAddress)", level: .success)
}
case "pending":
let transactions = activeNode.getPendingTransactions()
LogManager.log("Transactions en attente: \(transactions.count)", level: .info)
for (index, tx) in transactions.enumerated() {
LogManager.log("""
\(index + 1). De: \(tx.sender)
À: \(tx.receiver)
Montant: \(tx.amount)
Type: \(tx.type)
""", level: .info)
}
case "validity":
let isValid = activeNode.isChainValid()
LogManager.log("Chaîne valide: \(isValid)", level: .success)
case "exit":
LogManager.log("Au revoir!", level: .info)
case "createwallet":
let wallet = Wallet()
wallets[wallet.address] = wallet
LogManager.log("Nouveau wallet créé!", level: .success)
LogManager.log("Adresse: \(wallet.address)", level: .success)
case "listwallet":
LogManager.log("\nWallets disponibles:", level: .info)
for (address, _) in wallets {
LogManager.log("- \(address) (Solde: \(activeNode.getBalance(address)))", level: .info)
}
case "send":
LogManager.log("Votre adresse (wallet):", level: .info)
guard let senderAddress = readLine(),
let wallet = wallets[senderAddress]
else {
LogManager.log("Wallet non trouvé", level: .error)
break
}
LogManager.log("Adresse du destinataire:", level: .info)
guard let receiverAddress = readLine() else {
LogManager.log("Adresse du destinataire invalide", level: .error)
break
}
print("Montant:")
guard let amountStr = readLine(),
let amount = Int(amountStr),
amount > 0
else {
LogManager.log("Montant invalide", level: .error)
break
}
let transaction = Transaction(
sender: senderAddress,
receiver: receiverAddress,
amount: amount,
type: "TRANSFER"
)
transaction.senderPublicKey = wallet.getPublicKeyData()
if let signature = wallet.signTransaction(transaction) {
transaction.signature = signature
if activeNode.submitTransaction(transaction) {
LogManager.log("Transaction signée et propagée au réseau!", level: .success)
} else {
LogManager.log("Erreur lors de l'envoi de la transaction", level: .error)
}
} else {
LogManager.log("Erreur lors de la signature de la transaction", level: .error)
}
case "mempool":
activeNode.printMemPoolStatus()
case "switchnode":
if activeNode === node1 {
activeNode = node2
activeNodeName = "Node 2"
} else {
activeNode = node1
activeNodeName = "Node 1"
}
LogManager.log("Node actif: \(activeNodeName)", level: .info)
default:
LogManager.log("Commande inconnue", level: .error)
} }
} while command != "exit" } while command != "exit"

View File

@@ -7,7 +7,7 @@
import Foundation import Foundation
class Account { public class Account {
var id: String var id: String
var balance: Int var balance: Int

View File

@@ -0,0 +1,66 @@
//
// AccountManager.swift
// SwiftChain
//
// Created by Victor on 27/11/2024.
//
public class AccountManager {
private var accounts: [String: Account] = [:]
private let miningReward = 50
func getAccount(_ address: String) -> Account {
if let account = accounts[address] {
return account
}
let newAccount = Account(id: address, balance: 0)
accounts[address] = newAccount
return newAccount
}
func canProcessTransaction(_ transaction: Transaction) -> Bool {
if transaction.type == "MINING_REWARD" {
return true
}
let senderAccount = getAccount(transaction.sender)
return senderAccount.balance >= transaction.amount
}
func processTransaction(_ transaction: Transaction) -> Bool {
if !canProcessTransaction(transaction) {
return false
}
if transaction.type == "MINING_REWARD" {
let minerAccount = getAccount(transaction.receiver)
minerAccount.balance += miningReward
return true
}
let senderAccount = getAccount(transaction.sender)
let receiverAccount = getAccount(transaction.receiver)
senderAccount.balance -= transaction.amount
receiverAccount.balance += transaction.amount
return true
}
func getBalance(_ address: String) -> Int {
return getAccount(address).balance
}
func processBlock(_ block: Block) -> Bool {
let tempAccounts = accounts
for transaction in block.transactions {
if !processTransaction(transaction) {
accounts = tempAccounts
return false
}
}
return true
}
}

View File

@@ -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)
}

View File

@@ -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..<chain.count {
let block = chain[i]
let previousBlock = chain[i - 1]
if block.previousHash != previousBlock.hash {
print("Invalid chain at block \(i): incorrect previous hash")
return false
}
if !block.isValid() {
print("Invalid chain at block \(i): invalid block hash")
return false
}
}
return true
}
func validateChainTotally(_ chain: [Block]) -> 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..<chain.count {
let block = chain[i]
let previousBlock = chain[i - 1]
// Vérifier le chaînage
if block.previousHash != previousBlock.hash {
print("Chain: Invalid block sequence at height \(i)")
return false
}
// Vérifier l'index
if block.index != i {
print("Chain: Invalid block index at height \(i)")
return false
}
// Vérifier la chronologie
if block.timestamp <= previousBlock.timestamp {
print("Chain: Invalid timestamp at height \(i)")
return false
}
// Vérifier la difficulté
let expectedDifficulty = calculateExpectedDifficulty(at: i, chain: chain)
if block.difficulty != expectedDifficulty {
print("Chain: Invalid difficulty at height \(i)")
return false
}
// Vérifier le bloc lui-même
if !block.isValid() {
print("Chain: Invalid block at height \(i)")
return false
}
}
return true
}
private func calculateExpectedDifficulty(at index: Int, chain: [Block]) -> Int {
return chain[index].difficulty
}
}

View File

@@ -0,0 +1,38 @@
//
// LogManager.swift
// SwiftChain
//
// Created by Victor on 05/05/2025.
//
import os.log
public struct LogManager {
public enum LogLevel: String {
case info = "INFO"
case success = "SUCCÈS"
case warning = "ATTENTION"
case error = "ERREUR"
case network = "RÉSEAU"
case blockchain = "BLOCKCHAIN"
var logType: OSLogType {
switch self {
case .info, .success: return .info
case .warning: return .debug
case .error: return .error
case .network, .blockchain: return .default
}
}
}
static let log = OSLog(subsystem: "com.votreapp.swiftchain", category: "blockchain")
public static func log(_ message: String, level: LogLevel) {
os_log("%{public}@: %{public}@", log: log, type: level.logType, level.rawValue, message)
// Également afficher dans la console pour le CLI
let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
print("[\(timestamp)] [\(level.rawValue)] \(message)")
}
}

View File

@@ -0,0 +1,130 @@
//
// MemPool.swift
// SwiftChain
//
// Created by Victor on 27/11/2024.
//
public class MemPool {
private var pendingTransactions: [Transaction] = []
private let maxTransactionsPerBlock: Int = 10
private let accountManager: AccountManager
init(accountManager: AccountManager) {
self.accountManager = accountManager
}
/**
Ajoute une transaction au mempool après validation
*/
func addTransaction(_ transaction: Transaction) -> Bool {
if validateTransaction(transaction) {
pendingTransactions.append(transaction)
return true
}
return false
}
/**
Valide une transaction avant de l'ajouter au pool
*/
private func validateTransaction(_ transaction: Transaction) -> Bool {
// Vérifications basiques
if transaction.amount <= 0 {
print("MemPool: Transaction refusée - montant invalide")
return false
}
// Pas besoin de vérifier la signature pour les récompenses de minage
if transaction.type == "MINING_REWARD" {
return true
}
// Vérifier la signature
if !transaction.isSignatureValid() {
print("MemPool: Transaction refusée - signature invalide")
return false
}
// Vérifier le solde
if !accountManager.canProcessTransaction(transaction) {
print("MemPool: Transaction refusée - solde insuffisant")
return false
}
print("MemPool: Transaction validée avec succès")
return true
}
/**
Récupère un lot de transactions prêtes pour le prochain bloc
*/
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, removing the selected ones
pendingTransactions = remainingTransactions + pendingTransactions.dropFirst(validTransactions.count)
return validTransactions
}
/**
Vérifie si il y a des transactions en attente
*/
var hasPendingTransactions: Bool {
return !pendingTransactions.isEmpty
}
/**
Retourne toutes les transactions en attente sans les retirer
*/
func getAllPendingTransactions() -> [Transaction] {
return pendingTransactions
}
/**
* Nettoie le mempool en retirant les transactions qui sont dans la chaîne
*/
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
}
}
// Si la transaction n'est pas dans la chaîne, on la garde
if !isInChain {
remainingTransactions.append(pendingTx)
}
}
// Mettre à jour les transactions en attente
print("Mempool cleanup: removed \(pendingTransactions.count - remainingTransactions.count) transactions")
pendingTransactions = remainingTransactions
}
}

View File

@@ -0,0 +1,534 @@
//
// Node.swift
// SwiftChain
//
// Created by Victor on 27/11/2024.
//
import Foundation
import Network
public class Node {
private let nodeId = UUID().uuidString // Identifiant unique pour chaque nœud
private var processedMessages = Set<String>() // Pour traquer les messages déjà traités
// Network properties
private var peers: [NWConnection] = []
private var port: NWEndpoint.Port
private var listener: NWListener?
private let queue = DispatchQueue(label: "com.blockchain.node")
// Blockchain components
private let blockchain: Blockchain
private let accountManager: AccountManager
private var pendingTransactions: [Transaction] = []
private let maxTransactionsPerBlock: Int = 10
public init(port: NWEndpoint.Port = 8333) {
self.port = port
self.accountManager = AccountManager()
self.blockchain = Blockchain()
setupListener()
}
enum MessageType: String, Codable {
case transaction = "TX"
case blockchainRequest = "BC_REQ"
case blockchainResponse = "BC_RES"
case newBlock = "NEW_BLOCK"
}
struct NetworkMessage: Codable {
let type: MessageType
let data: Data
let senderId: String // Identifiant du nœud émetteur
let messageId: String // Identifiant unique du message
init(type: MessageType, data: Data, senderId: String) {
self.type = type
self.data = data
self.senderId = senderId
self.messageId = UUID().uuidString
}
}
// Configuration du listener
private func setupListener() {
let parameters = NWParameters.tcp
do {
listener = try NWListener(using: parameters, on: port)
listener?.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
print("Node is listening on port \(self?.port.rawValue ?? 0)")
case .failed(let error):
print("Listener failed with error: \(error)")
default:
break
}
}
listener?.newConnectionHandler = { [weak self] connection in
self?.handleNewConnection(connection)
print("New incoming connection from: \(connection.endpoint)")
}
listener?.start(queue: queue)
} catch {
print("Failed to create listener: \(error)")
}
}
public func mineBlock(minerAddress: String) -> Block? {
guard let lastBlock = blockchain.chain.last else {
print("No existing blockchain")
return nil
}
// Utiliser la méthode locale au lieu de memPool
var transactions = getTransactionsForBlock()
let miningReward = Transaction(
sender: "SYSTEM",
receiver: minerAddress,
amount: 50,
type: "MINING_REWARD"
)
transactions.append(miningReward)
let newBlock = Block(
transactions: transactions,
previousHash: lastBlock.hash,
index: blockchain.chain.count,
difficulty: blockchain.calculateNewDifficulty()
)
newBlock.miner = minerAddress
newBlock.timestamp = Int(Date().timeIntervalSince1970)
let miningTime = newBlock.mineBlock()
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
""")
return newBlock
}
return nil
}
// Public interface
public func submitTransaction(_ transaction: Transaction) -> Bool {
if addTransaction(transaction) {
broadcastTransaction(transaction)
return true
}
return false
}
public func getBalance(_ address: String) -> Int {
return accountManager.getBalance(address)
}
public func getPendingTransactions() -> [Transaction] {
return pendingTransactions
}
public func isChainValid() -> Bool {
return blockchain.validateChain(blockchain.chain)
}
// Connexion à un pair
public func connectToPeer(host: String, port: NWEndpoint.Port? = nil) {
let peerPort = port ?? self.port
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: peerPort)
let connection = NWConnection(to: endpoint, using: .tcp)
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
print("Connected to peer: \(host)")
self?.peers.append(connection)
self?.startReceiving(connection)
// Demander la blockchain au pair
self?.requestBlockchain(from: connection)
case .failed(let error):
print("Connection failed: \(error)")
case .cancelled:
print("Connection cancelled: \(host)")
default:
break
}
}
connection.start(queue: queue)
}
// Méthode pour lister les pairs connectés
public func listPeers() {
if peers.isEmpty {
print("Aucun pair connecté")
return
}
print("\nPairs connectés (\(peers.count)):")
for (index, peer) in peers.enumerated() {
print("\(index + 1). \(peer.endpoint)")
}
}
// Propagation d'une transaction
func broadcastTransaction(_ transaction: Transaction) {
do {
let transactionData = try JSONEncoder().encode(transaction)
let message = NetworkMessage(type: .transaction, data: transactionData, senderId: nodeId)
let messageData = try JSONEncoder().encode(message)
print("Broadcasting transaction to \(peers.count) peers")
for peer in peers {
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to send transaction: \(error)")
} else {
print("Transaction sent successfully to: \(peer.endpoint)")
}
})
}
} catch {
print("Failed to encode transaction: \(error)")
}
}
// Réception des messages
private func startReceiving(_ connection: NWConnection) {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] content, _, isComplete, error in
if let data = content {
self?.handleReceivedData(data, from: connection)
}
if let error = error {
print("Receive error: \(error)")
}
if !isComplete, error == nil {
self?.startReceiving(connection)
}
}
}
private func handleReceivedData(_ data: Data, from connection: NWConnection) {
do {
print("Received data of size: \(data.count) bytes")
let message = try JSONDecoder().decode(NetworkMessage.self, from: data)
print("Message decoded successfully, type: \(message.type)")
// Vérifier si le message a déjà été traité
if processedMessages.contains(message.messageId) {
print("Message déjà traité, ignoré")
return
}
// Ajouter le message à la liste des messages traités
processedMessages.insert(message.messageId)
switch message.type {
case .transaction:
let transaction = try JSONDecoder().decode(Transaction.self, from: message.data)
print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)")
if addTransaction(transaction) { // Utiliser la méthode locale
print("Transaction added to mempool successfully")
} else {
print("Failed to add transaction to mempool")
}
case .blockchainRequest:
// Envoyer notre blockchain au pair
sendBlockchain(to: connection)
case .blockchainResponse:
// Recevoir et traiter la blockchain
let receivedChain = try JSONDecoder().decode([Block].self, from: message.data)
handleReceivedBlockchain(receivedChain)
case .newBlock:
// Recevoir et traiter un nouveau bloc
let block = try JSONDecoder().decode(Block.self, from: message.data)
handleReceivedBlock(block)
}
} catch {
print("Failed to decode received data: \(error)")
}
}
private func handleNewConnection(_ connection: NWConnection) {
connection.start(queue: queue)
peers.append(connection)
startReceiving(connection)
print("New connection established with: \(connection.endpoint)")
// Envoyer notre blockchain au nouveau pair
sendBlockchain(to: connection)
}
private func requestBlockchain(from peer: NWConnection) {
do {
let message = NetworkMessage(type: .blockchainRequest, data: Data(), senderId: nodeId)
let messageData = try JSONEncoder().encode(message)
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to request blockchain: \(error)")
} else {
print("Blockchain request sent to: \(peer.endpoint)")
}
})
} catch {
print("Failed to encode blockchain request: \(error)")
}
}
private func sendBlockchain(to peer: NWConnection) {
do {
let chainData = try JSONEncoder().encode(blockchain.chain)
let message = NetworkMessage(type: .blockchainResponse, data: chainData, senderId: nodeId)
let messageData = try JSONEncoder().encode(message)
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to send blockchain: \(error)")
} else {
print("Blockchain sent to: \(peer.endpoint)")
}
})
} catch {
print("Failed to encode blockchain: \(error)")
}
}
private func handleReceivedBlockchain(_ receivedChain: [Block]) {
print("Received blockchain with \(receivedChain.count) blocks")
if receivedChain.count > blockchain.chain.count {
print("Received chain is longer, validating...")
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("Received chain is invalid")
}
} else {
print("Current chain is longer or equal, keeping it")
}
}
private func handleReceivedBlock(_ block: Block) {
print("Received new block: \(block.hash)")
if blockchain.chain.contains(where: { $0.hash == block.hash }) {
print("Bloc déjà dans la chaîne")
return
}
// Vérifier que le bloc suit bien notre dernier bloc
guard let lastBlock = blockchain.chain.last else {
print("No existing chain")
return
}
if block.previousHash != lastBlock.hash {
print("Le bloc ne se connecte pas à notre chaîne")
// Demander la chaîne complète aux pairs pour se synchroniser
for peer in peers {
requestBlockchain(from: peer)
}
return
}
if !block.isValid() {
print("Block is not valid")
return
}
// Traiter les transactions du bloc
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")
// Propager le bloc aux autres pairs (sauf celui qui nous l'a envoyé)
broadcastBlock(block)
}
private func broadcastBlock(_ block: Block) {
do {
let blockData = try JSONEncoder().encode(block)
let message = NetworkMessage(type: .newBlock, data: blockData, senderId: nodeId)
let messageData = try JSONEncoder().encode(message)
print("Broadcasting block to peers")
for peer in peers {
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to send block: \(error)")
} else {
print("Block sent successfully to: \(peer.endpoint)")
}
})
}
} catch {
print("Failed to encode block: \(error)")
}
}
}
// 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
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,47 @@
//
// Wallet.swift
// SwiftChain
//
// Created by Victor on 27/11/2024.
//
import CryptoKit
import Foundation
public class Wallet {
private let privateKey: Curve25519.Signing.PrivateKey
let publicKey: Curve25519.Signing.PublicKey
public let address: String
public init() {
// Générer une nouvelle paire de clés
privateKey = Curve25519.Signing.PrivateKey()
publicKey = privateKey.publicKey
// Créer une adresse au format swift_(hash)
let pubKeyData = publicKey.rawRepresentation
let hash = SHA256.hash(data: pubKeyData)
let hashString = hash.compactMap { String(format: "%02x", $0) }.joined()
address = "swift_" + hashString.prefix(40)
}
// Signer une transaction
public func signTransaction(_ transaction: Transaction) -> Data? {
let messageData = transaction.messageToSign()
return try? privateKey.signature(for: messageData)
}
// Vérifier une signature
static func verifySignature(for transaction: Transaction, signature: Data, publicKeyData: Data) -> Bool {
guard let publicKey = try? Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData) else {
return false
}
return publicKey.isValidSignature(signature, for: transaction.messageToSign())
}
// Obtenir la clé publique en format Data
public func getPublicKeyData() -> Data {
return publicKey.rawRepresentation
}
}

View File

@@ -0,0 +1,13 @@
# ``SwiftChainCore``
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
## Overview
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
## Topics
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->

View File

@@ -0,0 +1,18 @@
//
// SwiftChainCore.h
// SwiftChainCore
//
// Created by Victor on 02/12/2024.
//
#import <Foundation/Foundation.h>
//! 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 <SwiftChainCore/PublicHeader.h>

View File

@@ -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.
}
}
}