Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,8 @@ fmt: ## Format all Go code
cd xmss/rust && cargo fmt

sszgen: ## Regenerate SSZ encoding files from struct tags
@rm -f types/*_encoding.go
sszgen --path pkg/types --objs ChainConfig --output types/config_encoding.go
sszgen --path pkg/types --objs Checkpoint --output types/checkpoint_encoding.go
sszgen --path pkg/types --objs Validator --output types/validator_encoding.go
sszgen --path pkg/types --objs AttestationData,Attestation,SignedAttestation,AggregatedAttestation,SignedAggregatedAttestation --exclude-objs Checkpoint --output types/attestation_encoding.go
sszgen --path pkg/types --objs BlockHeader,BlockBody,Block,AggregatedSignatureProof,BlockSignatures,SignedBlock --exclude-objs Checkpoint,AttestationData,Attestation,AggregatedAttestation,AggregatedSignatureProof --output types/block_encoding.go
sszgen --path pkg/types --objs State --exclude-objs ChainConfig,Checkpoint,Validator,BlockHeader --output types/state_encoding.go
sszgen --path types --objs BlocksByRangeRequest --output types/blocks_by_range_encoding.go
@rm -f types/*_encoding.go types/encoding_gen.go
sszgen --path types --objs AttestationData,Attestation,SignedAttestation,AggregatedAttestation,SignedAggregatedAttestation,BlockHeader,BlockBody,Block,AggregatedSignatureProof,BlockSignatures,SignedBlock,BlocksByRangeRequest,Checkpoint,ChainConfig,State,Validator --output types/encoding_gen.go

clean: ## Remove build artifacts and generated files
rm -rf bin data
Expand Down Expand Up @@ -132,4 +126,3 @@ run-devnet: docker-build lean-quickstart ## Run local multi-client devnet
@echo "Starting local devnet with gean client (\"$(DOCKER_TAG)\" tag)."
@cd lean-quickstart \
&& NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --metrics > ../devnet.log 2>&1

2 changes: 1 addition & 1 deletion checkpoint/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/geanlabs/gean/types"
)

// Timeouts rs L9-13.
// HTTP timeouts for checkpoint sync requests.
const (
CheckpointConnectTimeout = 15 * time.Second
CheckpointReadTimeout = 15 * time.Second
Expand Down
6 changes: 0 additions & 6 deletions forkchoice/protoarray.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,6 @@ func (pa *ProtoArray) FindHead(justifiedRoot [32]byte) [32]byte {
return pa.nodes[bestDesc].Root
}

// FindHeadWithThreshold is like FindHead but with a minimum weight cutoff.
// Used for safe target computation (2/3 threshold).
func (pa *ProtoArray) FindHeadWithThreshold(justifiedRoot [32]byte, minScore int64) [32]byte {
return pa.FindHead(justifiedRoot) // cutoff applied during ApplyScoreChanges
}

// Prune removes all nodes below the finalized root.
func (pa *ProtoArray) Prune(finalizedRoot [32]byte) {
finalizedIdx, ok := pa.indices[finalizedRoot]
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.25.7
require (
github.com/cockroachdb/pebble v1.1.5
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1
github.com/ethereum/go-ethereum v1.17.2
github.com/ferranbt/fastssz v1.0.0
github.com/golang/snappy v1.0.0
github.com/libp2p/go-libp2p v0.48.0
Expand Down Expand Up @@ -37,7 +36,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54
github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum/go-ethereum v1.17.2 h1:ag6geu0kn8Hv5FLKTpH+Hm2DHD+iuFtuqKxEuwUsDOI=
github.com/ethereum/go-ethereum v1.17.2/go.mod h1:KHcRXfGOUfUmKg51IhQ0IowiqZ6PqZf08CMtk0g5K1o=
github.com/ferranbt/fastssz v1.0.0 h1:9EXXYsracSqQRBQiHeaVsG/KQeYblPf40hsQPb9Dzk8=
github.com/ferranbt/fastssz v1.0.0/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
Expand All @@ -61,8 +59,6 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
Expand Down
233 changes: 233 additions & 0 deletions internal/rlp/rlp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package rlp

import (
"encoding/binary"
"fmt"
)

type valueKind byte

const (
stringKind valueKind = iota
listKind
)

// SplitList decodes a top-level RLP list and returns each element as its raw
// encoded RLP value.
func SplitList(input []byte) ([][]byte, error) {
kind, payload, rest, err := split(input)
if err != nil {
return nil, err
}
if kind != listKind {
return nil, fmt.Errorf("rlp: expected list")
}
if len(rest) != 0 {
return nil, fmt.Errorf("rlp: trailing data")
}

items := make([][]byte, 0)
for len(payload) > 0 {
_, _, next, err := split(payload)
if err != nil {
return nil, err
}
rawLen := len(payload) - len(next)
items = append(items, payload[:rawLen])
payload = next
}
return items, nil
}

// Bytes decodes one RLP string value.
func Bytes(input []byte) ([]byte, error) {
kind, payload, rest, err := split(input)
if err != nil {
return nil, err
}
if kind != stringKind {
return nil, fmt.Errorf("rlp: expected string")
}
if len(rest) != 0 {
return nil, fmt.Errorf("rlp: trailing data")
}
return payload, nil
}

// String decodes one RLP string value as a Go string.
func String(input []byte) (string, error) {
b, err := Bytes(input)
if err != nil {
return "", err
}
return string(b), nil
}

// Uint64 decodes one RLP unsigned integer.
func Uint64(input []byte) (uint64, error) {
b, err := Bytes(input)
if err != nil {
return 0, err
}
if len(b) == 0 {
return 0, nil
}
if len(b) > 8 {
return 0, fmt.Errorf("rlp: uint64 overflow")
}
if len(b) > 1 && b[0] == 0 {
return 0, fmt.Errorf("rlp: non-minimal integer")
}
var buf [8]byte
copy(buf[8-len(b):], b)
return binary.BigEndian.Uint64(buf[:]), nil
}

// Uint16 decodes one RLP unsigned integer and verifies it fits in 16 bits.
func Uint16(input []byte) (uint16, error) {
v, err := Uint64(input)
if err != nil {
return 0, err
}
if v > uint64(^uint16(0)) {
return 0, fmt.Errorf("rlp: uint16 overflow")
}
return uint16(v), nil
}

// EncodeBytes encodes a byte string as RLP.
func EncodeBytes(payload []byte) []byte {
if len(payload) == 1 && payload[0] < 0x80 {
return []byte{payload[0]}
}
return encodePayload(0x80, 0xb7, payload)
}

// EncodeUint64 encodes an unsigned integer as RLP.
func EncodeUint64(v uint64) []byte {
if v == 0 {
return []byte{0x80}
}
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], v)
i := 0
for i < len(buf) && buf[i] == 0 {
i++
}
return EncodeBytes(buf[i:])
}

// EncodeUint16 encodes a uint16 as RLP.
func EncodeUint16(v uint16) []byte {
return EncodeUint64(uint64(v))
}

// EncodeList encodes already-encoded RLP values as an RLP list.
func EncodeList(items ...[]byte) []byte {
payloadLen := 0
for _, item := range items {
payloadLen += len(item)
}
payload := make([]byte, 0, payloadLen)
for _, item := range items {
payload = append(payload, item...)
}
return encodePayload(0xc0, 0xf7, payload)
}

func split(input []byte) (valueKind, []byte, []byte, error) {
if len(input) == 0 {
return 0, nil, nil, fmt.Errorf("rlp: empty input")
}

prefix := input[0]
switch {
case prefix < 0x80:
return stringKind, input[:1], input[1:], nil
case prefix <= 0xb7:
size := int(prefix - 0x80)
if len(input) < 1+size {
return 0, nil, nil, fmt.Errorf("rlp: short string")
}
if size == 1 && input[1] < 0x80 {
return 0, nil, nil, fmt.Errorf("rlp: non-minimal string")
}
return stringKind, input[1 : 1+size], input[1+size:], nil
case prefix <= 0xbf:
sizeOfSize := int(prefix - 0xb7)
size, err := readSize(input[1:], sizeOfSize)
if err != nil {
return 0, nil, nil, err
}
if size <= 55 {
return 0, nil, nil, fmt.Errorf("rlp: non-minimal long string")
}
offset := 1 + sizeOfSize
if len(input) < offset+size {
return 0, nil, nil, fmt.Errorf("rlp: short long string")
}
return stringKind, input[offset : offset+size], input[offset+size:], nil
case prefix <= 0xf7:
size := int(prefix - 0xc0)
if len(input) < 1+size {
return 0, nil, nil, fmt.Errorf("rlp: short list")
}
return listKind, input[1 : 1+size], input[1+size:], nil
default:
sizeOfSize := int(prefix - 0xf7)
size, err := readSize(input[1:], sizeOfSize)
if err != nil {
return 0, nil, nil, err
}
if size <= 55 {
return 0, nil, nil, fmt.Errorf("rlp: non-minimal long list")
}
offset := 1 + sizeOfSize
if len(input) < offset+size {
return 0, nil, nil, fmt.Errorf("rlp: short long list")
}
return listKind, input[offset : offset+size], input[offset+size:], nil
}
}

func readSize(input []byte, sizeOfSize int) (int, error) {
if sizeOfSize == 0 || sizeOfSize > 8 {
return 0, fmt.Errorf("rlp: invalid length size")
}
if len(input) < sizeOfSize {
return 0, fmt.Errorf("rlp: short length")
}
if input[0] == 0 {
return 0, fmt.Errorf("rlp: non-minimal length")
}
var size uint64
for _, b := range input[:sizeOfSize] {
size = size<<8 | uint64(b)
}
if size > uint64(int(^uint(0)>>1)) {
return 0, fmt.Errorf("rlp: length overflow")
}
return int(size), nil
}

func encodePayload(shortBase, longBase byte, payload []byte) []byte {
if len(payload) <= 55 {
out := make([]byte, 1+len(payload))
out[0] = shortBase + byte(len(payload))
copy(out[1:], payload)
return out
}

var lenBuf [8]byte
binary.BigEndian.PutUint64(lenBuf[:], uint64(len(payload)))
i := 0
for i < len(lenBuf) && lenBuf[i] == 0 {
i++
}
sizeBytes := lenBuf[i:]
out := make([]byte, 1+len(sizeBytes)+len(payload))
out[0] = longBase + byte(len(sizeBytes))
copy(out[1:], sizeBytes)
copy(out[1+len(sizeBytes):], payload)
return out
}
79 changes: 79 additions & 0 deletions internal/rlp/rlp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package rlp

import (
"bytes"
"encoding/hex"
"testing"
)

func TestEncodeExamples(t *testing.T) {
tests := []struct {
name string
got []byte
want string
}{
{name: "empty bytes", got: EncodeBytes(nil), want: "80"},
{name: "single byte", got: EncodeBytes([]byte{0x7f}), want: "7f"},
{name: "dog", got: EncodeBytes([]byte("dog")), want: "83646f67"},
{name: "small uint", got: EncodeUint64(15), want: "0f"},
{name: "larger uint", got: EncodeUint64(1024), want: "820400"},
{name: "list", got: EncodeList(EncodeBytes([]byte("cat")), EncodeBytes([]byte("dog"))), want: "c88363617483646f67"},
}

for _, tt := range tests {
if hex.EncodeToString(tt.got) != tt.want {
t.Fatalf("%s: got %x, want %s", tt.name, tt.got, tt.want)
}
}
}

func TestSplitListDecode(t *testing.T) {
encoded := EncodeList(EncodeBytes([]byte("udp")), EncodeUint16(30303))
items, err := SplitList(encoded)
if err != nil {
t.Fatalf("SplitList: %v", err)
}
if len(items) != 2 {
t.Fatalf("got %d items, want 2", len(items))
}
key, err := String(items[0])
if err != nil {
t.Fatalf("String: %v", err)
}
port, err := Uint16(items[1])
if err != nil {
t.Fatalf("Uint16: %v", err)
}
if key != "udp" || port != 30303 {
t.Fatalf("got (%q, %d), want (udp, 30303)", key, port)
}
}

func TestEncodeLongString(t *testing.T) {
payload := bytes.Repeat([]byte{'a'}, 56)
got := EncodeBytes(payload)
if len(got) != 58 || got[0] != 0xb8 || got[1] != 56 {
t.Fatalf("got long string prefix %x, want b838", got[:2])
}
if !bytes.Equal(got[2:], payload) {
t.Fatalf("long string payload mismatch")
}
}

func TestRejectNonCanonicalForms(t *testing.T) {
tests := []struct {
name string
fn func() error
}{
{name: "non-minimal string", fn: func() error { _, err := Bytes([]byte{0x81, 0x7f}); return err }},
{name: "non-minimal integer", fn: func() error { _, err := Uint64([]byte{0x82, 0x00, 0x01}); return err }},
{name: "non-minimal long string", fn: func() error { _, err := Bytes([]byte{0xb8, 0x01, 0x00}); return err }},
{name: "non-minimal length", fn: func() error { _, err := Bytes([]byte{0xb9, 0x00, 0x38}); return err }},
}

for _, tt := range tests {
if err := tt.fn(); err == nil {
t.Fatalf("%s: expected error", tt.name)
}
}
}
Loading