script: iterate over scripts incrementally (note: use --patience).

This commit is contained in:
Christopher Jeffrey 2017-08-24 23:59:01 -07:00
parent 57b98b2368
commit 861eea07fd
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
16 changed files with 1132 additions and 751 deletions

View File

@ -159,7 +159,7 @@ for (let i = 0; i < 100; i++) {
hash: encoding.NULL_HASH,
index: 0
},
script: new Script()
script: Script.write()
.pushData(Buffer.allocUnsafe(9))
.pushData(random.randomBytes(33))
.compile()

View File

@ -7,7 +7,7 @@ const Script = bcoin.script;
const Witness = bcoin.witness;
const Stack = bcoin.stack;
const output = new Script();
let output = Script.write();
output.pushSym('OP_DROP');
output.pushSym('OP_ADD');
output.pushInt(7);
@ -15,15 +15,15 @@ output.pushSym('OP_NUMEQUAL');
// Compile the script to its binary representation
// (you must do this if you change something!).
assert(output.getSmall(2) === 7); // compiled as OP_7
output.compile();
output = output.compile();
const input = new Script();
let input = Script.write();
input.setString(0, 'hello world'); // add some metadata
input.pushInt(2);
input.pushInt(5);
input.push(input.shift());
assert(input.getString(2) === 'hello world');
input.compile();
input = input.compile();
// A stack is another array-like object which contains
// only Buffers (whereas scripts contain Opcode objects).

View File

@ -1409,10 +1409,11 @@ RPC.prototype._createTemplate = async function _createTemplate(maxVersion, coinb
if (coinbase) {
const tx = attempt.toCoinbase();
const input = tx.inputs[0];
const script = input.script.write();
// Pop off the nonces.
input.script.pop();
input.script.compile();
script.pop();
script.compile();
if (attempt.witness) {
// We don't include the commitment
@ -1863,7 +1864,7 @@ RPC.prototype.signRawTransaction = async function signRawTransaction(args, help)
const redeem = Script.fromRaw(redeemRaw);
for (const op of redeem.code) {
for (const op of redeem.values()) {
if (!op.data)
continue;
@ -2341,7 +2342,6 @@ RPC.prototype._addBlock = async function _addBlock(block) {
// Recreate witness nonce (all zeroes).
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
tx.refresh();
block.refresh();

View File

@ -226,6 +226,7 @@ MempoolEntry.prototype.getDescRate = function getDescRate() {
MempoolEntry.prototype.memUsage = function memUsage() {
const tx = this.tx;
let total = 0;
total += 176; // mempool entry
@ -247,13 +248,6 @@ MempoolEntry.prototype.memUsage = function memUsage() {
total += 40; // script
total += 80; // script raw buffer
total += 32; // script code array
total += input.script.code.length * 40; // opcodes
for (const op of input.script.code) {
if (op.data)
total += 80; // op buffers
}
total += 96; // witness
total += 32; // witness items
@ -262,18 +256,9 @@ MempoolEntry.prototype.memUsage = function memUsage() {
total += 32; // output array
for (const output of tx.outputs) {
total += 104; // output
total += 40; // script
total += 80; // script raw buffer
total += 32; // script code array
total += output.script.code.length * 40; // opcodes
for (const op of output.script.code) {
if (op.data)
total += 80; // op buffers
}
}
total += tx.outputs.length * 104; // output
total += tx.outputs.length * 40; // script
total += tx.outputs.length * 80; // script raw buffer
return total;
};

View File

@ -238,27 +238,26 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
// Coinbase input.
const input = new Input();
const script = input.script.write();
// Height (required in v2+ blocks)
input.script.pushInt(this.height);
script.pushInt(this.height);
// Coinbase flags.
input.script.pushData(encoding.ZERO_HASH160);
script.pushData(encoding.ZERO_HASH160);
// Smaller nonce for good measure.
input.script.pushData(util.nonce(4));
script.pushData(util.nonce(4));
// Extra nonce: incremented when
// the nonce overflows.
input.script.pushData(encoding.ZERO_U64);
script.pushData(encoding.ZERO_U64);
input.script.compile();
script.compile();
// Set up the witness nonce.
if (this.witness) {
if (this.witness)
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
}
cb.inputs.push(input);
@ -279,7 +278,7 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
}
// Padding for the CB height (constant size).
const op = input.script.get(0);
const op = input.script.read().get();
assert(op);
const padding = 5 - op.getSize();
assert(padding >= 0);
@ -306,8 +305,9 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
}
// Setup coinbase flags (variable size).
input.script.setData(1, this.coinbaseFlags);
input.script.compile();
input.script.write()
.setData(1, this.coinbaseFlags)
.compile();
// Setup output script (variable size).
output.script.fromAddress(this.address);
@ -436,7 +436,6 @@ BlockTemplate.prototype.getCoinbase = function getCoinbase(nonce1, nonce2) {
if (this.witness) {
const input = tx.inputs[0];
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
tx.refresh();
}

View File

@ -808,7 +808,7 @@ MTX.prototype.signVector = function signVector(prev, vector, sig, ring) {
// Grab the redeem script's keys to figure
// out where our key should go.
const keys = [];
for (const op of prev.code) {
for (const op of prev.values()) {
if (op.data)
keys.push(op.data);
}

View File

@ -354,6 +354,24 @@ exports.isLowDER = function isLowDER(sig) {
return secp256k1.isLowS(sig.slice(0, -1));
};
/**
* Get a small integer from an opcode (OP_0-OP_16).
* @param {Number} index
* @returns {Number}
*/
exports.getSmall = function getSmall(op) {
assert(typeof op === 'number');
if (op === exports.opcodes.OP_0)
return 0;
if (op >= exports.opcodes.OP_1 && op <= exports.opcodes.OP_16)
return op - 0x50;
return -1;
};
/**
* Test whether the data element is a valid key.
* @param {Buffer} key

View File

@ -572,6 +572,158 @@ Opcode.fromSymbol = function fromSymbol(name) {
return Opcode.fromOp(value);
};
/**
* Instantiate opcode from buffer reader.
* Only reads opcode values.
* @param {BufferReader} br
* @returns {Number}
*/
Opcode.readOp = function readOp(br) {
const value = br.readU8();
if (opCache[value])
return value;
switch (value) {
case opcodes.OP_PUSHDATA1: {
if (br.left() < 1)
return -1;
const size = br.readU8();
if (br.left() < size) {
br.seek(br.left());
return -1;
}
br.seek(size);
return value;
}
case opcodes.OP_PUSHDATA2: {
if (br.left() < 2) {
br.seek(br.left());
return -1;
}
const size = br.readU16();
if (br.left() < size) {
br.seek(br.left());
return -1;
}
br.seek(size);
return value;
}
case opcodes.OP_PUSHDATA4: {
if (br.left() < 4) {
br.seek(br.left());
return -1;
}
const size = br.readU32();
if (br.left() < size) {
br.seek(br.left());
return -1;
}
br.seek(size);
return value;
}
default: {
if (br.left() < value) {
br.seek(br.left());
return -1;
}
br.seek(value);
return value;
}
}
};
/**
* Instantiate opcode from buffer reader.
* Reads pushdata size only.
* @param {BufferReader} br
* @returns {Number}
*/
Opcode.readLength = function readLength(br) {
const value = br.readU8();
if (opCache[value])
return -1;
switch (value) {
case opcodes.OP_PUSHDATA1: {
if (br.left() < 1)
return -1;
const size = br.readU8();
if (br.left() < size) {
br.seek(br.left());
return -1;
}
br.seek(size);
return size;
}
case opcodes.OP_PUSHDATA2: {
if (br.left() < 2) {
br.seek(br.left());
return -1;
}
const size = br.readU16();
if (br.left() < size) {
br.seek(br.left());
return -1;
}
br.seek(size);
return size;
}
case opcodes.OP_PUSHDATA4: {
if (br.left() < 4) {
br.seek(br.left());
return -1;
}
const size = br.readU32();
if (br.left() < size) {
br.seek(br.left());
return -1;
}
br.seek(size);
return size;
}
default: {
if (br.left() < value) {
br.seek(br.left());
return -1;
}
br.seek(value);
return value;
}
}
};
/**
* Instantiate opcode from buffer reader.
* @param {BufferReader} br

File diff suppressed because it is too large Load Diff

View File

@ -191,15 +191,6 @@ Witness.prototype.inject = function inject(witness) {
return this;
};
/**
* Compile witness (NOP).
* @returns {Witness}
*/
Witness.prototype.compile = function compile() {
return this;
};
/**
* "Guess" the type of the witness.
* This method is not 100% reliable.

View File

@ -516,7 +516,7 @@ function siphash24(data, key) {
}
function getPushes(items, script) {
for (const op of script.code) {
for (const op of script.values()) {
if (!op.data || op.data.length === 0)
continue;

View File

@ -119,14 +119,12 @@ function randomWitness(redeem) {
if (redeem)
witness.push(redeem);
witness.compile();
return witness;
}
function randomInputScript(redeem) {
const size = util.random(1, 100);
const script = new Script();
const script = Script.write();
for (let i = 0; i < size; i++) {
const len = util.random(0, 100);
@ -148,7 +146,7 @@ function isPushOnly(script) {
if (script.isPushOnly())
return true;
for (const op of script.code) {
for (const op of script.values()) {
if (op.value === Script.opcodes.NOP)
continue;

View File

@ -35,7 +35,7 @@ function createGenesisBlock(options) {
hash: encoding.NULL_HASH,
index: 0xffffffff
},
script: Script()
script: Script.write()
.pushInt(486604799)
.pushPush(Buffer.from([4]))
.pushData(flags)

View File

@ -568,10 +568,11 @@ describe('Chain', function() {
assert(output.script.isCommitment());
const commit = Buffer.from(output.script.getData(1));
const script = output.script.write();
const commit = Buffer.from(script.getData(1));
commit.fill(0, 10);
output.script.setData(1, commit);
output.script.compile();
script.setData(1, commit);
script.compile();
block.refresh(true);
block.merkleRoot = block.createMerkleRoot('hex');
@ -784,16 +785,16 @@ describe('Chain', function() {
it('should mine 111 multisig blocks', async () => {
const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW;
const redeem = new Script();
redeem.pushInt(20);
const sw = Script.write();
sw.pushInt(20);
for (let i = 0; i < 20; i++)
redeem.pushData(encoding.ZERO_KEY);
sw.pushData(encoding.ZERO_KEY);
redeem.pushInt(20);
redeem.pushOp(opcodes.OP_CHECKMULTISIG);
sw.pushInt(20);
sw.pushOp(opcodes.OP_CHECKMULTISIG);
redeem.compile();
const redeem = sw.compile();
const script = Script.fromScripthash(redeem.hash160());
@ -826,17 +827,17 @@ describe('Chain', function() {
const end = chain.height - 100;
const job = await cpu.createJob();
const script = new Script();
const sw = Script.write();
script.pushInt(20);
sw.pushInt(20);
for (let i = 0; i < 20; i++)
script.pushData(encoding.ZERO_KEY);
sw.pushData(encoding.ZERO_KEY);
script.pushInt(20);
script.pushOp(opcodes.OP_CHECKMULTISIG);
sw.pushInt(20);
sw.pushOp(opcodes.OP_CHECKMULTISIG);
script.compile();
const script = sw.compile();
for (let i = start; i <= end; i++) {
const block = await chain.db.getBlock(i);

View File

@ -127,8 +127,9 @@ describe('Mempool', function() {
// Fake signature
const input = fake.inputs[0];
input.script.setData(0, encoding.ZERO_SIG);
input.script.compile();
const sw = input.script.write();
sw.setData(0, encoding.ZERO_SIG);
sw.compile();
// balance: 11000
{

View File

@ -223,8 +223,9 @@ async function testP2SH(witness, nesting) {
assert(carol.account.change.getAddress().equals(change2));
const input = tx.inputs[0];
input[vector].setData(2, encoding.ZERO_SIG);
input[vector].compile();
const stack = input[vector].toStack();
stack.set(2, encoding.ZERO_SIG);
input[vector].fromStack(stack);
assert(!tx.verify(view, flags));
assert.strictEqual(tx.getFee(view), 10000);
@ -375,8 +376,9 @@ describe('Wallet', function() {
await alice.template(fake);
// Fake signature
const input = fake.inputs[0];
input.script.setData(0, encoding.ZERO_SIG);
input.script.compile();
const script = input.script.write();
script.setData(0, encoding.ZERO_SIG);
script.compile();
// balance: 11000
// Fake TX should temporarily change output.