diff --git a/.gitignore b/.gitignore
index dfc3e2077942a9e57e79ccac71c736f59cd7c5d9..b8beaa7dcb1c2b9ea776f693b731872e55e3cc48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,5 @@ test/integration/documents-currency.js*
 test/integration/documents-currency.d.ts
 test/fast/modules/crawler/block_pulling.js*
 test/fast/modules/crawler/block_pulling.d.ts
+test/fast/fork*.js*
+test/fast/fork*.d.ts
diff --git a/test/fast/fork-resolution-3-3.ts b/test/fast/fork-resolution-3-3.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b3d4fa084b77d5c4cd4ff3497e979f09aaf5ca40
--- /dev/null
+++ b/test/fast/fork-resolution-3-3.ts
@@ -0,0 +1,373 @@
+import * as assert from 'assert'
+
+describe("Fork resolution 3-3 algo", () => {
+
+  it('should switch on a valid fork', () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  |         `- C12 -- C13 -- C14 -- C15 -- C16
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C12", "B11"),
+      Block.from("C13"),
+      Block.from("C14"),
+      Block.from("C15"),
+      Block.from("C16")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 16)
+    assert.equal(bc.current.hash, "C16")
+  })
+
+  it('should not switch if no fork block 3-3 exist', async () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  |         `- C12 -- C13 -- C14 -- C15
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C12", "B11"),
+      Block.from("C13"),
+      Block.from("C14"),
+      Block.from("C15")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 13)
+    assert.equal(bc.current.hash, "B13")
+  })
+
+  it('should eliminate a fork with missing blocks', async () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  |         `- C12 -- C13 -- C14 -- C15 -- C16
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C14"),
+      Block.from("C15")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 13)
+    assert.equal(bc.current.hash, "B13")
+  })
+
+  it('should eliminate a fork out of fork window', async () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  +  -- C11 -- C12 -- C13 -- C14 -- C15 -- C16
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C10"),
+      Block.from("C11"),
+      Block.from("C12"),
+      Block.from("C13"),
+      Block.from("C14"),
+      Block.from("C15"),
+      Block.from("C16")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 13)
+    assert.equal(bc.current.hash, "B13")
+  })
+
+  it('should accept a fork right on the limit of the fork window', async () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  |` -- C11 -- C12 -- C13 -- C14 -- C15 -- C16
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C11", "B10"),
+      Block.from("C12"),
+      Block.from("C13"),
+      Block.from("C14"),
+      Block.from("C15"),
+      Block.from("C16")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 16)
+    assert.equal(bc.current.hash, "C16")
+  })
+
+  it('should eliminate a fork whose 2nd block is invalid', async () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  |         `- C12 -- C13 -- C14 -- C15 -- C16
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C12", "B11"),
+      Block.from("C13"),
+      Block.from("C14", "", () => false),
+      Block.from("C15"),
+      Block.from("C16")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 13)
+    assert.equal(bc.current.hash, "B13")
+  })
+
+  it('should select the longest fork', async () => {
+
+    // B10 -- B11 -- B12 -- B13
+    //  |         `- C12 -- C13 -- C14 -- C15 -- C16
+    //  |                `- D13 -- D14 -- D15 -- D16 -- D17
+    //  |
+    //  `- (= B13 - ForkWindowSize)
+
+    const bc = new Blockchain(Block.from("B10"))
+    bc.add(Block.from("B11"))
+    bc.add(Block.from("B12"))
+    bc.add(Block.from("B13"))
+    assert.equal(bc.current.number, 13)
+    const sbx = new BlockSandbox([
+      Block.from("C12", "B11"),
+      Block.from("C13"),
+      Block.from("C14"),
+      Block.from("C15"),
+      Block.from("C16"),
+      Block.from("D13", "C12"),
+      Block.from("D14"),
+      Block.from("D15"),
+      Block.from("D16"),
+      Block.from("D17")
+    ])
+    const switcher = new BlockchainSwitcher(bc, sbx)
+    switcher.tryToFork()
+    assert.equal(bc.current.number, 17)
+    assert.equal(bc.current.hash, "D17")
+  })
+})
+
+const avgGenTime = 5 * 60
+const forkWindowSize = 3
+
+class BlockchainSwitcher {
+
+  constructor(private bc:Blockchain, private sbx:BlockSandbox) {}
+
+  tryToFork() {
+    const current = this.bc.current
+    const numberStart = current.number + 3
+    const timeStart = current.time + 3*avgGenTime
+    const suites:Block[][] = []
+    const potentials:Block[] = this.sbx.getPotentials(numberStart, timeStart)
+    const invalids: { [hash:string]: Block } = {}
+    // Phase 1: find chains
+    for (const candidate of potentials) {
+      const suite:Block[] = []
+      if (!invalids[candidate.hash] && !BlockchainSwitcher.suitesContains(suites, candidate)) {
+        let previous:Block|null = candidate, commonRootFound = false
+        while (previous && previous.number > current.number - forkWindowSize) {
+          suite.push(previous)
+          const previousNumber = previous.number - 1
+          const previousHash = previous.previousHash
+          previous = this.bc.getBlock(previousNumber, previousHash)
+          if (previous) {
+            // Stop the loop: common block has been found
+            previous = null
+            suites.push(suite)
+            commonRootFound = true
+          } else {
+            // Have a look in sandboxes
+            previous = this.sbx.getBlock(previousNumber, previousHash)
+          }
+        }
+        // Forget about invalid blocks
+        if (!commonRootFound) {
+          for (const b of suite) {
+            invalids[b.hash] = b
+          }
+        }
+      }
+    }
+    // Phase 2: select the best chain
+    let longestChain:null|Block[] = null
+    for (const s of suites) {
+      s.reverse()
+      const reverted = this.bc.revertTo(s[0].number - 1)
+      let added = true, i = 0, successfulBlocks:Block[] = []
+      while (added) {
+        try {
+          this.bc.add(s[i])
+          successfulBlocks.push(s[i])
+        } catch (e) {
+          added = false
+        }
+        i++
+      }
+      if (successfulBlocks.length) {
+        this.bc.revertTo(this.bc.current.number - successfulBlocks.length)
+      }
+      reverted.reverse()
+      for (const b of reverted) {
+        this.bc.add(b)
+      }
+      if ((!longestChain && successfulBlocks.length > 0) || (longestChain && longestChain.length < successfulBlocks.length)) {
+        longestChain = successfulBlocks
+      }
+    }
+    // Phase 3: a best exist? apply it if it respects the 3-3 rule
+    if (longestChain) {
+      const b = longestChain[longestChain.length - 1]
+      if (b.number >= numberStart && b.time >= timeStart) {
+        this.bc.revertTo(longestChain[0].number - 1)
+        for (const b of longestChain) {
+          this.bc.add(b)
+        }
+      }
+    }
+    return this.bc.current
+  }
+
+  static suitesContains(suites:Block[][], block:Block) {
+    for (const suite of suites) {
+      for (const b of suite) {
+        if (b.number === block.number && b.hash === block.hash) {
+          return true
+        }
+      }
+    }
+    return false
+  }
+}
+
+class BlockSandbox {
+
+  constructor(private blocks:Block[] = []) {}
+
+  getBlock(number:number, hash:string) {
+    for (const b of this.blocks) {
+      if (b.number === number && b.hash === hash) {
+        return b
+      }
+    }
+    return null
+  }
+
+  getPotentials(numberStart:number, timeStart:number) {
+    const potentials = []
+    for (const b of this.blocks) {
+      if (b.number >= numberStart && b.time >= timeStart) {
+        potentials.push(b)
+      }
+    }
+    return potentials
+  }
+}
+
+
+class Blockchain {
+
+  private blocks:Block[] = []
+
+  constructor(rootBlock:Block) {
+    this.blocks.push(rootBlock)
+  }
+
+  get current() {
+    return this.blocks[this.blocks.length - 1]
+  }
+
+  add(block:Block) {
+    if (!block.chainsOn(this.current)) {
+      throw "Unchainable"
+    }
+    this.blocks.push(block)
+    return block
+  }
+
+  getBlock(number:number, hash:string) {
+    for (const b of this.blocks) {
+      if (b.number === number && b.hash === hash) {
+        return b
+      }
+    }
+    return null
+  }
+
+  revertTo(number:number) {
+    const reverted:Block[] = []
+    if (this.current.number < number) {
+      throw "Already below this number"
+    }
+    while (this.current.number > number) {
+      const poped:Block = this.blocks.pop() as Block
+      reverted.push(poped)
+    }
+    return reverted
+  }
+}
+
+class Block {
+
+  private constructor(public chain:string, public number:number, private thePreviousHash:string, private chainsOnHook: (previous:Block)=>boolean = () => true) {
+  }
+
+  get hash() {
+    return [this.chain, this.number].join('')
+  }
+
+  get time() {
+    return this.number * avgGenTime
+  }
+
+  get previousHash() {
+    return this.thePreviousHash || [this.chain, this.number - 1].join('')
+  }
+
+  chainsOn(previous:Block) {
+    return this.number === previous.number + 1 && (this.chain === previous.chain || this.previousHash === previous.hash) && this.chainsOnHook(previous)
+  }
+
+  static from(hash:string, previousHash = "", chainsOnHook: undefined|((previous:Block)=>boolean) = undefined) {
+    const match = hash.match(/([A-Z])(\d+)/)
+    const chain = match && match[1] || ""
+    const number = parseInt(match && match[2] || "0")
+    return new Block(chain, number, previousHash, chainsOnHook)
+  }
+}
\ No newline at end of file