Removing mutability from ScriptInterpreter, this should fix concurrency issues with running tests

This commit is contained in:
Chris Stewart 2016-08-02 11:14:19 -05:00
parent c27548cc20
commit 3d4e6ce7b0
2 changed files with 116 additions and 122 deletions

View file

@ -35,15 +35,6 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
*/ */
private lazy val maxScriptOps = 201 private lazy val maxScriptOps = 201
//TODO: Think about this more to change this from being mutable to immutable - perhaps
//passing this as an implicit argument to our loop function
/**
* The current operation count that a script has performed
* This is limited by bitcoin core to be 201 operations
*
*/
private var opCount = 0
/** /**
* Runs an entire script though our script programming language and * Runs an entire script though our script programming language and
* returns true or false depending on if the script was valid * returns true or false depending on if the script was valid
@ -65,7 +56,7 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
logger.error("P2SH scriptSigs are required to be push only by definition - see BIP16") logger.error("P2SH scriptSigs are required to be push only by definition - see BIP16")
ScriptProgram(programBeingExecuted,ScriptErrorSigPushOnly) ScriptProgram(programBeingExecuted,ScriptErrorSigPushOnly)
} else { } else {
val scriptSigExecutedProgram = loop(program) val scriptSigExecutedProgram = loop(program,0)
scriptPubKey match { scriptPubKey match {
case p2shScriptPubKey : P2SHScriptPubKey if (ScriptFlagUtil.p2shEnabled(program.flags)) => case p2shScriptPubKey : P2SHScriptPubKey if (ScriptFlagUtil.p2shEnabled(program.flags)) =>
executeP2shScript(scriptSigExecutedProgram, programBeingExecuted, p2shScriptPubKey) executeP2shScript(scriptSigExecutedProgram, programBeingExecuted, p2shScriptPubKey)
@ -78,7 +69,7 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
val scriptPubKeyProgram = ScriptProgram(scriptSigExecutedProgram.txSignatureComponent, val scriptPubKeyProgram = ScriptProgram(scriptSigExecutedProgram.txSignatureComponent,
scriptSigExecutedProgram.stack,scriptSigExecutedProgram.txSignatureComponent.scriptPubKey.asm) scriptSigExecutedProgram.stack,scriptSigExecutedProgram.txSignatureComponent.scriptPubKey.asm)
require(scriptPubKeyProgram.script == scriptSigExecutedProgram.txSignatureComponent.scriptPubKey.asm) require(scriptPubKeyProgram.script == scriptSigExecutedProgram.txSignatureComponent.scriptPubKey.asm)
val scriptPubKeyExecutedProgram : ExecutedScriptProgram = loop(scriptPubKeyProgram) val scriptPubKeyExecutedProgram : ExecutedScriptProgram = loop(scriptPubKeyProgram,0)
logger.info("Stack state after scriptPubKey execution: " + scriptPubKeyExecutedProgram.stack) logger.info("Stack state after scriptPubKey execution: " + scriptPubKeyExecutedProgram.stack)
@ -121,7 +112,7 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
scriptSigExecutedProgram scriptSigExecutedProgram
} else { } else {
val hashCheckProgram = ScriptProgram(originalProgram, scriptSigExecutedProgram.stack, p2shScriptPubKey.asm) val hashCheckProgram = ScriptProgram(originalProgram, scriptSigExecutedProgram.stack, p2shScriptPubKey.asm)
val hashesMatchProgram = loop(hashCheckProgram) val hashesMatchProgram = loop(hashCheckProgram,0)
hashesMatchProgram.stackTopIsTrue match { hashesMatchProgram.stackTopIsTrue match {
case true => case true =>
logger.info("Hashes matched between the p2shScriptSignature & the p2shScriptPubKey") logger.info("Hashes matched between the p2shScriptSignature & the p2shScriptPubKey")
@ -135,7 +126,7 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
if (ScriptFlagUtil.requirePushOnly(p2shRedeemScriptProgram.flags) && !BitcoinScriptUtil.isPushOnly(redeemScript.asm)) { if (ScriptFlagUtil.requirePushOnly(p2shRedeemScriptProgram.flags) && !BitcoinScriptUtil.isPushOnly(redeemScript.asm)) {
logger.error("p2sh redeem script must be push only operations whe SIGPUSHONLY flag is set") logger.error("p2sh redeem script must be push only operations whe SIGPUSHONLY flag is set")
ScriptProgram(p2shRedeemScriptProgram,ScriptErrorSigPushOnly) ScriptProgram(p2shRedeemScriptProgram,ScriptErrorSigPushOnly)
} else loop(p2shRedeemScriptProgram) } else loop(p2shRedeemScriptProgram,0)
case false => case false =>
logger.warn("P2SH scriptPubKey hash did not match the hash for the serialized redeemScript") logger.warn("P2SH scriptPubKey hash did not match the hash for the serialized redeemScript")
hashesMatchProgram hashesMatchProgram
@ -149,221 +140,212 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
* @return program the final state of the program after being evaluated by the interpreter * @return program the final state of the program after being evaluated by the interpreter
*/ */
@tailrec @tailrec
private def loop(program : ScriptProgram) : ExecutedScriptProgram = { private def loop(program : ScriptProgram, opCount: Int) : ExecutedScriptProgram = {
logger.debug("Stack: " + program.stack) logger.debug("Stack: " + program.stack)
logger.debug("Script: " + program.script) logger.debug("Script: " + program.script)
if (opCount > maxScriptOps && !program.isInstanceOf[ExecutedScriptProgram]) { if (opCount > maxScriptOps && !program.isInstanceOf[ExecutedScriptProgram]) {
logger.error("We have reached the maximum amount of script operations allowed") logger.error("We have reached the maximum amount of script operations allowed")
logger.error("Here are the remaining operations in the script: " + program.script) logger.error("Here are the remaining operations in the script: " + program.script)
loop(ScriptProgram(program,ScriptErrorOpCount)) loop(ScriptProgram(program,ScriptErrorOpCount),opCount)
} else if (program.script.flatMap(_.bytes).size > 10000 && !program.isInstanceOf[ExecutedScriptProgram]) { } else if (program.script.flatMap(_.bytes).size > 10000 && !program.isInstanceOf[ExecutedScriptProgram]) {
logger.error("We cannot run a script that is larger than 10,000 bytes") logger.error("We cannot run a script that is larger than 10,000 bytes")
program match { program match {
case p : PreExecutionScriptProgram => case p : PreExecutionScriptProgram =>
loop(ScriptProgram(ScriptProgram.toExecutionInProgress(p), ScriptErrorScriptSize)) loop(ScriptProgram(ScriptProgram.toExecutionInProgress(p), ScriptErrorScriptSize),opCount)
case _ : ExecutionInProgressScriptProgram | _ : ExecutedScriptProgram => case _ : ExecutionInProgressScriptProgram | _ : ExecutedScriptProgram =>
loop(ScriptProgram(program, ScriptErrorScriptSize)) loop(ScriptProgram(program, ScriptErrorScriptSize),opCount)
} }
} else { } else {
program match { program match {
case p : PreExecutionScriptProgram => loop(ScriptProgram.toExecutionInProgress(p,Some(p.stack))) case p : PreExecutionScriptProgram => loop(ScriptProgram.toExecutionInProgress(p,Some(p.stack)),opCount)
case p : ExecutedScriptProgram => case p : ExecutedScriptProgram =>
//reset opCount variable to zero since we may need to count the ops //reset opCount variable to zero since we may need to count the ops
//in the scriptPubKey - we don't want the op count of the scriptSig //in the scriptPubKey - we don't want the op count of the scriptSig
//to count towards the scriptPubKey op count //to count towards the scriptPubKey op count
logger.info("Final op count: " + opCount) logger.info("Final op count: " + opCount)
opCount = 0
p p
case p : ExecutionInProgressScriptProgram => case p : ExecutionInProgressScriptProgram =>
//increment the op count //increment the op count
if (p.script.headOption.isDefined && /* if (p.script.headOption.isDefined &&
BitcoinScriptUtil.countsTowardsScriptOpLimit(p.script.head)) opCount = opCount + 1 BitcoinScriptUtil.countsTowardsScriptOpLimit(p.script.head)) opCount = opCount + 1*/
p.script match { p.script match {
//if at any time we see that the program is not valid //if at any time we see that the program is not valid
//cease script execution //cease script execution
case _ if !p.script.intersect(Seq(OP_VERIF, OP_VERNOTIF)).isEmpty => case _ if !p.script.intersect(Seq(OP_VERIF, OP_VERNOTIF)).isEmpty =>
logger.error("Script is invalid even when a OP_VERIF or OP_VERNOTIF occurs in an unexecuted OP_IF branch") logger.error("Script is invalid even when a OP_VERIF or OP_VERNOTIF occurs in an unexecuted OP_IF branch")
loop(ScriptProgram(p, ScriptErrorBadOpCode)) loop(ScriptProgram(p, ScriptErrorBadOpCode),opCount)
//disabled splice operation //disabled splice operation
case _ if !p.script.intersect(Seq(OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT)).isEmpty => case _ if !p.script.intersect(Seq(OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT)).isEmpty =>
logger.error("Script is invalid because it contains a disabled splice operation") logger.error("Script is invalid because it contains a disabled splice operation")
loop(ScriptProgram(p, ScriptErrorDisabledOpCode)) loop(ScriptProgram(p, ScriptErrorDisabledOpCode),opCount)
//disabled bitwise operations //disabled bitwise operations
case _ if !p.script.intersect(Seq(OP_INVERT, OP_AND, OP_OR, OP_XOR)).isEmpty => case _ if !p.script.intersect(Seq(OP_INVERT, OP_AND, OP_OR, OP_XOR)).isEmpty =>
logger.error("Script is invalid because it contains a disabled bitwise operation") logger.error("Script is invalid because it contains a disabled bitwise operation")
loop(ScriptProgram(p, ScriptErrorDisabledOpCode)) loop(ScriptProgram(p, ScriptErrorDisabledOpCode),opCount)
//disabled arithmetic operations //disabled arithmetic operations
case _ if !p.script.intersect(Seq(OP_MUL, OP_2MUL, OP_DIV, OP_2DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT)).isEmpty => case _ if !p.script.intersect(Seq(OP_MUL, OP_2MUL, OP_DIV, OP_2DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT)).isEmpty =>
logger.error("Script is invalid because it contains a disabled arithmetic operation") logger.error("Script is invalid because it contains a disabled arithmetic operation")
loop(ScriptProgram(p, ScriptErrorDisabledOpCode)) loop(ScriptProgram(p, ScriptErrorDisabledOpCode),opCount)
//program cannot contain a push operation > 520 bytes //program cannot contain a push operation > 520 bytes
case _ if (p.script.exists(token => token.bytes.size > 520)) => case _ if (p.script.exists(token => token.bytes.size > 520)) =>
logger.error("We have a script constant that is larger than 520 bytes, this is illegal: " + p.script) logger.error("We have a script constant that is larger than 520 bytes, this is illegal: " + p.script)
loop(ScriptProgram(p, ScriptErrorPushSize)) loop(ScriptProgram(p, ScriptErrorPushSize),opCount)
//program stack size cannot be greater than 1000 elements //program stack size cannot be greater than 1000 elements
case _ if ((p.stack.size + p.altStack.size) > 1000) => case _ if ((p.stack.size + p.altStack.size) > 1000) =>
logger.error("We cannot have a stack + alt stack size larger than 1000 elements") logger.error("We cannot have a stack + alt stack size larger than 1000 elements")
loop(ScriptProgram(p, ScriptErrorStackSize)) loop(ScriptProgram(p, ScriptErrorStackSize),opCount)
//stack operations //stack operations
case OP_DUP :: t => loop(opDup(p)) case OP_DUP :: t => loop(opDup(p),calcOpCount(opCount,OP_DUP))
case OP_DEPTH :: t => loop(opDepth(p)) case OP_DEPTH :: t => loop(opDepth(p),calcOpCount(opCount,OP_DEPTH))
case OP_TOALTSTACK :: t => loop(opToAltStack(p)) case OP_TOALTSTACK :: t => loop(opToAltStack(p),calcOpCount(opCount,OP_TOALTSTACK))
case OP_FROMALTSTACK :: t => loop(opFromAltStack(p)) case OP_FROMALTSTACK :: t => loop(opFromAltStack(p),calcOpCount(opCount,OP_FROMALTSTACK))
case OP_DROP :: t => loop(opDrop(p)) case OP_DROP :: t => loop(opDrop(p),calcOpCount(opCount,OP_DROP))
case OP_IFDUP :: t => loop(opIfDup(p)) case OP_IFDUP :: t => loop(opIfDup(p),calcOpCount(opCount,OP_IFDUP))
case OP_NIP :: t => loop(opNip(p)) case OP_NIP :: t => loop(opNip(p),calcOpCount(opCount,OP_NIP))
case OP_OVER :: t => loop(opOver(p)) case OP_OVER :: t => loop(opOver(p),calcOpCount(opCount,OP_OVER))
case OP_PICK :: t => loop(opPick(p)) case OP_PICK :: t => loop(opPick(p),calcOpCount(opCount,OP_PICK))
case OP_ROLL :: t => loop(opRoll(p)) case OP_ROLL :: t => loop(opRoll(p),calcOpCount(opCount,OP_ROLL))
case OP_ROT :: t => loop(opRot(p)) case OP_ROT :: t => loop(opRot(p),calcOpCount(opCount,OP_ROT))
case OP_2ROT :: t => loop(op2Rot(p)) case OP_2ROT :: t => loop(op2Rot(p),calcOpCount(opCount,OP_2ROT))
case OP_2DROP :: t => loop(op2Drop(p)) case OP_2DROP :: t => loop(op2Drop(p),calcOpCount(opCount,OP_2DROP))
case OP_SWAP :: t => loop(opSwap(p)) case OP_SWAP :: t => loop(opSwap(p),calcOpCount(opCount,OP_SWAP))
case OP_TUCK :: t => loop(opTuck(p)) case OP_TUCK :: t => loop(opTuck(p),calcOpCount(opCount,OP_TUCK))
case OP_2DUP :: t => loop(op2Dup(p)) case OP_2DUP :: t => loop(op2Dup(p),calcOpCount(opCount,OP_2DUP))
case OP_3DUP :: t => loop(op3Dup(p)) case OP_3DUP :: t => loop(op3Dup(p),calcOpCount(opCount,OP_3DUP))
case OP_2OVER :: t => loop(op2Over(p)) case OP_2OVER :: t => loop(op2Over(p),calcOpCount(opCount,OP_2OVER))
case OP_2SWAP :: t => loop(op2Swap(p)) case OP_2SWAP :: t => loop(op2Swap(p),calcOpCount(opCount,OP_2SWAP))
//arithmetic operations //arithmetic operations
case OP_ADD :: t => loop(opAdd(p)) case OP_ADD :: t => loop(opAdd(p),calcOpCount(opCount,OP_ADD))
case OP_1ADD :: t => loop(op1Add(p)) case OP_1ADD :: t => loop(op1Add(p),calcOpCount(opCount,OP_1ADD))
case OP_1SUB :: t => loop(op1Sub(p)) case OP_1SUB :: t => loop(op1Sub(p),calcOpCount(opCount,OP_1SUB))
case OP_SUB :: t => loop(opSub(p)) case OP_SUB :: t => loop(opSub(p),calcOpCount(opCount,OP_SUB))
case OP_ABS :: t => loop(opAbs(p)) case OP_ABS :: t => loop(opAbs(p),calcOpCount(opCount,OP_ABS))
case OP_NEGATE :: t => loop(opNegate(p)) case OP_NEGATE :: t => loop(opNegate(p),calcOpCount(opCount,OP_NEGATE))
case OP_NOT :: t => loop(opNot(p)) case OP_NOT :: t => loop(opNot(p),calcOpCount(opCount,OP_NOT))
case OP_0NOTEQUAL :: t => loop(op0NotEqual(p)) case OP_0NOTEQUAL :: t => loop(op0NotEqual(p),calcOpCount(opCount,OP_0NOTEQUAL))
case OP_BOOLAND :: t => loop(opBoolAnd(p)) case OP_BOOLAND :: t => loop(opBoolAnd(p),calcOpCount(opCount,OP_BOOLAND))
case OP_BOOLOR :: t => loop(opBoolOr(p)) case OP_BOOLOR :: t => loop(opBoolOr(p),calcOpCount(opCount,OP_BOOLOR))
case OP_NUMEQUAL :: t => loop(opNumEqual(p)) case OP_NUMEQUAL :: t => loop(opNumEqual(p),calcOpCount(opCount,OP_NUMEQUAL))
case OP_NUMEQUALVERIFY :: t => loop(opNumEqualVerify(p)) case OP_NUMEQUALVERIFY :: t => loop(opNumEqualVerify(p),calcOpCount(opCount,OP_NUMEQUALVERIFY))
case OP_NUMNOTEQUAL :: t => loop(opNumNotEqual(p)) case OP_NUMNOTEQUAL :: t => loop(opNumNotEqual(p),calcOpCount(opCount,OP_NUMNOTEQUAL))
case OP_LESSTHAN :: t => loop(opLessThan(p)) case OP_LESSTHAN :: t => loop(opLessThan(p),calcOpCount(opCount,OP_LESSTHAN))
case OP_GREATERTHAN :: t => loop(opGreaterThan(p)) case OP_GREATERTHAN :: t => loop(opGreaterThan(p),calcOpCount(opCount,OP_GREATERTHAN))
case OP_LESSTHANOREQUAL :: t => loop(opLessThanOrEqual(p)) case OP_LESSTHANOREQUAL :: t => loop(opLessThanOrEqual(p),calcOpCount(opCount,OP_LESSTHANOREQUAL))
case OP_GREATERTHANOREQUAL :: t => loop(opGreaterThanOrEqual(p)) case OP_GREATERTHANOREQUAL :: t => loop(opGreaterThanOrEqual(p),calcOpCount(opCount,OP_GREATERTHANOREQUAL))
case OP_MIN :: t => loop(opMin(p)) case OP_MIN :: t => loop(opMin(p),calcOpCount(opCount,OP_MIN))
case OP_MAX :: t => loop(opMax(p)) case OP_MAX :: t => loop(opMax(p),calcOpCount(opCount,OP_MAX))
case OP_WITHIN :: t => loop(opWithin(p)) case OP_WITHIN :: t => loop(opWithin(p),calcOpCount(opCount,OP_WITHIN))
//bitwise operations //bitwise operations
case OP_EQUAL :: t => case OP_EQUAL :: t => loop(opEqual(p),calcOpCount(opCount,OP_EQUAL))
val newProgram = opEqual(p)
loop(newProgram)
case OP_EQUALVERIFY :: t => loop(opEqualVerify(p)) case OP_EQUALVERIFY :: t => loop(opEqualVerify(p),calcOpCount(opCount,OP_EQUALVERIFY))
case OP_0 :: t => loop(ScriptProgram(p, ScriptNumber.zero :: p.stack, t)) case OP_0 :: t => loop(ScriptProgram(p, ScriptNumber.zero :: p.stack, t),calcOpCount(opCount,OP_0))
case (scriptNumberOp : ScriptNumberOperation) :: t => case (scriptNumberOp : ScriptNumberOperation) :: t =>
loop(ScriptProgram(p, ScriptNumber(scriptNumberOp.underlying) :: p.stack, t)) loop(ScriptProgram(p, ScriptNumber(scriptNumberOp.underlying) :: p.stack, t),calcOpCount(opCount,scriptNumberOp))
case (bytesToPushOntoStack: BytesToPushOntoStack) :: t => case (bytesToPushOntoStack: BytesToPushOntoStack) :: t =>
loop(pushScriptNumberBytesToStack(p)) loop(pushScriptNumberBytesToStack(p),calcOpCount(opCount,bytesToPushOntoStack))
case (scriptNumber: ScriptNumber) :: t => case (scriptNumber: ScriptNumber) :: t =>
loop(ScriptProgram(p, scriptNumber :: p.stack, t)) loop(ScriptProgram(p, scriptNumber :: p.stack, t),calcOpCount(opCount,scriptNumber))
case OP_PUSHDATA1 :: t => loop(opPushData1(p)) case OP_PUSHDATA1 :: t => loop(opPushData1(p),calcOpCount(opCount,OP_PUSHDATA1))
case OP_PUSHDATA2 :: t => loop(opPushData2(p)) case OP_PUSHDATA2 :: t => loop(opPushData2(p),calcOpCount(opCount,OP_PUSHDATA2))
case OP_PUSHDATA4 :: t => loop(opPushData4(p)) case OP_PUSHDATA4 :: t => loop(opPushData4(p),calcOpCount(opCount,OP_PUSHDATA4))
case (x : ScriptConstant) :: t => loop(ScriptProgram(p, x :: p.stack, t)) case (x : ScriptConstant) :: t => loop(ScriptProgram(p, x :: p.stack, t),calcOpCount(opCount,x))
//control operations //control operations
case OP_IF :: t => loop(opIf(p)) case OP_IF :: t => loop(opIf(p),calcOpCount(opCount,OP_IF))
case OP_NOTIF :: t => loop(opNotIf(p)) case OP_NOTIF :: t => loop(opNotIf(p),calcOpCount(opCount,OP_NOTIF))
case OP_ELSE :: t => loop(opElse(p)) case OP_ELSE :: t => loop(opElse(p),calcOpCount(opCount,OP_ELSE))
case OP_ENDIF :: t => loop(opEndIf(p)) case OP_ENDIF :: t => loop(opEndIf(p),calcOpCount(opCount,OP_ENDIF))
case OP_RETURN :: t => loop(opReturn(p)) case OP_RETURN :: t => loop(opReturn(p),calcOpCount(opCount,OP_RETURN))
case OP_VERIFY :: t => loop(opVerify(p)) case OP_VERIFY :: t => loop(opVerify(p),calcOpCount(opCount,OP_VERIFY))
//crypto operations //crypto operations
case OP_HASH160 :: t => loop(opHash160(p)) case OP_HASH160 :: t => loop(opHash160(p),calcOpCount(opCount,OP_HASH160))
case OP_CHECKSIG :: t => loop(opCheckSig(p)) case OP_CHECKSIG :: t => loop(opCheckSig(p),calcOpCount(opCount,OP_CHECKSIG))
case OP_CHECKSIGVERIFY :: t => loop(opCheckSigVerify(p)) case OP_CHECKSIGVERIFY :: t => loop(opCheckSigVerify(p),calcOpCount(opCount,OP_CHECKSIGVERIFY))
case OP_SHA1 :: t => loop(opSha1(p)) case OP_SHA1 :: t => loop(opSha1(p),calcOpCount(opCount,OP_SHA1))
case OP_RIPEMD160 :: t => loop(opRipeMd160(p)) case OP_RIPEMD160 :: t => loop(opRipeMd160(p),calcOpCount(opCount,OP_RIPEMD160))
case OP_SHA256 :: t => loop(opSha256(p)) case OP_SHA256 :: t => loop(opSha256(p),calcOpCount(opCount,OP_SHA256))
case OP_HASH256 :: t => loop(opHash256(p)) case OP_HASH256 :: t => loop(opHash256(p),calcOpCount(opCount,OP_HASH256))
case OP_CODESEPARATOR :: t => loop(opCodeSeparator(p)) case OP_CODESEPARATOR :: t => loop(opCodeSeparator(p),calcOpCount(opCount,OP_CODESEPARATOR))
case OP_CHECKMULTISIG :: t => case OP_CHECKMULTISIG :: t =>
opCheckMultiSig(p) match { opCheckMultiSig(p) match {
case newProgram : ExecutedScriptProgram => case newProgram : ExecutedScriptProgram =>
//script was marked invalid for other reasons, don't need to update the opcount //script was marked invalid for other reasons, don't need to update the opcount
loop(newProgram) loop(newProgram,opCount)
case newProgram : ExecutionInProgressScriptProgram => case newProgram @ (_ : ExecutionInProgressScriptProgram | _ : PreExecutionScriptProgram) =>
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt val newOpCount = calcOpCount(opCount,OP_CHECKMULTISIG) + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt
loop(newProgram) loop(newProgram,newOpCount)
case newProgram : PreExecutionScriptProgram =>
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt
loop(newProgram)
} }
case OP_CHECKMULTISIGVERIFY :: t => case OP_CHECKMULTISIGVERIFY :: t =>
opCheckMultiSigVerify(p) match { opCheckMultiSigVerify(p) match {
case newProgram : ExecutedScriptProgram => case newProgram : ExecutedScriptProgram =>
//script was marked invalid for other reasons, don't need to update the opcount //script was marked invalid for other reasons, don't need to update the opcount
loop(newProgram) loop(newProgram,opCount)
case newProgram : ExecutionInProgressScriptProgram => case newProgram @ (_ : ExecutionInProgressScriptProgram | _ : PreExecutionScriptProgram) =>
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt val newOpCount = calcOpCount(opCount,OP_CHECKMULTISIGVERIFY) + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt
loop(newProgram) loop(newProgram,newOpCount)
case newProgram : PreExecutionScriptProgram =>
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt
loop(newProgram)
} }
//reserved operations //reserved operations
case OP_NOP :: t => case OP_NOP :: t =>
//script discourage upgradeable flag does not apply to a OP_NOP //script discourage upgradeable flag does not apply to a OP_NOP
loop(ScriptProgram(p, p.stack, t)) loop(ScriptProgram(p, p.stack, t),calcOpCount(opCount,OP_NOP))
//if we see an OP_NOP and the DISCOURAGE_UPGRADABLE_OP_NOPS flag is set we must fail our program //if we see an OP_NOP and the DISCOURAGE_UPGRADABLE_OP_NOPS flag is set we must fail our program
case (nop: NOP) :: t if ScriptFlagUtil.discourageUpgradableNOPs(p.flags) => case (nop: NOP) :: t if ScriptFlagUtil.discourageUpgradableNOPs(p.flags) =>
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set") logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs)) loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs),calcOpCount(opCount,nop))
case (nop: NOP) :: t => loop(ScriptProgram(p, p.stack, t)) case (nop: NOP) :: t => loop(ScriptProgram(p, p.stack, t),calcOpCount(opCount,nop))
case OP_RESERVED :: t => case OP_RESERVED :: t =>
logger.error("OP_RESERVED automatically marks transaction invalid") logger.error("OP_RESERVED automatically marks transaction invalid")
loop(ScriptProgram(p,ScriptErrorBadOpCode)) loop(ScriptProgram(p,ScriptErrorBadOpCode),calcOpCount(opCount,OP_RESERVED))
case OP_VER :: t => case OP_VER :: t =>
logger.error("Transaction is invalid when executing OP_VER") logger.error("Transaction is invalid when executing OP_VER")
loop(ScriptProgram(p,ScriptErrorBadOpCode)) loop(ScriptProgram(p,ScriptErrorBadOpCode),calcOpCount(opCount,OP_VER))
case OP_RESERVED1 :: t => case OP_RESERVED1 :: t =>
logger.error("Transaction is invalid when executing OP_RESERVED1") logger.error("Transaction is invalid when executing OP_RESERVED1")
loop(ScriptProgram(p,ScriptErrorBadOpCode)) loop(ScriptProgram(p,ScriptErrorBadOpCode),calcOpCount(opCount,OP_RESERVED1))
case OP_RESERVED2 :: t => case OP_RESERVED2 :: t =>
logger.error("Transaction is invalid when executing OP_RESERVED2") logger.error("Transaction is invalid when executing OP_RESERVED2")
loop(ScriptProgram(p,ScriptErrorBadOpCode)) loop(ScriptProgram(p,ScriptErrorBadOpCode),calcOpCount(opCount,OP_RESERVED2))
case (reservedOperation : ReservedOperation) :: t => case (reservedOperation : ReservedOperation) :: t =>
logger.error("Undefined operation found which automatically fails the script: " + reservedOperation) logger.error("Undefined operation found which automatically fails the script: " + reservedOperation)
loop(ScriptProgram(p,ScriptErrorBadOpCode)) loop(ScriptProgram(p,ScriptErrorBadOpCode),calcOpCount(opCount,reservedOperation))
//splice operations //splice operations
case OP_SIZE :: t => loop(opSize(p)) case OP_SIZE :: t => loop(opSize(p),calcOpCount(opCount,OP_SIZE))
//locktime operations //locktime operations
case OP_CHECKLOCKTIMEVERIFY :: t => case OP_CHECKLOCKTIMEVERIFY :: t =>
//check if CLTV is enforced yet //check if CLTV is enforced yet
if (ScriptFlagUtil.checkLockTimeVerifyEnabled(p.flags)) loop(opCheckLockTimeVerify(p)) if (ScriptFlagUtil.checkLockTimeVerifyEnabled(p.flags)) loop(opCheckLockTimeVerify(p),calcOpCount(opCount,OP_CHECKLOCKTIMEVERIFY))
//if not, check to see if we should discourage p //if not, check to see if we should discourage p
else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) { else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) {
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set") logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs)) loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs),calcOpCount(opCount,OP_CHECKLOCKTIMEVERIFY))
} }
//in this case, just reat OP_CLTV just like a NOP and remove it from the stack //in this case, just reat OP_CLTV just like a NOP and remove it from the stack
else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script)) else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script),calcOpCount(opCount,OP_CHECKLOCKTIMEVERIFY))
case OP_CHECKSEQUENCEVERIFY :: t => case OP_CHECKSEQUENCEVERIFY :: t =>
//check if CLTV is enforced yet //check if CLTV is enforced yet
if (ScriptFlagUtil.checkSequenceVerifyEnabled(p.flags)) loop(opCheckSequenceVerify(p)) if (ScriptFlagUtil.checkSequenceVerifyEnabled(p.flags)) loop(opCheckSequenceVerify(p),calcOpCount(opCount,OP_CHECKSEQUENCEVERIFY))
//if not, check to see if we should discourage p //if not, check to see if we should discourage p
else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) { else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) {
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set") logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs)) loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs),calcOpCount(opCount,OP_CHECKSEQUENCEVERIFY))
} }
//in this case, just reat OP_CSV just like a NOP and remove it from the stack //in this case, just reat OP_CSV just like a NOP and remove it from the stack
else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script)) else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script),calcOpCount(opCount,OP_CHECKSEQUENCEVERIFY))
//no more script operations to run, return whether the program is valid and the final state of the program //no more script operations to run, return whether the program is valid and the final state of the program
case Nil => loop(ScriptProgram.toExecutedProgram(p)) case Nil => loop(ScriptProgram.toExecutedProgram(p),opCount)
case h :: t => throw new RuntimeException(h + " was unmatched") case h :: t => throw new RuntimeException(h + " was unmatched")
} }
} }
@ -413,6 +395,16 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
currencyUnit >= CurrencyUnits.zero && currencyUnit <= Consensus.maxMoney currencyUnit >= CurrencyUnits.zero && currencyUnit <= Consensus.maxMoney
} }
/**
* Calculates the new op count after the execution of the given [[ScriptToken]]
* @param oldOpCount
* @param token
* @return
*/
private def calcOpCount(oldOpCount: Int, token: ScriptToken):Int = BitcoinScriptUtil.countsTowardsScriptOpLimit(token) match {
case true => oldOpCount + 1
case false => oldOpCount
}
} }

View file

@ -1,5 +1,6 @@
package org.bitcoins.core.protocol.blockchain package org.bitcoins.core.protocol.blockchain
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.util.BitcoinSLogger import org.bitcoins.core.util.BitcoinSLogger
import org.scalatest.{FlatSpec, MustMatchers} import org.scalatest.{FlatSpec, MustMatchers}
@ -16,4 +17,5 @@ class BlockTest extends FlatSpec with MustMatchers with BitcoinSLogger {
val block = Block(hex) val block = Block(hex)
block.hex must be (hex) block.hex must be (hex)
} }
} }