mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-18 21:34:39 +01:00
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:
parent
78683ed956
commit
5058abac74
@ -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")
|
||||
|
@ -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]] */
|
||||
|
@ -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]
|
||||
|
@ -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))
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user