Skip to content

Commit d3a17ad

Browse files
Merge branch 'master' into mel-batchpostingreport-bugfix
2 parents a05b312 + ed5d6bd commit d3a17ad

39 files changed

+1255
-437
lines changed

.github/workflows/_validation-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ jobs:
7070
# 3. Send 2 validation requests
7171
RESPONSE1=$(jq -n --slurpfile input ${{ env.INPUT_FILE }} \
7272
'{jsonrpc: "2.0", id: 1, method: "validation_validate", params: [$input[0]]}' | \
73-
curl -s -X POST http://localhost:4141/validation_validate \
73+
curl -s -X POST http://localhost:4141/ \
7474
-H "Content-Type: application/json" -d @-)
7575
7676
RESPONSE2=$(jq -n --slurpfile input ${{ env.INPUT_FILE }} \
7777
'{jsonrpc: "2.0", id: 2, method: "validation_validate", params: [$input[0]]}' | \
78-
curl -s -X POST http://localhost:4141/validation_validate \
78+
curl -s -X POST http://localhost:4141/ \
7979
-H "Content-Type: application/json" -d @-)
8080
8181
# 4. Stop the server

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ enum-iterator = { version = "2.0.1" }
5555
eyre = { version = "0.6.5" }
5656
fnv = { version = "1.0.7" }
5757
gperftools = { version = "0.2.0" }
58-
hex = { version = "0.4.3" }
58+
hex = { version = "0.4.3", default-features = false }
5959
k256 = { version = "0.13.4", default-features = false}
6060
lazy_static = { version = "1.4.0" }
6161
libc = { version = "0.2.132" }

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ build-node-deps: $(go_source) build-prover-header build-prover-lib build-jit .ma
181181
.PHONY: test-go-deps
182182
test-go-deps: \
183183
build-replay-env \
184+
build-validation-server \
184185
$(stylus_test_wasms) \
185186
$(arbitrator_stylus_lib) \
186187
$(arbitrator_generated_header) \

arbos/arbostypes/incomingmessage.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,19 @@ func (msg *L1IncomingMessage) FillInBatchGasFieldsWithParentBlock(batchFetcher F
200200
if msg.LegacyBatchGasCost == nil {
201201
return fmt.Errorf("failed to fetch batch mentioned by batch posting report: %w", err)
202202
}
203+
// Pre-arbos50, LegacyBatchGasCost and BatchDataStats are interchangeable.
204+
// Post-arbos50, BatchDataStats is required. However, this code doesn't
205+
// know which arbos version applies to the current block.
206+
//
207+
// Since we already have LegacyBatchGasCost, we don't return an error here.
208+
// Instead, we assume this is a pre-arbos50 block and proceed without
209+
// BatchDataStats. If that assumption is wrong (i.e. this is actually
210+
// post-arbos50), createBatchPostingReportTransaction will detect the
211+
// missing BatchDataStats and fail there, since it does know the arbos
212+
// version. In practice, any node that supports arbos50 populates both
213+
// fields together, so this fallback path should not be reached.
203214
log.Warn("Failed reading batch data for filling message - leaving BatchDataStats empty")
215+
return nil
204216
} else {
205217
gotHash := crypto.Keccak256Hash(batchData)
206218
if gotHash != batchHash {

broadcastclient/broadcastclient_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,131 @@ func TestServerMissingFeedServerVersion(t *testing.T) {
592592
}
593593
}
594594

595+
type accumulatingTransactionStreamer struct {
596+
mu sync.Mutex
597+
messages []*message.BroadcastFeedMessage
598+
}
599+
600+
func (ts *accumulatingTransactionStreamer) AddBroadcastMessages(feedMessages []*message.BroadcastFeedMessage) error {
601+
ts.mu.Lock()
602+
defer ts.mu.Unlock()
603+
ts.messages = append(ts.messages, feedMessages...)
604+
return nil
605+
}
606+
607+
func (ts *accumulatingTransactionStreamer) getMessages() []*message.BroadcastFeedMessage {
608+
ts.mu.Lock()
609+
defer ts.mu.Unlock()
610+
result := make([]*message.BroadcastFeedMessage, len(ts.messages))
611+
copy(result, ts.messages)
612+
return result
613+
}
614+
615+
// awaitCount waits until at least count messages have been received.
616+
// The timeout is a safety net to prevent the test from hanging.
617+
func (ts *accumulatingTransactionStreamer) awaitCount(t *testing.T, count int, timeout time.Duration) {
618+
t.Helper()
619+
deadline := time.After(timeout)
620+
for {
621+
if len(ts.getMessages()) >= count {
622+
return
623+
}
624+
select {
625+
case <-deadline:
626+
t.Fatalf("timed out waiting for %d messages, got %d", count, len(ts.getMessages()))
627+
case <-time.After(10 * time.Millisecond):
628+
}
629+
}
630+
}
631+
632+
func TestInvalidSignatureMessagesAreSkipped(t *testing.T) {
633+
t.Parallel()
634+
ctx, cancel := context.WithCancel(context.Background())
635+
defer cancel()
636+
637+
chainId := uint64(9742)
638+
639+
// Trusted key: broadcaster signs with this, client trusts this
640+
trustedKey, err := crypto.GenerateKey()
641+
Require(t, err)
642+
trustedAddr := crypto.PubkeyToAddress(trustedKey.PublicKey)
643+
trustedSigner := signature.DataSignerFromPrivateKey(trustedKey)
644+
645+
// Untrusted key: used to create messages with invalid signatures
646+
untrustedKey, err := crypto.GenerateKey()
647+
Require(t, err)
648+
untrustedSigner := signature.DataSignerFromPrivateKey(untrustedKey)
649+
650+
feedErrChan := make(chan error, 10)
651+
trustedBroadcaster := broadcaster.NewBroadcaster(func() *wsbroadcastserver.BroadcasterConfig { return &wsbroadcastserver.DefaultTestBroadcasterConfig }, chainId, feedErrChan, trustedSigner)
652+
653+
Require(t, trustedBroadcaster.Initialize())
654+
Require(t, trustedBroadcaster.Start(ctx))
655+
defer trustedBroadcaster.StopAndWait()
656+
657+
// Second broadcaster (not started) used only to create messages signed with the untrusted key
658+
untrustedBroadcaster := broadcaster.NewBroadcaster(func() *wsbroadcastserver.BroadcasterConfig { return &wsbroadcastserver.DefaultTestBroadcasterConfig }, chainId, make(chan error, 1), untrustedSigner)
659+
660+
ts := &accumulatingTransactionStreamer{}
661+
662+
clientFeedErrChan := make(chan error, 10)
663+
broadcastClient, err := newTestBroadcastClient(
664+
DefaultTestConfig,
665+
trustedBroadcaster.ListenerAddr(),
666+
chainId,
667+
0,
668+
ts,
669+
nil,
670+
clientFeedErrChan,
671+
&trustedAddr,
672+
t,
673+
)
674+
Require(t, err)
675+
broadcastClient.Start(ctx)
676+
defer broadcastClient.StopAndWait()
677+
678+
// Batch 1: valid messages (seq 0, 1) - should be delivered.
679+
Require(t, trustedBroadcaster.BroadcastFeedMessages(feedMessage(t, trustedBroadcaster, 0)))
680+
Require(t, trustedBroadcaster.BroadcastFeedMessages(feedMessage(t, trustedBroadcaster, 1)))
681+
ts.awaitCount(t, 2, 10*time.Second)
682+
683+
// Batch 2: invalid messages (seq 2, 3) signed with untrusted key - should be skipped.
684+
Require(t, trustedBroadcaster.BroadcastFeedMessages(feedMessage(t, untrustedBroadcaster, 2)))
685+
Require(t, trustedBroadcaster.BroadcastFeedMessages(feedMessage(t, untrustedBroadcaster, 3)))
686+
687+
// Sentinel (seq 2): a valid message that deterministically proves the client has
688+
// processed and skipped the invalid messages. WebSocket messages are ordered, so the
689+
// sentinel can only arrive after the invalid ones have been processed (and skipped).
690+
// Invalid messages don't advance nextSeqNum (stays at 2), so the sentinel is seq 2.
691+
Require(t, trustedBroadcaster.BroadcastFeedMessages(feedMessage(t, trustedBroadcaster, 2)))
692+
ts.awaitCount(t, 3, 10*time.Second)
693+
694+
// Verify: only valid messages were delivered, and all have trusted signatures.
695+
got := ts.getMessages()
696+
if len(got) != 3 {
697+
t.Fatalf("expected 3 messages, got %d", len(got))
698+
}
699+
for i, msg := range got {
700+
if msg.SequenceNumber != arbutil.MessageIndex(i) { // nolint: gosec
701+
t.Fatalf("message %d: unexpected seq number: %d", i, msg.SequenceNumber)
702+
}
703+
hash := msg.SignatureHash(chainId)
704+
sigPub, err := crypto.SigToPub(hash.Bytes(), msg.Signature)
705+
Require(t, err)
706+
signerAddr := crypto.PubkeyToAddress(*sigPub)
707+
if signerAddr != trustedAddr {
708+
t.Fatalf("message %d (seq %d): signed by %s, expected trusted signer %s", i, msg.SequenceNumber, signerAddr, trustedAddr)
709+
}
710+
}
711+
712+
// Verify no fatal errors occurred (invalid signatures are non-fatal since NIT-4017)
713+
select {
714+
case err := <-clientFeedErrChan:
715+
t.Fatalf("unexpected fatal feed error: %v", err)
716+
default:
717+
}
718+
}
719+
595720
func TestBroadcastClientReconnectsOnServerDisconnect(t *testing.T) {
596721
t.Parallel()
597722
ctx, cancel := context.WithCancel(context.Background())

changelog/bragaigor-nit-4598.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- If batchFetcher returns error use existing LegacyBatchGasCost value
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- Fix `rlp: expected List` error when fetching transaction receipts for blocks with Arbitrum legacy receipt encoding
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Ignored
2+
- Add support to timeboost auctioneer for BidFloorAgent
3+

changelog/pmikolajczyk-nit-3341.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Fixed
2+
- Block validator no longer crashes on timeout errors during validation. Timeout errors are retried separately from other validation failures, up to a configurable limit.
3+
4+
### Configuration
5+
- Added `--node.block-validator.validation-spawning-allowed-timeouts` (default `3`): maximum number of timeout errors allowed per validation before treating it as fatal. Timeout errors have their own counter, separate from `--node.block-validator.validation-spawning-allowed-attempts`.

0 commit comments

Comments
 (0)