Skip to content

[feature req] a couple more examples #2

@robbiemu

Description

@robbiemu

I am working in typescript, it would be awesome if there was a typescript example. The main reason I don't have one right now is because I am not yet clear on how I can race multiple threads to a goal.

Simple, clear example: signing a block in a (for learning) blockchain. This is done by generating a SHA256 of a block. We adjust how easy of a task that is by requiring more zeros at the front of the signature, and you are allowed to modify a value in the block to get there, called a nonce. The search is random, so you have to do a simple check very many times, and this is proof of work. With that out of the way, some example code:
note: this example is using rxjs for the pub/sub, but I could just as easily have used a Proxy if it was desirable

single threaded solution

export function detect(hash: string, difficulty: number) {
  let i, b
  for (i = 0, b = hash.length; i < b; i++) {
    if (hash[i] !== '0') {
      break;
    }
  }
  return i === difficulty;
}

export function getBlockAtDifficulty(block: Partial<Block>, difficulty: number) {
  let result: Block
  let nonce = 0
  do {
    result = new Block({ ...block, nonce: String(nonce) })
    nonce += 1
  } while (!detect(Hash.encode(String(result)), difficulty))
  return result
}

allowing the following test to pass:

describe('detect', () => {
  it('should find the nonce to produce a hash at a given difficulty', () => {

    const chain: Array<Block> = []
    const data = ['Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.']

    chain.push(Block.factoryGenesisBlock())
    chain.push(getBlockAtDifficulty(factoryNextBlock({ data }, chain), 4))

    expect(Hash.encode(String(chain[1])).slice(0, 4)).toEqual('0000')
  })
})

this is okay for learning purposes. But it is still singlethreaded. let's make better use of the cores with threadwork. This is what I have so far:

same library:

export interface MultiServiceThreadpoolResolution { [threadpool: string]: any }

@singleton
export class MultiService {
  private resolver = new Subject<MultiServiceThreadpoolResolution>()
  resolver$: Observable<MultiServiceThreadpoolResolution>
  constructor() {
    this.resolver$ = this.resolver.asObservable()
  }
  resolve(pr: Promise<any>, threadpool: string) {
    pr
      .then(v => this.resolver.next({ [threadpool]: v }))
      .catch(e => console.error(e))
  }
}

export function getBlockAtDifficultyMultiThreaded(block: Partial<Block>, difficulty: number) {
  const pool = rnew ThreadPool({ task: getBlockAtDifficulty });
  let result: Block

  return new Promise((resolve, reject) => {
    (async () => {
      try {
        result = await Promise.resolve(pool.run(block, difficulty))
        console.log(result)
      } catch (e) {
        reject(e)
      } finally {
        await pool.close()
        resolve(result)
      }
    })()
  })
}

with an imagined use like:

describe('detect', done => {
  it('should find the nonce to produce a hash at a given difficulty', () => {

    const chain: Array<Block> = []
    const data = ['Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.']

    chain.push(Block.factoryGenesisBlock())

    const service = new MultiService()
    service.resolve(getBlockAtDifficultyMultithreaded(factoryNextBlock({ data }, chain), 8), 'test-run')
    service.resolver$.pipe(first(value => value.hasOwnProperty('test-run')).subscribe(value => {
        expect(Hash.encode(String(value['test-run'])).slice(0, 8)).toEqual('00000000')
        done()
    })
  })
})

todo:

  • I need to change each thread so it adds to its nonce the nth number of its thread and the number of threads. This way each thread doesn't do the same work. So for example, with 4 threads the first thread needs to add 4 nonce starting at 0, the next adds 4 starting at 1, etc. How do I access the thread number within the thread?
  • I need to terminate all the threads as soon as any one of them finds a solution. The current pattern I believe will wait for all threads to resolve, then probably return an array?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions