fix: VRF issues with internal writes or 'strict' mode. #232
This commit is contained in:
@@ -170,6 +170,75 @@ describe('Testing the Profit Sharing Token', () => {
|
|||||||
expect(vrf['random_99_1'] == vrf['random_99_2']).toBe(true);
|
expect(vrf['random_99_1'] == vrf['random_99_2']).toBe(true);
|
||||||
expect(vrf['random_99_2'] == vrf['random_99_3']).toBe(true);
|
expect(vrf['random_99_2'] == vrf['random_99_3']).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow to test VRF on a standard forLocal Warp instance in strict mode', async () => {
|
||||||
|
const localWarp = WarpFactory.forLocal(1823);
|
||||||
|
|
||||||
|
const { contractTxId: vrfContractTxId } = await localWarp.createContract.deploy({
|
||||||
|
wallet,
|
||||||
|
initState: JSON.stringify(initialState),
|
||||||
|
src: contractSrc
|
||||||
|
});
|
||||||
|
|
||||||
|
await mineBlock(localWarp);
|
||||||
|
const vrfContract = localWarp.contract(vrfContractTxId).connect(wallet);
|
||||||
|
await vrfContract.writeInteraction(
|
||||||
|
{
|
||||||
|
function: 'vrf'
|
||||||
|
},
|
||||||
|
{ vrf: true, strict: true }
|
||||||
|
);
|
||||||
|
const result = await vrfContract.readState();
|
||||||
|
const lastTxId = Object.keys(result.cachedValue.validity).pop();
|
||||||
|
const vrf = (result.cachedValue.state as any).vrf[lastTxId];
|
||||||
|
|
||||||
|
expect(vrf).not.toBeUndefined();
|
||||||
|
expect(vrf['random_6_1'] == vrf['random_6_2']).toBe(true);
|
||||||
|
expect(vrf['random_6_2'] == vrf['random_6_3']).toBe(true);
|
||||||
|
expect(vrf['random_12_1'] == vrf['random_12_2']).toBe(true);
|
||||||
|
expect(vrf['random_12_2'] == vrf['random_12_3']).toBe(true);
|
||||||
|
expect(vrf['random_46_1'] == vrf['random_46_2']).toBe(true);
|
||||||
|
expect(vrf['random_46_2'] == vrf['random_46_3']).toBe(true);
|
||||||
|
expect(vrf['random_99_1'] == vrf['random_99_2']).toBe(true);
|
||||||
|
expect(vrf['random_99_2'] == vrf['random_99_3']).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow to test VRF on a standard forLocal Warp instance with internal writes', async () => {
|
||||||
|
const localWarp = WarpFactory.forLocal(1823);
|
||||||
|
|
||||||
|
const { contractTxId: vrfContractTxId } = await localWarp.createContract.deploy({
|
||||||
|
wallet,
|
||||||
|
initState: JSON.stringify(initialState),
|
||||||
|
src: contractSrc
|
||||||
|
});
|
||||||
|
|
||||||
|
await mineBlock(localWarp);
|
||||||
|
const vrfContract = localWarp
|
||||||
|
.contract(vrfContractTxId)
|
||||||
|
.setEvaluationOptions({
|
||||||
|
internalWrites: true
|
||||||
|
})
|
||||||
|
.connect(wallet);
|
||||||
|
const result2 = await vrfContract.writeInteraction(
|
||||||
|
{
|
||||||
|
function: 'vrf'
|
||||||
|
},
|
||||||
|
{ vrf: true, strict: true }
|
||||||
|
);
|
||||||
|
const result = await vrfContract.readState();
|
||||||
|
const lastTxId = Object.keys(result.cachedValue.validity).pop();
|
||||||
|
const vrf = (result.cachedValue.state as any).vrf[lastTxId];
|
||||||
|
|
||||||
|
expect(vrf).not.toBeUndefined();
|
||||||
|
expect(vrf['random_6_1'] == vrf['random_6_2']).toBe(true);
|
||||||
|
expect(vrf['random_6_2'] == vrf['random_6_3']).toBe(true);
|
||||||
|
expect(vrf['random_12_1'] == vrf['random_12_2']).toBe(true);
|
||||||
|
expect(vrf['random_12_2'] == vrf['random_12_3']).toBe(true);
|
||||||
|
expect(vrf['random_46_1'] == vrf['random_46_2']).toBe(true);
|
||||||
|
expect(vrf['random_46_2'] == vrf['random_46_3']).toBe(true);
|
||||||
|
expect(vrf['random_99_1'] == vrf['random_99_2']).toBe(true);
|
||||||
|
expect(vrf['random_99_2'] == vrf['random_99_3']).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class VrfDecorator extends ArweaveGatewayInteractionsLoader {
|
class VrfDecorator extends ArweaveGatewayInteractionsLoader {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
|
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
|
||||||
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
|
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
|
||||||
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
|
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
|
||||||
"FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0",
|
|
||||||
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
|
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
|
||||||
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
|
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
|
||||||
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",
|
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
|
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
|
||||||
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
|
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
|
||||||
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
|
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
|
||||||
"FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0",
|
|
||||||
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
|
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
|
||||||
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
|
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
|
||||||
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",
|
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
import { Tags, ArTransfer, emptyTransfer, ArWallet } from './deploy/CreateContract';
|
import { Tags, ArTransfer, emptyTransfer, ArWallet } from './deploy/CreateContract';
|
||||||
import { SourceData, SourceImpl } from './deploy/impl/SourceImpl';
|
import { SourceData, SourceImpl } from './deploy/impl/SourceImpl';
|
||||||
import { InnerWritesEvaluator } from './InnerWritesEvaluator';
|
import { InnerWritesEvaluator } from './InnerWritesEvaluator';
|
||||||
|
import { generateMockVrf } from '../utils/vrf';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of {@link Contract} that is backwards compatible with current style
|
* An implementation of {@link Contract} that is backwards compatible with current style
|
||||||
@@ -332,7 +333,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
// 3. Verify the callStack and search for any "internalWrites" transactions
|
// 3. Verify the callStack and search for any "internalWrites" transactions
|
||||||
// 4. For each found "internalWrite" transaction - generate additional tag:
|
// 4. For each found "internalWrite" transaction - generate additional tag:
|
||||||
// {name: 'InternalWrite', value: callingContractTxId}
|
// {name: 'InternalWrite', value: callingContractTxId}
|
||||||
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict);
|
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf);
|
||||||
|
|
||||||
if (strict && handlerResult.type !== 'ok') {
|
if (strict && handlerResult.type !== 'ok') {
|
||||||
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
||||||
@@ -352,7 +353,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
this.logger.debug('Tags with inner calls', tags);
|
this.logger.debug('Tags with inner calls', tags);
|
||||||
} else {
|
} else {
|
||||||
if (strict) {
|
if (strict) {
|
||||||
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict);
|
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf);
|
||||||
if (handlerResult.type !== 'ok') {
|
if (handlerResult.type !== 'ok') {
|
||||||
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
||||||
}
|
}
|
||||||
@@ -533,7 +534,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
sortKey?: string,
|
sortKey?: string,
|
||||||
tags: Tags = [],
|
tags: Tags = [],
|
||||||
transfer: ArTransfer = emptyTransfer,
|
transfer: ArTransfer = emptyTransfer,
|
||||||
strict = false
|
strict = false,
|
||||||
|
vrf = false
|
||||||
): Promise<InteractionResult<State, View>> {
|
): Promise<InteractionResult<State, View>> {
|
||||||
this.logger.info('Call contract input', input);
|
this.logger.info('Call contract input', input);
|
||||||
this.maybeResetRootContract();
|
this.maybeResetRootContract();
|
||||||
@@ -552,6 +554,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
if (caller) {
|
if (caller) {
|
||||||
effectiveCaller = caller;
|
effectiveCaller = caller;
|
||||||
} else if (this.signer) {
|
} else if (this.signer) {
|
||||||
|
// we're creating this transaction just to call the signing function on it
|
||||||
|
// - and retrieve the caller/owner
|
||||||
const dummyTx = await arweave.createTransaction({
|
const dummyTx = await arweave.createTransaction({
|
||||||
data: Math.random().toString().slice(-4),
|
data: Math.random().toString().slice(-4),
|
||||||
reward: '72600854',
|
reward: '72600854',
|
||||||
@@ -600,6 +604,10 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
|
|
||||||
dummyTx.sortKey = await this._sorter.createSortKey(dummyTx.block.id, dummyTx.id, dummyTx.block.height, true);
|
dummyTx.sortKey = await this._sorter.createSortKey(dummyTx.block.id, dummyTx.id, dummyTx.block.height, true);
|
||||||
dummyTx.strict = strict;
|
dummyTx.strict = strict;
|
||||||
|
if (vrf) {
|
||||||
|
dummyTx.vrf = generateMockVrf(dummyTx.sortKey, arweave);
|
||||||
|
}
|
||||||
|
|
||||||
const handleResult = await this.evalInteraction<Input, View>(
|
const handleResult = await this.evalInteraction<Input, View>(
|
||||||
{
|
{
|
||||||
interaction,
|
interaction,
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ import {
|
|||||||
import { Benchmark } from '../../../logging/Benchmark';
|
import { Benchmark } from '../../../logging/Benchmark';
|
||||||
import { LoggerFactory } from '../../../logging/LoggerFactory';
|
import { LoggerFactory } from '../../../logging/LoggerFactory';
|
||||||
import { ArweaveWrapper } from '../../../utils/ArweaveWrapper';
|
import { ArweaveWrapper } from '../../../utils/ArweaveWrapper';
|
||||||
import { bufToBn, sleep } from '../../../utils/utils';
|
import { sleep } from '../../../utils/utils';
|
||||||
import { GW_TYPE, InteractionsLoader } from '../InteractionsLoader';
|
import { GW_TYPE, InteractionsLoader } from '../InteractionsLoader';
|
||||||
import { InteractionsSorter } from '../InteractionsSorter';
|
import { InteractionsSorter } from '../InteractionsSorter';
|
||||||
import { EvaluationOptions } from '../StateEvaluator';
|
import { EvaluationOptions } from '../StateEvaluator';
|
||||||
import { LexicographicalInteractionsSorter } from './LexicographicalInteractionsSorter';
|
import { LexicographicalInteractionsSorter } from './LexicographicalInteractionsSorter';
|
||||||
import { WarpEnvironment } from '../../Warp';
|
import { WarpEnvironment } from '../../Warp';
|
||||||
import elliptic from 'elliptic';
|
import { generateMockVrf } from '../../../utils/vrf';
|
||||||
import { Evaluate } from '@idena/vrf-js';
|
|
||||||
|
|
||||||
const MAX_REQUEST = 100;
|
const MAX_REQUEST = 100;
|
||||||
|
|
||||||
@@ -41,10 +40,6 @@ export function bundledTxsFilter(tx: GQLEdgeInterface) {
|
|||||||
return !tx.node.parent?.id && !tx.node.bundledIn?.id;
|
return !tx.node.parent?.id && !tx.node.bundledIn?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EC = new elliptic.ec('secp256k1');
|
|
||||||
const key = EC.genKeyPair();
|
|
||||||
const pubKeyS = key.getPublic(true, 'hex');
|
|
||||||
|
|
||||||
export class ArweaveGatewayInteractionsLoader implements InteractionsLoader {
|
export class ArweaveGatewayInteractionsLoader implements InteractionsLoader {
|
||||||
private readonly logger = LoggerFactory.INST.create('ArweaveGatewayInteractionsLoader');
|
private readonly logger = LoggerFactory.INST.create('ArweaveGatewayInteractionsLoader');
|
||||||
|
|
||||||
@@ -179,14 +174,7 @@ export class ArweaveGatewayInteractionsLoader implements InteractionsLoader {
|
|||||||
return t.name == SmartWeaveTags.REQUEST_VRF && t.value === 'true';
|
return t.name == SmartWeaveTags.REQUEST_VRF && t.value === 'true';
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
const data = this.arweave.utils.stringToBuffer(interaction.sortKey);
|
interaction.vrf = generateMockVrf(interaction.sortKey, this.arweave);
|
||||||
const [index, proof] = Evaluate(key.getPrivate().toArray(), data);
|
|
||||||
interaction.vrf = {
|
|
||||||
index: this.arweave.utils.bufferTob64Url(index),
|
|
||||||
proof: this.arweave.utils.bufferTob64Url(proof),
|
|
||||||
bigint: bufToBn(index).toString(),
|
|
||||||
pubkey: pubKeyS
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export async function createInteractionTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// both reward and last_tx are irrelevant in case of interactions
|
// both reward and last_tx are irrelevant in case of interactions
|
||||||
// that are bundled. So to speed up the procees (and prevent the arweave-js
|
// that are bundled. So to speed up the process (and prevent the arweave-js
|
||||||
// from calling /tx_anchor and /price endpoints) - we're presetting theses
|
// from calling /tx_anchor and /price endpoints) - we're presetting these
|
||||||
// values here
|
// values here
|
||||||
if (dummy) {
|
if (dummy) {
|
||||||
options.reward = '72600854';
|
options.reward = '72600854';
|
||||||
|
|||||||
20
src/utils/vrf.ts
Normal file
20
src/utils/vrf.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import elliptic from 'elliptic';
|
||||||
|
import Arweave from 'arweave';
|
||||||
|
import { Evaluate } from '@idena/vrf-js';
|
||||||
|
import { bufToBn } from './utils';
|
||||||
|
import { VrfData } from '../legacy/gqlResult';
|
||||||
|
|
||||||
|
const EC = new elliptic.ec('secp256k1');
|
||||||
|
const key = EC.genKeyPair();
|
||||||
|
const pubKeyS = key.getPublic(true, 'hex');
|
||||||
|
|
||||||
|
export function generateMockVrf(sortKey: string, arweave: Arweave): VrfData {
|
||||||
|
const data = arweave.utils.stringToBuffer(sortKey);
|
||||||
|
const [index, proof] = Evaluate(key.getPrivate().toArray(), data);
|
||||||
|
return {
|
||||||
|
index: arweave.utils.bufferTob64Url(index),
|
||||||
|
proof: arweave.utils.bufferTob64Url(proof),
|
||||||
|
bigint: bufToBn(index).toString(),
|
||||||
|
pubkey: pubKeyS
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -31,12 +31,11 @@ async function main() {
|
|||||||
logging: false // Enable network request logging
|
logging: false // Enable network request logging
|
||||||
});
|
});
|
||||||
|
|
||||||
const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: false});
|
const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: true});
|
||||||
try {
|
try {
|
||||||
const contract = warp.contract("5Yt1IujBmOm1LSux9KDUTjCE7rJqepzP7gZKf_DyzWI");
|
const contract = warp.contract("Ws9hhYckc-zSnVmbBep6q_kZD5zmzYzDmgMC50nMiuE");
|
||||||
const cacheResult = await contract
|
const cacheResult = await contract
|
||||||
.setEvaluationOptions({
|
.setEvaluationOptions({
|
||||||
allowBigInt: true
|
|
||||||
})
|
})
|
||||||
.readState();
|
.readState();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user