Merge #12885: Reduce implementation code inside CScript

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
This commit is contained in:
Wladimir J. van der Laan 2018-04-23 21:08:42 +02:00
commit a49381dfa3
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
7 changed files with 106 additions and 118 deletions

View file

@ -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;

View file

@ -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);
@ -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)) {
@ -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));
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -385,6 +385,8 @@ private:
*/
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
{
@ -493,84 +495,16 @@ public:
}
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)
@ -588,34 +522,6 @@ public:
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:

View file

@ -1349,43 +1349,43 @@ 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
@ -1393,44 +1393,44 @@ BOOST_AUTO_TEST_CASE(script_FindAndDelete)
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:
@ -1438,13 +1438,13 @@ BOOST_AUTO_TEST_CASE(script_FindAndDelete)
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);
}

View file

@ -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++)