@neo-one/node-blockchain-esnext-esm
Version:
NEO•ONE NEO blockchain implementation.
620 lines (618 loc) • 32.6 kB
JavaScript
import { common, utils } from '@neo-one/client-common-esnext-esm';
import { Account, AccountUnclaimed, AccountUnspent, Asset, BlockData, ClaimTransaction, ContractTransaction, EnrollmentTransaction, Input, InvocationData, InvocationResultSuccess, InvocationTransaction, IssueTransaction, LogAction, MinerTransaction, NotificationAction, PublishTransaction, RegisterTransaction, ScriptContainerType, StateTransaction, StorageChangeAdd, StorageChangeDelete, StorageChangeModify, TransactionData, TransactionType, TriggerType, Validator, ValidatorsCount, } from '@neo-one/node-core-esnext-esm';
import { utils as commonUtils } from '@neo-one/utils-esnext-esm';
import BN from 'bn.js';
import _ from 'lodash';
import { GenesisBlockNotRegisteredError } from './errors';
import { getDescriptorChanges } from './getValidators';
import { BlockLikeStorageCache, OutputStorageCache, ReadAddDeleteStorageCache, ReadAddStorageCache, ReadAddUpdateMetadataStorageCache, ReadAddUpdateStorageCache, ReadAllAddUpdateDeleteStorageCache, ReadGetAllAddDeleteStorageCache, ReadGetAllAddStorageCache, ReadGetAllAddUpdateDeleteStorageCache, } from './StorageCache';
import { wrapExecuteScripts } from './wrapExecuteScripts';
export class WriteBatchBlockchain {
constructor(options) {
this.settings = options.settings;
this.currentBlockInternal = options.currentBlock;
this.currentHeaderInternal = options.currentHeader;
this.storage = options.storage;
this.vm = options.vm;
this.getValidators = options.getValidators;
const output = new OutputStorageCache(() => this.storage.output);
this.caches = {
account: new ReadAllAddUpdateDeleteStorageCache({
name: 'account',
readAllStorage: () => this.storage.account,
update: (value, update) => value.update(update),
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt160ToString(key.hash),
createAddChange: (value) => ({ type: 'account', value }),
createDeleteChange: (key) => ({ type: 'account', key }),
}),
accountUnspent: new ReadGetAllAddDeleteStorageCache({
name: 'accountUnspent',
readGetAllStorage: () => this.storage.accountUnspent,
getKeyFromValue: (value) => ({ hash: value.hash, input: value.input }),
getKeyString: (key) => `${common.uInt160ToString(key.hash)}:${common.uInt256ToString(key.input.hash)}:${key.input.index}`,
matchesPartialKey: (value, key) => common.uInt160Equal(value.hash, key.hash),
createAddChange: (value) => ({ type: 'accountUnspent', value }),
createDeleteChange: (key) => ({ type: 'accountUnspent', key }),
}),
accountUnclaimed: new ReadGetAllAddDeleteStorageCache({
name: 'accountUnclaimed',
readGetAllStorage: () => this.storage.accountUnclaimed,
getKeyFromValue: (value) => ({ hash: value.hash, input: value.input }),
getKeyString: (key) => `${common.uInt160ToString(key.hash)}:${common.uInt256ToString(key.input.hash)}:${key.input.index}`,
matchesPartialKey: (value, key) => common.uInt160Equal(value.hash, key.hash),
createAddChange: (value) => ({ type: 'accountUnclaimed', value }),
createDeleteChange: (key) => ({ type: 'accountUnclaimed', key }),
}),
action: new ReadGetAllAddStorageCache({
name: 'action',
readGetAllStorage: () => this.storage.action,
getKeyFromValue: (value) => ({
index: value.index,
}),
getKeyString: (key) => key.index.toString(10),
matchesPartialKey: (value, key) => (key.indexStart === undefined || value.index.gte(key.indexStart)) &&
(key.indexStop === undefined || value.index.lte(key.indexStop)),
createAddChange: (value) => ({ type: 'action', value }),
}),
asset: new ReadAddUpdateStorageCache({
name: 'asset',
readStorage: () => this.storage.asset,
update: (value, update) => value.update(update),
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt256ToString(key.hash),
createAddChange: (value) => ({ type: 'asset', value }),
}),
block: new BlockLikeStorageCache({
name: 'block',
readStorage: () => ({
get: this.storage.block.get,
tryGet: this.storage.block.tryGet,
}),
createAddChange: (value) => ({ type: 'block', value }),
}),
blockData: new ReadAddStorageCache({
name: 'blockData',
readStorage: () => this.storage.blockData,
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt256ToString(key.hash),
createAddChange: (value) => ({ type: 'blockData', value }),
}),
header: new BlockLikeStorageCache({
name: 'header',
readStorage: () => ({
get: this.storage.header.get,
tryGet: this.storage.header.tryGet,
}),
createAddChange: (value) => ({ type: 'header', value }),
}),
transaction: new ReadAddStorageCache({
name: 'transaction',
readStorage: () => this.storage.transaction,
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt256ToString(key.hash),
createAddChange: (value) => ({ type: 'transaction', value }),
onAdd: async (value) => {
await Promise.all(value.outputs.map(async (out, index) => output.add({ hash: value.hash, index, output: out })));
},
allowDupes: true,
}),
transactionData: new ReadAddUpdateStorageCache({
name: 'transactionData',
readStorage: () => this.storage.transactionData,
update: (value, update) => value.update(update),
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt256ToString(key.hash),
createAddChange: (value) => ({ type: 'transactionData', value }),
allowDupes: true,
}),
output,
contract: new ReadAddDeleteStorageCache({
name: 'contract',
readStorage: () => this.storage.contract,
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt160ToString(key.hash),
createAddChange: (value) => ({ type: 'contract', value }),
createDeleteChange: (key) => ({ type: 'contract', key }),
}),
storageItem: new ReadGetAllAddUpdateDeleteStorageCache({
name: 'storageItem',
readGetAllStorage: () => this.storage.storageItem,
update: (value, update) => value.update(update),
getKeyFromValue: (value) => ({
hash: value.hash,
key: value.key,
}),
getKeyString: (key) => `${common.uInt160ToString(key.hash)}:${key.key.toString('hex')}`,
matchesPartialKey: (value, key) => (key.hash === undefined || common.uInt160Equal(value.hash, key.hash)) &&
(key.prefix === undefined || key.prefix.every((byte, idx) => value.key[idx] === byte)),
createAddChange: (value) => ({ type: 'storageItem', value }),
createDeleteChange: (key) => ({ type: 'storageItem', key }),
}),
validator: new ReadAllAddUpdateDeleteStorageCache({
name: 'validator',
readAllStorage: () => this.storage.validator,
getKeyFromValue: (value) => ({ publicKey: value.publicKey }),
getKeyString: (key) => common.ecPointToString(key.publicKey),
createAddChange: (value) => ({ type: 'validator', value }),
update: (value, update) => value.update(update),
createDeleteChange: (key) => ({ type: 'validator', key }),
}),
invocationData: new ReadAddStorageCache({
name: 'invocationData',
readStorage: () => this.storage.invocationData,
getKeyFromValue: (value) => ({ hash: value.hash }),
getKeyString: (key) => common.uInt256ToString(key.hash),
createAddChange: (value) => ({ type: 'invocationData', value }),
}),
validatorsCount: new ReadAddUpdateMetadataStorageCache({
name: 'validatorsCount',
readStorage: () => this.storage.validatorsCount,
createAddChange: (value) => ({ type: 'validatorsCount', value }),
update: (value, update) => value.update(update),
}),
};
this.account = this.caches.account;
this.accountUnspent = this.caches.accountUnspent;
this.accountUnclaimed = this.caches.accountUnclaimed;
this.action = this.caches.action;
this.asset = this.caches.asset;
this.block = this.caches.block;
this.blockData = this.caches.blockData;
this.header = this.caches.header;
this.transaction = this.caches.transaction;
this.transactionData = this.caches.transactionData;
this.output = this.caches.output;
this.contract = this.caches.contract;
this.storageItem = this.caches.storageItem;
this.validator = this.caches.validator;
this.invocationData = this.caches.invocationData;
this.validatorsCount = this.caches.validatorsCount;
}
get currentBlock() {
if (this.currentBlockInternal === undefined) {
throw new GenesisBlockNotRegisteredError();
}
return this.currentBlockInternal;
}
get currentBlockIndex() {
return this.currentBlockInternal === undefined ? 0 : this.currentBlockInternal.index;
}
get currentHeader() {
if (this.currentHeaderInternal === undefined) {
throw new GenesisBlockNotRegisteredError();
}
return this.currentHeaderInternal;
}
getChangeSet() {
return Object.values(this.caches).reduce((acc, cache) => acc.concat(cache.getChangeSet()), []);
}
getTrackedChangeSet() {
return Object.values(this.caches).reduce((acc, cache) => acc.concat(cache.getTrackedChangeSet()), []);
}
async persistBlock(block) {
const [maybePrevBlockData, outputContractsList] = await Promise.all([
block.index === 0 ? Promise.resolve(undefined) : this.blockData.get({ hash: block.previousHash }),
Promise.all([
...new Set(block.transactions.reduce((acc, transaction) => acc.concat(transaction.outputs.map((output) => common.uInt160ToString(output.address))), [])),
].map(async (hash) => this.contract.tryGet({ hash: common.stringToUInt160(hash) }))),
this.block.add(block),
this.header.add(block.header),
]);
const prevBlockData = maybePrevBlockData === undefined
? {
lastGlobalTransactionIndex: utils.NEGATIVE_ONE,
lastGlobalActionIndex: utils.NEGATIVE_ONE,
systemFee: utils.ZERO,
}
: {
lastGlobalTransactionIndex: maybePrevBlockData.lastGlobalTransactionIndex,
lastGlobalActionIndex: maybePrevBlockData.lastGlobalActionIndex,
systemFee: maybePrevBlockData.systemFee,
};
const outputContracts = {};
outputContractsList.filter(commonUtils.notNull).forEach((outputContract) => {
outputContracts[outputContract.hashHex] = outputContract;
});
const [utxo, rest] = _.partition(block.transactions.map((transaction, idx) => [idx, transaction]), ([idx, transaction]) => ((transaction.type === TransactionType.Claim && transaction instanceof ClaimTransaction) ||
(transaction.type === TransactionType.Contract && transaction instanceof ContractTransaction) ||
(transaction.type === TransactionType.Miner && transaction instanceof MinerTransaction)) &&
!transaction.outputs.some((output) => outputContracts[common.uInt160ToString(output.address)] !== undefined));
const [globalActionIndex] = await Promise.all([
rest.length > 0
? this.persistTransactions(block, rest, prevBlockData.lastGlobalTransactionIndex, prevBlockData.lastGlobalActionIndex)
: Promise.resolve(prevBlockData.lastGlobalActionIndex),
utxo.length > 0
?
this.persistUTXOTransactions(block, utxo, prevBlockData.lastGlobalTransactionIndex)
: Promise.resolve(),
]);
await this.blockData.add(new BlockData({
hash: block.hash,
lastGlobalTransactionIndex: prevBlockData.lastGlobalTransactionIndex.add(new BN(block.transactions.length)),
lastGlobalActionIndex: globalActionIndex,
systemFee: prevBlockData.systemFee.add(block.getSystemFee({
getOutput: this.output.get,
governingToken: this.settings.governingToken,
utilityToken: this.settings.utilityToken,
fees: this.settings.fees,
registerValidatorFee: this.settings.registerValidatorFee,
})),
}));
}
async persistUTXOTransactions(block, transactions, lastGlobalTransactionIndex) {
const inputs = [];
const claims = [];
const outputWithInputs = [];
for (const idxAndTransaction of transactions) {
const transaction = idxAndTransaction[1];
inputs.push(...transaction.inputs);
if (transaction.type === TransactionType.Claim && transaction instanceof ClaimTransaction) {
claims.push(...transaction.claims);
}
outputWithInputs.push(...this.getOutputWithInput(transaction));
}
await Promise.all([
Promise.all(transactions.map(async ([idx, transaction]) => this.transaction.add(transaction))),
Promise.all(transactions.map(async ([idx, transaction]) => this.transactionData.add(new TransactionData({
hash: transaction.hash,
startHeight: block.index,
blockHash: block.hash,
index: idx,
globalIndex: lastGlobalTransactionIndex.add(new BN(idx + 1)),
})))),
this.updateAccounts(inputs, claims, outputWithInputs),
this.updateCoins(inputs, claims, block),
]);
}
async persistTransactions(block, transactions, lastGlobalTransactionIndex, lastGlobalActionIndex) {
let globalActionIndex = lastGlobalActionIndex.add(utils.ONE);
for (const [idx, transaction] of transactions) {
globalActionIndex = await this.persistTransaction(block, transaction, idx, lastGlobalTransactionIndex, globalActionIndex);
}
return globalActionIndex.sub(utils.ONE);
}
async persistTransaction(block, transactionIn, transactionIndex, lastGlobalTransactionIndex, globalActionIndexIn) {
let globalActionIndex = globalActionIndexIn;
const transaction = transactionIn;
const claims = transaction.type === TransactionType.Claim && transaction instanceof ClaimTransaction ? transaction.claims : [];
let accountChanges = {};
let validatorChanges = {};
let validatorsCountChanges = [];
if (transaction.type === TransactionType.State && transaction instanceof StateTransaction) {
({ accountChanges, validatorChanges, validatorsCountChanges } = await getDescriptorChanges({
transactions: [transaction],
getAccount: async (hash) => this.account.tryGet({ hash }).then((account) => (account === undefined ? new Account({ hash }) : account)),
governingTokenHash: this.settings.governingToken.hashHex,
}));
}
await Promise.all([
this.transaction.add(transaction),
this.transactionData.add(new TransactionData({
hash: transaction.hash,
blockHash: block.hash,
startHeight: block.index,
index: transactionIndex,
globalIndex: lastGlobalTransactionIndex.add(new BN(transactionIndex + 1)),
})),
this.updateAccounts(transaction.inputs, claims, this.getOutputWithInput(transaction), accountChanges),
this.updateCoins(transaction.inputs, claims, block),
this.processStateTransaction(validatorChanges, validatorsCountChanges),
]);
if (transaction.type === TransactionType.Register && transaction instanceof RegisterTransaction) {
await this.asset.add(new Asset({
hash: transaction.hash,
type: transaction.asset.type,
name: transaction.asset.name,
amount: transaction.asset.amount,
precision: transaction.asset.precision,
owner: transaction.asset.owner,
admin: transaction.asset.admin,
issuer: transaction.asset.admin,
expiration: this.currentBlockIndex + 2 * 2000000,
isFrozen: false,
}));
}
else if (transaction.type === TransactionType.Issue && transaction instanceof IssueTransaction) {
const results = await Promise.all(Object.entries(transaction.getTransactionResults({
getOutput: this.output.get,
})));
await Promise.all(results.map(async ([assetHex, value]) => {
const hash = common.stringToUInt256(assetHex);
const asset = await this.asset.get({ hash });
await this.asset.update(asset, {
available: asset.available.add(value.neg()),
});
}));
}
else if (transaction.type === TransactionType.Enrollment && transaction instanceof EnrollmentTransaction) {
await this.validator.add(new Validator({
publicKey: transaction.publicKey,
}));
}
else if (transaction.type === TransactionType.Publish && transaction instanceof PublishTransaction) {
const contract = await this.contract.tryGet({
hash: transaction.contract.hash,
});
if (contract === undefined) {
await this.contract.add(transaction.contract);
}
}
else if (transaction.type === TransactionType.Invocation && transaction instanceof InvocationTransaction) {
const temporaryBlockchain = new WriteBatchBlockchain({
settings: this.settings,
currentBlock: this.currentBlockInternal,
currentHeader: this.currentHeader,
storage: this,
vm: this.vm,
getValidators: this.getValidators,
});
const migratedContractHashes = [];
const voteUpdates = [];
const actions = [];
const result = await wrapExecuteScripts(async () => this.vm.executeScripts({
scripts: [{ code: transaction.script }],
blockchain: temporaryBlockchain,
scriptContainer: {
type: ScriptContainerType.Transaction,
value: transaction,
},
triggerType: TriggerType.Application,
action: {
blockIndex: block.index,
blockHash: block.hash,
transactionIndex,
transactionHash: transaction.hash,
},
gas: transaction.gas,
listeners: {
onLog: ({ message, scriptHash }) => {
actions.push(new LogAction({
index: globalActionIndex,
scriptHash,
message,
}));
globalActionIndex = globalActionIndex.add(utils.ONE);
},
onNotify: ({ args, scriptHash }) => {
actions.push(new NotificationAction({
index: globalActionIndex,
scriptHash,
args,
}));
globalActionIndex = globalActionIndex.add(utils.ONE);
},
onMigrateContract: ({ from, to }) => {
migratedContractHashes.push([from, to]);
},
onSetVotes: ({ address, votes }) => {
voteUpdates.push([address, votes]);
},
},
persistingBlock: block,
}));
const addActionsPromise = Promise.all(actions.map(async (action) => this.action.add(action)));
if (result instanceof InvocationResultSuccess) {
const assetChangeSet = temporaryBlockchain.asset.getChangeSet();
const assetHash = assetChangeSet
.map((change) => change.type === 'add' && change.change.type === 'asset' ? change.change.value.hash : undefined)
.find((value) => value !== undefined);
const contractsChangeSet = temporaryBlockchain.contract.getChangeSet();
const contractHashes = contractsChangeSet
.map((change) => change.type === 'add' && change.change.type === 'contract' ? change.change.value.hash : undefined)
.filter(commonUtils.notNull);
const deletedContractHashes = contractsChangeSet
.map((change) => change.type === 'delete' && change.change.type === 'contract' ? change.change.key.hash : undefined)
.filter(commonUtils.notNull);
const storageChanges = temporaryBlockchain.storageItem
.getChangeSet()
.map((change) => {
const addChange = change.type === 'add' && change.change.type === 'storageItem'
? { value: change.change.value, subType: change.subType }
: undefined;
if (addChange !== undefined) {
const options = {
hash: addChange.value.hash,
key: addChange.value.key,
value: addChange.value.value,
};
return addChange.subType === 'add' ? new StorageChangeAdd(options) : new StorageChangeModify(options);
}
const deleteChange = change.type === 'delete' && change.change.type === 'storageItem' ? change.change.key : undefined;
if (deleteChange !== undefined) {
return new StorageChangeDelete(deleteChange);
}
return undefined;
})
.filter(commonUtils.notNull);
temporaryBlockchain.getTrackedChangeSet().forEach((change) => {
this.caches[change.type].addTrackedChange(change.key, change.value);
});
await Promise.all([
this.invocationData.add(new InvocationData({
hash: transaction.hash,
assetHash,
contractHashes,
deletedContractHashes,
migratedContractHashes,
voteUpdates,
blockIndex: block.index,
transactionIndex,
actionIndexStart: globalActionIndexIn,
actionIndexStop: globalActionIndex,
result,
storageChanges,
})),
addActionsPromise,
]);
}
else {
await Promise.all([
this.invocationData.add(new InvocationData({
hash: transaction.hash,
assetHash: undefined,
contractHashes: [],
deletedContractHashes: [],
migratedContractHashes: [],
voteUpdates: [],
blockIndex: block.index,
transactionIndex,
actionIndexStart: globalActionIndexIn,
actionIndexStop: globalActionIndex,
result,
storageChanges: [],
})),
addActionsPromise,
]);
}
}
return globalActionIndex;
}
async processStateTransaction(validatorChanges, validatorsCountChanges) {
const validatorsCount = await this.validatorsCount.tryGet();
const validatorsCountVotes = validatorsCount === undefined ? [] : [...validatorsCount.votes];
for (const [index, value] of validatorsCountChanges.entries()) {
validatorsCountVotes[index] = value;
}
await Promise.all([
Promise.all(Object.entries(validatorChanges).map(async ([publicKeyHex, { registered, votes }]) => {
const publicKey = common.hexToECPoint(publicKeyHex);
const validator = await this.validator.tryGet({ publicKey });
if (validator === undefined) {
await this.validator.add(new Validator({
publicKey,
registered,
votes,
}));
}
else if (((registered !== undefined && !registered) || (registered === undefined && !validator.registered)) &&
((votes !== undefined && votes.eq(utils.ZERO)) || (votes === undefined && validator.votes.eq(utils.ZERO)))) {
await this.validator.delete({ publicKey: validator.publicKey });
}
else {
await this.validator.update(validator, { votes, registered });
}
})),
validatorsCount === undefined
? this.validatorsCount.add(new ValidatorsCount({
votes: validatorsCountVotes,
}))
: (async () => {
await this.validatorsCount.update(validatorsCount, {
votes: validatorsCountVotes,
});
})(),
]);
}
async updateAccounts(inputs, claims, outputs, accountChanges = {}) {
const [inputOutputs, claimOutputs] = await Promise.all([
this.getInputOutputs(inputs),
this.getInputOutputs(claims),
]);
const addressValues = Object.entries(_.groupBy(inputOutputs
.map(({ output }) => [output.address, output.asset, output.value.neg()])
.concat(outputs.map(({ output }) => [output.address, output.asset, output.value])), ([address]) => common.uInt160ToHex(address)));
const addressSpent = this.groupByAddress(inputOutputs);
const addressClaimed = _.mapValues(this.groupByAddress(claimOutputs), (values) => values.map(({ input }) => input));
const addressOutputs = _.groupBy(outputs, (output) => common.uInt160ToHex(output.output.address));
await Promise.all(addressValues.map(async ([address, values]) => {
const spent = addressSpent[address];
const claimed = addressClaimed[address];
const outs = addressOutputs[address];
const changes = accountChanges[address];
await this.updateAccount(common.hexToUInt160(address), values.map(([_address, asset, value]) => [asset, value]), spent === undefined ? [] : spent, claimed === undefined ? [] : claimed, outs === undefined ? [] : outs, changes === undefined ? [] : changes);
}));
}
getOutputWithInput(transaction) {
return transaction.outputs.map((output, index) => ({
output,
input: new Input({ hash: transaction.hash, index }),
}));
}
async getInputOutputs(inputs) {
return Promise.all(inputs.map(async (input) => {
const output = await this.output.get(input);
return { input, output };
}));
}
groupByAddress(inputOutputs) {
return _.groupBy(inputOutputs, ({ output }) => common.uInt160ToHex(output.address));
}
async updateAccount(address, values, spent, claimed, outputs, votes) {
const account = await this.account.tryGet({ hash: address });
const balances = values.reduce((acc, [asset, value]) => {
const key = common.uInt256ToHex(asset);
if (acc[key] === undefined) {
acc[key] = utils.ZERO;
}
acc[key] = acc[key].add(value);
return acc;
}, account === undefined
? {}
: Object.entries(account.balances).reduce((acc, [key, value]) => {
if (value === undefined) {
return {
...acc,
[key]: utils.ZERO,
};
}
return {
...acc,
[key]: value,
};
}, {}));
const promises = [];
promises.push(...spent.map(async ({ input }) => this.accountUnspent.delete({
hash: address,
input,
})));
promises.push(...outputs.map(async ({ input }) => this.accountUnspent.add(new AccountUnspent({ hash: address, input }))));
promises.push(...claimed.map(async (input) => this.accountUnclaimed.delete({
hash: address,
input,
})));
promises.push(...spent
.filter(({ output }) => common.uInt256Equal(output.asset, this.settings.governingToken.hash))
.map(async ({ input }) => this.accountUnclaimed.add(new AccountUnclaimed({ hash: address, input }))));
if (account === undefined) {
promises.push(this.account.add(new Account({
hash: address,
balances,
votes,
})));
}
else {
promises.push(this.account.update(account, { balances, votes }).then(async (newAccount) => {
if (newAccount.isDeletable()) {
await this.account.delete({ hash: address });
}
}));
}
await Promise.all(promises);
}
async updateCoins(inputs, claims, block) {
const inputClaims = inputs
.map((input) => ({ type: 'input', input, hash: input.hash }))
.concat(claims.map((input) => ({ type: 'claim', input, hash: input.hash })));
const hashInputClaims = Object.entries(_.groupBy(inputClaims, ({ hash }) => common.uInt256ToHex(hash)));
await Promise.all(hashInputClaims.map(async ([hash, values]) => this.updateCoin(common.hexToUInt256(hash), values, block)));
}
async updateCoin(hash, inputClaims, block) {
const spentCoins = await this.transactionData.get({ hash });
const endHeights = { ...spentCoins.endHeights };
const claimed = { ...spentCoins.claimed };
for (const inputClaim of inputClaims) {
if (inputClaim.type === 'input') {
endHeights[inputClaim.input.index] = block.index;
}
else {
claimed[inputClaim.input.index] = true;
}
}
await this.transactionData.update(spentCoins, {
endHeights,
claimed,
});
}
}
//# sourceMappingURL=WriteBatchBlockchain.js.map