Skip to content

Commit 508f27b

Browse files
committed
wip
1 parent f17472f commit 508f27b

File tree

6 files changed

+1543
-0
lines changed

6 files changed

+1543
-0
lines changed

evm-tests/run-mechanism-tests.sh

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/bash
2+
3+
echo "Starting Submechanism E2E Tests"
4+
echo "================================="
5+
6+
# Check if localnet is running
7+
if ! nc -z localhost 9944; then
8+
echo "Starting localnet..."
9+
../scripts/localnet.sh &>/dev/null &
10+
LOCALNET_PID=$!
11+
12+
# Wait for localnet to start
13+
i=1
14+
while [ $i -le 1000 ]; do
15+
if nc -z localhost 9944; then
16+
echo "Localnet is running after $i seconds"
17+
break
18+
fi
19+
sleep 1
20+
i=$((i + 1))
21+
done
22+
23+
# Check if localnet started successfully
24+
if [ "$i" -eq 1000 ]; then
25+
echo "Failed to start localnet"
26+
exit 1
27+
fi
28+
else
29+
echo "Localnet is already running"
30+
fi
31+
32+
# Wait a bit more for the node to be fully ready
33+
sleep 5
34+
35+
# Check if localnet is still running
36+
if ! nc -z localhost 9944; then
37+
echo "Localnet stopped unexpectedly"
38+
exit 1
39+
fi
40+
41+
echo "Setting up test environment..."
42+
43+
# Install dependencies if needed
44+
if [ ! -d "node_modules" ]; then
45+
echo "Installing dependencies..."
46+
yarn install
47+
fi
48+
49+
# Get metadata if needed
50+
if [ ! -d ".papi" ]; then
51+
echo "Getting metadata..."
52+
bash get-metadata.sh
53+
sleep 5
54+
fi
55+
56+
echo "Running mechanism e2e tests..."
57+
58+
# Run the mechanism tests
59+
yarn run test -- --grep "Submechanism E2E Tests"
60+
61+
TEST_EXIT_CODE=$?
62+
63+
if [ $TEST_EXIT_CODE -ne 0 ]; then
64+
echo "Mechanism tests failed with exit code $TEST_EXIT_CODE"
65+
if [ ! -z "$LOCALNET_PID" ]; then
66+
kill $LOCALNET_PID 2>/dev/null || true
67+
fi
68+
pkill node-subtensor 2>/dev/null || true
69+
exit $TEST_EXIT_CODE
70+
fi
71+
72+
echo "All mechanism tests passed!"
73+
74+
# Cleanup
75+
if [ ! -z "$LOCALNET_PID" ]; then
76+
kill $LOCALNET_PID 2>/dev/null || true
77+
fi
78+
pkill node-subtensor 2>/dev/null || true
79+
80+
exit 0

evm-tests/src/mechanism.ts

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
import { ApiPromise } from '@polkadot/api';
2+
import { Substrate } from './substrate';
3+
import { Subtensor } from './subtensor';
4+
5+
export class MechanismManager {
6+
constructor(
7+
private api: ApiPromise,
8+
private substrate: Substrate,
9+
private subtensor: Subtensor
10+
) {}
11+
12+
/**
13+
* Creates a subnet with the specified number of mechanisms
14+
*/
15+
async createSubnet(netuid: number, mechanismCount: number): Promise<void> {
16+
// Create the base subnet
17+
await this.substrate.createSubnet(netuid, 0);
18+
19+
// Set the mechanism count
20+
await this.substrate.setMechanismCount(netuid, mechanismCount);
21+
}
22+
23+
/**
24+
* Sets the mechanism count for a subnet
25+
*/
26+
async setMechanismCount(netuid: number, mechanismCount: number): Promise<void> {
27+
const tx = this.api.tx.subtensor.setMechanismCount(netuid, mechanismCount);
28+
await this.substrate.signAndSend(tx);
29+
}
30+
31+
/**
32+
* Sets custom emission split for mechanisms
33+
*/
34+
async setEmissionSplit(netuid: number, split: number[]): Promise<void> {
35+
const tx = this.api.tx.subtensor.setEmissionSplit(netuid, split);
36+
await this.substrate.signAndSend(tx);
37+
}
38+
39+
/**
40+
* Sets weights for a specific mechanism
41+
*/
42+
async setMechanismWeights(
43+
netuid: number,
44+
mechanismId: number,
45+
validatorUid: number,
46+
dests: number[],
47+
weights: number[]
48+
): Promise<void> {
49+
const tx = this.api.tx.subtensor.setMechanismWeights(
50+
netuid,
51+
mechanismId,
52+
dests,
53+
weights,
54+
0 // version_key
55+
);
56+
await this.substrate.signAndSend(tx);
57+
}
58+
59+
/**
60+
* Commits weights for a specific mechanism
61+
*/
62+
async commitMechanismWeights(
63+
netuid: number,
64+
mechanismId: number,
65+
hotkey: string,
66+
dests: number[],
67+
weights: number[],
68+
salt: number[],
69+
versionKey: number
70+
): Promise<string> {
71+
// Calculate commit hash
72+
const commitData = {
73+
hotkey,
74+
mechanismId,
75+
dests,
76+
weights,
77+
salt,
78+
versionKey
79+
};
80+
81+
const commitHash = this.api.createType('Hash', commitData).toString();
82+
83+
const tx = this.api.tx.subtensor.commitMechanismWeights(
84+
netuid,
85+
mechanismId,
86+
commitHash
87+
);
88+
89+
await this.substrate.signAndSend(tx);
90+
return commitHash;
91+
}
92+
93+
/**
94+
* Reveals weights for a specific mechanism
95+
*/
96+
async revealMechanismWeights(
97+
netuid: number,
98+
mechanismId: number,
99+
hotkey: string,
100+
dests: number[],
101+
weights: number[],
102+
salt: number[],
103+
versionKey: number
104+
): Promise<void> {
105+
const tx = this.api.tx.subtensor.revealMechanismWeights(
106+
netuid,
107+
mechanismId,
108+
dests,
109+
weights,
110+
salt,
111+
versionKey
112+
);
113+
114+
await this.substrate.signAndSend(tx);
115+
}
116+
117+
/**
118+
* Commits timelocked weights for a specific mechanism
119+
*/
120+
async commitTimelockedMechanismWeights(
121+
netuid: number,
122+
mechanismId: number,
123+
commitData: Uint8Array,
124+
revealRound: number,
125+
version: number
126+
): Promise<void> {
127+
const tx = this.api.tx.subtensor.commitTimelockedMechanismWeights(
128+
netuid,
129+
mechanismId,
130+
commitData,
131+
revealRound,
132+
version
133+
);
134+
135+
await this.substrate.signAndSend(tx);
136+
}
137+
138+
/**
139+
* Gets the mechanism count for a subnet
140+
*/
141+
async getMechanismCount(netuid: number): Promise<number> {
142+
const result = await this.api.query.subtensor.mechanismCountCurrent(netuid);
143+
return result.toNumber();
144+
}
145+
146+
/**
147+
* Checks if a mechanism exists
148+
*/
149+
async ensureMechanismExists(netuid: number, mechanismId: number): Promise<boolean> {
150+
try {
151+
const mechanismCount = await this.getMechanismCount(netuid);
152+
return mechanismId < mechanismCount;
153+
} catch (error) {
154+
return false;
155+
}
156+
}
157+
158+
/**
159+
* Gets weights for a specific mechanism and validator
160+
*/
161+
async getMechanismWeights(
162+
netuid: number,
163+
mechanismId: number,
164+
validatorUid: number
165+
): Promise<Array<[number, number]>> {
166+
const storageIndex = this.calculateStorageIndex(netuid, mechanismId);
167+
const result = await this.api.query.subtensor.weights(storageIndex, validatorUid);
168+
169+
if (result.isNone) {
170+
return [];
171+
}
172+
173+
return result.unwrap().toJSON() as Array<[number, number]>;
174+
}
175+
176+
/**
177+
* Gets incentive values for a specific mechanism
178+
*/
179+
async getMechanismIncentive(netuid: number, mechanismId: number): Promise<number[]> {
180+
const storageIndex = this.calculateStorageIndex(netuid, mechanismId);
181+
const result = await this.api.query.subtensor.incentive(storageIndex);
182+
183+
if (result.isNone) {
184+
return [];
185+
}
186+
187+
return result.unwrap().toJSON() as number[];
188+
}
189+
190+
/**
191+
* Gets bonds for a specific mechanism and validator
192+
*/
193+
async getMechanismBonds(
194+
netuid: number,
195+
mechanismId: number,
196+
validatorUid: number
197+
): Promise<Array<[number, number]>> {
198+
const storageIndex = this.calculateStorageIndex(netuid, mechanismId);
199+
const result = await this.api.query.subtensor.bonds(storageIndex, validatorUid);
200+
201+
if (result.isNone) {
202+
return [];
203+
}
204+
205+
return result.unwrap().toJSON() as Array<[number, number]>;
206+
}
207+
208+
/**
209+
* Splits emissions across mechanisms
210+
*/
211+
async splitEmissions(netuid: number, totalEmission: number): Promise<number[]> {
212+
// This would typically be called through the runtime, but for testing
213+
// we'll simulate the logic based on the mechanism count and split
214+
const mechanismCount = await this.getMechanismCount(netuid);
215+
const emissionSplit = await this.getEmissionSplit(netuid);
216+
217+
if (emissionSplit && emissionSplit.length > 0) {
218+
// Custom split
219+
return emissionSplit.map(split =>
220+
Math.floor((totalEmission * split) / 65535)
221+
);
222+
} else {
223+
// Even split
224+
const perMechanism = Math.floor(totalEmission / mechanismCount);
225+
const remainder = totalEmission % mechanismCount;
226+
227+
const result = new Array(mechanismCount).fill(perMechanism);
228+
if (remainder > 0) {
229+
result[0] += remainder; // Add remainder to first mechanism
230+
}
231+
232+
return result;
233+
}
234+
}
235+
236+
/**
237+
* Gets the emission split configuration for a subnet
238+
*/
239+
async getEmissionSplit(netuid: number): Promise<number[] | null> {
240+
const result = await this.api.query.subtensor.mechanismEmissionSplit(netuid);
241+
242+
if (result.isNone) {
243+
return null;
244+
}
245+
246+
return result.unwrap().toJSON() as number[];
247+
}
248+
249+
/**
250+
* Runs epoch processing with mechanisms
251+
*/
252+
async epochWithMechanisms(netuid: number, emission: number): Promise<Array<[string, number, number]>> {
253+
// This would typically be called through the runtime
254+
// For testing, we'll simulate the epoch processing
255+
const mechanismCount = await this.getMechanismCount(netuid);
256+
const mechanismEmissions = await this.splitEmissions(netuid, emission);
257+
258+
const results: Array<[string, number, number]> = [];
259+
260+
for (let i = 0; i < mechanismCount; i++) {
261+
const mechanismEmission = mechanismEmissions[i];
262+
const epochOutput = await this.epochMechanism(netuid, i, mechanismEmission);
263+
264+
// Aggregate results
265+
for (const [hotkey, terms] of epochOutput) {
266+
const existing = results.find(([h]) => h === hotkey);
267+
if (existing) {
268+
existing[1] += terms.serverEmission;
269+
existing[2] += terms.validatorEmission;
270+
} else {
271+
results.push([hotkey, terms.serverEmission, terms.validatorEmission]);
272+
}
273+
}
274+
}
275+
276+
return results;
277+
}
278+
279+
/**
280+
* Runs epoch processing for a specific mechanism
281+
*/
282+
async epochMechanism(
283+
netuid: number,
284+
mechanismId: number,
285+
emission: number
286+
): Promise<Map<string, { serverEmission: number; validatorEmission: number }>> {
287+
// This would typically be called through the runtime
288+
// For testing, we'll simulate the epoch processing
289+
const results = new Map<string, { serverEmission: number; validatorEmission: number }>();
290+
291+
// Get all neurons in the subnet
292+
const neuronCount = await this.subtensor.getSubnetworkN(netuid);
293+
294+
for (let uid = 0; uid < neuronCount; uid++) {
295+
const hotkey = await this.subtensor.getHotkeyForNetAndUid(netuid, uid);
296+
if (hotkey) {
297+
// Simulate emission distribution
298+
const serverEmission = Math.floor(emission * 0.5 / neuronCount);
299+
const validatorEmission = Math.floor(emission * 0.5 / neuronCount);
300+
301+
results.set(hotkey, { serverEmission, validatorEmission });
302+
}
303+
}
304+
305+
return results;
306+
}
307+
308+
/**
309+
* Calculates the storage index for a mechanism
310+
*/
311+
private calculateStorageIndex(netuid: number, mechanismId: number): number {
312+
const GLOBAL_MAX_SUBNET_COUNT = 4096;
313+
return mechanismId * GLOBAL_MAX_SUBNET_COUNT + netuid;
314+
}
315+
}

0 commit comments

Comments
 (0)