fix: sdk should not cache on requested block height if interactions come from redstone-sequencer
This commit is contained in:
committed by
Piotr Pędziwiatr
parent
c9924a7443
commit
7a7a9be1f8
@@ -146,7 +146,7 @@ export interface Contract<State = unknown> {
|
||||
tags?: Tags,
|
||||
transfer?: ArTransfer,
|
||||
strict?: boolean
|
||||
): Promise<string | null>;
|
||||
): Promise<any | null>;
|
||||
|
||||
/**
|
||||
* Returns the full call tree report the last
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
EvaluationOptions,
|
||||
Evolve,
|
||||
ExecutionContext,
|
||||
GQLEdgeInterface,
|
||||
GQLNodeInterface,
|
||||
HandlerApi,
|
||||
InnerWritesEvaluator,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
sleep,
|
||||
SmartWeave,
|
||||
SmartWeaveTags,
|
||||
SourceType,
|
||||
Tags
|
||||
} from '@smartweave';
|
||||
import { TransactionStatusResponse } from 'arweave/node/transactions';
|
||||
@@ -388,8 +390,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
const evolvedSrcTxId = Evolve.evolvedSrcTxId(cachedState?.cachedValue?.state);
|
||||
|
||||
let contractDefinition,
|
||||
interactions = [],
|
||||
sortedInteractions = [],
|
||||
interactions: GQLEdgeInterface[] = [],
|
||||
sortedInteractions: GQLEdgeInterface[] = [],
|
||||
handler;
|
||||
if (cachedBlockHeight != blockHeight) {
|
||||
[contractDefinition, interactions] = await Promise.all([
|
||||
@@ -421,17 +423,20 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
}
|
||||
}
|
||||
|
||||
const containsInteractionsFromSequencer = interactions.some((i) => i.node.source == SourceType.REDSTONE_SEQUENCER);
|
||||
this.logger.debug('containsInteractionsFromSequencer', containsInteractionsFromSequencer);
|
||||
|
||||
return {
|
||||
contractDefinition,
|
||||
blockHeight,
|
||||
interactions,
|
||||
sortedInteractions,
|
||||
handler,
|
||||
smartweave: this.smartweave,
|
||||
contract: this,
|
||||
evaluationOptions: this._evaluationOptions,
|
||||
currentNetworkInfo,
|
||||
cachedState
|
||||
cachedState,
|
||||
containsInteractionsFromSequencer
|
||||
};
|
||||
}
|
||||
|
||||
@@ -469,17 +474,19 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
|
||||
this.logger.debug('Creating execution context from tx:', benchmark.elapsed());
|
||||
|
||||
const containsInteractionsFromSequencer = interactions.some((i) => i.node.source == SourceType.REDSTONE_SEQUENCER);
|
||||
|
||||
return {
|
||||
contractDefinition,
|
||||
blockHeight,
|
||||
interactions,
|
||||
sortedInteractions,
|
||||
handler,
|
||||
smartweave: this.smartweave,
|
||||
contract: this,
|
||||
evaluationOptions: this._evaluationOptions,
|
||||
caller,
|
||||
cachedState
|
||||
cachedState,
|
||||
containsInteractionsFromSequencer
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,6 @@ export type ExecutionContext<State, Api = unknown> = {
|
||||
* block height used for all operations - either requested block height or current network block height
|
||||
*/
|
||||
blockHeight: number;
|
||||
/**
|
||||
* all the interactions registered for this contract
|
||||
*/
|
||||
interactions: GQLEdgeInterface[];
|
||||
/**
|
||||
* interaction sorted using either {@link LexicographicalInteractionsSorter} or {@link BlockHeightInteractionsSorter}
|
||||
* - crucial for proper and deterministic state evaluation
|
||||
@@ -57,4 +53,9 @@ export type ExecutionContext<State, Api = unknown> = {
|
||||
currentBlockData?: BlockData;
|
||||
caller?: string; // note: this is only set for "viewState" operations
|
||||
cachedState?: BlockHeightCacheResult<EvalStateResult<State>>;
|
||||
|
||||
// if the interactions list contains interactions from sequencer
|
||||
// we cannot cache at requested block height - as it may happen that after this state
|
||||
// evaluation - new transactions will be available on the same block height.
|
||||
containsInteractionsFromSequencer: boolean;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum SmartWeaveTags {
|
||||
INPUT = 'Input',
|
||||
CONTENT_TYPE = 'Content-Type',
|
||||
CONTRACT_SRC_TX_ID = 'Contract-Src', // note: should be named Contract-Src-Tx-Id
|
||||
SDK = 'SDK',
|
||||
MIN_FEE = 'Min-Fee',
|
||||
INIT_STATE = 'Init-State',
|
||||
INIT_STATE_TX = 'Init-State-TX',
|
||||
|
||||
@@ -33,12 +33,4 @@ export interface CreateContract {
|
||||
deploy(contractData: ContractData): Promise<string>;
|
||||
|
||||
deployFromSourceTx(contractData: FromSrcTxContractData): Promise<string>;
|
||||
|
||||
/**
|
||||
* TODO: I would like to add the contract upgrade feature here
|
||||
* - as an "evolution" of the current "evolve" ;-)
|
||||
* @param contractTxId
|
||||
* @param contractData
|
||||
*/
|
||||
update(contractTxId: string, contractData: ContractData): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,15 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
||||
const contractTxId = executionContext.contractDefinition.txId;
|
||||
|
||||
this.cLogger.debug(`onStateEvaluated: cache update for contract ${contractTxId} [${transaction.block.height}]`);
|
||||
await this.putInCache(contractTxId, transaction, state);
|
||||
|
||||
// TODO: this will be problematic if we decide to cache only "onStateEvaluated" and containsInteractionsFromSequencer = true
|
||||
await this.putInCache(
|
||||
contractTxId,
|
||||
transaction,
|
||||
state,
|
||||
executionContext.blockHeight,
|
||||
executionContext.containsInteractionsFromSequencer
|
||||
);
|
||||
await this.cache.flush();
|
||||
}
|
||||
|
||||
@@ -128,7 +136,13 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
||||
state: EvalStateResult<State>
|
||||
): Promise<void> {
|
||||
if (executionContext.evaluationOptions.updateCacheForEachInteraction) {
|
||||
await this.putInCache(executionContext.contractDefinition.txId, transaction, state);
|
||||
await this.putInCache(
|
||||
executionContext.contractDefinition.txId,
|
||||
transaction,
|
||||
state,
|
||||
executionContext.blockHeight,
|
||||
executionContext.containsInteractionsFromSequencer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +189,9 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
||||
protected async putInCache<State>(
|
||||
contractTxId: string,
|
||||
transaction: GQLNodeInterface,
|
||||
state: EvalStateResult<State>
|
||||
state: EvalStateResult<State>,
|
||||
requestedBlockHeight: number = null,
|
||||
containsInteractionsFromSequencer = false
|
||||
): Promise<void> {
|
||||
if (transaction.dry) {
|
||||
return;
|
||||
@@ -183,8 +199,15 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
|
||||
if (transaction.confirmationStatus !== undefined && transaction.confirmationStatus !== 'confirmed') {
|
||||
return;
|
||||
}
|
||||
const transactionId = transaction.id;
|
||||
|
||||
const blockHeight = transaction.block.height;
|
||||
|
||||
if (requestedBlockHeight !== null && requestedBlockHeight == blockHeight && containsInteractionsFromSequencer) {
|
||||
this.cLogger.debug('skipping caching of the last block');
|
||||
return;
|
||||
}
|
||||
const transactionId = transaction.id;
|
||||
|
||||
const stateToCache = new EvalStateResult(state.state, state.validity, transactionId, transaction.block.id);
|
||||
|
||||
await this.cache.put(new BlockHeightKey(contractTxId, blockHeight), stateToCache);
|
||||
|
||||
@@ -66,6 +66,7 @@ export class DefaultCreateContract implements CreateContract {
|
||||
contractTX.addTag(SmartWeaveTags.APP_NAME, 'SmartWeaveContract');
|
||||
contractTX.addTag(SmartWeaveTags.APP_VERSION, '0.3.0');
|
||||
contractTX.addTag(SmartWeaveTags.CONTRACT_SRC_TX_ID, srcTxId);
|
||||
contractTX.addTag(SmartWeaveTags.SDK, 'RedStone');
|
||||
contractTX.addTag('Content-Type', 'application/json');
|
||||
|
||||
await this.arweave.transactions.sign(contractTX, wallet);
|
||||
@@ -77,8 +78,4 @@ export class DefaultCreateContract implements CreateContract {
|
||||
throw new Error('Unable to write Contract Initial State');
|
||||
}
|
||||
}
|
||||
|
||||
update(contractTxId: string, contractData: ContractData): Promise<void> {
|
||||
throw new Error('Not implemented yet');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { arrayToHex, GQLEdgeInterface, InteractionsSorter } from '@smartweave';
|
||||
import { arrayToHex, GQLEdgeInterface, InteractionsSorter, LoggerFactory, SourceType } from '@smartweave';
|
||||
import Arweave from 'arweave';
|
||||
|
||||
/**
|
||||
* implementation that is based on current's SDK sorting alg. (which seems to be wrong ;-))
|
||||
* implementation that is based on current's SDK sorting alg.
|
||||
*/
|
||||
export class LexicographicalInteractionsSorter implements InteractionsSorter {
|
||||
private readonly logger = LoggerFactory.INST.create('LexicographicalInteractionsSorter');
|
||||
|
||||
constructor(private readonly arweave: Arweave) {}
|
||||
|
||||
async sort(transactions: GQLEdgeInterface[]): Promise<GQLEdgeInterface[]> {
|
||||
@@ -19,11 +21,12 @@ export class LexicographicalInteractionsSorter implements InteractionsSorter {
|
||||
const { node } = txInfo;
|
||||
|
||||
// might have been already set by the RedStone Sequencer
|
||||
if (txInfo.sortKey !== undefined) {
|
||||
return;
|
||||
if (txInfo.node.sortKey !== undefined && txInfo.node.source == SourceType.REDSTONE_SEQUENCER) {
|
||||
this.logger.debug('Using sortkey from sequencer', txInfo.node.sortKey);
|
||||
txInfo.sortKey = txInfo.node.sortKey;
|
||||
} else {
|
||||
txInfo.sortKey = await this.createSortKey(node.block.id, node.id, node.block.height);
|
||||
}
|
||||
|
||||
txInfo.sortKey = await this.createSortKey(node.block.id, node.id, node.block.height);
|
||||
}
|
||||
|
||||
public async createSortKey(blockId: string, transactionId: string, blockHeight: number) {
|
||||
@@ -31,7 +34,7 @@ export class LexicographicalInteractionsSorter implements InteractionsSorter {
|
||||
const txIdBytes = this.arweave.utils.b64UrlToBuffer(transactionId);
|
||||
const concatenated = this.arweave.utils.concatBuffers([blockHashBytes, txIdBytes]);
|
||||
const hashed = arrayToHex(await this.arweave.crypto.hash(concatenated));
|
||||
const blockHeightString = `000000${blockHeight}`.slice(-12);
|
||||
const blockHeightString = `${blockHeight}`.padStart(12, '0');
|
||||
|
||||
return `${blockHeightString},${hashed}`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Arweave from 'arweave';
|
||||
import { ArWallet, GQLNodeInterface, GQLTagInterface } from '@smartweave';
|
||||
import { ArWallet, GQLNodeInterface, GQLTagInterface, SmartWeaveTags } from '@smartweave';
|
||||
import Transaction from 'arweave/node/lib/transaction';
|
||||
import { CreateTransactionInterface } from 'arweave/node/common';
|
||||
import { BlockData } from 'arweave/node/blocks';
|
||||
@@ -35,11 +35,12 @@ export async function createTx(
|
||||
interactionTx.addTag(tag.name.toString(), tag.value.toString());
|
||||
}
|
||||
}
|
||||
interactionTx.addTag('App-Name', 'SmartWeaveAction');
|
||||
interactionTx.addTag(SmartWeaveTags.APP_NAME, 'SmartWeaveAction');
|
||||
// use real SDK version here?
|
||||
interactionTx.addTag('App-Version', '0.3.0');
|
||||
interactionTx.addTag('Contract', contractId);
|
||||
interactionTx.addTag('Input', JSON.stringify(input));
|
||||
interactionTx.addTag(SmartWeaveTags.APP_VERSION, '0.3.0');
|
||||
interactionTx.addTag(SmartWeaveTags.SDK, 'RedStone');
|
||||
interactionTx.addTag(SmartWeaveTags.CONTRACT_TX_ID, contractId);
|
||||
interactionTx.addTag(SmartWeaveTags.INPUT, JSON.stringify(input));
|
||||
|
||||
await arweave.transactions.sign(interactionTx, wallet);
|
||||
return interactionTx;
|
||||
|
||||
@@ -47,11 +47,14 @@ export interface GQLNodeInterface {
|
||||
id: string;
|
||||
};
|
||||
dry?: boolean;
|
||||
sortKey?: string; //added dynamically by the LexicographicalInteractionsSorter
|
||||
sortKey?: string; // added dynamically by RedStone Sequencer
|
||||
confirmationStatus?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface GQLEdgeInterface {
|
||||
// added dynamically by the LexicographicalInteractionsSorter
|
||||
// or rewritten from GQLNodeInterface.sortKey (if added there by RedStone Sequencer)
|
||||
sortKey?: string;
|
||||
cursor: string;
|
||||
node: GQLNodeInterface;
|
||||
|
||||
73
tools/bundle.ts
Normal file
73
tools/bundle.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable */
|
||||
import Arweave from 'arweave';
|
||||
import {
|
||||
LoggerFactory,
|
||||
MemCache,
|
||||
RedstoneGatewayContractDefinitionLoader,
|
||||
RedstoneGatewayInteractionsLoader,
|
||||
SmartWeaveNodeFactory
|
||||
} from '../src';
|
||||
import { readJSON } from '../../redstone-smartweave-examples/src/_utils';
|
||||
import { TsLogFactory } from '../src/logging/node/TsLogFactory';
|
||||
|
||||
const logger = LoggerFactory.INST.create('Contract');
|
||||
|
||||
LoggerFactory.use(new TsLogFactory());
|
||||
LoggerFactory.INST.logLevel('error');
|
||||
LoggerFactory.INST.logLevel('info', 'Contract');
|
||||
LoggerFactory.INST.logLevel('debug', 'RedstoneGatewayInteractionsLoader');
|
||||
LoggerFactory.INST.logLevel('error', 'DefaultStateEvaluator');
|
||||
LoggerFactory.INST.logLevel('error', 'LexicographicalInteractionsSorter');
|
||||
|
||||
async function main() {
|
||||
const arweave = Arweave.init({
|
||||
host: 'arweave.net', // Hostname or IP address for a Arweave host
|
||||
port: 443, // Port
|
||||
protocol: 'https', // Network protocol http or https
|
||||
timeout: 60000, // Network request timeouts in milliseconds
|
||||
logging: false // Enable network request logging
|
||||
});
|
||||
|
||||
const smartweave = SmartWeaveNodeFactory.memCachedBased(arweave)
|
||||
.setInteractionsLoader(
|
||||
new RedstoneGatewayInteractionsLoader('http://localhost:5666', { notCorrupted: true })
|
||||
)
|
||||
.setDefinitionLoader(
|
||||
new RedstoneGatewayContractDefinitionLoader('https://gateway.redstone.finance', arweave, new MemCache())
|
||||
)
|
||||
.build();
|
||||
|
||||
const jwk = readJSON('../redstone-node/.secrets/redstone-jwk.json');
|
||||
// connecting to a given contract
|
||||
const token = smartweave
|
||||
.contract("OrO8n453N6bx921wtsEs-0OCImBLCItNU5oSbFKlFuU")
|
||||
.setEvaluationOptions({
|
||||
sequencerAddress: "http://localhost:5666/"
|
||||
})
|
||||
// connecting wallet to a contract. It is required before performing any "writeInteraction"
|
||||
// calling "writeInteraction" without connecting to a wallet first will cause a runtime error.
|
||||
.connect(jwk);
|
||||
|
||||
const result1 = await token.readState();
|
||||
|
||||
logger.info("Amount of computed interactions before 'bundleInteraction':", Object.keys(result1.validity).length);
|
||||
|
||||
const result = await token.bundleInteraction<any>({
|
||||
function: "transfer",
|
||||
data: {
|
||||
target: "fake-from-bundle",
|
||||
qty: 18599333,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info("Result from the sequencer", result.id);
|
||||
|
||||
// the new transaction is instantly available - ie. during the state read operation
|
||||
const result2 = await token.readState();
|
||||
|
||||
logger.info("Amount of computed interactions after 'bundleInteraction':", Object.keys(result2.validity).length);
|
||||
|
||||
}
|
||||
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
@@ -16,10 +16,11 @@ import {TsLogFactory} from '../src/logging/node/TsLogFactory';
|
||||
const logger = LoggerFactory.INST.create('Contract');
|
||||
|
||||
LoggerFactory.use(new TsLogFactory());
|
||||
LoggerFactory.INST.logLevel('fatal');
|
||||
LoggerFactory.INST.logLevel('error');
|
||||
LoggerFactory.INST.logLevel('info', 'Contract');
|
||||
LoggerFactory.INST.logLevel('debug', 'RedstoneGatewayInteractionsLoader');
|
||||
LoggerFactory.INST.logLevel('error', 'DefaultStateEvaluator');
|
||||
LoggerFactory.INST.logLevel('error', 'DefaultStateEvaluator');
|
||||
|
||||
async function main() {
|
||||
printTestInfo();
|
||||
@@ -66,9 +67,9 @@ async function main() {
|
||||
|
||||
const jwk = readJSON("../redstone-node/.secrets/redstone-jwk.json");
|
||||
const contract = smartweave.contract(LOOT_CONTRACT)
|
||||
/*.setEvaluationOptions({
|
||||
.setEvaluationOptions({
|
||||
sequencerAddress: "http://localhost:5666/"
|
||||
})*/
|
||||
})
|
||||
.connect(jwk);
|
||||
const bundledInteraction = await contract.bundleInteraction({
|
||||
function: "generate"
|
||||
@@ -78,7 +79,7 @@ async function main() {
|
||||
|
||||
// bundlr balance I-5rWUehEv-MjdK9gFw09RxfSLQX9DIHxG614Wf8qo0 -h https://node1.bundlr.network/ -c arweave
|
||||
|
||||
//await contract.readState();
|
||||
await contract.readState();
|
||||
|
||||
const heapUsedAfter = Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100;
|
||||
const rssUsedAfter = Math.round((process.memoryUsage().rss / 1024 / 1024) * 100) / 100;
|
||||
|
||||
Reference in New Issue
Block a user