369 lines
6.4 KiB
JavaScript
369 lines
6.4 KiB
JavaScript
/*!
|
|
* records.js - chaindb records
|
|
* Copyright (c) 2024 The Handshake Developers (MIT License).
|
|
* https://github.com/handshake-org/hsd
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const bio = require('bufio');
|
|
const {BufferMap} = require('buffer-map');
|
|
const consensus = require('../protocol/consensus');
|
|
const Network = require('../protocol/network');
|
|
|
|
/**
|
|
* ChainFlags
|
|
*/
|
|
|
|
class ChainFlags extends bio.Struct {
|
|
/**
|
|
* Create chain flags.
|
|
* @alias module:blockchain.ChainFlags
|
|
* @constructor
|
|
*/
|
|
|
|
constructor(options) {
|
|
super();
|
|
|
|
this.network = Network.primary;
|
|
this.spv = false;
|
|
this.prune = false;
|
|
this.indexTX = false;
|
|
this.indexAddress = false;
|
|
|
|
if (options)
|
|
this.fromOptions(options);
|
|
}
|
|
|
|
fromOptions(options) {
|
|
this.network = Network.get(options.network);
|
|
|
|
if (options.spv != null) {
|
|
assert(typeof options.spv === 'boolean');
|
|
this.spv = options.spv;
|
|
}
|
|
|
|
if (options.prune != null) {
|
|
assert(typeof options.prune === 'boolean');
|
|
this.prune = options.prune;
|
|
}
|
|
|
|
if (options.indexTX != null) {
|
|
assert(typeof options.indexTX === 'boolean');
|
|
this.indexTX = options.indexTX;
|
|
}
|
|
|
|
if (options.indexAddress != null) {
|
|
assert(typeof options.indexAddress === 'boolean');
|
|
this.indexAddress = options.indexAddress;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
getSize() {
|
|
return 12;
|
|
}
|
|
|
|
write(bw) {
|
|
let flags = 0;
|
|
|
|
if (this.spv)
|
|
flags |= 1 << 0;
|
|
|
|
if (this.prune)
|
|
flags |= 1 << 1;
|
|
|
|
if (this.indexTX)
|
|
flags |= 1 << 2;
|
|
|
|
if (this.indexAddress)
|
|
flags |= 1 << 3;
|
|
|
|
bw.writeU32(this.network.magic);
|
|
bw.writeU32(flags);
|
|
bw.writeU32(0);
|
|
|
|
return bw;
|
|
}
|
|
|
|
read(br) {
|
|
this.network = Network.fromMagic(br.readU32());
|
|
|
|
const flags = br.readU32();
|
|
|
|
this.spv = (flags & 1) !== 0;
|
|
this.prune = (flags & 2) !== 0;
|
|
this.indexTX = (flags & 4) !== 0;
|
|
this.indexAddress = (flags & 8) !== 0;
|
|
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Chain State
|
|
*/
|
|
|
|
class ChainState extends bio.Struct {
|
|
/**
|
|
* Create chain state.
|
|
* @alias module:blockchain.ChainState
|
|
* @constructor
|
|
*/
|
|
|
|
constructor() {
|
|
super();
|
|
this.tip = consensus.ZERO_HASH;
|
|
this.tx = 0;
|
|
this.coin = 0;
|
|
this.value = 0;
|
|
this.burned = 0;
|
|
this.committed = false;
|
|
}
|
|
|
|
inject(state) {
|
|
this.tip = state.tip;
|
|
this.tx = state.tx;
|
|
this.coin = state.coin;
|
|
this.value = state.value;
|
|
this.burned = state.burned;
|
|
return this;
|
|
}
|
|
|
|
connect(block) {
|
|
this.tx += block.txs.length;
|
|
}
|
|
|
|
disconnect(block) {
|
|
this.tx -= block.txs.length;
|
|
}
|
|
|
|
add(coin) {
|
|
this.coin += 1;
|
|
this.value += coin.value;
|
|
}
|
|
|
|
spend(coin) {
|
|
this.coin -= 1;
|
|
this.value -= coin.value;
|
|
}
|
|
|
|
burn(coin) {
|
|
this.coin += 1;
|
|
this.burned += coin.value;
|
|
}
|
|
|
|
unburn(coin) {
|
|
this.coin -= 1;
|
|
this.burned -= coin.value;
|
|
}
|
|
|
|
commit(hash) {
|
|
assert(Buffer.isBuffer(hash));
|
|
this.tip = hash;
|
|
this.committed = true;
|
|
return this.encode();
|
|
}
|
|
|
|
getSize() {
|
|
return 64;
|
|
}
|
|
|
|
write(bw) {
|
|
bw.writeHash(this.tip);
|
|
bw.writeU64(this.tx);
|
|
bw.writeU64(this.coin);
|
|
bw.writeU64(this.value);
|
|
bw.writeU64(this.burned);
|
|
return bw;
|
|
}
|
|
|
|
read(br) {
|
|
this.tip = br.readHash();
|
|
this.tx = br.readU64();
|
|
this.coin = br.readU64();
|
|
this.value = br.readU64();
|
|
this.burned = br.readU64();
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* State Cache
|
|
*/
|
|
|
|
class StateCache {
|
|
/**
|
|
* Create state cache.
|
|
* @alias module:blockchain.StateCache
|
|
* @constructor
|
|
*/
|
|
|
|
constructor(network) {
|
|
this.network = network;
|
|
this.bits = [];
|
|
this.updates = [];
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
for (let i = 0; i < 32; i++)
|
|
this.bits.push(null);
|
|
|
|
for (const {bit} of this.network.deploys) {
|
|
assert(!this.bits[bit]);
|
|
this.bits[bit] = new BufferMap();
|
|
}
|
|
}
|
|
|
|
set(bit, entry, state) {
|
|
const cache = this.bits[bit];
|
|
|
|
assert(cache);
|
|
|
|
if (cache.get(entry.hash) !== state) {
|
|
cache.set(entry.hash, state);
|
|
this.updates.push(new CacheUpdate(bit, entry.hash, state));
|
|
}
|
|
}
|
|
|
|
get(bit, entry) {
|
|
const cache = this.bits[bit];
|
|
|
|
assert(cache);
|
|
|
|
const state = cache.get(entry.hash);
|
|
|
|
if (state == null)
|
|
return -1;
|
|
|
|
return state;
|
|
}
|
|
|
|
commit() {
|
|
this.updates.length = 0;
|
|
}
|
|
|
|
drop() {
|
|
for (const {bit, hash} of this.updates) {
|
|
const cache = this.bits[bit];
|
|
assert(cache);
|
|
cache.delete(hash);
|
|
}
|
|
|
|
this.updates.length = 0;
|
|
}
|
|
|
|
insert(bit, hash, state) {
|
|
const cache = this.bits[bit];
|
|
assert(cache);
|
|
cache.set(hash, state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cache Update
|
|
*/
|
|
|
|
class CacheUpdate {
|
|
/**
|
|
* Create cache update.
|
|
* @constructor
|
|
* @ignore
|
|
*/
|
|
|
|
constructor(bit, hash, state) {
|
|
this.bit = bit;
|
|
this.hash = hash;
|
|
this.state = state;
|
|
}
|
|
|
|
encode() {
|
|
const data = Buffer.allocUnsafe(1);
|
|
data[0] = this.state;
|
|
return data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tree related state.
|
|
*/
|
|
|
|
class TreeState extends bio.Struct {
|
|
/**
|
|
* Create tree state.
|
|
* @constructor
|
|
* @ignore
|
|
*/
|
|
|
|
constructor() {
|
|
super();
|
|
this.treeRoot = consensus.ZERO_HASH;
|
|
this.commitHeight = 0;
|
|
this.compactionRoot = consensus.ZERO_HASH;
|
|
this.compactionHeight = 0;
|
|
|
|
this.committed = false;
|
|
}
|
|
|
|
inject(state) {
|
|
this.treeRoot = state.treeRoot;
|
|
this.commitHeight = state.treeHeight;
|
|
this.compactionHeight = state.compactionHeight;
|
|
this.compactionRoot = state.compactionRoot;
|
|
|
|
return this;
|
|
}
|
|
|
|
compact(hash, height) {
|
|
assert(Buffer.isBuffer(hash));
|
|
assert((height >>> 0) === height);
|
|
|
|
this.compactionRoot = hash;
|
|
this.compactionHeight = height;
|
|
};
|
|
|
|
commit(hash, height) {
|
|
assert(Buffer.isBuffer(hash));
|
|
assert((height >>> 0) === height);
|
|
|
|
this.treeRoot = hash;
|
|
this.commitHeight = height;
|
|
this.committed = true;
|
|
return this.encode();
|
|
}
|
|
|
|
getSize() {
|
|
return 72;
|
|
}
|
|
|
|
write(bw) {
|
|
bw.writeHash(this.treeRoot);
|
|
bw.writeU32(this.commitHeight);
|
|
bw.writeHash(this.compactionRoot);
|
|
bw.writeU32(this.compactionHeight);
|
|
|
|
return bw;
|
|
}
|
|
|
|
read(br) {
|
|
this.treeRoot = br.readHash();
|
|
this.commitHeight = br.readU32();
|
|
this.compactionRoot = br.readHash();
|
|
this.compactionHeight = br.readU32();
|
|
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
exports.ChainFlags = ChainFlags;
|
|
exports.ChainState = ChainState;
|
|
exports.StateCache = StateCache;
|
|
exports.TreeState = TreeState;
|
|
exports.CacheUpdate = CacheUpdate;
|