@aztec/end-to-end 0.0.1-commit.24de95ac → 0.0.1-commit.3469e52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/bench/client_flows/benchmark.d.ts +3 -2
- package/dest/bench/client_flows/benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/benchmark.js +21 -1
- package/dest/bench/client_flows/client_flows_benchmark.d.ts +21 -15
- package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.js +116 -121
- package/dest/bench/client_flows/config.d.ts +1 -1
- package/dest/bench/client_flows/data_extractor.d.ts +1 -1
- package/dest/bench/client_flows/data_extractor.js +7 -27
- package/dest/bench/utils.d.ts +5 -5
- package/dest/bench/utils.d.ts.map +1 -1
- package/dest/bench/utils.js +18 -11
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +6 -7
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +98 -113
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +19 -13
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +91 -70
- package/dest/e2e_deploy_contract/deploy_test.d.ts +5 -4
- package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
- package/dest/e2e_deploy_contract/deploy_test.js +18 -13
- package/dest/e2e_epochs/epochs_test.d.ts +11 -9
- package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
- package/dest/e2e_epochs/epochs_test.js +19 -16
- package/dest/e2e_fees/bridging_race.notest.d.ts +1 -1
- package/dest/e2e_fees/bridging_race.notest.js +4 -6
- package/dest/e2e_fees/fees_test.d.ts +20 -16
- package/dest/e2e_fees/fees_test.d.ts.map +1 -1
- package/dest/e2e_fees/fees_test.js +127 -139
- package/dest/e2e_l1_publisher/write_json.d.ts +3 -3
- package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
- package/dest/e2e_l1_publisher/write_json.js +23 -18
- package/dest/e2e_multi_validator/utils.d.ts +1 -1
- package/dest/e2e_multi_validator/utils.js +1 -1
- package/dest/e2e_nested_contract/nested_contract_test.d.ts +6 -9
- package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
- package/dest/e2e_nested_contract/nested_contract_test.js +32 -39
- package/dest/e2e_p2p/inactivity_slash_test.d.ts +3 -3
- package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -1
- package/dest/e2e_p2p/inactivity_slash_test.js +7 -6
- package/dest/e2e_p2p/p2p_network.d.ts +225 -18
- package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
- package/dest/e2e_p2p/p2p_network.js +117 -110
- package/dest/e2e_p2p/shared.d.ts +6 -6
- package/dest/e2e_p2p/shared.d.ts.map +1 -1
- package/dest/e2e_p2p/shared.js +6 -5
- package/dest/e2e_token_contract/token_contract_test.d.ts +16 -9
- package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
- package/dest/e2e_token_contract/token_contract_test.js +90 -92
- package/dest/fixtures/e2e_prover_test.d.ts +12 -18
- package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
- package/dest/fixtures/e2e_prover_test.js +98 -109
- package/dest/fixtures/fixtures.d.ts +2 -3
- package/dest/fixtures/fixtures.d.ts.map +1 -1
- package/dest/fixtures/fixtures.js +2 -3
- package/dest/fixtures/get_acvm_config.d.ts +1 -1
- package/dest/fixtures/get_acvm_config.js +1 -1
- package/dest/fixtures/get_bb_config.d.ts +1 -1
- package/dest/fixtures/get_bb_config.d.ts.map +1 -1
- package/dest/fixtures/index.d.ts +1 -1
- package/dest/fixtures/l1_to_l2_messaging.d.ts +4 -3
- package/dest/fixtures/l1_to_l2_messaging.d.ts.map +1 -1
- package/dest/fixtures/l1_to_l2_messaging.js +2 -2
- package/dest/fixtures/logging.d.ts +1 -1
- package/dest/fixtures/setup.d.ts +216 -0
- package/dest/fixtures/setup.d.ts.map +1 -0
- package/dest/fixtures/setup.js +684 -0
- package/dest/fixtures/setup_p2p_test.d.ts +4 -4
- package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
- package/dest/fixtures/setup_p2p_test.js +18 -10
- package/dest/fixtures/token_utils.d.ts +5 -2
- package/dest/fixtures/token_utils.d.ts.map +1 -1
- package/dest/fixtures/token_utils.js +7 -4
- package/dest/fixtures/utils.d.ts +5 -192
- package/dest/fixtures/utils.d.ts.map +1 -1
- package/dest/fixtures/utils.js +4 -648
- package/dest/fixtures/web3signer.d.ts +1 -1
- package/dest/fixtures/web3signer.js +1 -1
- package/dest/fixtures/with_telemetry_utils.d.ts +2 -2
- package/dest/fixtures/with_telemetry_utils.d.ts.map +1 -1
- package/dest/fixtures/with_telemetry_utils.js +2 -2
- package/dest/index.d.ts +1 -1
- package/dest/quality_of_service/grafana_client.d.ts +41 -0
- package/dest/quality_of_service/grafana_client.d.ts.map +1 -0
- package/dest/quality_of_service/{alert_checker.js → grafana_client.js} +1 -1
- package/dest/quality_of_service/prometheus_client.d.ts +38 -0
- package/dest/quality_of_service/prometheus_client.d.ts.map +1 -0
- package/dest/quality_of_service/prometheus_client.js +67 -0
- package/dest/shared/cross_chain_test_harness.d.ts +5 -3
- package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
- package/dest/shared/cross_chain_test_harness.js +3 -3
- package/dest/shared/gas_portal_test_harness.d.ts +2 -2
- package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
- package/dest/shared/gas_portal_test_harness.js +1 -1
- package/dest/shared/index.d.ts +2 -2
- package/dest/shared/index.d.ts.map +1 -1
- package/dest/shared/jest_setup.d.ts +1 -1
- package/dest/shared/submit-transactions.d.ts +1 -1
- package/dest/shared/submit-transactions.d.ts.map +1 -1
- package/dest/shared/uniswap_l1_l2.d.ts +3 -27
- package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
- package/dest/shared/uniswap_l1_l2.js +43 -23
- package/dest/simulators/index.d.ts +1 -1
- package/dest/simulators/lending_simulator.d.ts +2 -2
- package/dest/simulators/lending_simulator.d.ts.map +1 -1
- package/dest/simulators/lending_simulator.js +5 -3
- package/dest/simulators/token_simulator.d.ts +1 -1
- package/dest/simulators/token_simulator.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.d.ts +8 -5
- package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.js +45 -10
- package/dest/spartan/tx_metrics.d.ts +52 -0
- package/dest/spartan/tx_metrics.d.ts.map +1 -0
- package/dest/spartan/tx_metrics.js +248 -0
- package/dest/spartan/utils.d.ts +66 -24
- package/dest/spartan/utils.d.ts.map +1 -1
- package/dest/spartan/utils.js +326 -133
- package/package.json +43 -40
- package/src/bench/client_flows/benchmark.ts +24 -2
- package/src/bench/client_flows/client_flows_benchmark.ts +157 -162
- package/src/bench/client_flows/data_extractor.ts +6 -28
- package/src/bench/utils.ts +22 -14
- package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +107 -142
- package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +140 -124
- package/src/e2e_deploy_contract/deploy_test.ts +22 -15
- package/src/e2e_epochs/epochs_test.ts +39 -25
- package/src/e2e_fees/bridging_race.notest.ts +4 -7
- package/src/e2e_fees/fees_test.ts +180 -215
- package/src/e2e_l1_publisher/write_json.ts +26 -20
- package/src/e2e_multi_validator/utils.ts +1 -1
- package/src/e2e_nested_contract/nested_contract_test.ts +35 -55
- package/src/e2e_p2p/inactivity_slash_test.ts +10 -9
- package/src/e2e_p2p/p2p_network.ts +175 -180
- package/src/e2e_p2p/shared.ts +15 -7
- package/src/e2e_token_contract/token_contract_test.ts +105 -118
- package/src/fixtures/e2e_prover_test.ts +120 -153
- package/src/fixtures/fixtures.ts +2 -5
- package/src/fixtures/get_acvm_config.ts +1 -1
- package/src/fixtures/l1_to_l2_messaging.ts +4 -2
- package/src/fixtures/setup.ts +1010 -0
- package/src/fixtures/setup_p2p_test.ts +23 -9
- package/src/fixtures/token_utils.ts +4 -4
- package/src/fixtures/utils.ts +27 -947
- package/src/fixtures/web3signer.ts +1 -1
- package/src/fixtures/with_telemetry_utils.ts +2 -2
- package/src/guides/up_quick_start.sh +1 -1
- package/src/quality_of_service/{alert_checker.ts → grafana_client.ts} +1 -1
- package/src/quality_of_service/prometheus_client.ts +113 -0
- package/src/shared/cross_chain_test_harness.ts +6 -9
- package/src/shared/gas_portal_test_harness.ts +2 -2
- package/src/shared/index.ts +1 -1
- package/src/shared/uniswap_l1_l2.ts +53 -67
- package/src/simulators/lending_simulator.ts +6 -4
- package/src/spartan/DEVELOP.md +7 -0
- package/src/spartan/setup_test_wallets.ts +56 -13
- package/src/spartan/tx_metrics.ts +231 -0
- package/src/spartan/utils.ts +379 -75
- package/dest/fixtures/setup_l1_contracts.d.ts +0 -6
- package/dest/fixtures/setup_l1_contracts.d.ts.map +0 -1
- package/dest/fixtures/setup_l1_contracts.js +0 -17
- package/dest/fixtures/snapshot_manager.d.ts +0 -95
- package/dest/fixtures/snapshot_manager.d.ts.map +0 -1
- package/dest/fixtures/snapshot_manager.js +0 -505
- package/dest/quality_of_service/alert_checker.d.ts +0 -41
- package/dest/quality_of_service/alert_checker.d.ts.map +0 -1
- package/src/fixtures/setup_l1_contracts.ts +0 -26
- package/src/fixtures/snapshot_manager.ts +0 -665
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { AztecNode } from '@aztec/aztec.js/node';
|
|
2
|
+
import type { L2BlockNew } from '@aztec/stdlib/block';
|
|
3
|
+
import type { TopicType } from '@aztec/stdlib/p2p';
|
|
4
|
+
import { Tx, type TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
5
|
+
|
|
6
|
+
import { createHistogram } from 'perf_hooks';
|
|
7
|
+
|
|
8
|
+
export type TxInclusionData = {
|
|
9
|
+
txHash: string;
|
|
10
|
+
sentAt: number;
|
|
11
|
+
minedAt: number;
|
|
12
|
+
attestedAt: number;
|
|
13
|
+
blocknumber: number;
|
|
14
|
+
priorityFee: number;
|
|
15
|
+
totalFee: number;
|
|
16
|
+
positionInBlock: number;
|
|
17
|
+
group: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class TxInclusionMetrics {
|
|
21
|
+
private data = new Map<string, TxInclusionData>();
|
|
22
|
+
private groups = new Set<string>();
|
|
23
|
+
private blocks = new Map<number, Promise<L2BlockNew | undefined>>();
|
|
24
|
+
|
|
25
|
+
private p2pGossipLatencyByTopic: Partial<Record<TopicType, { p50: number; p95: number }>> = {};
|
|
26
|
+
|
|
27
|
+
private attestationLatency: { p50: number; p95: number } | undefined;
|
|
28
|
+
private attestationCounts: { success: number; failedBad: number; failedNode: number } | undefined;
|
|
29
|
+
private reqRespStats: { fraction: number; delayP50: number; delayP95: number } | undefined;
|
|
30
|
+
private peerStats: { avgCount: number; connectionDurationP50: number; connectionDurationP95: number } | undefined;
|
|
31
|
+
private mempoolMinedDelay:
|
|
32
|
+
| { txP50: number; txP95: number; attestationP50: number; attestationP95: number }
|
|
33
|
+
| undefined;
|
|
34
|
+
|
|
35
|
+
constructor(private aztecNode: AztecNode) {}
|
|
36
|
+
|
|
37
|
+
recordSentTx(tx: Tx, group: string): void {
|
|
38
|
+
const txHash = tx.getTxHash().toString();
|
|
39
|
+
const priorityFees = tx.getGasSettings().maxPriorityFeesPerGas;
|
|
40
|
+
|
|
41
|
+
this.data.set(txHash, {
|
|
42
|
+
txHash,
|
|
43
|
+
sentAt: Math.trunc(Date.now() / 1000),
|
|
44
|
+
minedAt: -1,
|
|
45
|
+
attestedAt: -1,
|
|
46
|
+
blocknumber: -1,
|
|
47
|
+
priorityFee: Number(priorityFees.feePerDaGas + priorityFees.feePerL2Gas),
|
|
48
|
+
totalFee: -1,
|
|
49
|
+
positionInBlock: -1,
|
|
50
|
+
group,
|
|
51
|
+
});
|
|
52
|
+
this.groups.add(group);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async recordMinedTx(txReceipt: TxReceipt): Promise<void> {
|
|
56
|
+
const { status, txHash, blockNumber } = txReceipt;
|
|
57
|
+
if (status !== TxStatus.SUCCESS || !blockNumber) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!this.blocks.has(blockNumber)) {
|
|
62
|
+
this.blocks.set(blockNumber, this.aztecNode.getBlock(blockNumber));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const block = await this.blocks.get(blockNumber)!;
|
|
66
|
+
if (!block) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const data = this.data.get(txHash.toString())!;
|
|
70
|
+
data.blocknumber = blockNumber;
|
|
71
|
+
data.minedAt = Number(block.header.globalVariables.timestamp);
|
|
72
|
+
data.attestedAt = -1;
|
|
73
|
+
data.totalFee = Number(txReceipt.transactionFee ?? 0n);
|
|
74
|
+
data.positionInBlock = block.body.txEffects.findIndex(txEffect => txEffect.txHash.equals(txHash));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public inclusionTimeInSeconds(group: string): {
|
|
78
|
+
count: number;
|
|
79
|
+
group: string;
|
|
80
|
+
min: number;
|
|
81
|
+
mean: number;
|
|
82
|
+
max: number;
|
|
83
|
+
median: number;
|
|
84
|
+
p99: number;
|
|
85
|
+
} {
|
|
86
|
+
const histogram = createHistogram({});
|
|
87
|
+
for (const tx of this.data.values()) {
|
|
88
|
+
if (!tx.blocknumber || tx.group !== group || tx.minedAt === -1) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
histogram.record(tx.minedAt - tx.sentAt);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (histogram.count === 0) {
|
|
96
|
+
return {
|
|
97
|
+
group,
|
|
98
|
+
count: 0,
|
|
99
|
+
mean: 0,
|
|
100
|
+
max: 0,
|
|
101
|
+
median: 0,
|
|
102
|
+
min: 0,
|
|
103
|
+
p99: 0,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
group,
|
|
109
|
+
count: histogram.count,
|
|
110
|
+
mean: histogram.mean,
|
|
111
|
+
max: histogram.max,
|
|
112
|
+
median: histogram.percentile(50),
|
|
113
|
+
min: histogram.min,
|
|
114
|
+
p99: histogram.percentile(99),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public recordP2PGossipLatency(topicName: TopicType, p50: number, p95: number): void {
|
|
119
|
+
this.p2pGossipLatencyByTopic[topicName] = { p50, p95 };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public recordAttestationLatency(p50: number, p95: number): void {
|
|
123
|
+
this.attestationLatency = { p50, p95 };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public recordAttestationCounts(success: number, failedBad: number, failedNode: number): void {
|
|
127
|
+
this.attestationCounts = { success, failedBad, failedNode };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public recordReqRespStats(fraction: number, delayP50: number, delayP95: number): void {
|
|
131
|
+
this.reqRespStats = { fraction, delayP50, delayP95 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public recordPeerStats(avgCount: number, connectionDurationP50: number, connectionDurationP95: number): void {
|
|
135
|
+
this.peerStats = { avgCount, connectionDurationP50, connectionDurationP95 };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public recordMempoolMinedDelay(txP50: number, txP95: number, attestationP50: number, attestationP95: number): void {
|
|
139
|
+
this.mempoolMinedDelay = { txP50, txP95, attestationP50, attestationP95 };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
toGithubActionBenchmarkJSON(): Array<{ name: string; unit: string; value: number; range?: number; extra?: string }> {
|
|
143
|
+
const data: Array<{ name: string; unit: string; value: number; range?: number; extra?: string }> = [];
|
|
144
|
+
for (const group of this.groups) {
|
|
145
|
+
const stats = this.inclusionTimeInSeconds(group);
|
|
146
|
+
|
|
147
|
+
data.push(
|
|
148
|
+
{
|
|
149
|
+
name: `${group}/avg_inclusion`,
|
|
150
|
+
unit: 's',
|
|
151
|
+
value: stats.mean,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: `${group}/median_inclusion`,
|
|
155
|
+
unit: 's',
|
|
156
|
+
value: stats.median,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: `${group}/p99_inclusion`,
|
|
160
|
+
unit: 's',
|
|
161
|
+
value: stats.p99,
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const [topic, { p50, p95 }] of Object.entries(this.p2pGossipLatencyByTopic)) {
|
|
167
|
+
data.push({
|
|
168
|
+
name: `p2p_gossip_latency/${topic}/p50`,
|
|
169
|
+
unit: 'ms',
|
|
170
|
+
value: p50,
|
|
171
|
+
});
|
|
172
|
+
data.push({
|
|
173
|
+
name: `p2p_gossip_latency/${topic}/p95`,
|
|
174
|
+
unit: 'ms',
|
|
175
|
+
value: p95,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.attestationLatency) {
|
|
180
|
+
data.push(
|
|
181
|
+
{ name: 'attestation_latency/p50', unit: 'ms', value: this.attestationLatency.p50 },
|
|
182
|
+
{ name: 'attestation_latency/p95', unit: 'ms', value: this.attestationLatency.p95 },
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.attestationCounts) {
|
|
187
|
+
const { success, failedBad, failedNode } = this.attestationCounts;
|
|
188
|
+
const total = success + failedBad + failedNode;
|
|
189
|
+
const ratio = total > 0 ? success / total : 0;
|
|
190
|
+
data.push(
|
|
191
|
+
{ name: 'attestation/success_count', unit: 'count', value: success },
|
|
192
|
+
{ name: 'attestation/failed_bad_proposal_count', unit: 'count', value: failedBad },
|
|
193
|
+
{ name: 'attestation/failed_node_issue_count', unit: 'count', value: failedNode },
|
|
194
|
+
{ name: 'attestation/success_ratio', unit: 'ratio', value: ratio },
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.reqRespStats) {
|
|
199
|
+
data.push(
|
|
200
|
+
{ name: 'req_resp/txs_requested_fraction', unit: 'ratio', value: this.reqRespStats.fraction },
|
|
201
|
+
{ name: 'req_resp/delay_p50', unit: 'ms', value: this.reqRespStats.delayP50 },
|
|
202
|
+
{ name: 'req_resp/delay_p95', unit: 'ms', value: this.reqRespStats.delayP95 },
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (this.peerStats) {
|
|
207
|
+
data.push(
|
|
208
|
+
{ name: 'peers/avg_count', unit: 'peers', value: this.peerStats.avgCount },
|
|
209
|
+
{ name: 'peers/connection_duration_p50', unit: 'ms', value: this.peerStats.connectionDurationP50 },
|
|
210
|
+
{ name: 'peers/connection_duration_p95', unit: 'ms', value: this.peerStats.connectionDurationP95 },
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (this.mempoolMinedDelay) {
|
|
215
|
+
data.push(
|
|
216
|
+
{ name: 'mempool/tx_mined_delay_p50', unit: 'ms', value: this.mempoolMinedDelay.txP50 },
|
|
217
|
+
{ name: 'mempool/tx_mined_delay_p95', unit: 'ms', value: this.mempoolMinedDelay.txP95 },
|
|
218
|
+
{ name: 'mempool/attestation_mined_delay_p50', unit: 'ms', value: this.mempoolMinedDelay.attestationP50 },
|
|
219
|
+
{ name: 'mempool/attestation_mined_delay_p95', unit: 'ms', value: this.mempoolMinedDelay.attestationP95 },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const scenario = process.env.BENCH_SCENARIO?.trim();
|
|
224
|
+
if (!scenario) {
|
|
225
|
+
return data;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const scenarioPrefix = `scenario/${scenario}/`;
|
|
229
|
+
return data.map(entry => ({ ...entry, name: `${scenarioPrefix}${entry.name}` }));
|
|
230
|
+
}
|
|
231
|
+
}
|