Skip to content
This repository has been archived by the owner on Oct 28, 2022. It is now read-only.

Commit

Permalink
Ability to redeem from p2sh output (#404)
Browse files Browse the repository at this point in the history
- New methods in AbstractKit:
  * redeem(from: Output, to: String, feeRate: Int, publicKey: PublicKey, signatureScriptFunction: (Data, Data) -> Data)
  * watch(transaction: BitcoinCore.TransactionFilter, delegate: IWatchedTransactionDelegate)
  * changePublicKey() throws -> PublicKey
  * receivePublicKey() throws -> PublicKey
  * publicKey(byPath path: String) throws -> PublicKey
  • Loading branch information
esen committed Jul 13, 2019
1 parent 31b4bc7 commit 38fead4
Show file tree
Hide file tree
Showing 32 changed files with 759 additions and 67 deletions.
14 changes: 7 additions & 7 deletions BitcoinCashKit/BitcoinCashKit/Network/TestNet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ class TestNet: INetwork {
return Block(
withHeader: BlockHeader(
version: 0x20000000,
headerHash: "000000000000058417bfcbfaa5bd7c0449743d9a386331db58e4453bc77ae536".reversedData!,
previousBlockHeaderHash: "000000000000041abedc84c2ab85f72febbee655ed9d1dfdc9497126026e1bba".reversedData!,
merkleRoot: "cccf617e3ab704923dd45399649e7a5be11aa71ce344b7099b580c9d85445948".reversedData!,
timestamp: 1559627940,
bits: 0x1a065b0f,
nonce: 1911921100
headerHash: "00000000000001c4a2ebbed0841005d527c5177f323cd3df5d9f70463d9c28c7".reversedData!,
previousBlockHeaderHash: "000000000000046bf1879dc49620b0b12d4faaeda6f0ee033fc2cb86382ce571".reversedData!,
merkleRoot: "a9e1f20ec48ce7fae824c0dd76b4c4ec354d623e0ad2fac7b66a06984c0c0f81".reversedData!,
timestamp: 1562665562,
bits: 0x1a0509d6,
nonce: 437871866
),
height: 1307081)
height: 1313735)
}

}
12 changes: 12 additions & 0 deletions BitcoinCore/BitcoinCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@
2FA5D74EDAFB77BC86918E63 /* PeerDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DE9CAB1F15B04E2C54C9 /* PeerDiscovery.swift */; };
2FA5D806A7727E3BED0E54E7 /* DataObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D50922572F91F966DD4A /* DataObjects.swift */; };
2FA5D8284C7C94155AC50B7B /* GetBlockHashesTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DFC7FA0A5363BFFB8E28 /* GetBlockHashesTaskTests.swift */; };
2FA5D84AA43DA05CAC68B912 /* WatchedTransactionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D8E67003F981BCB89CE3 /* WatchedTransactionManager.swift */; };
2FA5D8B8A3A3F14EDCE2EB25 /* SignatureScriptSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D79D58E62B0A4FA3952A /* SignatureScriptSerializer.swift */; };
2FA5D91DCF7F320369162C5A /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D6676E3CB6028E1B6018 /* ReachabilityManager.swift */; };
2FA5D9D3DA2DF9BD402CE5DD /* SentTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF037E951950A0F7FF03 /* SentTransaction.swift */; };
2FA5D9ED521660FA5EEF360E /* MerkleBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D5C9BB0E185AF72B9174 /* MerkleBlock.swift */; };
2FA5DA409AB91D54163250D7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D9929AB239235F9B6895 /* Logger.swift */; };
2FA5DA939D3783E4377AF546 /* WatchedTransactionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D9C845F4D930457A5C6F /* WatchedTransactionManagerTests.swift */; };
2FA5DB74D510BA9F6D9525A4 /* BloomFilterManagerDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D0A6A8C6C5A4E2AAFB74 /* BloomFilterManagerDelegateTests.swift */; };
2FA5DB7F5BD8E25DCAF9AC83 /* PeerAddressManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5DF9ADB516A45C797889D /* PeerAddressManagerTests.swift */; };
2FA5DBABD18D15E003686989 /* BlockSyncerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D2DDF60A9C5DA3776D8A /* BlockSyncerState.swift */; };
Expand Down Expand Up @@ -287,11 +290,14 @@
2FA5D632A9EFB1F0CF2523DF /* PeerHostManagerDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerHostManagerDelegateTests.swift; sourceTree = "<group>"; };
2FA5D65667DA8BFD91E91C8B /* GetBlockHashesTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBlockHashesTask.swift; sourceTree = "<group>"; };
2FA5D6676E3CB6028E1B6018 /* ReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = "<group>"; };
2FA5D79D58E62B0A4FA3952A /* SignatureScriptSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignatureScriptSerializer.swift; sourceTree = "<group>"; };
2FA5D82EFF381CE6233790A9 /* KitStateProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KitStateProviderTests.swift; sourceTree = "<group>"; };
2FA5D859C070F9718CACF08F /* KitStateProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KitStateProvider.swift; sourceTree = "<group>"; };
2FA5D880471F191DBCDAE574 /* TransactionSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionSerializer.swift; sourceTree = "<group>"; };
2FA5D8E67003F981BCB89CE3 /* WatchedTransactionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchedTransactionManager.swift; sourceTree = "<group>"; };
2FA5D9929AB239235F9B6895 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
2FA5D99586B15F4477D695F7 /* IPeerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTests.swift; sourceTree = "<group>"; };
2FA5D9C845F4D930457A5C6F /* WatchedTransactionManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchedTransactionManagerTests.swift; sourceTree = "<group>"; };
2FA5DA9D7F78A26107F7209C /* InputSignerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSignerTests.swift; sourceTree = "<group>"; };
2FA5DAAAC2F759FD42948DDE /* IPeerTaskRequesterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPeerTaskRequesterTests.swift; sourceTree = "<group>"; };
2FA5DADEB0FD09138FD308A1 /* HDPrivateKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HDPrivateKeyTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -501,6 +507,7 @@
2FA5DAE996A847B90E5D033A /* UnspentOutputSelectorTests.swift */,
58AAAECE6429C4CBD7FD2394 /* UnspentOutputProviderTests.swift */,
58AAAA81938DAE46B8074C9D /* UnspentOutputSelectorSingleNoChangeTests.swift */,
2FA5D9C845F4D930457A5C6F /* WatchedTransactionManagerTests.swift */,
);
path = Managers;
sourceTree = "<group>";
Expand Down Expand Up @@ -707,6 +714,7 @@
58AAA481A82D05C914AB0CCA /* UnspentOutputProvider.swift */,
58AAA7A391E4B2F8C4E99B97 /* SyncedReadyPeerManager.swift */,
58AAA67094BBC66D3A385341 /* UnspentOutputSelectorSingleNoChange.swift */,
2FA5D8E67003F981BCB89CE3 /* WatchedTransactionManager.swift */,
);
path = Managers;
sourceTree = "<group>";
Expand Down Expand Up @@ -939,6 +947,7 @@
2FA5DCD21585D41C520DF764 /* TransactionInputSerializer.swift */,
2FA5DEC34722F594F85688C0 /* TransactionOutputSerializer.swift */,
2FA5D880471F191DBCDAE574 /* TransactionSerializer.swift */,
2FA5D79D58E62B0A4FA3952A /* SignatureScriptSerializer.swift */,
);
path = Serializers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1293,6 +1302,7 @@
58AAA7B5F60F31613515F273 /* BlockDiscoveryBatchTest.swift in Sources */,
58AAAA0E73B45A6262D8272F /* MerkleBranchTests.swift in Sources */,
58AAAE80BE4074DB3B1F0B1C /* UnspentOutputSelectorSingleNoChangeTests.swift in Sources */,
2FA5DA939D3783E4377AF546 /* WatchedTransactionManagerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1438,6 +1448,8 @@
58AAAF1F346D79776D04CEA6 /* UnspentOutputSelectorChain.swift in Sources */,
58AAAE8B6F9F832A13753E78 /* UnspentOutputSelectorSingleNoChange.swift in Sources */,
58AAAF671C4B3988EF6764B1 /* InsightApi.swift in Sources */,
2FA5D84AA43DA05CAC68B912 /* WatchedTransactionManager.swift in Sources */,
2FA5D8B8A3A3F14EDCE2EB25 /* SignatureScriptSerializer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
30 changes: 27 additions & 3 deletions BitcoinCore/BitcoinCore/Core/AbstractKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ open class AbstractKit {
return bitcoinCore.transactions(fromHash: fromHash, limit: limit)
}

open func send(to address: String, value: Int, feeRate: Int) throws {
try bitcoinCore.send(to: address, value: value, feeRate: feeRate)
open func send(to address: String, value: Int, feeRate: Int) throws -> FullTransaction {
return try bitcoinCore.send(to: address, value: value, feeRate: feeRate)
}

public func send(to hash: Data, scriptType: ScriptType, value: Int, feeRate: Int) throws -> FullTransaction {
return try bitcoinCore.send(to: hash, scriptType: scriptType, value: value, feeRate: feeRate)
}

public func redeem(from unspentOutput: UnspentOutput, to address: String, feeRate: Int, signatureScriptFunction: (Data, Data) -> Data) throws -> FullTransaction {
return try bitcoinCore.redeem(from: unspentOutput, to: address, feeRate: feeRate, signatureScriptFunction: signatureScriptFunction)
}

open func validate(address: String) throws {
Expand All @@ -54,8 +62,24 @@ open class AbstractKit {
return bitcoinCore.receiveAddress(for: type)
}

open func changePublicKey() throws -> PublicKey {
return try bitcoinCore.changePublicKey()
}

open func receivePublicKey() throws -> PublicKey {
return try bitcoinCore.receivePublicKey()
}

public func publicKey(byPath path: String) throws -> PublicKey {
return try bitcoinCore.publicKey(byPath: path)
}

open func watch(transaction: BitcoinCore.TransactionFilter, delegate: IWatchedTransactionDelegate) {
bitcoinCore.watch(transaction: transaction, delegate: delegate)
}

open var debugInfo: String {
return bitcoinCore.debugInfo
}

}
}
38 changes: 35 additions & 3 deletions BitcoinCore/BitcoinCore/Core/BitcoinCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class BitcoinCore {
private let cache: OutputsCache
private var dataProvider: IDataProvider
private let addressManager: IAddressManager
private let watchedTransactionManager: IWatchedTransactionManager
private let addressConverter: AddressConverterChain
private let unspentOutputSelector: UnspentOutputSelectorChain
private let kitStateProvider: IKitStateProvider & ISyncStateListener
Expand Down Expand Up @@ -63,6 +64,10 @@ public class BitcoinCore {
return self
}

func publicKey(byPath path: String) throws -> PublicKey {
return try addressManager.publicKey(byPath: path)
}

public func prepend(scriptBuilder: IScriptBuilder) {
self.scriptBuilder.prepend(scriptBuilder: scriptBuilder)
}
Expand All @@ -86,7 +91,7 @@ public class BitcoinCore {
blockValidatorChain: BlockValidatorChain, addressManager: IAddressManager, addressConverter: AddressConverterChain, unspentOutputSelector: UnspentOutputSelectorChain, kitStateProvider: IKitStateProvider & ISyncStateListener,
scriptBuilder: ScriptBuilderChain, transactionBuilder: ITransactionBuilder, transactionCreator: ITransactionCreator,
paymentAddressParser: IPaymentAddressParser, networkMessageParser: NetworkMessageParser, networkMessageSerializer: NetworkMessageSerializer,
syncManager: SyncManager) {
syncManager: SyncManager, watchedTransactionManager: WatchedTransactionManager) {
self.storage = storage
self.cache = cache
self.dataProvider = dataProvider
Expand All @@ -109,6 +114,7 @@ public class BitcoinCore {
self.networkMessageSerializer = networkMessageSerializer

self.syncManager = syncManager
self.watchedTransactionManager = watchedTransactionManager
}

}
Expand Down Expand Up @@ -143,8 +149,17 @@ extension BitcoinCore {
return dataProvider.transactions(fromHash: fromHash, limit: limit)
}

public func send(to address: String, value: Int, feeRate: Int) throws {
try transactionCreator.create(to: address, value: value, feeRate: feeRate, senderPay: true)
public func send(to address: String, value: Int, feeRate: Int) throws -> FullTransaction {
return try transactionCreator.create(to: address, value: value, feeRate: feeRate, senderPay: true)
}

public func send(to hash: Data, scriptType: ScriptType, value: Int, feeRate: Int) throws -> FullTransaction {
let address = try addressConverter.convert(keyHash: hash, type: scriptType)
return try send(to: address.stringValue, value: value, feeRate: feeRate)
}

func redeem(from unspentOutput: UnspentOutput, to address: String, feeRate: Int, signatureScriptFunction: (Data, Data) -> Data) throws -> FullTransaction {
return try transactionCreator.create(from: unspentOutput, to: address, feeRate: feeRate, signatureScriptFunction: signatureScriptFunction)
}

public func validate(address: String) throws {
Expand All @@ -163,6 +178,18 @@ extension BitcoinCore {
return (try? addressManager.receiveAddress(for: type)) ?? ""
}

public func changePublicKey() throws -> PublicKey {
return try addressManager.changePublicKey()
}

public func receivePublicKey() throws -> PublicKey {
return try addressManager.receivePublicKey()
}

func watch(transaction: BitcoinCore.TransactionFilter, delegate: IWatchedTransactionDelegate) {
watchedTransactionManager.add(transactionFilter: transaction, delegatedTo: delegate)
}

public var debugInfo: String {
return dataProvider.debugInfo
}
Expand Down Expand Up @@ -244,6 +271,11 @@ extension BitcoinCore {
case newWallet // Sync from lastCheckpointBlock. Api restore enabled
}

public enum TransactionFilter {
case p2shOutput(scriptHash: Data)
case outpoint(transactionHash: Data, outputIndex: Int)
}

}

extension BitcoinCore.KitState {
Expand Down
12 changes: 8 additions & 4 deletions BitcoinCore/BitcoinCore/Core/BitcoinCoreBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public class BitcoinCoreBuilder {
let initialSyncer = InitialSyncer(storage: storage, listener: kitStateProvider, stateManager: stateManager, blockDiscovery: blockDiscovery, addressManager: addressManager, logger: logger)

let bloomFilterLoader = BloomFilterLoader(bloomFilterManager: bloomFilterManager, peerManager: peerManager)
let watchedTransactionManager = WatchedTransactionManager(bloomFilterManager: bloomFilterManager)

let blockValidatorChain = BlockValidatorChain(proofOfWorkValidator: ProofOfWorkValidator(difficultyEncoder: DifficultyEncoder()))
let blockchain = Blockchain(storage: storage, blockValidator: blockValidatorChain, factory: factory, listener: dataProvider)
Expand All @@ -199,7 +200,8 @@ public class BitcoinCoreBuilder {

let inputSigner = InputSigner(hdWallet: hdWallet, network: network)
let scriptBuilder = ScriptBuilderChain()
let transactionBuilder = TransactionBuilder(unspentOutputSelector: unspentOutputSelector, unspentOutputProvider: unspentOutputProvider, addressManager: addressManager, addressConverter: addressConverter, inputSigner: inputSigner, scriptBuilder: scriptBuilder, factory: factory)
let transactionSizeCalculator = TransactionSizeCalculator()
let transactionBuilder = TransactionBuilder(unspentOutputSelector: unspentOutputSelector, unspentOutputProvider: unspentOutputProvider, addressManager: addressManager, addressConverter: addressConverter, inputSigner: inputSigner, scriptBuilder: scriptBuilder, factory: factory, transactionSizeCalculator: transactionSizeCalculator)
let transactionSender = TransactionSender(transactionSyncer: transactionSyncer, peerManager: peerManager, initialBlockDownload: initialBlockDownload, syncedReadyPeerManager: syncedReadyPeerManager, logger: logger)
let transactionCreator = TransactionCreator(transactionBuilder: transactionBuilder, transactionProcessor: transactionProcessor, transactionSender: transactionSender, bloomFilterManager: bloomFilterManager)

Expand All @@ -224,20 +226,22 @@ public class BitcoinCoreBuilder {
paymentAddressParser: paymentAddressParser,
networkMessageParser: networkMessageParser,
networkMessageSerializer: networkMessageSerializer,
syncManager: syncManager)
syncManager: syncManager,
watchedTransactionManager: watchedTransactionManager)

initialSyncer.delegate = syncManager
bloomFilterManager.delegate = bloomFilterLoader
dataProvider.delegate = bitcoinCore
kitStateProvider.delegate = bitcoinCore
transactionProcessor.transactionListener = watchedTransactionManager

bloomFilterManager.add(provider: watchedTransactionManager)

peerGroup.peerTaskHandler = bitcoinCore.peerTaskHandlerChain
peerGroup.inventoryItemsHandler = bitcoinCore.inventoryItemsHandlerChain

bitcoinCore.prepend(scriptBuilder: ScriptBuilder())
bitcoinCore.prepend(addressConverter: Base58AddressConverter(addressVersion: network.pubKeyHash, addressScriptVersion: network.scriptHash))

let transactionSizeCalculator = TransactionSizeCalculator()
bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelector(calculator: transactionSizeCalculator, provider: unspentOutputProvider))
bitcoinCore.prepend(unspentOutputSelector: UnspentOutputSelectorSingleNoChange(calculator: transactionSizeCalculator, provider: unspentOutputProvider))
// this part can be moved to another place
Expand Down
26 changes: 24 additions & 2 deletions BitcoinCore/BitcoinCore/Core/Protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public protocol IStorage {
func publicKey(byRawOrKeyHash: Data) -> PublicKey?
func add(publicKeys: [PublicKey])
func publicKeysWithUsedState() -> [PublicKeyWithUsedState]
func publicKey(byPath: String) -> PublicKey?
}

public protocol IAddressSelector {
Expand All @@ -134,17 +135,19 @@ public protocol IAddressSelector {

public protocol IAddressManager {
func changePublicKey() throws -> PublicKey
func receivePublicKey() throws -> PublicKey
func receiveAddress(for type: ScriptType) throws -> String
func fillGap() throws
func addKeys(keys: [PublicKey]) throws
func gapShifts() -> Bool
func publicKey(byPath: String) throws -> PublicKey
}

public protocol IBloomFilterManagerDelegate: class {
func bloomFilterUpdated(bloomFilter: BloomFilter)
}

public protocol IBloomFilterManager {
public protocol IBloomFilterManager: AnyObject {
var delegate: IBloomFilterManagerDelegate? { get set }
var bloomFilter: BloomFilter? { get }
func regenerateBloomFilter()
Expand Down Expand Up @@ -340,12 +343,14 @@ public protocol ITransactionSyncer: class {
}

public protocol ITransactionCreator {
func create(to address: String, value: Int, feeRate: Int, senderPay: Bool) throws
func create(to address: String, value: Int, feeRate: Int, senderPay: Bool) throws -> FullTransaction
func create(from: UnspentOutput, to address: String, feeRate: Int, signatureScriptFunction: (Data, Data) -> Data) throws -> FullTransaction
}

protocol ITransactionBuilder {
func fee(for value: Int, feeRate: Int, senderPay: Bool, address: String?) throws -> Int
func buildTransaction(value: Int, feeRate: Int, senderPay: Bool, toAddress: String) throws -> FullTransaction
func buildTransaction(from: UnspentOutput, to: String, feeRate: Int, signatureScriptFunction: (Data, Data) -> Data) throws -> FullTransaction
}

protocol IBlockchain {
Expand Down Expand Up @@ -527,3 +532,20 @@ protocol ITransactionSender {
protocol IMerkleBlockHandler: AnyObject {
func handle(merkleBlock: MerkleBlock) throws
}

protocol ITransactionListener: class {
func onReceive(transaction: FullTransaction)
}

public protocol IWatchedTransactionDelegate {
func transactionReceived(transaction: FullTransaction, outputIndex: Int)
func transactionReceived(transaction: FullTransaction, inputIndex: Int)
}

protocol IWatchedTransactionManager {
func add(transactionFilter: BitcoinCore.TransactionFilter, delegatedTo: IWatchedTransactionDelegate)
}

protocol IBloomFilterProvider {
func filterElements() -> [Data]
}
Loading

0 comments on commit 38fead4

Please sign in to comment.