Skip to content

Commit 7717b16

Browse files
authored
Dump p2p on files instead of hex logs (Consensys#8177)
1 parent 847bb34 commit 7717b16

File tree

50 files changed

+814
-95
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+814
-95
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ the [releases page](https://github.com/Consensys/teku/releases).
1717
- Deposit tree snapshots will be loaded from database as a default unless custom snapshot has been provided.
1818
- Added hidden option `--Xdeposit-contract-logs-syncing-enabled` to allow disabling the syncing of the deposit contract logs from the EL. This is useful when running a non-validating node. It is advisable to be used alongside with `--Xeth1-missing-deposits-event-logging-enabled=false` to avoid unnecessary logging of missing deposits.
1919
- Updated the bootnodes for Chiado and Gnosis networks
20+
- Added hidden option `--Xp2p-dumps-to-file-enabled` to enable saving p2p dumps to file.
2021

2122
### Bug Fixes

eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/forkchoice/ForkChoiceTestExecutor.java

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import static org.assertj.core.api.Assertions.assertThat;
1717
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
18+
import static org.mockito.Mockito.mock;
1819
import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.safeJoin;
1920
import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis;
2021

@@ -69,6 +70,7 @@
6970
import tech.pegasys.teku.statetransition.forkchoice.MergeTransitionBlockValidator;
7071
import tech.pegasys.teku.statetransition.forkchoice.NoopForkChoiceNotifier;
7172
import tech.pegasys.teku.statetransition.forkchoice.TickProcessor;
73+
import tech.pegasys.teku.statetransition.util.DebugDataDumper;
7274
import tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator;
7375
import tech.pegasys.teku.statetransition.validation.InternalValidationResult;
7476
import tech.pegasys.teku.storage.client.RecentChainData;
@@ -139,6 +141,7 @@ spec, new SignedBlockAndState(anchorBlock, anchorState)),
139141
new TickProcessor(spec, recentChainData),
140142
transitionBlockValidator,
141143
true,
144+
mock(DebugDataDumper.class),
142145
storageSystem.getMetricsSystem());
143146
final ExecutionLayerChannelStub executionLayer =
144147
new ExecutionLayerChannelStub(spec, false, Optional.empty());

ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoice.java

+9
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
import tech.pegasys.teku.statetransition.attestation.DeferredAttestations;
7575
import tech.pegasys.teku.statetransition.blobs.BlobSidecarManager;
7676
import tech.pegasys.teku.statetransition.block.BlockImportPerformance;
77+
import tech.pegasys.teku.statetransition.util.DebugDataDumper;
78+
import tech.pegasys.teku.statetransition.util.noop.NoOpDebugDataDumper;
7779
import tech.pegasys.teku.statetransition.validation.AttestationStateSelector;
7880
import tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator;
7981
import tech.pegasys.teku.statetransition.validation.InternalValidationResult;
@@ -108,6 +110,8 @@ public class ForkChoice implements ForkChoiceUpdatedResultSubscriber {
108110

109111
private final LabelledMetric<Counter> getProposerHeadSelectedCounter;
110112

113+
private final DebugDataDumper debugDataDumper;
114+
111115
public ForkChoice(
112116
final Spec spec,
113117
final EventThread forkChoiceExecutor,
@@ -118,6 +122,7 @@ public ForkChoice(
118122
final TickProcessor tickProcessor,
119123
final MergeTransitionBlockValidator transitionBlockValidator,
120124
final boolean forkChoiceLateBlockReorgEnabled,
125+
final DebugDataDumper debugDataDumper,
121126
final MetricsSystem metricsSystem) {
122127
this.spec = spec;
123128
this.forkChoiceExecutor = forkChoiceExecutor;
@@ -132,6 +137,7 @@ public ForkChoice(
132137
this.forkChoiceLateBlockReorgEnabled = forkChoiceLateBlockReorgEnabled;
133138
this.lastProcessHeadSlot.set(UInt64.ZERO);
134139
LOG.debug("forkChoiceLateBlockReorgEnabled is set to {}", forkChoiceLateBlockReorgEnabled);
140+
this.debugDataDumper = debugDataDumper;
135141
getProposerHeadSelectedCounter =
136142
metricsSystem.createLabelledCounter(
137143
TekuMetricCategory.BEACON,
@@ -161,6 +167,7 @@ public ForkChoice(
161167
new TickProcessor(spec, recentChainData),
162168
transitionBlockValidator,
163169
false,
170+
new NoOpDebugDataDumper(),
164171
metricsSystem);
165172
}
166173

@@ -753,6 +760,8 @@ private void reportInvalidBlock(final SignedBeaconBlock block, final BlockImport
753760
if (result.getFailureReason() == FailureReason.BLOCK_IS_FROM_FUTURE) {
754761
return;
755762
}
763+
debugDataDumper.saveInvalidBlockToFile(
764+
block, result.getFailureReason().name(), result.getFailureCause());
756765
P2P_LOG.onInvalidBlock(
757766
block.getSlot(),
758767
block.getRoot(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2024
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.statetransition.util;
15+
16+
import com.google.common.annotations.VisibleForTesting;
17+
import java.io.IOException;
18+
import java.nio.file.Files;
19+
import java.nio.file.NoSuchFileException;
20+
import java.nio.file.Path;
21+
import java.sql.Date;
22+
import java.text.DateFormat;
23+
import java.text.SimpleDateFormat;
24+
import java.util.Optional;
25+
import java.util.function.Supplier;
26+
import org.apache.logging.log4j.LogManager;
27+
import org.apache.logging.log4j.Logger;
28+
import org.apache.tuweni.bytes.Bytes;
29+
import org.apache.tuweni.bytes.Bytes32;
30+
import tech.pegasys.teku.infrastructure.time.SystemTimeProvider;
31+
import tech.pegasys.teku.infrastructure.time.TimeProvider;
32+
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
33+
import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock;
34+
35+
public class DebugDataDumper {
36+
private static final Logger LOG = LogManager.getLogger();
37+
38+
private static final String GOSSIP_MESSAGES_DIR = "gossip_messages";
39+
private static final String DECODING_ERROR_SUB_DIR = "decoding_error";
40+
private static final String REJECTED_SUB_DIR = "rejected";
41+
private static final String INVALID_BLOCK_DIR = "invalid_blocks";
42+
43+
private boolean enabled;
44+
private final Path directory;
45+
46+
public DebugDataDumper(final Path directory) {
47+
this.enabled = true;
48+
this.directory = directory;
49+
50+
final Path gossipMessagesPath = this.directory.resolve(GOSSIP_MESSAGES_DIR);
51+
createDirectory(gossipMessagesPath, GOSSIP_MESSAGES_DIR, "gossip messages");
52+
createDirectory(
53+
gossipMessagesPath.resolve(DECODING_ERROR_SUB_DIR),
54+
DECODING_ERROR_SUB_DIR,
55+
"gossip messages with decoding errors");
56+
createDirectory(
57+
gossipMessagesPath.resolve(REJECTED_SUB_DIR), REJECTED_SUB_DIR, "rejected gossip messages");
58+
createDirectory(this.directory.resolve(INVALID_BLOCK_DIR), INVALID_BLOCK_DIR, "invalid blocks");
59+
}
60+
61+
public void saveGossipMessageDecodingError(
62+
final String topic,
63+
final Optional<UInt64> arrivalTimestamp,
64+
final Supplier<Bytes> originalMessage,
65+
final Throwable error) {
66+
if (!enabled) {
67+
return;
68+
}
69+
final String formattedTimestamp = formatOptionalTimestamp(arrivalTimestamp);
70+
final String fileName = String.format("%s.ssz", formattedTimestamp);
71+
final Path topicPath =
72+
Path.of(GOSSIP_MESSAGES_DIR)
73+
.resolve(DECODING_ERROR_SUB_DIR)
74+
.resolve(topic.replaceAll("/", "_"));
75+
final boolean success =
76+
saveBytesToFile(
77+
"gossip message with decoding error",
78+
topicPath.resolve(fileName),
79+
originalMessage.get());
80+
if (success) {
81+
LOG.warn("Failed to decode gossip message on topic {}", topic, error);
82+
}
83+
}
84+
85+
public void saveGossipRejectedMessageToFile(
86+
final String topic,
87+
final Optional<UInt64> arrivalTimestamp,
88+
final Supplier<Bytes> decodedMessage,
89+
final Optional<String> reason) {
90+
if (!enabled) {
91+
return;
92+
}
93+
final String formattedTimestamp = formatOptionalTimestamp(arrivalTimestamp);
94+
final String fileName = String.format("%s.ssz", formattedTimestamp);
95+
final Path topicPath =
96+
Path.of(GOSSIP_MESSAGES_DIR).resolve(REJECTED_SUB_DIR).resolve(topic.replaceAll("/", "_"));
97+
final boolean success =
98+
saveBytesToFile(
99+
"rejected gossip message", topicPath.resolve(fileName), decodedMessage.get());
100+
if (success) {
101+
LOG.warn(
102+
"Rejecting gossip message on topic {}, reason: {}",
103+
topic,
104+
reason.orElse("failed validation"));
105+
}
106+
}
107+
108+
public void saveInvalidBlockToFile(
109+
final SignedBeaconBlock block,
110+
final String failureReason,
111+
final Optional<Throwable> failureCause) {
112+
if (!enabled) {
113+
return;
114+
}
115+
final UInt64 slot = block.getSlot();
116+
final Bytes32 blockRoot = block.getRoot();
117+
final String fileName = String.format("%s_%s.ssz", slot, blockRoot.toUnprefixedHexString());
118+
final boolean success =
119+
saveBytesToFile(
120+
"invalid block", Path.of(INVALID_BLOCK_DIR).resolve(fileName), block.sszSerialize());
121+
if (success) {
122+
LOG.warn(
123+
"Rejecting invalid block at slot {} with root {} because {}",
124+
slot,
125+
blockRoot,
126+
failureReason,
127+
failureCause.orElse(null));
128+
}
129+
}
130+
131+
@VisibleForTesting
132+
protected boolean saveBytesToFile(
133+
final String description, final Path relativeFilePath, final Bytes bytes) {
134+
final Path path = directory.resolve(relativeFilePath);
135+
try {
136+
Files.write(path, bytes.toArray());
137+
} catch (NoSuchFileException e) {
138+
return saveAfterCreatingTopicDirectory(description, path, relativeFilePath, bytes);
139+
} catch (IOException e) {
140+
LOG.error("Failed to save {} bytes to file.", description, e);
141+
return false;
142+
}
143+
return true;
144+
}
145+
146+
private boolean saveAfterCreatingTopicDirectory(
147+
final String description, final Path path, final Path relativeFilePath, final Bytes bytes) {
148+
if (!path.getParent().toFile().mkdirs()) {
149+
LOG.error(
150+
"Failed to save {} bytes to file. No such directory {} to save file.",
151+
description,
152+
relativeFilePath.getParent());
153+
return false;
154+
}
155+
try {
156+
Files.write(path, bytes.toArray());
157+
} catch (IOException e) {
158+
LOG.error("Failed to save {} bytes to file.", description, e);
159+
if (!path.getParent().toFile().exists()) {
160+
this.enabled = false;
161+
LOG.error(
162+
"{} directory does not exist. Disabling saving debug data to file.",
163+
relativeFilePath.getParent());
164+
}
165+
return false;
166+
}
167+
return true;
168+
}
169+
170+
private void createDirectory(
171+
final Path path, final String directoryName, final String description) {
172+
if (!enabled) {
173+
return;
174+
}
175+
if (path.toFile().mkdirs()) {
176+
LOG.debug("{} directory has been created to save {}.", directoryName, description);
177+
} else {
178+
if (!path.toFile().exists()) {
179+
this.enabled = false;
180+
LOG.error(
181+
"Unable to create {} directory to save {}. Disabling saving debug data to file.",
182+
directoryName,
183+
description);
184+
}
185+
}
186+
}
187+
188+
private String formatOptionalTimestamp(final Optional<UInt64> maybeTimestamp) {
189+
return formatOptionalTimestamp(maybeTimestamp, new SystemTimeProvider());
190+
}
191+
192+
@VisibleForTesting
193+
String formatOptionalTimestamp(
194+
final Optional<UInt64> maybeTimestamp, final TimeProvider timeProvider) {
195+
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH_mm_ss.SS");
196+
final Date date =
197+
maybeTimestamp
198+
.map(timestamp -> new Date(timestamp.longValue()))
199+
.orElse(new Date(timeProvider.getTimeInMillis().longValue()));
200+
return df.format(date);
201+
}
202+
203+
@VisibleForTesting
204+
boolean isEnabled() {
205+
return enabled;
206+
}
207+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2024
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.statetransition.util.noop;
15+
16+
import java.nio.file.Path;
17+
import java.util.Optional;
18+
import java.util.function.Supplier;
19+
import org.apache.tuweni.bytes.Bytes;
20+
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
21+
import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock;
22+
import tech.pegasys.teku.statetransition.util.DebugDataDumper;
23+
24+
public class NoOpDebugDataDumper extends DebugDataDumper {
25+
26+
public NoOpDebugDataDumper() {
27+
super(Path.of("."));
28+
}
29+
30+
@Override
31+
public void saveGossipMessageDecodingError(
32+
final String topic,
33+
final Optional<UInt64> arrivalTimestamp,
34+
final Supplier<Bytes> originalMessage,
35+
final Throwable error) {}
36+
37+
@Override
38+
public void saveGossipRejectedMessageToFile(
39+
final String topic,
40+
final Optional<UInt64> arrivalTimestamp,
41+
final Supplier<Bytes> decodedMessage,
42+
final Optional<String> reason) {}
43+
44+
@Override
45+
public void saveInvalidBlockToFile(
46+
final SignedBeaconBlock block,
47+
final String failureReason,
48+
final Optional<Throwable> failureCause) {}
49+
}

0 commit comments

Comments
 (0)