Skip to content

Commit

Permalink
Merge pull request #2014 from OffchainLabs/data-poster-time
Browse files Browse the repository at this point in the history
Fix data poster time RLP encoding
  • Loading branch information
joshuacolvin0 authored Dec 12, 2023
2 parents 17ee43f + 9aa58c6 commit 69bc63a
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 8 deletions.
27 changes: 21 additions & 6 deletions arbnode/dataposter/data_poster.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package dataposter

import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
Expand Down Expand Up @@ -527,8 +528,24 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim

// the mutex must be held by the caller
func (p *DataPoster) saveTx(ctx context.Context, prevTx, newTx *storage.QueuedTransaction) error {
if prevTx != nil && prevTx.Data.Nonce != newTx.Data.Nonce {
return fmt.Errorf("prevTx nonce %v doesn't match newTx nonce %v", prevTx.Data.Nonce, newTx.Data.Nonce)
if prevTx != nil {
if prevTx.Data.Nonce != newTx.Data.Nonce {
return fmt.Errorf("prevTx nonce %v doesn't match newTx nonce %v", prevTx.Data.Nonce, newTx.Data.Nonce)
}

// Check if prevTx is the same as newTx and we don't need to do anything
oldEnc, err := rlp.EncodeToBytes(prevTx)
if err != nil {
return fmt.Errorf("failed to encode prevTx: %w", err)
}
newEnc, err := rlp.EncodeToBytes(newTx)
if err != nil {
return fmt.Errorf("failed to encode newTx: %w", err)
}
if bytes.Equal(oldEnc, newEnc) {
// No need to save newTx as it's the same as prevTx
return nil
}
}
if err := p.queue.Put(ctx, newTx.Data.Nonce, prevTx, newTx); err != nil {
return fmt.Errorf("putting new tx in the queue: %w", err)
Expand All @@ -537,10 +554,8 @@ func (p *DataPoster) saveTx(ctx context.Context, prevTx, newTx *storage.QueuedTr
}

func (p *DataPoster) sendTx(ctx context.Context, prevTx *storage.QueuedTransaction, newTx *storage.QueuedTransaction) error {
if prevTx == nil || (newTx.FullTx.Hash() != prevTx.FullTx.Hash()) {
if err := p.saveTx(ctx, prevTx, newTx); err != nil {
return err
}
if err := p.saveTx(ctx, prevTx, newTx); err != nil {
return err
}
if err := p.client.SendTransaction(ctx, newTx.FullTx); err != nil {
if !strings.Contains(err.Error(), "already known") && !strings.Contains(err.Error(), "nonce too low") {
Expand Down
3 changes: 2 additions & 1 deletion arbnode/dataposter/dbstorage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package dbstorage
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"strconv"
Expand Down Expand Up @@ -140,7 +141,7 @@ func (s *Storage) Put(ctx context.Context, index uint64, prev, new *storage.Queu
return fmt.Errorf("encoding previous item: %w", err)
}
if !bytes.Equal(stored, prevEnc) {
return fmt.Errorf("replacing different item than expected at index: %v, stored: %v, prevEnc: %v", index, stored, prevEnc)
return fmt.Errorf("replacing different item than expected at index: %v, stored: %v, prevEnc: %v", index, hex.EncodeToString(stored), hex.EncodeToString(prevEnc))
}
newEnc, err := s.encDec().Encode(new)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion arbnode/dataposter/slice/slicestorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package slice
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"

Expand Down Expand Up @@ -90,7 +91,7 @@ func (s *Storage) Put(_ context.Context, index uint64, prev, new *storage.Queued
return fmt.Errorf("encoding previous item: %w", err)
}
if !bytes.Equal(prevEnc, s.queue[queueIdx]) {
return fmt.Errorf("replacing different item than expected at index: %v, stored: %v, prevEnc: %v", index, s.queue[queueIdx], prevEnc)
return fmt.Errorf("replacing different item than expected at index: %v, stored: %v, prevEnc: %v", index, hex.EncodeToString(s.queue[queueIdx]), hex.EncodeToString(prevEnc))
}
s.queue[queueIdx] = newEnc
} else {
Expand Down
42 changes: 42 additions & 0 deletions arbnode/dataposter/storage/storage.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Copyright 2021-2023, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE

package storage

import (
"errors"
"fmt"
"io"
"time"

"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -30,6 +34,44 @@ type QueuedTransaction struct {
NextReplacement time.Time
}

type queuedTransactionForEncoding struct {
FullTx *types.Transaction
Data types.DynamicFeeTx
Meta []byte
Sent bool
Created *RlpTime `rlp:"optional"` // may be earlier than the tx was given to the tx poster
NextReplacement *RlpTime `rlp:"optional"`
}

func (qt *QueuedTransaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, queuedTransactionForEncoding{
FullTx: qt.FullTx,
Data: qt.Data,
Meta: qt.Meta,
Sent: qt.Sent,
Created: (*RlpTime)(&qt.Created),
NextReplacement: (*RlpTime)(&qt.NextReplacement),
})
}

func (qt *QueuedTransaction) DecodeRLP(s *rlp.Stream) error {
var qtEnc queuedTransactionForEncoding
if err := s.Decode(&qtEnc); err != nil {
return err
}
qt.FullTx = qtEnc.FullTx
qt.Data = qtEnc.Data
qt.Meta = qtEnc.Meta
qt.Sent = qtEnc.Sent
if qtEnc.Created != nil {
qt.Created = time.Time(*qtEnc.Created)
}
if qtEnc.NextReplacement != nil {
qt.NextReplacement = time.Time(*qtEnc.NextReplacement)
}
return nil
}

// LegacyQueuedTransaction is used for backwards compatibility.
// Before https://github.com/OffchainLabs/nitro/pull/1773: the queuedTransaction
// looked like this and was rlp encoded directly. After the pr, we are store
Expand Down
42 changes: 42 additions & 0 deletions arbnode/dataposter/storage/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2021-2023, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE

package storage

import (
"io"
"time"

"github.com/ethereum/go-ethereum/rlp"
)

// time.Time doesn't encode as anything in RLP. This fixes that.
// It encodes a timestamp as a uint64 unix timestamp in seconds,
// so any subsecond precision is lost.
type RlpTime time.Time

type rlpTimeEncoding struct {
Seconds uint64
Nanos uint64
}

func (b *RlpTime) DecodeRLP(s *rlp.Stream) error {
var enc rlpTimeEncoding
err := s.Decode(&enc)
if err != nil {
return err
}
*b = RlpTime(time.Unix(int64(enc.Seconds), int64(enc.Nanos)))
return nil
}

func (b RlpTime) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, rlpTimeEncoding{
Seconds: uint64(time.Time(b).Unix()),
Nanos: uint64(time.Time(b).Nanosecond()),
})
}

func (b RlpTime) String() string {
return time.Time(b).String()
}
17 changes: 17 additions & 0 deletions arbnode/dataposter/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math/big"
"path"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
Expand Down Expand Up @@ -366,3 +367,19 @@ func TestLength(t *testing.T) {

}
}

func TestTimeEncoding(t *testing.T) {
now := storage.RlpTime(time.Now())
enc, err := rlp.EncodeToBytes(now)
if err != nil {
t.Fatal("failed to encode time", err)
}
var dec storage.RlpTime
err = rlp.DecodeBytes(enc, &dec)
if err != nil {
t.Fatal("failed to decode time", err)
}
if !time.Time(dec).Equal(time.Time(now)) {
t.Fatalf("time %v encoded then decoded to %v", now, dec)
}
}

0 comments on commit 69bc63a

Please sign in to comment.