0

I've been trying to wrap my head around multiprocessing using an old python bitcoin mining program. Although relatively useless for mining, I figured this would be a great way to explore multiprocessing. However, I've hit a wall when it comes to stopping the processes when one of them achieves the goal they are all working towards.

I want to kill all multiprocessing pools when one of them finds the solution. Then allow the program to continue. I have tried terminate() and join(). I've attempted to include an Event(). I've tried using Process instead of Pool with the direction of a similar issue here: Killing a multiprocessing process when condition is met. However, same problem. How can I stop all processes after a condition is met without exiting the program with something like sys.exit() that would kill the entire program?

I tried also apply_sync with the direction from this post: Python Multiprocess Pool. How to exit the script when one of the worker process determines no more work needs to be done? However, it did not solve the problem of needing to continue executing the final functions of the program. In fact, it actually slowed the program significantly.

For clarity, I've included the code I tried based on the above mentioned link here:

from multiprocessing import Pool
from hashlib import sha256
import time





def SHA256(text):
    return sha256(text.encode("ascii")).hexdigest()

def solution_helper(args):
    solution, nonce = do_job(args)
    if solution:
        print(f"\nNonce Found: {nonce}\n")
        return True
    else:
        return False


class Mining():
    def __init__(self, workers, initargs):
        self.pool = Pool(processes=workers, initargs=initargs)

    def callback(self, result):
        if result:
            print('Solution Found...Terminating Processes...')
            self.pool.terminate()

    def do_job(self):
        for args in values:
            start_nonce = args[0]
            end_nonce = args[1]
            prefix_str = '0'*difficulty
            self.pool.apply_async(solution_helper, args=args, callback=self.callback)
            start = time.time()
            for nonce in range(start_nonce, end_nonce):
                text = str(block_number) + transactions + previous_hash + str(nonce)
                new_hash = SHA256(text)
                if new_hash.startswith(prefix_str):
                    print(f"Hashing: {text}")
                    print(f"\nSuccessfully mined bitcoin with nonce value: {nonce}\n")
                    print(f"New hash: {new_hash}")
                    total_time = str((time.time()-start))
                    print(f"\nEnd mning... Mining took {total_time} seconds\n")
                    return new_hash, nonce

        self.pool.close()
        self.pool.join()
        print('.Goodbye.')


block_number = 5
transactions = """
        bill->steve->20,
        jan->phillis->45
        """
previous_hash = '0000000b7c7723e4d3a8654c975fe4dd23d4d37f22d0ea7e5abde2225d1567dc6'
values = [(20000, 100000), (100000, 1000000), (1000000, 10000000), (10000000, 100000000)]
difficulty = 4
m = Mining(5, values)
m.do_job()

Here's the basic concept. It works great to start the processes, but I cannot figure out how to stop them:

from multiprocessing import Pool
from hashlib import sha256
import functools


MAX_NONCE = 1000000000

def SHA256(text):
    return sha256(text.encode("ascii")).hexdigest()

def nonce(block_number, transactions, previous_hash, prefix_str):
    import time
    start = time.time()
    for nonce in range(MAX_NONCE):
        text = str(block_number) + transactions + previous_hash + str(nonce)
        new_hash = SHA256(text)
        if new_hash.startswith(prefix_str):
            print(f"\nYay! Successfully mined bitcoins with nonce value:{nonce}")
            total_time = str((time.time()-start))
            print(f"\nend mining. Mining took: {total_time} seconds\n")
            print(new_hash + "\n")

def mine(block_number, transactions, previous_hash, prefix_zeros):
    from multiprocessing import Pool
    with Pool(4) as p:
        prefix_str = '0'*prefix_zeros
        p.map(nonce(block_number, transactions, previous_hash, prefix_str), [20000, 40000, 60000, 80000, 100000])


if __name__=='__main__':
    transactions="""
        bill->steve->20,
        jan->phillis->45
        """
    difficulty=7
    print("\nstart mining\n")
    new_hash = mine(5, transactions, '0000000b7c7723e4d3a8654c975fe4dd23d4d37f22d0ea7e5abde2225d1567dc6', difficulty)
    # Do some other things... Here is where I'd like to get to after the multiproccesses are killed
    print(f"\nMission Complete...{new_hash}\n") <---This never gets a chance to happen
Josh Crouse
  • 343
  • 1
  • 13
  • 1
    Does this answer your question? [Python Multiprocess Pool. How to exit the script when one of the worker process determines no more work needs to be done?](https://stackoverflow.com/questions/33447055/python-multiprocess-pool-how-to-exit-the-script-when-one-of-the-worker-process) – noxdafox Feb 14 '21 at 21:13
  • @noxdafox I believe it may. I'm going to make a few changes based on that post and see what happens. It appears on the surface `apply_async` with a `callback` function may solve the issue. Thanks, I'll let you know if it does the job. – Josh Crouse Feb 14 '21 at 21:49
  • @noxdafox I'm not super familiar with OOP. Why are you passing `init` in `w=Workers(num_proc, init, [total_count]`? – Josh Crouse Feb 14 '21 at 22:31
  • @noxdafox It does seem to exit the process, but also exits the entire program. That solution doesn't actually solve my problem. I need to allow the rest of the program to run. It also magnified the amount of time it took to find a solution almost x5. So, I'm not sure if that is the best way to go. Thank you for the direction though. – Josh Crouse Feb 14 '21 at 23:21
  • You call to `p.map` is totally wrong. The first argument to `map` will be the *return value* from your call to `nonce`, which will be `None`. So you will call `nonce` once and then you will get an exception: `TypeError: 'NoneType' object is not callable`. The second argument, `[20000, 40000, 60000, 80000, 100000]`, is not referenced anywhere by function `nonce`. You only get to pass to `map` the name of the function. If your function takes fixed arguments, then you need to use `functools.partial`. – Booboo Feb 16 '21 at 20:34
  • Change `nonce` to return `True` on success and `False` on failure. Instead of using `p.map`, make multiple calls to `p.apply_asyc` and build a list of `AsyncResult` instances. Then periodically loop through them calling `ready()` to see if a result has come back. If `ready` returns `True` then call `get()` on the instance and if the result is `True` you are done. When you get out of the `with` block, `p.terminate()` will be called for you, killing all processes in the pool. You could also instead use a callback with `apply_async` and an event. – Booboo Feb 16 '21 at 20:46

0 Answers0