Able to parse a List[ScriptToken] -> binary tree -> List[ScriptToken] for one set of test cases inside of ControlOperationsInterpreterTet -- this includes nested OP_IFs

This commit is contained in:
Chris Stewart 2017-09-07 15:41:01 -05:00
parent 78683ed956
commit 5058abac74
4 changed files with 117 additions and 80 deletions

View File

@ -16,4 +16,4 @@ mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => {
assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false)
testOptions in Test += Tests.Argument("-oF")
//testOptions in Test += Tests.Argument("-oF")

View File

@ -157,31 +157,82 @@ trait ControlOperationsInterpreter {
}
/** The loop that parses a list of [[ScriptToken]]s into a [[BinaryTree]]. */
@tailrec
private def loop(script : List[ScriptToken], tree : BinaryTree[ScriptToken]) : BinaryTree[ScriptToken] = {
/* logger.debug("Script : " + script)
logger.debug("Tree: " + tree)*/
private def loop(script : List[ScriptToken], tree : BinaryTree[ScriptToken]): BinaryTree[ScriptToken] = {
/* logger.debug("Script : " + script) */
logger.debug("Tree: " + tree)
logger.debug("script: " + (if (script.nonEmpty) script else Nil))
script match {
case OP_IF :: t =>
val (newTail, parsedTree) = parseOpIf(script)
val newTree = insertSubTree(tree,parsedTree)
loop(newTail, newTree)
case OP_NOTIF :: t =>
val (newTail, parsedTree) = parseOpNotIf(script)
val newTree = insertSubTree(tree,parsedTree)
loop(newTail, newTree)
case OP_ELSE :: t =>
val (newTail, parsedTree) = parseOpElse(script)
val newTree = insertSubTree(tree,parsedTree)
loop(newTail, newTree)
case OP_ENDIF :: t =>
val (newTail, parsedTree) = parseOpEndIf(script)
val newTree = insertSubTree(tree,parsedTree)
loop(newTail, newTree)
require(t.isEmpty, "Must not have any tail after parsing an OP_ENDIF, got: "+ t)
require(tree.value.isDefined && Seq(OP_IF,OP_NOTIF,OP_ELSE).contains(tree.value.get),
"Can only insert an OP_ENDIF on a tree root of OP_IF/NOTIF/ELSE, got: " + tree.value)
require(tree.right == Some(Empty), "Must have an empty right branch when inserting an OP_ENDIF onto our btree, got: " + tree.right)
Node(tree.value.get,tree.left.getOrElse(Empty),Leaf(OP_ENDIF))
case h :: t if (h == OP_IF || h == OP_NOTIF) =>
require(tree.left.getOrElse(Empty) == Empty, "Must have an empty left branch of the parent tree to insert an OP_IF, got: " + tree.left)
//find last OP_ENDIF in t
val endifs = t.zipWithIndex.filter(_._1 == OP_ENDIF)
if (endifs.size % 2 == 0) {
logger.debug("Even amount of OP_ENDIFs")
val nested = endifs.head
val nestedEndIfIndex = nested._2
logger.debug("nestedEndIfIndex: " + nestedEndIfIndex)
val nestedIf = t.take(nestedEndIfIndex)
logger.debug("nestedIf: " + nestedIf)
val opIfTree = loop(nestedIf,Node(h,Empty,Empty))
//add the last OP_ENDIF
val withENDIF = Node(tree.value.get, insertSubTree(opIfTree,Leaf(OP_ENDIF)),Empty)
logger.debug("withENDIF: " + withENDIF)
val remaining = t.splitAt(nestedEndIfIndex + 1)._2
logger.debug("remaining: " + remaining)
logger.debug("Parent tree: " + tree)
val remainingTree = loop(remaining,withENDIF)
//need to insert remainingTree in the OP_IF tree correctly, not sure if this is right
val fullTree = Node(withENDIF.value.get, withENDIF.left.getOrElse(Empty),
insertSubTree(withENDIF.right.getOrElse(Empty),remainingTree.right.getOrElse(Empty)))
logger.debug("Done with even amounts OP_ENDIFs")
fullTree
} else {
//if we have an odd amount of endifs it means we need to parse the first OP_IF
logger.debug("odd amounts of OP_ENDIFs")
val nested = endifs.last
val nestedEndIfIndex = nested._2
logger.debug("nestedEndIfIndex: " + nestedEndIfIndex)
val nestedIf = t.take(nestedEndIfIndex)
logger.debug("nestedIf: " + nestedIf)
val opIfTree = loop(nestedIf,Node(h,Empty,Empty))
//add the last OP_ENDIF
val withENDIF = insertSubTree(opIfTree,Leaf(OP_ENDIF))
val remaining = t.splitAt(nestedEndIfIndex + 1)._2
logger.debug("remaining: " + remaining)
val remainingTree = loop(remaining,Empty)
//need to insert remainingTree in the OP_IF tree correctly, not sure if this is right
val fullIf = Node(withENDIF.value.get, withENDIF.left.getOrElse(Empty),
insertSubTree(withENDIF.right.getOrElse(Empty),remainingTree))
val fullTree = tree match {
case Empty => fullIf
case l: Leaf[ScriptToken] => Node(l.v,fullIf,Empty)
case n: Node[ScriptToken] =>
require(n.l == Empty, "We can only insert an OP_IF on a left branch, it was not empty: " + n.l)
Node(n.v,fullIf,n.r)
}
logger.debug("Done with odd amounts of OP_ENDIFS")
fullTree
}
case h :: t if h == OP_ELSE =>
require(tree.value.isDefined && Seq(OP_IF, OP_NOTIF, OP_ELSE).contains(tree.value.get),
"Parent of OP_ELSE has to be an OP_IF/NOTIF/ELSE, got: " + tree.value)
require(tree.right.getOrElse(Empty) == Empty,"Right branch of tree should be Empty for an OP_ELSE, got: " + tree.right.get)
val subTree = loop(t,Node(OP_ELSE,Empty,Empty))
Node(tree.value.get,tree.left.getOrElse(Empty), subTree)
case (x: ScriptConstant) :: t => loop(t, insertSubTree(tree, Leaf(x)))
case (x: BytesToPushOntoStack) :: t => loop(t, insertSubTree(tree, Leaf(x)))
case h :: t => loop(t,insertSubTree(tree,Leaf(h)))
case Nil => tree
case Nil =>
logger.debug("Done parsing tree, got: " + tree)
tree
}
}
@ -191,58 +242,19 @@ trait ControlOperationsInterpreter {
* @param subTree the parse tree that needs to be inserted into the control flow of the program
* @return the full parse tree combined
*/
private def insertSubTree(tree : BinaryTree[ScriptToken],subTree : BinaryTree[ScriptToken]) : BinaryTree[ScriptToken] = {
//TODO: Optimize this to a tailrec function
//logger.debug("Inserting subTree: " + subTree + " into tree: " + tree)
tree match {
case Empty => subTree
case leaf : Leaf[ScriptToken] => Node(leaf.v, subTree, Empty)
case node : Node[ScriptToken] =>
if (subTree.value.isDefined && subTree.value.get == OP_ELSE) {
//need to insert the OP_ELSE within the proper OP_IF
//get count of OP_IFs and OP_ENDIFS inside of the tree
val opIfCount = node.l.count[ScriptToken](OP_IF)
val opNotIfCount = node.l.count[ScriptToken](OP_NOTIF)
val opEndIfCount = node.l.count[ScriptToken](OP_ENDIF)
//means that the subtree is not balanced, need to insert the OP_ELSE inside
//the left subtree
if (opIfCount + opNotIfCount != opEndIfCount) Node(node.v,insertSubTree(tree.left.get,subTree),node.r)
else Node(node.v,node.l,insertSubTree(tree.right.getOrElse(Empty),subTree))
} else if (node.r.value.isDefined && node.r.value.get == OP_ELSE) {
//since there is an OP_ELSE defined to right
//we need to insert all script tokens on that node
Node(node.v,node.l,insertSubTree(node.r,subTree))
}
else Node(node.v, insertSubTree(node.l, subTree), node.r)
private def insertSubTree(tree: BinaryTree[ScriptToken],
subTree: BinaryTree[ScriptToken]): BinaryTree[ScriptToken] = tree match {
case Empty => subTree
case leaf: Leaf[ScriptToken] =>
Node(leaf.v,subTree,Empty)
case node : Node[ScriptToken] if (node.v == OP_IF || node.v == OP_ELSE) =>
if (subTree.value.isDefined && Seq(OP_ELSE,OP_ENDIF).contains(subTree.value.get)) {
Node(node.v,node.l,insertSubTree(node.r,subTree))
} else {
Node(node.v,insertSubTree(node.l,subTree),node.r)
}
}
/** Parses an [[OP_IF]] [[ScriptToken]]. */
private def parseOpIf(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_IF :: t => (t, Node(OP_IF,Empty,Empty))
case h :: t => throw new IllegalArgumentException("Cannot parse " + h + " as an OP_IF")
case Nil => (script,Empty)
}
/** Parses an [[OP_NOTIF]] [[ScriptToken]]. */
private def parseOpNotIf(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_NOTIF :: t => (t, Node(OP_NOTIF,Empty,Empty))
case h :: t => throw new IllegalArgumentException("Cannot parse " + h + " as an OP_NOTIF")
case Nil => (script,Empty)
}
/** Parses an [[OP_ELSE]] [[ScriptToken]]. */
private def parseOpElse(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_ELSE :: t => (t,Node(OP_ELSE,Empty,Empty))
case h :: t => throw new RuntimeException("Cannot parse " + h + " as an OP_ELSE")
case Nil => (script,Empty)
}
/** Parses an [[OP_ENDIF]] [[ScriptToken]]. */
private def parseOpEndIf(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_ENDIF :: t => (t,Leaf(OP_ENDIF))
case h :: t => throw new IllegalArgumentException("Cannot parse " + h + " as an OP_ENDIF")
case Nil => (script,Empty)
case node: Node[ScriptToken] =>
Node(node.v,insertSubTree(node.l,subTree), node.r)
}
/** Checks if an [[OP_IF]]/[[OP_NOTIF]] [[ScriptToken]] has a matching [[OP_ENDIF]] */

View File

@ -100,7 +100,7 @@ trait BinaryTree[+T] {
}
}
def toSeq : Seq[T] = {
def toSeq: Seq[T] = {
@tailrec
def loop(tree : BinaryTree[T], accum : List[T], remainder : List[BinaryTree[T]]) : List[T] = tree match {
case Leaf(x) => if (remainder.isEmpty) accum ++ List(x) else loop(remainder.head,accum ++ List(x),remainder.tail)
@ -108,10 +108,10 @@ trait BinaryTree[+T] {
case Node(v,l,r) =>
loop(l,accum ++ List(v), r :: remainder)
}
loop(this,List(),List())
loop(this,Nil,Nil)
}
def toList : List[T] = toSeq.toList
def toList: List[T] = toSeq.toList
}
case class Node[T](v: T, l: BinaryTree[T], r: BinaryTree[T]) extends BinaryTree[T]

View File

@ -5,8 +5,11 @@ import org.bitcoins.core.script.ScriptProgram
import org.bitcoins.core.script.arithmetic.OP_ADD
import org.bitcoins.core.script.bitwise.OP_EQUAL
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.OP_CHECKSIG
import org.bitcoins.core.script.locktime.OP_CHECKSEQUENCEVERIFY
import org.bitcoins.core.script.reserved.{OP_RESERVED, OP_VER}
import org.bitcoins.core.script.result.{ScriptErrorInvalidStackOperation, ScriptErrorOpReturn}
import org.bitcoins.core.script.stack.OP_DROP
import org.bitcoins.core.util._
import org.scalatest.{FlatSpec, MustMatchers}
@ -14,7 +17,8 @@ import org.scalatest.{FlatSpec, MustMatchers}
* Created by chris on 1/6/16.
*/
class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with ControlOperationsInterpreter {
private def logger = BitcoinSLogger.logger
/*
"ControlOperationsInterpreter" must "have OP_VERIFY evaluate to true with '1' on the stack" in {
val stack = List(OP_TRUE)
val script = List(OP_VERIFY)
@ -123,6 +127,7 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
OP_ELSE, OP_1, OP_IF, OP_1, OP_ELSE, OP_RETURN, OP_ELSE, OP_1, OP_ENDIF, OP_ELSE, OP_RETURN, OP_ENDIF, OP_ADD, OP_2, OP_EQUAL)
findMatchingOpEndIf(script) must be (20)
}
*/
it must "parse a script as a binary tree then convert it back to the original list" in {
@ -139,16 +144,19 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
val script3 = List(OP_IF, OP_1, OP_ELSE, OP_0, OP_ENDIF)
val bTree3 = parseBinaryTree(script3)
bTree3.toSeq must be (script3)
val subTree1 = Node(OP_IF,Leaf(OP_0),Node(OP_ELSE,Leaf(OP_1),Leaf(OP_ENDIF)))
val subTree2 = Node(OP_ELSE,Node(OP_IF, Node(OP_2,Empty,Empty),Node(OP_ELSE,Node(OP_3,Empty,Empty), Leaf(OP_ENDIF))),Leaf(OP_ENDIF))
val expected: BinaryTree[ScriptToken] = Node(OP_IF,subTree1,subTree2)
val script4 = List(OP_IF, OP_IF, OP_0, OP_ELSE, OP_1, OP_ENDIF, OP_ELSE, OP_IF, OP_2, OP_ELSE, OP_3, OP_ENDIF, OP_ENDIF)
val bTree4 = parseBinaryTree(script4)
bTree4.left.get must be (subTree1)
bTree4.right.get must be (subTree2)
logger.debug("bTree4: " + bTree4)
bTree4.toSeq must be (script4)
val script5 = List(OP_IF, OP_1,OP_ELSE, OP_2, OP_ELSE, OP_3, OP_ENDIF)
parseBinaryTree(script5).toSeq must be (script5)
}
/*
it must "parse a script into a binary tree and have the OP_IF expression on the left branch and the OP_ELSE expression on the right branch"in {
val script = List(OP_IF,OP_0,OP_ELSE,OP_1,OP_ENDIF)
val bTree = parseBinaryTree(script)
@ -498,6 +506,23 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
removeFirstOpIf(asm) must be (Seq(OP_ELSE,OP_1,OP_ENDIF))
}
it must "remove the else statement but leave the OP_CHECKSIG after the OP_ENDIF in a RefundHTLC" in {
val tree = Node(OP_IF,Node(BytesToPushOntoStack(33),
Leaf(ScriptConstant("02a46b47ef58133c1539f30c12fce4ab01def25afc19c234f9da84f0bf1b2005c9")),Empty),
Node(OP_ELSE,
Node(BytesToPushOntoStack(8),
Node(ScriptConstant("fbeff03aa652d349"),
Node(OP_CHECKSEQUENCEVERIFY,
Node(OP_DROP,
Node(BytesToPushOntoStack(33),
Node(ScriptConstant("02d4b71bbbfac82806402d91004c80915b3fef98251a29bba1f5011119d80bb33e"),
Node(OP_ENDIF, Leaf(OP_CHECKSIG),Empty),Empty),Empty),Empty),Empty),Empty), Empty), Empty))
val result = removeFirstOpElse(tree)
result.toList must be (List(OP_IF, BytesToPushOntoStack(33),
ScriptConstant("02a46b47ef58133c1539f30c12fce4ab01def25afc19c234f9da84f0bf1b2005c9"), OP_ENDIF, OP_CHECKSIG))
}*/
}