0

I am studying blockchain and I am implementing a really simple "proof of work".

Proof of work:

export function mineBlock(difficulty: number, block) {
  const prefix = Array(difficulty + 1).join("0");

  function mine(block, difficulty) {
    const nonce = block.nonce + 1;
    const newBlock = {...block, nonce};
    const hash = calculateHash(newBlock);

    return hash.substring(0, difficulty) === prefix
                ? {...newBlock, hash}
                : mine({...newBlock, hash}, difficulty);
  }

  return trampoline(mine(block, difficulty));

}

Trampoline:

export function trampoline(func) {
  let result = func;

  while(result && typeof(result) === "function") {
    result = result();
  }

  return result;
}

I still get "Maximum call stack size exceeded" error, even trampolining mine function.

I've read a lot of other questions on StackOverflow and articles on various blog, but a lot of them only focus on "factorial" or "fibonacci" examples, where trampolines or TCE solves the problem... but that's not the case.

I am working with Node 10, so I don't mind if this doesn't work in browsers.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Michele Riva
  • 552
  • 9
  • 24
  • 2
    `trampoline(mine(block, difficulty))` this is not passing the function, this is executing the function and passing the result. `trampoline(mine)` - this passes a function (but you probably need to `bind` the arguments before you pass it). – Nir Alfasi Apr 18 '19 at 22:10
  • 1
    In addition to the given comment here is another hint: if you use a trampoline, there is no more recursion in the provided function. –  Apr 19 '19 at 06:23
  • Hi @alfasin thank you so much. I've tried passing the function as you suggested but I still get the same error :( https://pastebin.com/raw/T7HM8k33 – Michele Riva Apr 19 '19 at 08:13
  • 2
    If I had to guess based on your trampoline implementation, you will want `: () => mine(...)` instead of `: mine(...)` in the last line of `mine`. – Mulan Apr 19 '19 at 13:32

1 Answers1

2

Based on your trampoline -

export function trampoline(func) {
  let result = func;

  while(result && typeof(result) === "function") { // <--
    result = result();
  }

  return result;
}

You probably intended -

function mineBlock(difficulty, block) {
  const prefix = Array(difficulty + 1).join("0");

  function mine(block, difficulty) {
    const nonce = block.nonce + 1;
    const newBlock = {...block, nonce};
    const hash = calculateHash(newBlock);

    return hash.substring(0, difficulty) === prefix
                ? {...newBlock, hash}
                  // add `() => ...`
                : () => mine({...newBlock, hash}, difficulty); // <--
  }

  return trampoline(mine(block, difficulty));
}

But don't stop there. difficulty is shadowed unnecessarily. It's an argument of mine, but it never changes in the recurring call. You can remove it

function mineBlock(difficulty, block) {
  const prefix = Array(difficulty + 1).join("0")

  function mine(block) { // <--
    const nonce = block.nonce + 1
    const newBlock = {...block, nonce}
    const hash = calculateHash(newBlock)

    return hash.substring(0, difficulty) === prefix
                ? {...newBlock, hash}
                : () => mine({...newBlock, hash}) // <--
  }

  return trampoline(mine(block)) // <--
}

See how you write calculateHash as a separate function? You've mixed concerns of "checking difficulty" in with "mining". This should be a separate function too -

function checkDifficulty(n, hash) {
  return hash.substr(0,n) === "0".repeat(n)
}

function mineBlock(difficulty, block) {
  function mine(block) { 
    const nonce = block.nonce + 1
    const newBlock = {...block, nonce}
    const hash = calculateHash(newBlock)

    return checkDifficulty(difficulty, hash) // <--
                ? {...newBlock, hash}
                : () => mine({...newBlock, hash})
  }

  return trampoline(mine(block)) // <--
}

Separate concern of updating the nonce and hash as well -

function checkDifficulty(n, hash) {
  return hash.substr(0,n) === "0".repeat(n)
}

function nextNonce(block) {
  return updateHash({ ...block, nonce: block.nonce + 1 })
}

function updateHash(block) {
  return { ...block, hash: calculateHash(block) }
}

function mineBlock(difficulty, block) {
  function mine(block) {
    const newBlock = nextNonce(block) // <--

    return checkDifficulty(difficulty, newBlock.hash)
                ? newBlock
                : () => mine(newBlock)
  }

  return trampoline(mine(block)) // <--
}

Lastly, simplify mine by moving the nextNonce call outside of the loop

function checkDifficulty(n, hash) {
  return hash.substr(0,n) === "0".repeat(n)
}

function nextNonce(block) {
  return updateHash({ ...block, nonce: block.nonce + 1 })
}

function updateHash(block) {
  return { ...block, hash: calculateHash(block) }
}

function mineBlock(difficulty, block) {
  function mine(b) {
    return checkDifficulty(difficulty, b.hash)
                ? b
                : () => mine(nextNonce(b)) // <--
  }

  return trampoline(mine(nextNonce(block)) // <--
}

These are just possible lines you can draw in the sand. Hopefully this gives you an idea of how you can begin to make improvements to your program. You might chose to draw different lines and that's okay.

We might use a simple while loop now -

function mineBlock(difficulty, block) {
  let b = block
  while (!checkDifficulty(difficulty, b.hash))
    b = nextNonce(b)
  return b
}

Or an entirely different trampoline -

const loop = f =>
{ let a = f ()
  while (a && a.recur === recur)
    a = f (...a.values)
  return a
}

const recur = (...values) =>
  ({ recur, values })

const mineBlock = (difficulty, block) =>
  loop
    ( (b = block) =>
        checkDifficulty (difficulty, b)
          ? b
          : recur (nextNonce (b))
    )
Mulan
  • 129,518
  • 31
  • 228
  • 259