Fixing bug where script constants were not being pushed onto the stack as one script constant. I.e. 0x05 was pushing 5 individual script tokens onto the stack instead of one script token with the 5 script tokens inside of it

This commit is contained in:
Chris Stewart 2016-02-03 10:06:49 -06:00
parent a617615cad
commit 45a688fd19
6 changed files with 52 additions and 20 deletions

View File

@ -92,16 +92,18 @@ trait ConstantInterpreter {
* @return
*/
@tailrec
def takeUntilBytesNeeded(scriptTokens : List[ScriptToken], accum : List[ScriptToken]) : (List[ScriptToken],List[ScriptToken]) = {
val bytesSum = accum.map(_.bytesSize).sum
if (bytesSum == bytesNeeded) (scriptTokens,accum)
def takeUntilBytesNeeded(scriptTokens : List[ScriptToken], scriptConstantAccum : ScriptConstant) : (List[ScriptToken],ScriptConstant) = {
val bytesSum = scriptConstantAccum.bytesSize
if (bytesSum == bytesNeeded) (scriptTokens,scriptConstantAccum)
else if (bytesSum > bytesNeeded) throw new RuntimeException("We cannot have more bytes than what our script number specified")
else takeUntilBytesNeeded(scriptTokens.tail, scriptTokens.head :: accum)
else takeUntilBytesNeeded(scriptTokens.tail, ScriptConstantImpl(scriptConstantAccum.hex + scriptTokens.head.hex))
}
val (newScript,tokensToBePushed) = takeUntilBytesNeeded(script.tail,List())
(tokensToBePushed ++ stack, newScript)
val (newScript,newScriptConstant) = takeUntilBytesNeeded(script.tail,ScriptConstantImpl(""))
//see if the new script constant can be converted into a script number
val scriptNumber : Option[ScriptNumber] = ScriptNumberFactory.fromHex(newScriptConstant.hex)
if (scriptNumber.isDefined) (scriptNumber.get :: stack, newScript)
else (newScriptConstant :: stack, newScript)
}
/**

View File

@ -94,13 +94,6 @@ case object OP_FALSE extends ScriptNumberOperation {
override def opCode = OP_0.opCode
override def scriptNumber = OP_0.scriptNumber
}
/**
* The number -1 is pushed onto the stack.
*/
case object OP_1NEGATE extends ScriptNumberOperation {
override def opCode = 79
override def scriptNumber = ScriptNumberFactory.factory(-1).get
}
/**
* The number 1 is pushed onto the stack.
@ -110,6 +103,15 @@ case object OP_TRUE extends ScriptNumberOperation {
override def scriptNumber = ScriptNumberFactory.factory(1).get
}
/**
* The number -1 is pushed onto the stack.
*/
case object OP_1NEGATE extends ScriptNumberOperation {
override def opCode = 79
override def scriptNumber = ScriptNumberFactory.factory(-1).get
}
/**
* The number 1 is pushed onto the stack.
*/

View File

@ -128,6 +128,20 @@ trait ControlOperationsInterpreter {
false
}
/**
* Marks transaction as invalid if top stack value is not true.
* @param stack
* @param script
* @return
*/
def opVerify(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken],List[ScriptToken],Boolean) = {
require(script.headOption.isDefined && script.head == OP_VERIFY, "Script top must be OP_VERIFY")
require(stack.size > 0, "Stack must have at least one element on it to run OP_VERIFY")
if (stack.head != OP_0) {
(stack,script.tail,true)
} else (stack,script.tail,false)
}
/**
* Parses a list of script tokens into its corresponding binary tree

View File

@ -71,6 +71,10 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
case OP_ELSE :: t => loop(opElse(stack,script))
case OP_ENDIF :: t => loop(opEndIf(stack,script))
case OP_RETURN :: t => opReturn(stack,script)
case OP_VERIFY :: t =>
val (newStack,newScript,result) = opVerify(stack,script)
if (result) loop(newStack,newScript)
else false
//crypto operations
case OP_HASH160 :: t => loop(hash160(stack,script))

View File

@ -2,7 +2,7 @@ package org.scalacoin.marshallers.script
import org.scalacoin.script.arithmetic.OP_ADD
import org.scalacoin.script.bitwise.OP_EQUAL
import org.scalacoin.script.constant.{OP_1NEGATE, ScriptConstantImpl}
import org.scalacoin.script.constant.{OP_1, OP_1NEGATE, ScriptConstantImpl}
import org.scalacoin.util.{ScalacoinUtil, TestUtil}
import org.scalatest.{FlatSpec, MustMatchers}
@ -60,4 +60,6 @@ class ScriptParserTest extends FlatSpec with MustMatchers with ScriptParser with
}

View File

@ -4,6 +4,7 @@ import java.io.File
import org.scalacoin.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY}
import org.scalacoin.script.constant._
import org.scalacoin.script.control.OP_VERIFY
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160}
import org.scalacoin.script.interpreter.testprotocol.{CoreTestCaseProtocol, CoreTestCase}
import org.scalacoin.script.stack.OP_DUP
@ -33,10 +34,17 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
it must "evaluate a script that asks to push 20 bytes onto the stack correctly" in {
val stack = List(ScriptConstantImpl("68ca4fec736264c13b859bac43d5173df6871682"))
val script = List(ScriptNumberImpl(20), ScriptConstantImpl("68ca4fec736264c13b859bac43d5173df6871682"), OP_EQUAL)
run(stack,script) must be (true)
}
it must "evaluate a 5 byte representation of 0x0000000001 as 0x01 when pushed onto the stack" in {
//this is for the following test case inside of script_valid.json
//["1 0x05 0x01 0x00 0x00 0x00 0x00", "VERIFY", "P2SH,STRICTENC", "values >4 bytes can be cast to boolean"]
val stack = List(OP_1)
val script = List(ScriptNumberImpl(5), ScriptNumberImpl(1), OP_0, OP_0, OP_0, OP_0,OP_VERIFY)
run(stack,script) must equal (true)
}
@ -46,13 +54,13 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
val source = scala.io.Source.fromFile("src/test/scala/org/scalacoin/script/interpreter/script_valid.json")
//use this to represent a single test case from script_valid.json
val lines =
/* val lines =
"""
|
|[["1", "NOTIF 0 NOTIF RETURN ELSE RETURN ELSE RETURN ENDIF ELSE 0 NOTIF 1 ELSE RETURN ELSE 1 ENDIF ELSE RETURN ENDIF ADD 2 EQUAL", "P2SH,STRICTENC"]]
""".stripMargin
|[["1 0x05 0x01 0x00 0x00 0x00 0x00", "VERIFY", "P2SH,STRICTENC", "values >4 bytes can be cast to boolean"]]
""".stripMargin*/
//val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val json = lines.parseJson
val testCasesOpt : Seq[Option[CoreTestCase]] = json.convertTo[Seq[Option[CoreTestCase]]]
val testCases : Seq[CoreTestCase] = testCasesOpt.flatten