168 lines
2.8 KiB
JavaScript
168 lines
2.8 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const AirdropProof = require('../primitives/airdropproof');
|
|
const {TREE_LEAVES} = AirdropProof;
|
|
|
|
/**
|
|
* Field
|
|
*/
|
|
|
|
class Field {
|
|
constructor(size = 0) {
|
|
assert((size >>> 0) === size);
|
|
|
|
this.size = size;
|
|
this.field = Buffer.alloc((size + 7) >>> 3, 0x00);
|
|
this.dirty = false;
|
|
}
|
|
|
|
set(i, val) {
|
|
assert((i >>> 0) === i);
|
|
assert(i < this.size);
|
|
assert((val >>> 0) === val);
|
|
assert(val === 0 || val === 1);
|
|
|
|
if (val)
|
|
this.field[i >>> 3] |= 1 << (7 - (i & 7));
|
|
else
|
|
this.field[i >>> 3] &= ~(1 << (7 - (i & 7)));
|
|
|
|
this.dirty = true;
|
|
|
|
return this;
|
|
}
|
|
|
|
get(i) {
|
|
assert((i >>> 0) === i);
|
|
|
|
if (i >= this.size)
|
|
return 1;
|
|
|
|
return (this.field[i >>> 3] >> (7 - (i & 7))) & 1;
|
|
}
|
|
|
|
isSpent(i) {
|
|
return Boolean(this.get(i));
|
|
}
|
|
|
|
spend(i) {
|
|
return this.set(i, 1);
|
|
}
|
|
|
|
unspend(i) {
|
|
return this.set(i, 0);
|
|
}
|
|
|
|
encode() {
|
|
this.dirty = false;
|
|
return this.field;
|
|
}
|
|
|
|
decode(data) {
|
|
assert(Buffer.isBuffer(data));
|
|
this.field = data;
|
|
this.dirty = false;
|
|
return this;
|
|
}
|
|
|
|
static decode(size, data) {
|
|
return new this(size).decode(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* BitField
|
|
*/
|
|
|
|
class BitField extends Field {
|
|
constructor() {
|
|
super(TREE_LEAVES);
|
|
}
|
|
|
|
static decode(data) {
|
|
return new this().decode(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* BitView
|
|
*/
|
|
|
|
class BitView {
|
|
constructor() {
|
|
this.bits = new Map();
|
|
}
|
|
|
|
spend(field, tx) {
|
|
assert(field instanceof Field);
|
|
assert(tx && tx.isCoinbase());
|
|
|
|
for (let i = 1; i < tx.inputs.length; i++) {
|
|
const input = tx.inputs[i];
|
|
const output = tx.output(i);
|
|
const {witness} = input;
|
|
|
|
assert(output && witness.items.length === 1);
|
|
|
|
const {covenant} = output;
|
|
|
|
if (!covenant.isNone())
|
|
continue;
|
|
|
|
const proof = AirdropProof.decode(witness.items[0]);
|
|
const index = proof.position();
|
|
|
|
if (!this.bits.has(index))
|
|
this.bits.set(index, field.get(index));
|
|
|
|
if (this.bits.get(index) !== 0)
|
|
return false;
|
|
|
|
this.bits.set(index, 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
undo(tx) {
|
|
assert(tx && tx.isCoinbase());
|
|
|
|
for (let i = 1; i < tx.inputs.length; i++) {
|
|
const input = tx.inputs[i];
|
|
const output = tx.output(i);
|
|
const {witness} = input;
|
|
|
|
assert(output && witness.items.length === 1);
|
|
|
|
const {covenant} = output;
|
|
|
|
if (!covenant.isNone())
|
|
continue;
|
|
|
|
const proof = AirdropProof.decode(witness.items[0]);
|
|
const index = proof.position();
|
|
|
|
this.bits.set(index, 0);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
commit(field) {
|
|
assert(field instanceof Field);
|
|
|
|
for (const [bit, spent] of this.bits)
|
|
field.set(bit, spent);
|
|
|
|
return field.dirty;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
exports.Field = Field;
|
|
exports.BitField = BitField;
|
|
exports.BitView = BitView;
|