Skip to content

Commit

Permalink
Merge bitcoin#12885: Reduce implementation code inside CScript
Browse files Browse the repository at this point in the history
54a5a21 [MOVEONLY] Turn CScript::GetOp2 into a function and move to cpp (Pieter Wuille)
6a7456a [MOVEONLY] Move CSCript::FindAndDelete to interpreter (Pieter Wuille)
33a8ecf Delete unused non-const-iterator CSCript::GetOp overloads (Pieter Wuille)
2fb168b Make iterators in CScript::FindAndDelete const (Pieter Wuille)

Pull request description:

  This PR moves `FindAndDelete` and `GetOp2` out of CScript (the first is only used inside the interpreter and moved there, the second does not actually depend on any script specifics and works on any vector). Furthermore, all non-const-iterator versions of GetOp are replaced by const ones, removing a number of methods in the process.

  The longer term goal here is making the script interpreter independent from the CScript representation.

  Note for reviewers: both `FindAndDelete` and `GetScriptOp` are consensus critical.

Tree-SHA512: c4ccf91c0b33c37cff0d474aa8dd2dab25b5b7655e2ed69a9b15e29daf0a67b21d51c23e1defb3a72ec762bd6138de96f69c6db1fb9c1fe1e976e421261aedb7
  • Loading branch information
laanwj committed Apr 23, 2018
2 parents 8609ddb + 54a5a21 commit a49381d
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 118 deletions.
2 changes: 1 addition & 1 deletion src/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ std::string FormatScript(const CScript& script)
while (it != script.end()) {
CScript::const_iterator it2 = it;
std::vector<unsigned char> vch;
if (script.GetOp2(it, op, &vch)) {
if (script.GetOp(it, op, vch)) {
if (op == OP_0) {
ret += "0 ";
continue;
Expand Down
32 changes: 30 additions & 2 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,34 @@ bool static CheckMinimalPush(const valtype& data, opcodetype opcode) {
return true;
}

int FindAndDelete(CScript& script, const CScript& b)
{
int nFound = 0;
if (b.empty())
return nFound;
CScript result;
CScript::const_iterator pc = script.begin(), pc2 = script.begin(), end = script.end();
opcodetype opcode;
do
{
result.insert(result.end(), pc2, pc);
while (static_cast<size_t>(end - pc) >= b.size() && std::equal(b.begin(), b.end(), pc))
{
pc = pc + b.size();
++nFound;
}
pc2 = pc;
}
while (script.GetOp(pc, opcode));

if (nFound > 0) {
result.insert(result.end(), pc2, end);
script = std::move(result);
}

return nFound;
}

bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
{
static const CScriptNum bnZero(0);
Expand Down Expand Up @@ -891,7 +919,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&

// Drop the signature in pre-segwit scripts but not segwit scripts
if (sigversion == SigVersion::BASE) {
scriptCode.FindAndDelete(CScript(vchSig));
FindAndDelete(scriptCode, CScript(vchSig));
}

if (!CheckSignatureEncoding(vchSig, flags, serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
Expand Down Expand Up @@ -955,7 +983,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
{
valtype& vchSig = stacktop(-isig-k);
if (sigversion == SigVersion::BASE) {
scriptCode.FindAndDelete(CScript(vchSig));
FindAndDelete(scriptCode, CScript(vchSig));
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,6 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C

size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);

int FindAndDelete(CScript& script, const CScript& b);

#endif // BITCOIN_SCRIPT_INTERPRETER_H
52 changes: 52 additions & 0 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,55 @@ bool CScript::HasValidOps() const
}
return true;
}

bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet)
{
opcodeRet = OP_INVALIDOPCODE;
if (pvchRet)
pvchRet->clear();
if (pc >= end)
return false;

// Read instruction
if (end - pc < 1)
return false;
unsigned int opcode = *pc++;

// Immediate operand
if (opcode <= OP_PUSHDATA4)
{
unsigned int nSize = 0;
if (opcode < OP_PUSHDATA1)
{
nSize = opcode;
}
else if (opcode == OP_PUSHDATA1)
{
if (end - pc < 1)
return false;
nSize = *pc++;
}
else if (opcode == OP_PUSHDATA2)
{
if (end - pc < 2)
return false;
nSize = ReadLE16(&pc[0]);
pc += 2;
}
else if (opcode == OP_PUSHDATA4)
{
if (end - pc < 4)
return false;
nSize = ReadLE32(&pc[0]);
pc += 4;
}
if (end - pc < 0 || (unsigned int)(end - pc) < nSize)
return false;
if (pvchRet)
pvchRet->assign(pc, pc + nSize);
pc += nSize;
}

opcodeRet = static_cast<opcodetype>(opcode);
return true;
}
102 changes: 4 additions & 98 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ class CScriptNum
*/
typedef prevector<28, unsigned char> CScriptBase;

bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet);

/** Serialized script, used inside transaction inputs and outputs */
class CScript : public CScriptBase
{
Expand Down Expand Up @@ -493,84 +495,16 @@ class CScript : public CScriptBase
}


bool GetOp(iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>& vchRet)
{
// Wrapper so it can be called with either iterator or const_iterator
const_iterator pc2 = pc;
bool fRet = GetOp2(pc2, opcodeRet, &vchRet);
pc = begin() + (pc2 - begin());
return fRet;
}

bool GetOp(iterator& pc, opcodetype& opcodeRet)
{
const_iterator pc2 = pc;
bool fRet = GetOp2(pc2, opcodeRet, nullptr);
pc = begin() + (pc2 - begin());
return fRet;
}

bool GetOp(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>& vchRet) const
{
return GetOp2(pc, opcodeRet, &vchRet);
return GetScriptOp(pc, end(), opcodeRet, &vchRet);
}

bool GetOp(const_iterator& pc, opcodetype& opcodeRet) const
{
return GetOp2(pc, opcodeRet, nullptr);
return GetScriptOp(pc, end(), opcodeRet, nullptr);
}

bool GetOp2(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet) const
{
opcodeRet = OP_INVALIDOPCODE;
if (pvchRet)
pvchRet->clear();
if (pc >= end())
return false;

// Read instruction
if (end() - pc < 1)
return false;
unsigned int opcode = *pc++;

// Immediate operand
if (opcode <= OP_PUSHDATA4)
{
unsigned int nSize = 0;
if (opcode < OP_PUSHDATA1)
{
nSize = opcode;
}
else if (opcode == OP_PUSHDATA1)
{
if (end() - pc < 1)
return false;
nSize = *pc++;
}
else if (opcode == OP_PUSHDATA2)
{
if (end() - pc < 2)
return false;
nSize = ReadLE16(&pc[0]);
pc += 2;
}
else if (opcode == OP_PUSHDATA4)
{
if (end() - pc < 4)
return false;
nSize = ReadLE32(&pc[0]);
pc += 4;
}
if (end() - pc < 0 || (unsigned int)(end() - pc) < nSize)
return false;
if (pvchRet)
pvchRet->assign(pc, pc + nSize);
pc += nSize;
}

opcodeRet = static_cast<opcodetype>(opcode);
return true;
}

/** Encode/decode small integers: */
static int DecodeOP_N(opcodetype opcode)
Expand All @@ -588,34 +522,6 @@ class CScript : public CScriptBase
return (opcodetype)(OP_1+n-1);
}

int FindAndDelete(const CScript& b)
{
int nFound = 0;
if (b.empty())
return nFound;
CScript result;
iterator pc = begin(), pc2 = begin();
opcodetype opcode;
do
{
result.insert(result.end(), pc2, pc);
while (static_cast<size_t>(end() - pc) >= b.size() && std::equal(b.begin(), b.end(), pc))
{
pc = pc + b.size();
++nFound;
}
pc2 = pc;
}
while (GetOp(pc, opcode));

if (nFound > 0) {
result.insert(result.end(), pc2, end());
*this = result;
}

return nFound;
}

/**
* Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
* as 20 sigops. With pay-to-script-hash, that changed:
Expand Down
32 changes: 16 additions & 16 deletions src/test/script_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1349,102 +1349,102 @@ BOOST_AUTO_TEST_CASE(script_FindAndDelete)
s = CScript() << OP_1 << OP_2;
d = CScript(); // delete nothing should be a no-op
expect = s;
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 0);
BOOST_CHECK(s == expect);

s = CScript() << OP_1 << OP_2 << OP_3;
d = CScript() << OP_2;
expect = CScript() << OP_1 << OP_3;
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);

s = CScript() << OP_3 << OP_1 << OP_3 << OP_3 << OP_4 << OP_3;
d = CScript() << OP_3;
expect = CScript() << OP_1 << OP_4;
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 4);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 4);
BOOST_CHECK(s == expect);

s = ScriptFromHex("0302ff03"); // PUSH 0x02ff03 onto stack
d = ScriptFromHex("0302ff03");
expect = CScript();
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);

s = ScriptFromHex("0302ff030302ff03"); // PUSH 0x2ff03 PUSH 0x2ff03
d = ScriptFromHex("0302ff03");
expect = CScript();
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 2);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 2);
BOOST_CHECK(s == expect);

s = ScriptFromHex("0302ff030302ff03");
d = ScriptFromHex("02");
expect = s; // FindAndDelete matches entire opcodes
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 0);
BOOST_CHECK(s == expect);

s = ScriptFromHex("0302ff030302ff03");
d = ScriptFromHex("ff");
expect = s;
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 0);
BOOST_CHECK(s == expect);

// This is an odd edge case: strip of the push-three-bytes
// prefix, leaving 02ff03 which is push-two-bytes:
s = ScriptFromHex("0302ff030302ff03");
d = ScriptFromHex("03");
expect = CScript() << ParseHex("ff03") << ParseHex("ff03");
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 2);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 2);
BOOST_CHECK(s == expect);

// Byte sequence that spans multiple opcodes:
s = ScriptFromHex("02feed5169"); // PUSH(0xfeed) OP_1 OP_VERIFY
d = ScriptFromHex("feed51");
expect = s;
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0); // doesn't match 'inside' opcodes
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 0); // doesn't match 'inside' opcodes
BOOST_CHECK(s == expect);

s = ScriptFromHex("02feed5169"); // PUSH(0xfeed) OP_1 OP_VERIFY
d = ScriptFromHex("02feed51");
expect = ScriptFromHex("69");
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);

s = ScriptFromHex("516902feed5169");
d = ScriptFromHex("feed51");
expect = s;
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 0);
BOOST_CHECK(s == expect);

s = ScriptFromHex("516902feed5169");
d = ScriptFromHex("02feed51");
expect = ScriptFromHex("516969");
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);

s = CScript() << OP_0 << OP_0 << OP_1 << OP_1;
d = CScript() << OP_0 << OP_1;
expect = CScript() << OP_0 << OP_1; // FindAndDelete is single-pass
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);

s = CScript() << OP_0 << OP_0 << OP_1 << OP_0 << OP_1 << OP_1;
d = CScript() << OP_0 << OP_1;
expect = CScript() << OP_0 << OP_1; // FindAndDelete is single-pass
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 2);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 2);
BOOST_CHECK(s == expect);

// Another weird edge case:
// End with invalid push (not enough data)...
s = ScriptFromHex("0003feed");
d = ScriptFromHex("03feed"); // ... can remove the invalid push
expect = ScriptFromHex("00");
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);

s = ScriptFromHex("0003feed");
d = ScriptFromHex("00");
expect = ScriptFromHex("03feed");
BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1);
BOOST_CHECK_EQUAL(FindAndDelete(s, d), 1);
BOOST_CHECK(s == expect);
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/sighash_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ uint256 static SignatureHashOld(CScript scriptCode, const CTransaction& txTo, un

// In case concatenating two scripts ends up with two codeseparators,
// or an extra one at the end, this prevents all those possible incompatibilities.
scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR));
FindAndDelete(scriptCode, CScript(OP_CODESEPARATOR));

// Blank out other inputs' signatures
for (unsigned int i = 0; i < txTmp.vin.size(); i++)
Expand Down

0 comments on commit a49381d

Please sign in to comment.