Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

soroban-rpc: Remove panics from internal codebase #1167

Merged
merged 12 commits into from
Jan 25, 2024
4 changes: 1 addition & 3 deletions cmd/soroban-rpc/internal/config/config_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,6 @@ func (o *ConfigOption) setValue(i interface{}) (err error) {
if o.CustomSetValue != nil {
return o.CustomSetValue(o, i)
}
// it's unfortunate that Set below panics when it cannot set the value..
// we'll want to catch this so that we can alert the user nicely.
defer func() {
if recoverRes := recover(); recoverRes != nil {
var ok bool
Expand All @@ -101,7 +99,7 @@ func (o *ConfigOption) setValue(i interface{}) (err error) {
}
}()
parser := func(option *ConfigOption, i interface{}) error {
panic(fmt.Sprintf("no parser for flag %s", o.Name))
return errors.Errorf("no parser for flag %s", o.Name)
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
}
switch o.ConfigKey.(type) {
case *bool:
Expand Down
12 changes: 7 additions & 5 deletions cmd/soroban-rpc/internal/config/log_format.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package config

import "fmt"
import (
"fmt"
)

type LogFormat int

Expand Down Expand Up @@ -47,13 +49,13 @@ func (f *LogFormat) UnmarshalTOML(i interface{}) error {
}
}

func (f LogFormat) String() string {
func (f LogFormat) String() (string, error) {
switch f {
case LogFormatText:
return "text"
return "text", nil
case LogFormatJSON:
return "json"
return "json", nil
default:
panic(fmt.Sprintf("unknown log format: %d", f))
return "", fmt.Errorf("unknown log format: %d", f)
}
}
2 changes: 1 addition & 1 deletion cmd/soroban-rpc/internal/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (cfg *Config) options() ConfigOptions {
return nil
},
MarshalTOML: func(option *ConfigOption) (interface{}, error) {
return cfg.LogFormat.String(), nil
return cfg.LogFormat.String()
},
},
{
Expand Down
16 changes: 13 additions & 3 deletions cmd/soroban-rpc/internal/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,17 @@ func (m *MemoryStore) Scan(eventRange Range, f func(xdr.DiagnosticEvent, Cursor,
}

firstLedgerInRange := eventRange.Start.Ledger
firstLedgerInWindow := m.eventsByLedger.Get(0).LedgerSeq
firstBucket, err := m.eventsByLedger.Get(0)
if err != nil {
return 0, err
}
firstLedgerInWindow := firstBucket.LedgerSeq
lastLedgerInWindow := firstLedgerInWindow + (m.eventsByLedger.Len() - 1)
for i := firstLedgerInRange - firstLedgerInWindow; i < m.eventsByLedger.Len(); i++ {
bucket := m.eventsByLedger.Get(i)
bucket, err := m.eventsByLedger.Get(i)
if err != nil {
return 0, err
}
events := bucket.BucketContent
if bucket.LedgerSeq == firstLedgerInRange {
// we need to seek for the beginning of the events in the first bucket in the range
Expand Down Expand Up @@ -146,7 +153,10 @@ func (m *MemoryStore) validateRange(eventRange *Range) error {
if m.eventsByLedger.Len() == 0 {
return errors.New("event store is empty")
}
firstBucket := m.eventsByLedger.Get(0)
firstBucket, err := m.eventsByLedger.Get(0)
if err != nil {
return err
}
min := Cursor{Ledger: firstBucket.LedgerSeq}
if eventRange.Start.Cmp(min) < 0 {
if eventRange.ClampStart {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ func NewLedgerBucketWindow[T any](retentionWindow uint32) *LedgerBucketWindow[T]
}

// Append adds a new bucket to the window. If the window is full a bucket will be evicted and returned.
func (w *LedgerBucketWindow[T]) Append(bucket LedgerBucket[T]) *LedgerBucket[T] {
func (w *LedgerBucketWindow[T]) Append(bucket LedgerBucket[T]) (*LedgerBucket[T], error) {
length := w.Len()
if length > 0 {
expectedLedgerSequence := w.buckets[w.start].LedgerSeq + length
if expectedLedgerSequence != bucket.LedgerSeq {
panic(fmt.Errorf("ledgers not contiguous: expected ledger sequence %v but received %v", expectedLedgerSequence, bucket.LedgerSeq))
return &LedgerBucket[T]{}, fmt.Errorf("ledgers not contiguous: expected ledger sequence %v but received %v", expectedLedgerSequence, bucket.LedgerSeq)
}
}

Expand All @@ -57,7 +57,7 @@ func (w *LedgerBucketWindow[T]) Append(bucket LedgerBucket[T]) *LedgerBucket[T]
w.start = (w.start + 1) % length
}

return evicted
return evicted, nil
}

// Len returns the length (number of buckets in the window)
Expand All @@ -66,11 +66,11 @@ func (w *LedgerBucketWindow[T]) Len() uint32 {
}

// Get obtains a bucket from the window
func (w *LedgerBucketWindow[T]) Get(i uint32) *LedgerBucket[T] {
func (w *LedgerBucketWindow[T]) Get(i uint32) (*LedgerBucket[T], error) {
length := w.Len()
if i >= length {
panic(fmt.Errorf("index out of range [%d] with length %d", i, length))
return nil, fmt.Errorf("index out of range [%d] with length %d", i, length)
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
}
index := (w.start + i) % length
return &w.buckets[index]
return &w.buckets[index], nil
}
183 changes: 87 additions & 96 deletions cmd/soroban-rpc/internal/ledgerbucketwindow/ledgerbucketwindow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,118 +18,109 @@ func TestAppend(t *testing.T) {
m := NewLedgerBucketWindow[uint32](3)
require.Equal(t, uint32(0), m.Len())

// test appending first bucket of events
evicted := m.Append(bucket(5))
// Test appending first bucket of events
evicted, err := m.Append(bucket(5))
require.NoError(t, err)
require.Nil(t, evicted)
require.Equal(t, uint32(1), m.Len())
require.Equal(t, bucket(5), *m.Get(0))

// the next bucket must follow the previous bucket (ledger 5)

require.PanicsWithError(
t, "ledgers not contiguous: expected ledger sequence 6 but received 10",
func() {
m.Append(LedgerBucket[uint32]{
LedgerSeq: 10,
LedgerCloseTimestamp: 100,
BucketContent: 10,
})
},
)
require.PanicsWithError(
t, "ledgers not contiguous: expected ledger sequence 6 but received 4",
func() {
m.Append(LedgerBucket[uint32]{
LedgerSeq: 4,
LedgerCloseTimestamp: 100,
BucketContent: 4,
})
},
)
require.PanicsWithError(
t, "ledgers not contiguous: expected ledger sequence 6 but received 5",
func() {
m.Append(LedgerBucket[uint32]{
LedgerSeq: 5,
LedgerCloseTimestamp: 100,
BucketContent: 5,
})
},
)
// check that none of the calls above modified our buckets

firstBucket, err := m.Get(0)
require.NoError(t, err)
require.Equal(t, bucket(5), *firstBucket)

// The next bucket must follow the previous bucket (ledger 5)
_, err = m.Append(LedgerBucket[uint32]{
LedgerSeq: 10,
LedgerCloseTimestamp: 100,
BucketContent: 10,
})
require.Errorf(t, err, "ledgers not contiguous: expected ledger sequence 6 but received 10")

_, err = m.Append(LedgerBucket[uint32]{
LedgerSeq: 4,
LedgerCloseTimestamp: 100,
BucketContent: 4,
})
require.Errorf(t, err, "ledgers not contiguous: expected ledger sequence 6 but received 4")

// Check that none of the calls above modified our buckets
require.Equal(t, uint32(1), m.Len())
require.Equal(t, bucket(5), *m.Get(0))
firstBucket, err = m.Get(0)
require.NoError(t, err)
require.Equal(t, bucket(5), *firstBucket)

// append ledger 6 bucket, now we have two buckets filled
evicted = m.Append(bucket(6))
// Append ledger 6 bucket, now we have two buckets filled
evicted, err = m.Append(bucket(6))
require.NoError(t, err)
require.Nil(t, evicted)
require.Equal(t, uint32(2), m.Len())
require.Equal(t, bucket(5), *m.Get(0))
require.Equal(t, bucket(6), *m.Get(1))

// the next bucket of events must follow the previous bucket (ledger 6)
require.PanicsWithError(
t, "ledgers not contiguous: expected ledger sequence 7 but received 10",
func() {
m.Append(LedgerBucket[uint32]{
LedgerSeq: 10,
LedgerCloseTimestamp: 100,
BucketContent: 10,
})
},
)
require.PanicsWithError(
t, "ledgers not contiguous: expected ledger sequence 7 but received 4",
func() {
m.Append(LedgerBucket[uint32]{
LedgerSeq: 4,
LedgerCloseTimestamp: 100,
BucketContent: 4,
})
},
)
require.PanicsWithError(
t, "ledgers not contiguous: expected ledger sequence 7 but received 5",
func() {
m.Append(LedgerBucket[uint32]{
LedgerSeq: 5,
LedgerCloseTimestamp: 100,
BucketContent: 5,
})
},
)

// append ledger 7, now we have all three buckets filled
evicted = m.Append(bucket(7))
require.Nil(t, evicted)

secondBucket, err := m.Get(1)
require.NoError(t, err)
require.Equal(t, bucket(6), *secondBucket)

// Append ledger 7, now we have all three buckets filled
evicted, err = m.Append(bucket(7))
require.NoError(t, err)
require.Nil(t, evicted)
require.Equal(t, uint32(3), m.Len())
require.Equal(t, bucket(5), *m.Get(0))
require.Equal(t, bucket(6), *m.Get(1))
require.Equal(t, bucket(7), *m.Get(2))

// append ledger 8, but all buckets are full, so we need to evict ledger 5
evicted = m.Append(bucket(8))
thirdBucket, err := m.Get(2)
require.NoError(t, err)
require.Equal(t, bucket(7), *thirdBucket)

// Append ledger 8, but all buckets are full, so we need to evict ledger 5
evicted, err = m.Append(bucket(8))
require.NoError(t, err)
require.Equal(t, bucket(5), *evicted)
require.Equal(t, uint32(3), m.Len())
require.Equal(t, bucket(6), *m.Get(0))
require.Equal(t, bucket(7), *m.Get(1))
require.Equal(t, bucket(8), *m.Get(2))

// append ledger 9 events, but all buckets are full, so we need to evict ledger 6
evicted = m.Append(bucket(9))
firstBucket, err = m.Get(0)
require.NoError(t, err)
require.Equal(t, bucket(6), *firstBucket)

secondBucket, err = m.Get(1)
require.NoError(t, err)
require.Equal(t, bucket(7), *secondBucket)

thirdBucket, err = m.Get(2)
require.NoError(t, err)
require.Equal(t, bucket(8), *thirdBucket)

// Append ledger 9 events, but all buckets are full, so we need to evict ledger 6
evicted, err = m.Append(bucket(9))
require.NoError(t, err)
require.Equal(t, bucket(6), *evicted)
require.Equal(t, uint32(3), m.Len())
require.Equal(t, bucket(7), *m.Get(0))
require.Equal(t, bucket(8), *m.Get(1))
require.Equal(t, bucket(9), *m.Get(2))

// append ledger 10, but all buckets are full, so we need to evict ledger 7.
firstBucket, err = m.Get(0)
require.NoError(t, err)
require.Equal(t, bucket(7), *firstBucket)

secondBucket, err = m.Get(1)
require.NoError(t, err)
require.Equal(t, bucket(8), *secondBucket)

thirdBucket, err = m.Get(2)
require.NoError(t, err)
require.Equal(t, bucket(9), *thirdBucket)

// Append ledger 10, but all buckets are full, so we need to evict ledger 7.
// The start index must have wrapped around
evicted = m.Append(bucket(10))
evicted, err = m.Append(bucket(10))
require.NoError(t, err)
require.Equal(t, bucket(7), *evicted)
require.Equal(t, uint32(3), m.Len())
require.Equal(t, bucket(8), *m.Get(0))
require.Equal(t, bucket(9), *m.Get(1))
require.Equal(t, bucket(10), *m.Get(2))

firstBucket, err = m.Get(0)
require.NoError(t, err)
require.Equal(t, bucket(8), *firstBucket)

secondBucket, err = m.Get(1)
require.NoError(t, err)
require.Equal(t, bucket(9), *secondBucket)

thirdBucket, err = m.Get(2)
require.NoError(t, err)
require.Equal(t, bucket(10), *thirdBucket)
}
7 changes: 5 additions & 2 deletions cmd/soroban-rpc/internal/methods/get_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type GetTransactionRequest struct {
}

type transactionGetter interface {
GetTransaction(hash xdr.Hash) (transactions.Transaction, bool, transactions.StoreRange)
GetTransaction(hash xdr.Hash) (transactions.Transaction, bool, transactions.StoreRange, error)
}

func GetTransaction(getter transactionGetter, request GetTransactionRequest) (GetTransactionResponse, error) {
Expand All @@ -84,7 +84,10 @@ func GetTransaction(getter transactionGetter, request GetTransactionRequest) (Ge
}
}

tx, found, storeRange := getter.GetTransaction(txHash)
tx, found, storeRange, err := getter.GetTransaction(txHash)
if err != nil {
return GetTransactionResponse{}, err
}
response := GetTransactionResponse{
LatestLedger: storeRange.LastLedger.Sequence,
LatestLedgerCloseTime: storeRange.LastLedger.CloseTime,
Expand Down
5 changes: 4 additions & 1 deletion cmd/soroban-rpc/internal/methods/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ type HealthCheckResult struct {
// NewHealthCheck returns a health check json rpc handler
func NewHealthCheck(txStore *transactions.MemoryStore, maxHealthyLedgerLatency time.Duration) jrpc2.Handler {
return handler.New(func(ctx context.Context) (HealthCheckResult, error) {
ledgerInfo := txStore.GetLatestLedger()
ledgerInfo, err := txStore.GetLatestLedger()
if err != nil {
return HealthCheckResult{}, err
}
if ledgerInfo.Sequence < 1 {
return HealthCheckResult{}, jrpc2.Error{
Code: jrpc2.InternalError,
Expand Down
8 changes: 6 additions & 2 deletions cmd/soroban-rpc/internal/methods/send_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type SendTransactionRequest struct {
// LatestLedgerStore is a store which returns the latest ingested ledger.
type LatestLedgerStore interface {
// GetLatestLedger returns the latest ingested ledger.
GetLatestLedger() transactions.LedgerInfo
GetLatestLedger() (transactions.LedgerInfo, error)
}

// NewSendTransactionHandler returns a submit transaction json rpc handler
Expand All @@ -74,7 +74,11 @@ func NewSendTransactionHandler(daemon interfaces.Daemon, logger *log.Entry, stor
}
txHash := hex.EncodeToString(hash[:])

ledgerInfo := store.GetLatestLedger()
ledgerInfo, err := store.GetLatestLedger()
if err != nil {
return SendTransactionResponse{}, err
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
}

resp, err := submitter.SubmitTransaction(ctx, request.Transaction)
if err != nil {
logger.WithError(err).
Expand Down
Loading
Loading