The posts by
Sergio here and on his blog was what I had in mind when I said there are rare examples out there already. The first problem was that some of those examples were quite redundant such as the OP_0 OP_CheckSig one which fails right away. But at the same time it is the reason why I started this.
Here is a long rant explaining what I had in mind starting this topic:
As you may have noticed, over the pat 2 years I've been implementing bitcoin from scratch without looking at source codes. Sometimes I come up with some ideas which I explore a little. Right now I'm trying to close the chapter on my
Blockchain namespace and as I was implementing scripts and came across that post I came up with the idea of some sort of Just In-Time optimization. It is inspired by what .net JIT compiler does.
Basically it first compiles any code written in any of the .net languages (C#, F#, VB,..) into an
Intermediate Language, then JIT compiler converts it into
machine code. You can click on the links to see it for an example code, what happens is that the compiler also optimizes the code so the first line (int temp...) doesn't even exist in the final code because it is not used and also the second line is turned into 1 command (
ror) instead of being 4 (2 shifts, 1 OR and 1 subtract).
So importing that idea into scripts would be something like this which I'm currently experimenting with. You have the script (raw bytes):
47304402201a5f741e3ffdb9f33a1828e72829d9c100df79e754dac1f0d5d4251d77c300df022012a87bbed7547657de6ef328cadf1b99e3121429df590fa61cac535c98d38e3c0121034746a7851078bcc646b301be2d0d537dff68ecbf23f432f4338e44add5f4cf5d
76a914a3646f520269f61bba6f56c975eaa593827a3a7188ac
convert it to a list of OPs (IOperation[]) (loosely coupled object oriented approach)
PushDataOp()
PushDataOp()
DUPOp()
Hash160Op()
PushDataOp()
EqualVerifyOp()
CheckSigOp()
then run it. This is the pseudo-code of what is actually being run:
stack.push <- first pushOp
stack.push <- second pushOp
stack.peek <- DUP
stack.push
stack.pop <- HASH160
hash.compute
stack.push
stack.push <- third pushOp
stack.pop <- EqualVerify
stack.pop
compare
stack.push
stack.pop
check.true
stack.pop <- CheckSig
stack.pop
checksig
each "stack" operation also consists of multiple checks to expand allocated memory for example, to move index/position and a lot of copies which don't show up in above pseudo-code.
As you can see, this very simple script is doing a ton of pointless operations with the stack. This is a micro optimization which will make a little difference but the idea stands:
You turn this pattern into a single
IOperation such as
P2PKHOp(), then it would become an object containing these
IOperations but instead of calling their
Run() it uses the workaround:
hash.compute(OperationList[1].Data)
compare(OperationList[4].Data)
checksig(OperationList[0].Data, OperationList[1].Data)
As it can be seen, usage of a stack was completely eliminated.
Bitcoin contains a lot of stack manipulation OPs which could be optimized:
OP_DUP + OP_DROP => nothing
OP_SWAP + OP_NIP => OP_DROP
OP_CheckSig + OP_Drop => only remove and verify 2 preceding pushes and skip signature verification!