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_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 {
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
|
||||
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
|
||||
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
|
||||
"FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0",
|
||||
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
|
||||
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
|
||||
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
|
||||
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
|
||||
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
|
||||
"FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0",
|
||||
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
|
||||
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
|
||||
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import { Tags, ArTransfer, emptyTransfer, ArWallet } from './deploy/CreateContract';
|
||||
import { SourceData, SourceImpl } from './deploy/impl/SourceImpl';
|
||||
import { InnerWritesEvaluator } from './InnerWritesEvaluator';
|
||||
import { generateMockVrf } from '../utils/vrf';
|
||||
|
||||
/**
|
||||
* 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
|
||||
// 4. For each found "internalWrite" transaction - generate additional tag:
|
||||
// {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') {
|
||||
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);
|
||||
} else {
|
||||
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') {
|
||||
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
||||
}
|
||||
@@ -533,7 +534,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
sortKey?: string,
|
||||
tags: Tags = [],
|
||||
transfer: ArTransfer = emptyTransfer,
|
||||
strict = false
|
||||
strict = false,
|
||||
vrf = false
|
||||
): Promise<InteractionResult<State, View>> {
|
||||
this.logger.info('Call contract input', input);
|
||||
this.maybeResetRootContract();
|
||||
@@ -552,6 +554,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
if (caller) {
|
||||
effectiveCaller = caller;
|
||||
} 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({
|
||||
data: Math.random().toString().slice(-4),
|
||||
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.strict = strict;
|
||||
if (vrf) {
|
||||
dummyTx.vrf = generateMockVrf(dummyTx.sortKey, arweave);
|
||||
}
|
||||
|
||||
const handleResult = await this.evalInteraction<Input, View>(
|
||||
{
|
||||
interaction,
|
||||
|
||||
@@ -9,14 +9,13 @@ import {
|
||||
import { Benchmark } from '../../../logging/Benchmark';
|
||||
import { LoggerFactory } from '../../../logging/LoggerFactory';
|
||||
import { ArweaveWrapper } from '../../../utils/ArweaveWrapper';
|
||||
import { bufToBn, sleep } from '../../../utils/utils';
|
||||
import { sleep } from '../../../utils/utils';
|
||||
import { GW_TYPE, InteractionsLoader } from '../InteractionsLoader';
|
||||
import { InteractionsSorter } from '../InteractionsSorter';
|
||||
import { EvaluationOptions } from '../StateEvaluator';
|
||||
import { LexicographicalInteractionsSorter } from './LexicographicalInteractionsSorter';
|
||||
import { WarpEnvironment } from '../../Warp';
|
||||
import elliptic from 'elliptic';
|
||||
import { Evaluate } from '@idena/vrf-js';
|
||||
import { generateMockVrf } from '../../../utils/vrf';
|
||||
|
||||
const MAX_REQUEST = 100;
|
||||
|
||||
@@ -41,10 +40,6 @@ export function bundledTxsFilter(tx: GQLEdgeInterface) {
|
||||
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 {
|
||||
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';
|
||||
})
|
||||
) {
|
||||
const data = this.arweave.utils.stringToBuffer(interaction.sortKey);
|
||||
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
|
||||
};
|
||||
interaction.vrf = generateMockVrf(interaction.sortKey, this.arweave);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ export async function createInteractionTx(
|
||||
}
|
||||
|
||||
// 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
|
||||
// from calling /tx_anchor and /price endpoints) - we're presetting theses
|
||||
// that are bundled. So to speed up the process (and prevent the arweave-js
|
||||
// from calling /tx_anchor and /price endpoints) - we're presetting these
|
||||
// values here
|
||||
if (dummy) {
|
||||
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
|
||||
});
|
||||
|
||||
const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: false});
|
||||
const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: true});
|
||||
try {
|
||||
const contract = warp.contract("5Yt1IujBmOm1LSux9KDUTjCE7rJqepzP7gZKf_DyzWI");
|
||||
const contract = warp.contract("Ws9hhYckc-zSnVmbBep6q_kZD5zmzYzDmgMC50nMiuE");
|
||||
const cacheResult = await contract
|
||||
.setEvaluationOptions({
|
||||
allowBigInt: true
|
||||
})
|
||||
.readState();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user