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