fix: VRF issues with internal writes or 'strict' mode. #232

This commit is contained in:
ppe
2022-10-03 15:41:34 +02:00
committed by just_ppe
parent 4216259a77
commit c1bfb2f585
8 changed files with 107 additions and 25 deletions

View File

@@ -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 {

View File

@@ -20,7 +20,6 @@
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
"FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0",
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",

View File

@@ -20,7 +20,6 @@
"lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218",
"UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols",
"f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME",
"FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0",
"usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A",
"UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM",
"nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo",

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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
View 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
};
}

View File

@@ -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();