diff --git a/mod/beacon/validator/block_builder.go b/mod/beacon/validator/block_builder.go index 6fe82bd7a5..7e5d71dd89 100644 --- a/mod/beacon/validator/block_builder.go +++ b/mod/beacon/validator/block_builder.go @@ -22,6 +22,7 @@ package validator import ( "context" + "fmt" "time" engineprimitives "github.com/berachain/beacon-kit/mod/engine-primitives/pkg/engine-primitives" @@ -303,7 +304,12 @@ func (s *Service[ )) // Set the graffiti on the block body. - body.SetGraffiti(bytes.ToBytes32([]byte(s.cfg.Graffiti))) + sizedGraffiti := bytes.ExtendToSize([]byte(s.cfg.Graffiti), bytes.B32Size) + graffiti, err := bytes.ToBytes32(sizedGraffiti) + if err != nil { + return fmt.Errorf("failed processing graffiti: %w", err) + } + body.SetGraffiti(graffiti) // Get the epoch to find the active fork version. epoch := s.chainSpec.SlotToEpoch(blk.GetSlot()) diff --git a/mod/consensus-types/pkg/types/block.go b/mod/consensus-types/pkg/types/block.go index 52036d1a5c..6f0b828b97 100644 --- a/mod/consensus-types/pkg/types/block.go +++ b/mod/consensus-types/pkg/types/block.go @@ -233,8 +233,8 @@ func (b *BeaconBlock) GetHeader() *BeaconBlockHeader { } } -// GetExecutionNumber retrieves the execution number of the BeaconBlock from +// GetTimestamp retrieves the timestamp of the BeaconBlock from // the ExecutionPayload. -func (b *BeaconBlock) GetExecutionNumber() math.U64 { - return b.Body.ExecutionPayload.Number +func (b *BeaconBlock) GetTimestamp() math.U64 { + return b.Body.ExecutionPayload.Timestamp } diff --git a/mod/consensus-types/pkg/types/block_test.go b/mod/consensus-types/pkg/types/block_test.go index 8d2a2b9271..ad4ba20ad1 100644 --- a/mod/consensus-types/pkg/types/block_test.go +++ b/mod/consensus-types/pkg/types/block_test.go @@ -42,7 +42,7 @@ func generateValidBeaconBlock() *types.BeaconBlock { StateRoot: common.Root{5, 4, 3, 2, 1}, Body: &types.BeaconBlockBody{ ExecutionPayload: &types.ExecutionPayload{ - Number: 10, + Timestamp: 10, ExtraData: []byte("dummy extra data for testing"), Transactions: [][]byte{ []byte("tx1"), @@ -101,7 +101,7 @@ func TestBeaconBlock(t *testing.T) { block := generateValidBeaconBlock() require.NotNil(t, block.Body) - require.Equal(t, math.U64(10), block.GetExecutionNumber()) + require.Equal(t, math.U64(10), block.GetTimestamp()) require.Equal(t, version.Deneb, block.Version()) require.False(t, block.IsNil()) diff --git a/mod/consensus-types/pkg/types/genesis.go b/mod/consensus-types/pkg/types/genesis.go index 6dc80054ea..0d3877256a 100644 --- a/mod/consensus-types/pkg/types/genesis.go +++ b/mod/consensus-types/pkg/types/genesis.go @@ -21,6 +21,7 @@ package types import ( + "fmt" "math/big" engineprimitives "github.com/berachain/beacon-kit/mod/engine-primitives/pkg/engine-primitives" @@ -131,22 +132,31 @@ func DefaultGenesisDeneb() *Genesis[ func DefaultGenesisExecutionPayloadHeaderDeneb() ( *ExecutionPayloadHeader, error, ) { + stateRoot, err := byteslib.ToBytes32( + hex.MustToBytes( + "0x12965ab9cbe2d2203f61d23636eb7e998f167cb79d02e452f532535641e35bcc", + ), + ) + if err != nil { + return nil, fmt.Errorf("failed generating state root: %w", err) + } + + receiptsRoot, err := byteslib.ToBytes32( + hex.MustToBytes( + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ), + ) + if err != nil { + return nil, fmt.Errorf("failed generating receipts root: %w", err) + } return &ExecutionPayloadHeader{ ParentHash: common.ExecutionHash{}, FeeRecipient: common.ExecutionAddress{}, - StateRoot: byteslib.ToBytes32( - hex.MustToBytes( - "0x12965ab9cbe2d2203f61d23636eb7e998f167cb79d02e452f532535641e35bcc", - ), - ), - ReceiptsRoot: byteslib.ToBytes32( - hex.MustToBytes( - "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - ), - ), - LogsBloom: [256]byte{}, - Random: common.Bytes32{}, - Number: 0, + StateRoot: stateRoot, + ReceiptsRoot: receiptsRoot, + LogsBloom: [256]byte{}, + Random: common.Bytes32{}, + Number: 0, //nolint:mnd // default value. GasLimit: math.U64(30000000), GasUsed: 0, diff --git a/mod/da/pkg/types/sidecar_test.go b/mod/da/pkg/types/sidecar_test.go index b3f0ebbe36..30480f4289 100644 --- a/mod/da/pkg/types/sidecar_test.go +++ b/mod/da/pkg/types/sidecar_test.go @@ -21,6 +21,7 @@ package types_test import ( + "strconv" "testing" ctypes "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" @@ -39,22 +40,20 @@ func TestSidecarMarshalling(t *testing.T) { for i := range blob { blob[i] = byte(i % 256) } + inclusionProof := make([]common.Root, 0) + for i := int(1); i <= 8; i++ { + it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size) + proof, err := byteslib.ToBytes32(it) + require.NoError(t, err) + inclusionProof = append(inclusionProof, common.Root(proof)) + } sidecar := types.BuildBlobSidecar( 1, &ctypes.BeaconBlockHeader{}, &blob, eip4844.KZGCommitment{}, eip4844.KZGProof{}, - []common.Root{ - common.Root(byteslib.ToBytes32([]byte("1"))), - common.Root(byteslib.ToBytes32([]byte("2"))), - common.Root(byteslib.ToBytes32([]byte("3"))), - common.Root(byteslib.ToBytes32([]byte("4"))), - common.Root(byteslib.ToBytes32([]byte("5"))), - common.Root(byteslib.ToBytes32([]byte("6"))), - common.Root(byteslib.ToBytes32([]byte("7"))), - common.Root(byteslib.ToBytes32([]byte("8"))), - }, + inclusionProof, ) // Marshal the sidecar @@ -79,39 +78,47 @@ func TestSidecarMarshalling(t *testing.T) { func TestHasValidInclusionProof(t *testing.T) { tests := []struct { name string - sidecar *types.BlobSidecar + sidecar func(t *testing.T) *types.BlobSidecar kzgOffset uint64 expectedResult bool }{ { name: "Invalid inclusion proof", - sidecar: types.BuildBlobSidecar( - math.U64(0), - &ctypes.BeaconBlockHeader{ - BodyRoot: [32]byte{3}, - }, - &eip4844.Blob{}, - eip4844.KZGCommitment{}, - eip4844.KZGProof{}, - []common.Root{ - common.Root(byteslib.ToBytes32([]byte("4"))), - common.Root(byteslib.ToBytes32([]byte("5"))), - common.Root(byteslib.ToBytes32([]byte("6"))), - }, - ), + sidecar: func(t *testing.T) *types.BlobSidecar { + t.Helper() + inclusionProof := make([]common.Root, 0) + for i := int(1); i <= 8; i++ { + it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size) + proof, err := byteslib.ToBytes32(it) + require.NoError(t, err) + inclusionProof = append(inclusionProof, common.Root(proof)) + } + return types.BuildBlobSidecar( + math.U64(0), + &ctypes.BeaconBlockHeader{ + BodyRoot: [32]byte{3}, + }, + &eip4844.Blob{}, + eip4844.KZGCommitment{}, + eip4844.KZGProof{}, + inclusionProof, + ) + }, kzgOffset: 0, expectedResult: false, }, { name: "Empty inclusion proof", - sidecar: types.BuildBlobSidecar( - math.U64(0), - &ctypes.BeaconBlockHeader{}, - &eip4844.Blob{}, - eip4844.KZGCommitment{}, - eip4844.KZGProof{}, - []common.Root{}, - ), + sidecar: func(*testing.T) *types.BlobSidecar { + return types.BuildBlobSidecar( + math.U64(0), + &ctypes.BeaconBlockHeader{}, + &eip4844.Blob{}, + eip4844.KZGCommitment{}, + eip4844.KZGProof{}, + []common.Root{}, + ) + }, kzgOffset: 0, expectedResult: false, }, @@ -119,7 +126,8 @@ func TestHasValidInclusionProof(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := tt.sidecar.HasValidInclusionProof(tt.kzgOffset) + sidecar := tt.sidecar(t) + result := sidecar.HasValidInclusionProof(tt.kzgOffset) require.Equal(t, tt.expectedResult, result, "Result should match expected value") }) @@ -129,31 +137,32 @@ func TestHasValidInclusionProof(t *testing.T) { func TestHashTreeRoot(t *testing.T) { tests := []struct { name string - sidecar *types.BlobSidecar + sidecar func(t *testing.T) *types.BlobSidecar expectedResult common.Root expectError bool }{ { name: "Valid BlobSidecar", - sidecar: types.BuildBlobSidecar( - math.U64(1), - &ctypes.BeaconBlockHeader{ - BodyRoot: [32]byte{7, 8, 9}, - }, - &eip4844.Blob{0, 1, 2, 3, 4, 5, 6, 7}, - eip4844.KZGCommitment{1, 2, 3}, - eip4844.KZGProof{4, 5, 6}, - []common.Root{ - common.Root(byteslib.ToBytes32([]byte("1"))), - common.Root(byteslib.ToBytes32([]byte("2"))), - common.Root(byteslib.ToBytes32([]byte("3"))), - common.Root(byteslib.ToBytes32([]byte("4"))), - common.Root(byteslib.ToBytes32([]byte("5"))), - common.Root(byteslib.ToBytes32([]byte("6"))), - common.Root(byteslib.ToBytes32([]byte("7"))), - common.Root(byteslib.ToBytes32([]byte("8"))), - }, - ), + sidecar: func(t *testing.T) *types.BlobSidecar { + t.Helper() + inclusionProof := make([]common.Root, 0) + for i := int(1); i <= 8; i++ { + it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size) + proof, err := byteslib.ToBytes32(it) + require.NoError(t, err) + inclusionProof = append(inclusionProof, common.Root(proof)) + } + return types.BuildBlobSidecar( + math.U64(1), + &ctypes.BeaconBlockHeader{ + BodyRoot: [32]byte{7, 8, 9}, + }, + &eip4844.Blob{0, 1, 2, 3, 4, 5, 6, 7}, + eip4844.KZGCommitment{1, 2, 3}, + eip4844.KZGProof{4, 5, 6}, + inclusionProof, + ) + }, expectedResult: [32]uint8{ 0xce, 0x75, 0x41, 0x87, 0x48, 0x46, 0x6d, 0x26, 0x9e, 0x72, 0x5d, 0xac, 0x5a, 0x6e, 0x36, 0xed, 0x8c, 0x2a, 0x98, 0x19, 0x6b, 0xe1, @@ -165,7 +174,8 @@ func TestHashTreeRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.NotPanics(t, func() { - result := tt.sidecar.HashTreeRoot() + sidecar := tt.sidecar(t) + result := sidecar.HashTreeRoot() require.Equal( t, tt.expectedResult, diff --git a/mod/da/pkg/types/sidecars_test.go b/mod/da/pkg/types/sidecars_test.go index d2128c0257..bfd2b2bc2a 100644 --- a/mod/da/pkg/types/sidecars_test.go +++ b/mod/da/pkg/types/sidecars_test.go @@ -21,6 +21,7 @@ package types_test import ( + "strconv" "testing" ctypes "github.com/berachain/beacon-kit/mod/consensus-types/pkg/types" @@ -34,22 +35,21 @@ import ( func TestEmptySidecarMarshalling(t *testing.T) { // Create an empty BlobSidecar + inclusionProof := make([]common.Root, 0) + for i := int(1); i <= 8; i++ { + it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size) + proof, err := byteslib.ToBytes32(it) + require.NoError(t, err) + inclusionProof = append(inclusionProof, common.Root(proof)) + } + sidecar := types.BuildBlobSidecar( math.U64(0), &ctypes.BeaconBlockHeader{}, &eip4844.Blob{}, eip4844.KZGCommitment{}, [48]byte{}, - []common.Root{ - common.Root(byteslib.ToBytes32([]byte("1"))), - common.Root(byteslib.ToBytes32([]byte("2"))), - common.Root(byteslib.ToBytes32([]byte("3"))), - common.Root(byteslib.ToBytes32([]byte("4"))), - common.Root(byteslib.ToBytes32([]byte("5"))), - common.Root(byteslib.ToBytes32([]byte("6"))), - common.Root(byteslib.ToBytes32([]byte("7"))), - common.Root(byteslib.ToBytes32([]byte("8"))), - }, + inclusionProof, ) // Marshal the empty sidecar @@ -85,6 +85,14 @@ func TestEmptySidecarMarshalling(t *testing.T) { func TestValidateBlockRoots(t *testing.T) { // Create a sample BlobSidecar with valid roots + inclusionProof := make([]common.Root, 0) + for i := int(1); i <= 8; i++ { + it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size) + proof, err := byteslib.ToBytes32(it) + require.NoError(t, err) + inclusionProof = append(inclusionProof, common.Root(proof)) + } + validSidecar := types.BuildBlobSidecar( math.U64(0), &ctypes.BeaconBlockHeader{ @@ -95,16 +103,7 @@ func TestValidateBlockRoots(t *testing.T) { &eip4844.Blob{}, [48]byte{}, [48]byte{}, - []common.Root{ - common.Root(byteslib.ToBytes32([]byte("1"))), - common.Root(byteslib.ToBytes32([]byte("2"))), - common.Root(byteslib.ToBytes32([]byte("3"))), - common.Root(byteslib.ToBytes32([]byte("4"))), - common.Root(byteslib.ToBytes32([]byte("5"))), - common.Root(byteslib.ToBytes32([]byte("6"))), - common.Root(byteslib.ToBytes32([]byte("7"))), - common.Root(byteslib.ToBytes32([]byte("8"))), - }, + inclusionProof, ) // Validate the sidecar with valid roots @@ -128,16 +127,7 @@ func TestValidateBlockRoots(t *testing.T) { &eip4844.Blob{}, eip4844.KZGCommitment{}, eip4844.KZGProof{}, - []common.Root{ - common.Root(byteslib.ToBytes32([]byte("1"))), - common.Root(byteslib.ToBytes32([]byte("2"))), - common.Root(byteslib.ToBytes32([]byte("3"))), - common.Root(byteslib.ToBytes32([]byte("4"))), - common.Root(byteslib.ToBytes32([]byte("5"))), - common.Root(byteslib.ToBytes32([]byte("6"))), - common.Root(byteslib.ToBytes32([]byte("7"))), - common.Root(byteslib.ToBytes32([]byte("8"))), - }, + inclusionProof, ) // Validate the sidecar with invalid roots sidecarsInvalid := types.BlobSidecars{ diff --git a/mod/engine-primitives/pkg/engine-primitives/attributes.go b/mod/engine-primitives/pkg/engine-primitives/attributes.go index cb6bd5673b..47398e66ca 100644 --- a/mod/engine-primitives/pkg/engine-primitives/attributes.go +++ b/mod/engine-primitives/pkg/engine-primitives/attributes.go @@ -63,10 +63,8 @@ type PayloadAttributes[ ParentBeaconBlockRoot common.Root `json:"parentBeaconBlockRoot"` } -// NewPayloadAttributes creates a new PayloadAttributes. -func NewPayloadAttributes[ - WithdrawalT any, -]( +// New empty PayloadAttributes. +func (p *PayloadAttributes[WithdrawalT]) New( forkVersion uint32, timestamp uint64, prevRandao common.Bytes32, @@ -74,7 +72,7 @@ func NewPayloadAttributes[ withdrawals []WithdrawalT, parentBeaconBlockRoot common.Root, ) (*PayloadAttributes[WithdrawalT], error) { - p := &PayloadAttributes[WithdrawalT]{ + p = &PayloadAttributes[WithdrawalT]{ version: forkVersion, Timestamp: math.U64(timestamp), PrevRandao: prevRandao, @@ -90,27 +88,6 @@ func NewPayloadAttributes[ return p, nil } -// New empty PayloadAttributes. -func (p *PayloadAttributes[WithdrawalT]) New( - forkVersion uint32, - timestamp uint64, - prevRandao common.Bytes32, - suggestedFeeRecipient common.ExecutionAddress, - withdrawals []WithdrawalT, - parentBeaconBlockRoot common.Root, -) (*PayloadAttributes[WithdrawalT], error) { - var err error - p, err = NewPayloadAttributes( - forkVersion, - timestamp, - prevRandao, - suggestedFeeRecipient, - withdrawals, - parentBeaconBlockRoot, - ) - return p, err -} - // IsNil returns true if the PayloadAttributes is nil. func (p *PayloadAttributes[WithdrawalT]) IsNil() bool { return p == nil diff --git a/mod/engine-primitives/pkg/engine-primitives/attributes_test.go b/mod/engine-primitives/pkg/engine-primitives/attributes_test.go index a41a303eab..c5bef2b981 100644 --- a/mod/engine-primitives/pkg/engine-primitives/attributes_test.go +++ b/mod/engine-primitives/pkg/engine-primitives/attributes_test.go @@ -29,76 +29,97 @@ import ( "github.com/stretchr/testify/require" ) -type Withdrawal struct{} +type testWithdrawal struct{} -func TestPayloadAttributes(t *testing.T) { - forkVersion := uint32(1) - timestamp := uint64(123456789) - prevRandao := common.Bytes32{1, 2, 3} - suggestedFeeRecipient := common.ExecutionAddress{} - withdrawals := []Withdrawal{} - parentBeaconBlockRoot := common.Root{} - - payloadAttributes, err := engineprimitives.NewPayloadAttributes[Withdrawal]( - forkVersion, - timestamp, - prevRandao, - suggestedFeeRecipient, - withdrawals, - parentBeaconBlockRoot, - ) - require.NoError(t, err) - require.NotNil(t, payloadAttributes) - - require.False(t, payloadAttributes.IsNil()) - - require.Equal( - t, - suggestedFeeRecipient, - payloadAttributes.GetSuggestedFeeRecipient(), - ) - require.Equal(t, forkVersion, payloadAttributes.Version()) - - require.NoError(t, payloadAttributes.Validate()) +type payloadAttributesInput struct { + forkVersion uint32 + timestamp uint64 + prevRandao common.Bytes32 + suggestedFeeRecipient common.ExecutionAddress + withdrawals []testWithdrawal + parentBeaconBlockRoot common.Root } -func TestNewPayloadAttributes_ErrorCases(t *testing.T) { - forkVersion := uint32(1) - prevRandao := common.Bytes32{1, 2, 3} - suggestedFeeRecipient := common.ExecutionAddress{} - withdrawals := []Withdrawal{} - parentBeaconBlockRoot := common.Root{} +func TestPayloadAttributes(t *testing.T) { + // default valid data + validInput := payloadAttributesInput{ + forkVersion: uint32(1), + timestamp: uint64(123456789), + prevRandao: common.Bytes32{1, 2, 3}, + suggestedFeeRecipient: common.ExecutionAddress{}, + withdrawals: []testWithdrawal{}, + parentBeaconBlockRoot: common.Root{}, + } + tests := []struct { + name string + input func() payloadAttributesInput + wantErr error + }{ + { + name: "Valid payload attributes", + input: func() payloadAttributesInput { + return validInput + }, + wantErr: nil, + }, + { + name: "Invalid timestamp", + input: func() payloadAttributesInput { + res := validInput + res.timestamp = 0 + return res + }, + wantErr: engineprimitives.ErrInvalidTimestamp, + }, + { + name: "Invalid PreRandao", + input: func() payloadAttributesInput { + res := validInput + res.prevRandao = common.Bytes32{} + return res + }, + wantErr: engineprimitives.ErrEmptyPrevRandao, + }, + { + name: "Invalid nil withdrawals on Capella", + input: func() payloadAttributesInput { + res := validInput + res.forkVersion = version.Capella + res.withdrawals = nil + return res + }, + wantErr: engineprimitives.ErrNilWithdrawals, + }, + } - // Test case where Timestamp is 0 - _, err := engineprimitives.NewPayloadAttributes[Withdrawal]( - forkVersion, - 0, - prevRandao, - suggestedFeeRecipient, - withdrawals, - parentBeaconBlockRoot, - ) - require.ErrorIs(t, err, engineprimitives.ErrInvalidTimestamp) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + in := tt.input() + p := &engineprimitives.PayloadAttributes[testWithdrawal]{} + got, err := p.New( + in.forkVersion, + in.timestamp, + in.prevRandao, + in.suggestedFeeRecipient, + in.withdrawals, + in.parentBeaconBlockRoot, + ) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.NotNil(t, got) - // Test case where PrevRandao is an empty array - _, err = engineprimitives.NewPayloadAttributes[Withdrawal]( - forkVersion, - 123456789, - common.Bytes32{}, - suggestedFeeRecipient, - withdrawals, - parentBeaconBlockRoot, - ) - require.ErrorIs(t, err, engineprimitives.ErrEmptyPrevRandao) + require.False(t, got.IsNil()) + require.NoError(t, got.Validate()) - // Test case where Withdrawals is nil and version is equal to Capella - _, err = engineprimitives.NewPayloadAttributes[Withdrawal]( - version.Capella, - 123456789, - prevRandao, - suggestedFeeRecipient, - nil, - parentBeaconBlockRoot, - ) - require.ErrorIs(t, err, engineprimitives.ErrNilWithdrawals) + require.Equal( + t, + in.suggestedFeeRecipient, + got.GetSuggestedFeeRecipient(), + ) + require.Equal(t, in.forkVersion, got.Version()) + } + }) + } } diff --git a/mod/engine-primitives/pkg/engine-primitives/mocks/blobs_bundle.mock.go b/mod/engine-primitives/pkg/engine-primitives/mocks/blobs_bundle.mock.go index af1804a20a..94ad0fad6e 100644 --- a/mod/engine-primitives/pkg/engine-primitives/mocks/blobs_bundle.mock.go +++ b/mod/engine-primitives/pkg/engine-primitives/mocks/blobs_bundle.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/engine-primitives/pkg/engine-primitives/mocks/built_execution_payload_env.mock.go b/mod/engine-primitives/pkg/engine-primitives/mocks/built_execution_payload_env.mock.go index 4e89e3dea7..0ec103484e 100644 --- a/mod/engine-primitives/pkg/engine-primitives/mocks/built_execution_payload_env.mock.go +++ b/mod/engine-primitives/pkg/engine-primitives/mocks/built_execution_payload_env.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/engine-primitives/pkg/engine-primitives/mocks/payload_attributer.mock.go b/mod/engine-primitives/pkg/engine-primitives/mocks/payload_attributer.mock.go index 9b7e3685d4..40b04768d9 100644 --- a/mod/engine-primitives/pkg/engine-primitives/mocks/payload_attributer.mock.go +++ b/mod/engine-primitives/pkg/engine-primitives/mocks/payload_attributer.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/execution/pkg/client/client.go b/mod/execution/pkg/client/client.go index e14d8cc559..0f2f6e690f 100644 --- a/mod/execution/pkg/client/client.go +++ b/mod/execution/pkg/client/client.go @@ -98,7 +98,7 @@ func (s *EngineClient[ ]) Start( ctx context.Context, ) error { - // Start the Clien. + // Start the Client. go s.Client.Start(ctx) s.logger.Info( diff --git a/mod/execution/pkg/client/engine.go b/mod/execution/pkg/client/engine.go index aad0e3efe9..0d2e02ee51 100644 --- a/mod/execution/pkg/client/engine.go +++ b/mod/execution/pkg/client/engine.go @@ -29,7 +29,6 @@ import ( "github.com/berachain/beacon-kit/mod/errors" ethclient "github.com/berachain/beacon-kit/mod/execution/pkg/client/ethclient" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" - "github.com/berachain/beacon-kit/mod/primitives/pkg/version" ) /* -------------------------------------------------------------------------- */ @@ -61,7 +60,8 @@ func (s *EngineClient[ s.metrics.incrementNewPayloadTimeout() } return nil, s.handleRPCError(err) - } else if result == nil { + } + if result == nil { return nil, engineerrors.ErrNilPayloadStatus } @@ -116,11 +116,12 @@ func (s *EngineClient[ s.metrics.incrementForkchoiceUpdateTimeout() } return nil, nil, s.handleRPCError(err) - } else if result == nil { + } + if result == nil { return nil, nil, engineerrors.ErrNilForkchoiceResponse } - latestValidHash, err := processPayloadStatusResult((&result.PayloadStatus)) + latestValidHash, err := processPayloadStatusResult(&result.PayloadStatus) if err != nil { return nil, latestValidHash, err } @@ -149,16 +150,16 @@ func (s *EngineClient[ // Call and check for errors. result, err := s.Client.GetPayload(cctx, payloadID, forkVersion) - switch { - case err != nil: + if err != nil { if errors.Is(err, engineerrors.ErrEngineAPITimeout) { s.metrics.incrementGetPayloadTimeout() } return result, s.handleRPCError(err) - case result == nil: + } + if result == nil { return result, engineerrors.ErrNilExecutionPayloadEnvelope - case result.GetBlobsBundle() == nil && - ((forkVersion >= version.Deneb) || (forkVersion >= version.DenebPlus)): + } + if result.GetBlobsBundle() == nil { return result, engineerrors.ErrNilBlobsBundle } diff --git a/mod/execution/pkg/client/ethclient/engine.go b/mod/execution/pkg/client/ethclient/engine.go index 7fe54f115a..3db74e302f 100644 --- a/mod/execution/pkg/client/ethclient/engine.go +++ b/mod/execution/pkg/client/ethclient/engine.go @@ -40,14 +40,13 @@ func (s *Client[ExecutionPayloadT]) NewPayload( versionedHashes []common.ExecutionHash, parentBlockRoot *common.Root, ) (*engineprimitives.PayloadStatusV1, error) { - switch payload.Version() { - case version.Deneb, version.DenebPlus: - return s.NewPayloadV3( - ctx, payload, versionedHashes, parentBlockRoot, - ) - default: + if payload.Version() < version.Deneb { return nil, ErrInvalidVersion } + + return s.NewPayloadV3( + ctx, payload, versionedHashes, parentBlockRoot, + ) } // NewPayloadV3 is used to call the underlying JSON-RPC method for newPayload. @@ -78,12 +77,11 @@ func (s *Client[ExecutionPayloadT]) ForkchoiceUpdated( attrs any, forkVersion uint32, ) (*engineprimitives.ForkchoiceResponseV1, error) { - switch forkVersion { - case version.Deneb, version.DenebPlus: - return s.ForkchoiceUpdatedV3(ctx, state, attrs) - default: + if forkVersion < version.Deneb { return nil, ErrInvalidVersion } + + return s.ForkchoiceUpdatedV3(ctx, state, attrs) } // ForkchoiceUpdatedV3 calls the engine_forkchoiceUpdatedV3 method via JSON-RPC. @@ -129,12 +127,11 @@ func (s *Client[ExecutionPayloadT]) GetPayload( payloadID engineprimitives.PayloadID, forkVersion uint32, ) (engineprimitives.BuiltExecutionPayloadEnv[ExecutionPayloadT], error) { - switch forkVersion { - case version.Deneb, version.DenebPlus: - return s.GetPayloadV3(ctx, payloadID) - default: + if forkVersion < version.Deneb { return nil, ErrInvalidVersion } + + return s.GetPayloadV3(ctx, payloadID) } // GetPayloadV3 calls the engine_getPayloadV3 method via JSON-RPC. diff --git a/mod/execution/pkg/client/ethclient/rpc/client.go b/mod/execution/pkg/client/ethclient/rpc/client.go index bac4c479f3..7517e64f31 100644 --- a/mod/execution/pkg/client/ethclient/rpc/client.go +++ b/mod/execution/pkg/client/ethclient/rpc/client.go @@ -46,6 +46,10 @@ type Client struct { // jwtRefershInterval is the interval at which the JWT token should be // refreshed. jwtRefreshInterval time.Duration + + // mu protects header for concurrent access. + mu sync.RWMutex + // header is the HTTP header used for RPC requests. header http.Header } @@ -76,13 +80,14 @@ func NewClient(url string, options ...func(rpc *Client)) *Client { // Start starts the rpc client. func (rpc *Client) Start(ctx context.Context) { ticker := time.NewTicker(rpc.jwtRefreshInterval) + defer ticker.Stop() + if err := rpc.updateHeader(); err != nil { panic(err) } for { select { case <-ctx.Done(): - ticker.Stop() return case <-ticker.C: if err := rpc.updateHeader(); err != nil { @@ -143,7 +148,10 @@ func (rpc *Client) CallRaw( if err != nil { return nil, err } - req.Header = rpc.header + + rpc.mu.RLock() + req.Header = rpc.header.Clone() + rpc.mu.RUnlock() response, err := rpc.client.Do(req) if err != nil { diff --git a/mod/execution/pkg/client/ethclient/rpc/header.go b/mod/execution/pkg/client/ethclient/rpc/header.go index f12b566bf2..4b649c8857 100644 --- a/mod/execution/pkg/client/ethclient/rpc/header.go +++ b/mod/execution/pkg/client/ethclient/rpc/header.go @@ -29,7 +29,9 @@ func (rpc *Client) updateHeader() error { return err } - // Add the JWT token to the headers. + // Add the JWT token to the headers. Access header safely. + rpc.mu.Lock() + defer rpc.mu.Unlock() rpc.header.Set("Authorization", "Bearer "+token) return nil } diff --git a/mod/execution/pkg/deposit/contract.go b/mod/execution/pkg/deposit/contract.go index 1a9f30b4f5..11f56f5dbe 100644 --- a/mod/execution/pkg/deposit/contract.go +++ b/mod/execution/pkg/deposit/contract.go @@ -23,6 +23,7 @@ package deposit import ( "context" "errors" + "fmt" gethprimitives "github.com/berachain/beacon-kit/mod/geth-primitives" "github.com/berachain/beacon-kit/mod/geth-primitives/pkg/bind" @@ -91,13 +92,29 @@ func (dc *WrappedBeaconDepositContract[ deposits := make([]DepositT, 0) for logs.Next() { - var d DepositT + var ( + cred bytes.B32 + pubKey bytes.B48 + d DepositT + sign bytes.B96 + ) + pubKey, err = bytes.ToBytes48(logs.Event.Pubkey) + if err != nil { + return nil, fmt.Errorf("failed reading pub key: %w", err) + } + cred, err = bytes.ToBytes32(logs.Event.Credentials) + if err != nil { + return nil, fmt.Errorf("failed reading credentials: %w", err) + } + sign, err = bytes.ToBytes96(logs.Event.Signature) + if err != nil { + return nil, fmt.Errorf("failed reading signature: %w", err) + } deposits = append(deposits, d.New( - bytes.ToBytes48(logs.Event.Pubkey), - WithdrawalCredentialsT( - bytes.ToBytes32(logs.Event.Credentials)), + pubKey, + WithdrawalCredentialsT(cred), math.U64(logs.Event.Amount), - bytes.ToBytes96(logs.Event.Signature), + sign, logs.Event.Index, )) } diff --git a/mod/execution/pkg/deposit/service.go b/mod/execution/pkg/deposit/service.go index fc4732c93e..59b15d0b79 100644 --- a/mod/execution/pkg/deposit/service.go +++ b/mod/execution/pkg/deposit/service.go @@ -22,6 +22,9 @@ package deposit import ( "context" + "maps" + "slices" + "sync" asynctypes "github.com/berachain/beacon-kit/mod/async/pkg/types" "github.com/berachain/beacon-kit/mod/log" @@ -52,8 +55,10 @@ type Service[ subFinalizedBlockEvents chan async.Event[BeaconBlockT] // metrics is the metrics for the deposit service. metrics *metrics - // failedBlocks is a map of blocks that failed to be processed to be - // retried. + // mu protects failedBlocks for concurrent access. + mu sync.RWMutex + // failedBlocks is a map of blocks that failed to be processed + // and should be retried. failedBlocks map[math.U64]struct{} } @@ -132,3 +137,27 @@ func (s *Service[ ]) Name() string { return "deposit-handler" } + +func (s *Service[ + _, _, _, _, _, +]) markFailedBlock(blockNum math.U64) { + s.mu.Lock() + defer s.mu.Unlock() + s.failedBlocks[blockNum] = struct{}{} +} + +func (s *Service[ + _, _, _, _, _, +]) clearFailedBlock(blockNum math.U64) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.failedBlocks, blockNum) +} + +func (s *Service[ + _, _, _, _, _, +]) getFailedBlocks() []math.U64 { + s.mu.RLock() + defer s.mu.RUnlock() + return slices.Collect(maps.Keys(s.failedBlocks)) +} diff --git a/mod/execution/pkg/deposit/sync.go b/mod/execution/pkg/deposit/sync.go index f87281016f..cedc596362 100644 --- a/mod/execution/pkg/deposit/sync.go +++ b/mod/execution/pkg/deposit/sync.go @@ -52,17 +52,18 @@ func (s *Service[ case <-ctx.Done(): return case <-ticker.C: - if len(s.failedBlocks) == 0 { + failedBlks := s.getFailedBlocks() + if len(failedBlks) == 0 { continue } s.logger.Warn( "Failed to get deposits from block(s), retrying...", "num_blocks", - s.failedBlocks, + failedBlks, ) // Fetch deposits for blocks that failed to be processed. - for blockNum := range s.failedBlocks { + for _, blockNum := range failedBlks { s.fetchAndStoreDeposits(ctx, blockNum) } } @@ -74,8 +75,9 @@ func (s *Service[ ]) fetchAndStoreDeposits(ctx context.Context, blockNum math.U64) { deposits, err := s.dc.ReadDeposits(ctx, blockNum) if err != nil { + s.logger.Error("Failed to read deposits", "error", err) s.metrics.markFailedToGetBlockLogs(blockNum) - s.failedBlocks[blockNum] = struct{}{} + s.markFailedBlock(blockNum) return } @@ -88,9 +90,9 @@ func (s *Service[ if err = s.ds.EnqueueDeposits(deposits); err != nil { s.logger.Error("Failed to store deposits", "error", err) - s.failedBlocks[blockNum] = struct{}{} + s.markFailedBlock(blockNum) return } - delete(s.failedBlocks, blockNum) + s.clearFailedBlock(blockNum) } diff --git a/mod/node-api/backend/backend.go b/mod/node-api/backend/backend.go index edefe8dea6..3779cda072 100644 --- a/mod/node-api/backend/backend.go +++ b/mod/node-api/backend/backend.go @@ -150,12 +150,12 @@ func (b *Backend[ return b.sb.BlockStore().GetSlotByStateRoot(root) } -// GetSlotByExecutionNumber retrieves the slot by a given execution number from +// GetParentSlotByTimestamp retrieves the parent slot by a given timestamp from // the block store. func (b *Backend[ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, -]) GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) { - return b.sb.BlockStore().GetSlotByExecutionNumber(executionNumber) +]) GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) { + return b.sb.BlockStore().GetParentSlotByTimestamp(timestamp) } // stateFromSlot returns the state at the given slot, after also processing the diff --git a/mod/node-api/backend/mocks/availability_store.mock.go b/mod/node-api/backend/mocks/availability_store.mock.go index 08d384065d..17d2d80e87 100644 --- a/mod/node-api/backend/mocks/availability_store.mock.go +++ b/mod/node-api/backend/mocks/availability_store.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/beacon_block_header.mock.go b/mod/node-api/backend/mocks/beacon_block_header.mock.go index afec4e3be8..c9d0cd0b6d 100644 --- a/mod/node-api/backend/mocks/beacon_block_header.mock.go +++ b/mod/node-api/backend/mocks/beacon_block_header.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/beacon_state.mock.go b/mod/node-api/backend/mocks/beacon_state.mock.go index fdd75d78f8..184d2d7024 100644 --- a/mod/node-api/backend/mocks/beacon_state.mock.go +++ b/mod/node-api/backend/mocks/beacon_state.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/block_store.mock.go b/mod/node-api/backend/mocks/block_store.mock.go index e19547d3e9..4e532a619f 100644 --- a/mod/node-api/backend/mocks/block_store.mock.go +++ b/mod/node-api/backend/mocks/block_store.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks @@ -22,27 +22,27 @@ func (_m *BlockStore[BeaconBlockT]) EXPECT() *BlockStore_Expecter[BeaconBlockT] return &BlockStore_Expecter[BeaconBlockT]{mock: &_m.Mock} } -// GetSlotByBlockRoot provides a mock function with given fields: root -func (_m *BlockStore[BeaconBlockT]) GetSlotByBlockRoot(root common.Root) (math.U64, error) { - ret := _m.Called(root) +// GetParentSlotByTimestamp provides a mock function with given fields: timestamp +func (_m *BlockStore[BeaconBlockT]) GetParentSlotByTimestamp(timestamp math.U64) (math.U64, error) { + ret := _m.Called(timestamp) if len(ret) == 0 { - panic("no return value specified for GetSlotByBlockRoot") + panic("no return value specified for GetParentSlotByTimestamp") } var r0 math.U64 var r1 error - if rf, ok := ret.Get(0).(func(common.Root) (math.U64, error)); ok { - return rf(root) + if rf, ok := ret.Get(0).(func(math.U64) (math.U64, error)); ok { + return rf(timestamp) } - if rf, ok := ret.Get(0).(func(common.Root) math.U64); ok { - r0 = rf(root) + if rf, ok := ret.Get(0).(func(math.U64) math.U64); ok { + r0 = rf(timestamp) } else { r0 = ret.Get(0).(math.U64) } - if rf, ok := ret.Get(1).(func(common.Root) error); ok { - r1 = rf(root) + if rf, ok := ret.Get(1).(func(math.U64) error); ok { + r1 = rf(timestamp) } else { r1 = ret.Error(1) } @@ -50,55 +50,55 @@ func (_m *BlockStore[BeaconBlockT]) GetSlotByBlockRoot(root common.Root) (math.U return r0, r1 } -// BlockStore_GetSlotByBlockRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSlotByBlockRoot' -type BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT any] struct { +// BlockStore_GetParentSlotByTimestamp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetParentSlotByTimestamp' +type BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT any] struct { *mock.Call } -// GetSlotByBlockRoot is a helper method to define mock.On call -// - root common.Root -func (_e *BlockStore_Expecter[BeaconBlockT]) GetSlotByBlockRoot(root interface{}) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { - return &BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]{Call: _e.mock.On("GetSlotByBlockRoot", root)} +// GetParentSlotByTimestamp is a helper method to define mock.On call +// - timestamp math.U64 +func (_e *BlockStore_Expecter[BeaconBlockT]) GetParentSlotByTimestamp(timestamp interface{}) *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT] { + return &BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT]{Call: _e.mock.On("GetParentSlotByTimestamp", timestamp)} } -func (_c *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]) Run(run func(root common.Root)) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { +func (_c *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT]) Run(run func(timestamp math.U64)) *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(common.Root)) + run(args[0].(math.U64)) }) return _c } -func (_c *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]) Return(_a0 math.U64, _a1 error) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { +func (_c *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT]) Return(_a0 math.U64, _a1 error) *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT] { _c.Call.Return(_a0, _a1) return _c } -func (_c *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]) RunAndReturn(run func(common.Root) (math.U64, error)) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { +func (_c *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT]) RunAndReturn(run func(math.U64) (math.U64, error)) *BlockStore_GetParentSlotByTimestamp_Call[BeaconBlockT] { _c.Call.Return(run) return _c } -// GetSlotByExecutionNumber provides a mock function with given fields: executionNumber -func (_m *BlockStore[BeaconBlockT]) GetSlotByExecutionNumber(executionNumber math.U64) (math.U64, error) { - ret := _m.Called(executionNumber) +// GetSlotByBlockRoot provides a mock function with given fields: root +func (_m *BlockStore[BeaconBlockT]) GetSlotByBlockRoot(root common.Root) (math.U64, error) { + ret := _m.Called(root) if len(ret) == 0 { - panic("no return value specified for GetSlotByExecutionNumber") + panic("no return value specified for GetSlotByBlockRoot") } var r0 math.U64 var r1 error - if rf, ok := ret.Get(0).(func(math.U64) (math.U64, error)); ok { - return rf(executionNumber) + if rf, ok := ret.Get(0).(func(common.Root) (math.U64, error)); ok { + return rf(root) } - if rf, ok := ret.Get(0).(func(math.U64) math.U64); ok { - r0 = rf(executionNumber) + if rf, ok := ret.Get(0).(func(common.Root) math.U64); ok { + r0 = rf(root) } else { r0 = ret.Get(0).(math.U64) } - if rf, ok := ret.Get(1).(func(math.U64) error); ok { - r1 = rf(executionNumber) + if rf, ok := ret.Get(1).(func(common.Root) error); ok { + r1 = rf(root) } else { r1 = ret.Error(1) } @@ -106,30 +106,30 @@ func (_m *BlockStore[BeaconBlockT]) GetSlotByExecutionNumber(executionNumber mat return r0, r1 } -// BlockStore_GetSlotByExecutionNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSlotByExecutionNumber' -type BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT any] struct { +// BlockStore_GetSlotByBlockRoot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSlotByBlockRoot' +type BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT any] struct { *mock.Call } -// GetSlotByExecutionNumber is a helper method to define mock.On call -// - executionNumber math.U64 -func (_e *BlockStore_Expecter[BeaconBlockT]) GetSlotByExecutionNumber(executionNumber interface{}) *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT] { - return &BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT]{Call: _e.mock.On("GetSlotByExecutionNumber", executionNumber)} +// GetSlotByBlockRoot is a helper method to define mock.On call +// - root common.Root +func (_e *BlockStore_Expecter[BeaconBlockT]) GetSlotByBlockRoot(root interface{}) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { + return &BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]{Call: _e.mock.On("GetSlotByBlockRoot", root)} } -func (_c *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT]) Run(run func(executionNumber math.U64)) *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT] { +func (_c *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]) Run(run func(root common.Root)) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(math.U64)) + run(args[0].(common.Root)) }) return _c } -func (_c *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT]) Return(_a0 math.U64, _a1 error) *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT] { +func (_c *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]) Return(_a0 math.U64, _a1 error) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { _c.Call.Return(_a0, _a1) return _c } -func (_c *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT]) RunAndReturn(run func(math.U64) (math.U64, error)) *BlockStore_GetSlotByExecutionNumber_Call[BeaconBlockT] { +func (_c *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT]) RunAndReturn(run func(common.Root) (math.U64, error)) *BlockStore_GetSlotByBlockRoot_Call[BeaconBlockT] { _c.Call.Return(run) return _c } diff --git a/mod/node-api/backend/mocks/deposit_store.mock.go b/mod/node-api/backend/mocks/deposit_store.mock.go index 71c1c1e4a2..6988e28a7e 100644 --- a/mod/node-api/backend/mocks/deposit_store.mock.go +++ b/mod/node-api/backend/mocks/deposit_store.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/node.mock.go b/mod/node-api/backend/mocks/node.mock.go index 248413332d..52202ef37b 100644 --- a/mod/node-api/backend/mocks/node.mock.go +++ b/mod/node-api/backend/mocks/node.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/state_processor.mock.go b/mod/node-api/backend/mocks/state_processor.mock.go index bb85657785..a7103536ed 100644 --- a/mod/node-api/backend/mocks/state_processor.mock.go +++ b/mod/node-api/backend/mocks/state_processor.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/storage_backend.mock.go b/mod/node-api/backend/mocks/storage_backend.mock.go index 1f7ea3673b..88276f0e33 100644 --- a/mod/node-api/backend/mocks/storage_backend.mock.go +++ b/mod/node-api/backend/mocks/storage_backend.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/validator.mock.go b/mod/node-api/backend/mocks/validator.mock.go index 176906895a..6649c07394 100644 --- a/mod/node-api/backend/mocks/validator.mock.go +++ b/mod/node-api/backend/mocks/validator.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/withdrawal.mock.go b/mod/node-api/backend/mocks/withdrawal.mock.go index c6468d3c14..d7dbcb6747 100644 --- a/mod/node-api/backend/mocks/withdrawal.mock.go +++ b/mod/node-api/backend/mocks/withdrawal.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/mocks/withdrawal_credentials.mock.go b/mod/node-api/backend/mocks/withdrawal_credentials.mock.go index 13524fe9df..174e215add 100644 --- a/mod/node-api/backend/mocks/withdrawal_credentials.mock.go +++ b/mod/node-api/backend/mocks/withdrawal_credentials.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-api/backend/types.go b/mod/node-api/backend/types.go index 55ab1fe885..db3aa3972b 100644 --- a/mod/node-api/backend/types.go +++ b/mod/node-api/backend/types.go @@ -83,8 +83,8 @@ type BlockStore[BeaconBlockT any] interface { GetSlotByBlockRoot(root common.Root) (math.Slot, error) // GetSlotByStateRoot retrieves the slot by a given state root. GetSlotByStateRoot(root common.Root) (math.Slot, error) - // GetSlotByExecutionNumber retrieves the slot by a given execution number. - GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) + // GetParentSlotByTimestamp retrieves the parent slot by a given timestamp. + GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) } // DepositStore defines the interface for deposit storage. diff --git a/mod/node-api/engines/echo/vaildator.go b/mod/node-api/engines/echo/vaildator.go index 7e733772fd..3ddf58ae50 100644 --- a/mod/node-api/engines/echo/vaildator.go +++ b/mod/node-api/engines/echo/vaildator.go @@ -62,7 +62,7 @@ func ConstructValidator() *validator.Validate { validators := map[string](func(fl validator.FieldLevel) bool){ "state_id": ValidateStateID, "block_id": ValidateBlockID, - "execution_id": ValidateExecutionID, + "timestamp_id": ValidateTimestampID, "validator_id": ValidateValidatorID, "epoch": ValidateUint64, "slot": ValidateUint64, @@ -97,7 +97,7 @@ func ValidateBlockID(fl validator.FieldLevel) bool { return validateStateBlockIDs(fl.Field().String(), allowedValues) } -func ValidateExecutionID(fl validator.FieldLevel) bool { +func ValidateTimestampID(fl validator.FieldLevel) bool { allowedValues := map[string]bool{ utils.StateIDHead: true, utils.StateIDGenesis: true, @@ -106,7 +106,7 @@ func ValidateExecutionID(fl validator.FieldLevel) bool { } value := fl.Field().String() - if utils.IsExecutionNumberPrefix(value) { + if utils.IsTimestampIDPrefix(value) { return ValidateUint64Dec(value[1:]) } diff --git a/mod/node-api/handlers/proof/backend.go b/mod/node-api/handlers/proof/backend.go index 376c039206..7aaa90ecb3 100644 --- a/mod/node-api/handlers/proof/backend.go +++ b/mod/node-api/handlers/proof/backend.go @@ -28,7 +28,7 @@ import ( type Backend[BeaconBlockHeaderT, BeaconStateT, ValidatorT any] interface { BlockBackend[BeaconBlockHeaderT] StateBackend[BeaconStateT] - GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) + GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) } type BlockBackend[BeaconBlockHeaderT any] interface { diff --git a/mod/node-api/handlers/proof/block_proposer.go b/mod/node-api/handlers/proof/block_proposer.go index 234f521091..a252ff9308 100644 --- a/mod/node-api/handlers/proof/block_proposer.go +++ b/mod/node-api/handlers/proof/block_proposer.go @@ -26,8 +26,9 @@ import ( "github.com/berachain/beacon-kit/mod/node-api/handlers/utils" ) -// GetBlockProposer returns the block proposer pubkey for the given block id -// along with a merkle proof that can be verified against the beacon block root. +// GetBlockProposer returns the block proposer pubkey for the given timestamp +// id along with a merkle proof that can be verified against the beacon block +// root. func (h *Handler[ BeaconBlockHeaderT, _, _, ContextT, _, _, ]) GetBlockProposer(c ContextT) (any, error) { @@ -37,8 +38,8 @@ func (h *Handler[ if err != nil { return nil, err } - slot, beaconState, blockHeader, err := h.resolveExecutionID( - params.ExecutionID, + slot, beaconState, blockHeader, err := h.resolveTimestampID( + params.TimestampID, ) if err != nil { return nil, err diff --git a/mod/node-api/handlers/proof/execution_fee_recipient.go b/mod/node-api/handlers/proof/execution_fee_recipient.go index 0617207bb6..1513d3a047 100644 --- a/mod/node-api/handlers/proof/execution_fee_recipient.go +++ b/mod/node-api/handlers/proof/execution_fee_recipient.go @@ -27,7 +27,7 @@ import ( ) // GetExecutionFeeRecipient returns the fee recipient from the latest execution -// payload header for the given block id, along with the proof that can be +// payload header for the given timestamp id, along with the proof that can be // verified against the beacon block root. func (h *Handler[ BeaconBlockHeaderT, _, _, ContextT, _, _, @@ -38,8 +38,8 @@ func (h *Handler[ if err != nil { return nil, err } - slot, beaconState, blockHeader, err := h.resolveExecutionID( - params.ExecutionID, + slot, beaconState, blockHeader, err := h.resolveTimestampID( + params.TimestampID, ) if err != nil { return nil, err diff --git a/mod/node-api/handlers/proof/execution_number.go b/mod/node-api/handlers/proof/execution_number.go index 1fe9e4a8f9..4c128de2cf 100644 --- a/mod/node-api/handlers/proof/execution_number.go +++ b/mod/node-api/handlers/proof/execution_number.go @@ -27,7 +27,7 @@ import ( ) // GetExecutionNumber returns the block number from the latest execution -// payload header for the given block id, along with the proof that can be +// payload header for the given timestamp id, along with the proof that can be // verified against the beacon block root. func (h *Handler[ BeaconBlockHeaderT, _, _, ContextT, _, _, @@ -38,8 +38,8 @@ func (h *Handler[ if err != nil { return nil, err } - slot, beaconState, blockHeader, err := h.resolveExecutionID( - params.ExecutionID, + slot, beaconState, blockHeader, err := h.resolveTimestampID( + params.TimestampID, ) if err != nil { return nil, err diff --git a/mod/node-api/handlers/proof/handler.go b/mod/node-api/handlers/proof/handler.go index c70eba6e52..d96dfc8f0f 100644 --- a/mod/node-api/handlers/proof/handler.go +++ b/mod/node-api/handlers/proof/handler.go @@ -71,11 +71,11 @@ func NewHandler[ return h } -// Get the slot from the given input of execution id, beacon state, and beacon +// Get the slot from the given input of timestamp id, beacon state, and beacon // block header for the resolved slot. func (h *Handler[ BeaconBlockHeaderT, BeaconStateT, _, _, _, _, -]) resolveExecutionID(executionID string) ( +]) resolveTimestampID(timestampID string) ( math.Slot, BeaconStateT, BeaconBlockHeaderT, error, ) { var ( @@ -83,7 +83,7 @@ func (h *Handler[ blockHeader BeaconBlockHeaderT ) - slot, err := utils.SlotFromExecutionID(executionID, h.backend) + slot, err := utils.ParentSlotFromTimestampID(timestampID, h.backend) if err != nil { return 0, beaconState, blockHeader, err } diff --git a/mod/node-api/handlers/proof/routes.go b/mod/node-api/handlers/proof/routes.go index d82927e14c..3b8aa838c5 100644 --- a/mod/node-api/handlers/proof/routes.go +++ b/mod/node-api/handlers/proof/routes.go @@ -34,17 +34,17 @@ func ( h.BaseHandler.AddRoutes([]*handlers.Route[ContextT]{ { Method: http.MethodGet, - Path: "bkit/v1/proof/block_proposer/:execution_id", + Path: "bkit/v1/proof/block_proposer/:timestamp_id", Handler: h.GetBlockProposer, }, { Method: http.MethodGet, - Path: "bkit/v1/proof/execution_number/:execution_id", + Path: "bkit/v1/proof/execution_number/:timestamp_id", Handler: h.GetExecutionNumber, }, { Method: http.MethodGet, - Path: "bkit/v1/proof/execution_fee_recipient/:execution_id", + Path: "bkit/v1/proof/execution_fee_recipient/:timestamp_id", Handler: h.GetExecutionFeeRecipient, }, }) diff --git a/mod/node-api/handlers/proof/types/request.go b/mod/node-api/handlers/proof/types/request.go index 9d782025a2..649543c541 100644 --- a/mod/node-api/handlers/proof/types/request.go +++ b/mod/node-api/handlers/proof/types/request.go @@ -23,19 +23,19 @@ package types import "github.com/berachain/beacon-kit/mod/node-api/handlers/types" // BlockProposerRequest is the request for the -// `/proof/block_proposer/{execution_id}` endpoint. +// `/proof/block_proposer/{timestamp_id}` endpoint. type BlockProposerRequest struct { - types.ExecutionIDRequest + types.TimestampIDRequest } // ExecutionNumberRequest is the request for the -// `/proof/execution_number/{execution_id}` endpoint. +// `/proof/execution_number/{timestamp_id}` endpoint. type ExecutionNumberRequest struct { - types.ExecutionIDRequest + types.TimestampIDRequest } // ExecutionFeeRecipientRequest is the request for the -// `/proof/execution_fee_recipient/{execution_id}` endpoint. +// `/proof/execution_fee_recipient/{timestamp_id}` endpoint. type ExecutionFeeRecipientRequest struct { - types.ExecutionIDRequest + types.TimestampIDRequest } diff --git a/mod/node-api/handlers/proof/types/response.go b/mod/node-api/handlers/proof/types/response.go index a668823a84..cb73353c6d 100644 --- a/mod/node-api/handlers/proof/types/response.go +++ b/mod/node-api/handlers/proof/types/response.go @@ -27,7 +27,7 @@ import ( ) // BlockProposerResponse is the response for the -// `/proof/block_proposer/{execution_id}` endpoint. +// `/proof/block_proposer/{timestamp_id}` endpoint. type BlockProposerResponse[BeaconBlockHeaderT any] struct { // BeaconBlockHeader is the block header of which the hash tree root is the // beacon block root to verify against. @@ -47,7 +47,7 @@ type BlockProposerResponse[BeaconBlockHeaderT any] struct { } // ExecutionNumberResponse is the response for the -// `/proof/execution_number/{execution_id}` endpoint. +// `/proof/execution_number/{timestamp_id}` endpoint. type ExecutionNumberResponse[BeaconBlockHeaderT any] struct { // BeaconBlockHeader is the block header of which the hash tree root is the // beacon block root to verify against. @@ -65,7 +65,7 @@ type ExecutionNumberResponse[BeaconBlockHeaderT any] struct { } // ExecutionFeeRecipientResponse is the response for the -// `/proof/execution_fee_recipient/{execution_id}` endpoint. +// `/proof/execution_fee_recipient/{timestamp_id}` endpoint. type ExecutionFeeRecipientResponse[BeaconBlockHeaderT any] struct { // BeaconBlockHeader is the block header of which the hash tree root is the // beacon block root to verify against. diff --git a/mod/node-api/handlers/types/request.go b/mod/node-api/handlers/types/request.go index 81df7c22a7..7d2c81aa7f 100644 --- a/mod/node-api/handlers/types/request.go +++ b/mod/node-api/handlers/types/request.go @@ -28,6 +28,6 @@ type BlockIDRequest struct { BlockID string `param:"block_id" validate:"required,block_id"` } -type ExecutionIDRequest struct { - ExecutionID string `param:"execution_id" validate:"required,execution_id"` +type TimestampIDRequest struct { + TimestampID string `param:"timestamp_id" validate:"required,timestamp_id"` } diff --git a/mod/node-api/handlers/utils/constants.go b/mod/node-api/handlers/utils/constants.go index 6f9025223e..63b5a91e6b 100644 --- a/mod/node-api/handlers/utils/constants.go +++ b/mod/node-api/handlers/utils/constants.go @@ -27,7 +27,7 @@ const ( StateIDFinalized = "finalized" StateIDJustified = "justified" StateIDHead = "head" - ExecutionIDPrefix = "n" + TimestampIDPrefix = "t" ) const ( diff --git a/mod/node-api/handlers/utils/id.go b/mod/node-api/handlers/utils/id.go index eced1532f9..a9ed206910 100644 --- a/mod/node-api/handlers/utils/id.go +++ b/mod/node-api/handlers/utils/id.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + "github.com/berachain/beacon-kit/mod/errors" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" ) @@ -70,36 +71,39 @@ func SlotFromBlockID[StorageBackendT interface { return storage.GetSlotByBlockRoot(root) } -// SlotFromExecutionID returns a slot from the execution number ID. +// ParentSlotFromTimestampID returns the parent slot corresponding to the +// timestamp ID. // -// NOTE: `executionID` shares the same semantics as `stateID`, with the -// modification of being able to query by beacon block -// instead of . +// NOTE: `timestampID` shares the same semantics as `stateID`, with the +// modification of being able to query by next block's instead of +// the current block's . // -// The must be prefixed by the 'n', followed by the execution -// number in decimal notation. For example 'n1722463215' corresponds to -// the slot with execution number 1722463215. Providing just the string -// '1722463215' (without the prefix 'n') will query for the beacon block with -// slot 1722463215. -func SlotFromExecutionID[StorageBackendT interface { - GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) -}](executionID string, storage StorageBackendT) (math.Slot, error) { - if !IsExecutionNumberPrefix(executionID) { - return slotFromStateID(executionID) +// The must be prefixed by the 't', followed by the timestamp +// in decimal UNIX notation. For example 't1728681738' corresponds to the slot +// which has the next block with a timestamp of 1728681738. Providing just the +// string '1728681738' (without the prefix 't') will query for the beacon block +// for slot 1728681738. +func ParentSlotFromTimestampID[StorageBackendT interface { + GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) +}](timestampID string, storage StorageBackendT) (math.Slot, error) { + if !IsTimestampIDPrefix(timestampID) { + return slotFromStateID(timestampID) } - // Parse the execution number from the executionID. - executionNumber, err := U64FromString(executionID[1:]) + // Parse the timestamp from the timestampID. + timestamp, err := U64FromString(timestampID[1:]) if err != nil { - return 0, err + return 0, errors.Wrapf( + err, "failed to parse timestamp from timestampID: %s", timestampID, + ) } - return storage.GetSlotByExecutionNumber(executionNumber) + return storage.GetParentSlotByTimestamp(timestamp) } -// IsExecutionNumberPrefix checks if the given executionID is prefixed -// with the execution number prefix. -func IsExecutionNumberPrefix(executionID string) bool { - return strings.HasPrefix(executionID, ExecutionIDPrefix) +// IsTimestampIDPrefix checks if the given timestampID is prefixed with the +// correct prefix 't'. +func IsTimestampIDPrefix(timestampID string) bool { + return strings.HasPrefix(timestampID, TimestampIDPrefix) } // U64FromString returns a math.U64 from the given string. Errors if the given diff --git a/mod/node-core/pkg/components/interfaces.go b/mod/node-core/pkg/components/interfaces.go index c7d608a258..73c30acbe4 100644 --- a/mod/node-core/pkg/components/interfaces.go +++ b/mod/node-core/pkg/components/interfaces.go @@ -113,7 +113,9 @@ type ( GetParentBlockRoot() common.Root // GetStateRoot returns the state root of the block. GetStateRoot() common.Root - GetExecutionNumber() math.U64 + // GetTimestamp returns the timestamp of the block from the execution + // payload. + GetTimestamp() math.U64 } // BeaconBlockBody represents a generic interface for the body of a beacon @@ -293,10 +295,9 @@ type ( GetSlotByBlockRoot(root common.Root) (math.Slot, error) // GetSlotByStateRoot retrieves the slot by a given root from the store. GetSlotByStateRoot(root common.Root) (math.Slot, error) - // GetSlotByExecutionNumber retrieves the slot by a given execution - // number - // from the store. - GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) + // GetParentSlotByTimestamp retrieves the parent slot by a given + // timestamp from the store. + GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) } ConsensusEngine interface { @@ -1091,7 +1092,7 @@ type ( ChainSpec() common.ChainSpec GetSlotByBlockRoot(root common.Root) (math.Slot, error) GetSlotByStateRoot(root common.Root) (math.Slot, error) - GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) + GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) NodeAPIBeaconBackend[ BeaconStateT, BeaconBlockHeaderT, ForkT, ValidatorT, WithdrawalCredentialsT, @@ -1125,7 +1126,7 @@ type ( ] interface { BlockBackend[BeaconBlockHeaderT] StateBackend[BeaconStateT, ForkT] - GetSlotByExecutionNumber(executionNumber math.U64) (math.Slot, error) + GetParentSlotByTimestamp(timestamp math.U64) (math.Slot, error) } GenesisBackend interface { diff --git a/mod/node-core/pkg/components/types.go b/mod/node-core/pkg/components/types.go index 5cd9c14aa7..2abea7ccac 100644 --- a/mod/node-core/pkg/components/types.go +++ b/mod/node-core/pkg/components/types.go @@ -29,7 +29,6 @@ import ( "github.com/berachain/beacon-kit/mod/node-core/pkg/components/signer" "github.com/berachain/beacon-kit/mod/node-core/pkg/services/version" "github.com/berachain/beacon-kit/mod/primitives/pkg/async" - "github.com/berachain/beacon-kit/mod/primitives/pkg/service" "github.com/berachain/beacon-kit/mod/primitives/pkg/transition" "github.com/berachain/beacon-kit/mod/storage/pkg/manager" ) @@ -125,15 +124,6 @@ type ( ValidatorUpdateEvent = async.Event[transition.ValidatorUpdates] ) -// Messages. -type ( - // SlotMessage is a type alias for the slot message. - SlotMessage = async.Event[*SlotData] - - // StatusMessage is a type alias for the status message. - StatusMessage = async.Event[*service.StatusEvent] -) - /* -------------------------------------------------------------------------- */ /* Dispatcher */ /* -------------------------------------------------------------------------- */ diff --git a/mod/node-core/pkg/services/registry/mocks/basic.mock.go b/mod/node-core/pkg/services/registry/mocks/basic.mock.go index dc9cb38ece..859234017a 100644 --- a/mod/node-core/pkg/services/registry/mocks/basic.mock.go +++ b/mod/node-core/pkg/services/registry/mocks/basic.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-core/pkg/services/registry/mocks/dispatcher.mock.go b/mod/node-core/pkg/services/registry/mocks/dispatcher.mock.go index 238233dcc0..7ed6605786 100644 --- a/mod/node-core/pkg/services/registry/mocks/dispatcher.mock.go +++ b/mod/node-core/pkg/services/registry/mocks/dispatcher.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-core/pkg/services/registry/mocks/registry_option.mock.go b/mod/node-core/pkg/services/registry/mocks/registry_option.mock.go index 58b47f4514..232e194ebf 100644 --- a/mod/node-core/pkg/services/registry/mocks/registry_option.mock.go +++ b/mod/node-core/pkg/services/registry/mocks/registry_option.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/node-core/pkg/services/version/version.go b/mod/node-core/pkg/services/version/version.go index 8e7d985781..b204bcc9e9 100644 --- a/mod/node-core/pkg/services/version/version.go +++ b/mod/node-core/pkg/services/version/version.go @@ -68,13 +68,13 @@ func (v *ReportingService) Start(ctx context.Context) error { ticker := time.NewTicker(v.reportingInterval) v.metrics.reportVersion(v.version) go func() { + defer ticker.Stop() for { select { case <-ticker.C: v.metrics.reportVersion(v.version) continue case <-ctx.Done(): - ticker.Stop() return } } diff --git a/mod/primitives/pkg/bytes/b20.go b/mod/primitives/pkg/bytes/b20.go index dacf6678b1..022181713a 100644 --- a/mod/primitives/pkg/bytes/b20.go +++ b/mod/primitives/pkg/bytes/b20.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" ) @@ -35,9 +37,17 @@ const ( type B20 [20]byte // ToBytes20 is a utility function that transforms a byte slice into a fixed -// 20-byte array. If the input exceeds 20 bytes, it gets truncated. -func ToBytes20(input []byte) B20 { - return B20(ExtendToSize(input, B20Size)) +// 20-byte array. It errs if input has not the required size. +func ToBytes20(input []byte) (B20, error) { + if len(input) != B20Size { + return B20{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B20Size, + ) + } + return B20(input), nil } /* -------------------------------------------------------------------------- */ @@ -78,6 +88,6 @@ func (h B20) MarshalSSZ() ([]byte, error) { } // HashTreeRoot returns the hash tree root of the B20. -func (h B20) HashTreeRoot() B32 { - return ToBytes32(h[:]) +func (h B20) HashTreeRoot() (B32, error) { + return ToBytes32(ExtendToSize(h[:], B32Size)) } diff --git a/mod/primitives/pkg/bytes/b20_test.go b/mod/primitives/pkg/bytes/b20_test.go index ef7aedfd77..bd6601145f 100644 --- a/mod/primitives/pkg/bytes/b20_test.go +++ b/mod/primitives/pkg/bytes/b20_test.go @@ -66,9 +66,8 @@ func TestBytes20MarshalText(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalText() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, string(got), - "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) }) } } @@ -95,8 +94,8 @@ func TestBytes20MarshalSSZ(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalSSZ() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } @@ -123,8 +122,9 @@ func TestBytes20HashTreeRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.input.HashTreeRoot() - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + got, err := tt.input.HashTreeRoot() + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } @@ -157,10 +157,10 @@ func TestBytes20UnmarshalText(t *testing.T) { var got bytes.B20 err := got.UnmarshalText([]byte(tt.input)) if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) + require.Error(t, err) } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) } }) } @@ -194,10 +194,10 @@ func TestBytes20UnmarshalJSON(t *testing.T) { var got bytes.B20 err := got.UnmarshalJSON([]byte(tt.input)) if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) + require.Error(t, err) } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) } }) } @@ -205,68 +205,49 @@ func TestBytes20UnmarshalJSON(t *testing.T) { func TestToBytes20(t *testing.T) { tests := []struct { - name string - input []byte - want bytes.B20 + name string + input []byte + wantRes bytes.B20 + wantErr error }{ { name: "exact 20 bytes", - input: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, - 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14}, - want: bytes.B20{ + input: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, }, - }, - { - name: "less than 20 bytes", - input: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, - want: bytes.B20{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + wantRes: bytes.B20{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, }, + wantErr: nil, }, { - name: "less than 20 bytes", - input: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, - want: bytes.B20{ - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - }, + name: "less than 20 bytes", + input: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + wantRes: bytes.B20{}, + wantErr: bytes.ErrIncorrectLength, }, { name: "more than 20 bytes", input: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, - 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}, - want: bytes.B20{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, - 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14}, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + }, + wantRes: bytes.B20{}, + wantErr: bytes.ErrIncorrectLength, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := bytes.ToBytes20(tt.input) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + result, err := bytes.ToBytes20(tt.input) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRes, result) + } }) } } diff --git a/mod/primitives/pkg/bytes/b256.go b/mod/primitives/pkg/bytes/b256.go index a3186fa79a..80daf08290 100644 --- a/mod/primitives/pkg/bytes/b256.go +++ b/mod/primitives/pkg/bytes/b256.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" "github.com/prysmaticlabs/gohashtree" ) @@ -36,9 +38,17 @@ const ( type B256 [256]byte // ToBytes256 is a utility function that transforms a byte slice into a fixed -// 256-byte array. If the input exceeds 256 bytes, it gets truncated. -func ToBytes256(input []byte) B256 { - return B256(ExtendToSize(input, B256Size)) +// 256-byte array. It errs if input has not the required size. +func ToBytes256(input []byte) (B256, error) { + if len(input) != B256Size { + return B256{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B256Size, + ) + } + return B256(input), nil } /* -------------------------------------------------------------------------- */ diff --git a/mod/primitives/pkg/bytes/b32.go b/mod/primitives/pkg/bytes/b32.go index 881a34d34e..701c9822de 100644 --- a/mod/primitives/pkg/bytes/b32.go +++ b/mod/primitives/pkg/bytes/b32.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" ) @@ -32,14 +34,20 @@ const ( // B32 represents a 32-byte fixed-size byte array. // For SSZ purposes it is serialized a `Vector[Byte, 32]`. -type B32 [32]byte +type B32 [B32Size]byte // ToBytes32 is a utility function that transforms a byte slice into a fixed -// 32-byte array. If the input exceeds 32 bytes, it gets truncated. -func ToBytes32(input []byte) B32 { - var b B32 - copy(b[:], input) - return b +// 32-byte array It errs if input has not the required size. +func ToBytes32(input []byte) (B32, error) { + if len(input) != B32Size { + return B32{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B32Size, + ) + } + return B32(input), nil } /* -------------------------------------------------------------------------- */ diff --git a/mod/primitives/pkg/bytes/b32_test.go b/mod/primitives/pkg/bytes/b32_test.go index 5409d08868..9844f1b9dd 100644 --- a/mod/primitives/pkg/bytes/b32_test.go +++ b/mod/primitives/pkg/bytes/b32_test.go @@ -18,6 +18,7 @@ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND // TITLE. +//nolint:lll // long strings. package bytes_test import ( @@ -27,6 +28,254 @@ import ( "github.com/stretchr/testify/require" ) +func TestBytes32UnmarshalText(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B32 + wantErr bool + }{ + { + name: "valid input", + input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + want: bytes.B32{ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + }, + wantErr: false, + }, + { + name: "invalid input - not hex", + input: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B32 + err := got.UnmarshalText([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestBytes32UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B32 + wantErr bool + }{ + { + name: "valid input", + input: `"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"`, + want: bytes.B32{ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + }, + wantErr: false, + }, + { + name: "invalid input - not hex", + input: `"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"`, + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: `"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"`, + wantErr: true, + }, + { + name: "invalid input - extra characters", + input: `"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B32 + err := got.UnmarshalJSON([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestBytes32MarshalText(t *testing.T) { + tests := []struct { + name string + input bytes.B32 + want string + }{ + { + name: "valid input", + input: bytes.B32{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20}, + want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + }, + { + name: "empty input", + input: bytes.B32{}, + want: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.input.MarshalText() + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) + }) + } +} + +func TestBytes32String(t *testing.T) { + tests := []struct { + name string + input bytes.B32 + want string + }{ + { + name: "valid input", + input: bytes.B32{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}, + want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", + }, + { + name: "empty input", + input: bytes.B32{}, + want: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.input.String() + require.Equal(t, tt.want, got) + }) + } +} + +func TestToBytes32(t *testing.T) { + tests := []struct { + name string + input []byte + wantRes bytes.B32 + wantErr error + }{ + { + name: "Input less than 32 bytes", + input: []byte{1, 2, 3}, + wantRes: bytes.B32{}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Input exactly 32 bytes", + input: make([]byte, 32), + wantRes: bytes.B32{}, + wantErr: nil, + }, + { + name: "Input more than 32 bytes", + input: make([]byte, 40), + wantRes: bytes.B32{}, + wantErr: bytes.ErrIncorrectLength, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := bytes.ToBytes32(tt.input) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRes, result) + } + }) + } +} + func TestB32MarshalSSZ(t *testing.T) { tests := []struct { name string @@ -49,8 +298,8 @@ func TestB32MarshalSSZ(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalSSZ() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } diff --git a/mod/primitives/pkg/bytes/b4.go b/mod/primitives/pkg/bytes/b4.go index 6b34bcf664..44e26b71e0 100644 --- a/mod/primitives/pkg/bytes/b4.go +++ b/mod/primitives/pkg/bytes/b4.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" ) @@ -35,9 +37,17 @@ const ( type B4 [4]byte // ToBytes4 is a utility function that transforms a byte slice into a fixed -// 4-byte array. If the input exceeds 4 bytes, it gets truncated. -func ToBytes4(input []byte) B4 { - return B4(ExtendToSize(input, B4Size)) +// 4-byte array. It errs if input has not the required size. +func ToBytes4(input []byte) (B4, error) { + if len(input) != B4Size { + return B4{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B4Size, + ) + } + return B4(input), nil } /* -------------------------------------------------------------------------- */ @@ -78,6 +88,6 @@ func (h B4) MarshalSSZ() ([]byte, error) { } // HashTreeRoot returns the hash tree root of the B8. -func (h B4) HashTreeRoot() B32 { - return ToBytes32(h[:]) +func (h B4) HashTreeRoot() (B32, error) { + return ToBytes32(ExtendToSize(h[:], B32Size)) } diff --git a/mod/primitives/pkg/bytes/b48.go b/mod/primitives/pkg/bytes/b48.go index eb0bc19e5f..ddee763d1e 100644 --- a/mod/primitives/pkg/bytes/b48.go +++ b/mod/primitives/pkg/bytes/b48.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" "github.com/prysmaticlabs/gohashtree" ) @@ -36,9 +38,17 @@ const ( type B48 [48]byte // ToBytes48 is a utility function that transforms a byte slice into a fixed -// 48-byte array. If the input exceeds 48 bytes, it gets truncated. -func ToBytes48(input []byte) B48 { - return B48(ExtendToSize(input, B48Size)) +// 48-byte array. It errs if input has not the required size. +func ToBytes48(input []byte) (B48, error) { + if len(input) != B48Size { + return B48{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B48Size, + ) + } + return B48(input), nil } /* -------------------------------------------------------------------------- */ diff --git a/mod/primitives/pkg/bytes/b48_test.go b/mod/primitives/pkg/bytes/b48_test.go index 979b4b2452..56615d7a71 100644 --- a/mod/primitives/pkg/bytes/b48_test.go +++ b/mod/primitives/pkg/bytes/b48_test.go @@ -18,6 +18,7 @@ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND // TITLE. +//nolint:lll // long strings. package bytes_test import ( @@ -28,6 +29,330 @@ import ( "github.com/stretchr/testify/require" ) +func TestBytes48String(t *testing.T) { + tests := []struct { + name string + input bytes.B48 + want string + }{ + { + name: "valid input", + input: bytes.B48{ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + }, + want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + }, + { + name: "empty input", + input: bytes.B48{}, + want: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.input.String() + require.Equal(t, tt.want, got) + }) + } +} + +func TestBytes48MarshalText(t *testing.T) { + tests := []struct { + name string + input bytes.B48 + want string + }{ + { + name: "valid input", + input: bytes.B48{ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + }, + want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + }, + { + name: "empty input", + input: bytes.B48{}, + want: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.input.MarshalText() + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) + }) + } +} + +func TestBytes48UnmarshalText(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B48 + wantErr bool + }{ + { + name: "valid input", + input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + want: bytes.B48{ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + }, + }, + { + name: "invalid input - not hex", + input: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B48 + err := got.UnmarshalText([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestToBytes48(t *testing.T) { + tests := []struct { + name string + input []byte + wantRes bytes.B48 + wantErr error + }{ + { + name: "Input less than 48 bytes", + input: []byte{1, 2, 3}, + wantRes: bytes.B48{}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Input exactly 48 bytes", + input: make([]byte, 48), + wantRes: bytes.B48{}, + }, + { + name: "Input more than 48 bytes", + input: make([]byte, 60), + wantRes: bytes.B48{}, + wantErr: bytes.ErrIncorrectLength, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := bytes.ToBytes48(tt.input) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRes, result) + } + }) + } +} + +func TestB48UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + expected bytes.B48 + wantErr bool + }{ + { + name: "Valid input", + input: `"0x010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`, + expected: bytes.B48{1, 2, 3}, + wantErr: false, + }, + { + name: "Empty input", + input: `"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`, + expected: bytes.B48{}, + wantErr: false, + }, + { + name: "Invalid input - not hex", + input: `"invalid"`, + expected: bytes.B48{}, + wantErr: true, + }, + { + name: "Invalid input - odd length", + input: `"0x010203"`, + expected: bytes.B48{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B48 + err := got.UnmarshalJSON([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, got) + } + }) + } +} + func TestB48_HashTreeRoot(t *testing.T) { tests := []struct { name string @@ -75,8 +400,8 @@ func TestB48MarshalSSZ(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalSSZ() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } diff --git a/mod/primitives/pkg/bytes/b4_test.go b/mod/primitives/pkg/bytes/b4_test.go new file mode 100644 index 0000000000..2fbb3d25ee --- /dev/null +++ b/mod/primitives/pkg/bytes/b4_test.go @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package bytes_test + +import ( + "testing" + + "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" + "github.com/stretchr/testify/require" +) + +func TestBytes4UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B4 + wantErr bool + }{ + { + name: "valid input", + input: `"0x01020304"`, + want: bytes.B4{0x01, 0x02, 0x03, 0x04}, + wantErr: false, + }, + { + name: "invalid input - not hex", + input: `"01020304"`, + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: `"0x010203"`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B4 + err := got.UnmarshalJSON([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestBytes4String(t *testing.T) { + tests := []struct { + name string + h bytes.B4 + want string + }{ + { + name: "non-empty bytes", + h: bytes.B4{0x01, 0x02, 0x03, 0x04}, + want: "0x01020304", + }, + { + name: "empty bytes", + h: bytes.B4{}, + want: "0x00000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.h.String() + require.Equal(t, tt.want, got) + }) + } +} + +func TestBytes4MarshalText(t *testing.T) { + tests := []struct { + name string + h bytes.B4 + want string + }{ + { + name: "valid bytes", + h: bytes.B4{0x01, 0x02, 0x03, 0x04}, + want: "0x01020304", + }, + { + name: "all zeros", + h: bytes.B4{0x00, 0x00, 0x00, 0x00}, + want: "0x00000000", + }, + { + name: "all ones", + h: bytes.B4{0xFF, 0xFF, 0xFF, 0xFF}, + want: "0xffffffff", + }, + { + name: "mixed bytes", + h: bytes.B4{0xAA, 0xBB, 0xCC, 0xDD}, + want: "0xaabbccdd", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.h.MarshalText() + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) + }) + } +} + +func TestBytes4UnmarshalText(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B4 + wantErr bool + }{ + { + name: "valid input", + input: "0x01020304", + want: bytes.B4{0x01, 0x02, 0x03, 0x04}, + wantErr: false, + }, + { + name: "invalid input - not hex", + input: "01020304", + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: "0x010203", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B4 + err := got.UnmarshalText([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestToBytes4(t *testing.T) { + tests := []struct { + name string + input []byte + wantRes bytes.B4 + wantErr error + }{ + { + name: "Input less than 4 bytes", + input: []byte{0x01, 0x02}, + wantRes: bytes.B4{}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Input exactly 4 bytes", + input: []byte{0x01, 0x02, 0x03, 0x04}, + wantRes: bytes.B4{0x01, 0x02, 0x03, 0x04}, + }, + { + name: "Input more than 4 bytes", + input: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + wantRes: bytes.B4{}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Empty input", + input: []byte{}, + wantRes: bytes.B4{}, + wantErr: bytes.ErrIncorrectLength, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := bytes.ToBytes4(tt.input) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRes, result) + } + }) + } +} + +func TestBytes4MarshalSSZ(t *testing.T) { + tests := []struct { + name string + input bytes.B4 + want []byte + }{ + { + name: "marshal B4", + input: bytes.B4{0x01, 0x02, 0x03, 0x04}, + want: []byte{0x01, 0x02, 0x03, 0x04}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.input.MarshalSSZ() + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestBytes4HashTreeRoot(t *testing.T) { + tests := []struct { + name string + input bytes.B4 + want bytes.B32 + }{ + { + name: "hash tree root", + input: bytes.B4{0x01, 0x02, 0x03, 0x04}, + want: bytes.B32{0x01, 0x02, 0x03, 0x04}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.input.HashTreeRoot() + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/mod/primitives/pkg/bytes/b8.go b/mod/primitives/pkg/bytes/b8.go index cf4b9bd887..14252bfeda 100644 --- a/mod/primitives/pkg/bytes/b8.go +++ b/mod/primitives/pkg/bytes/b8.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" ) @@ -35,9 +37,17 @@ const ( type B8 [8]byte // ToBytes8 is a utility function that transforms a byte slice into a fixed -// 8-byte array. If the input exceeds 8 bytes, it gets truncated. -func ToBytes8(input []byte) B8 { - return B8(ExtendToSize(input, B8Size)) +// 8-byte array. It errs if input has not the required size. +func ToBytes8(input []byte) (B8, error) { + if len(input) != B8Size { + return B8{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B8Size, + ) + } + return B8(input), nil } /* -------------------------------------------------------------------------- */ @@ -78,6 +88,6 @@ func (h B8) MarshalSSZ() ([]byte, error) { } // HashTreeRoot returns the hash tree root of the B8. -func (h B8) HashTreeRoot() B32 { - return ToBytes32(h[:]) +func (h B8) HashTreeRoot() (B32, error) { + return ToBytes32(ExtendToSize(h[:], B32Size)) } diff --git a/mod/primitives/pkg/bytes/b8_test.go b/mod/primitives/pkg/bytes/b8_test.go index 8fac38bd68..91d49d7763 100644 --- a/mod/primitives/pkg/bytes/b8_test.go +++ b/mod/primitives/pkg/bytes/b8_test.go @@ -27,6 +27,198 @@ import ( "github.com/stretchr/testify/require" ) +func TestBytes8UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B8 + wantErr bool + }{ + { + name: "valid input", + input: `"0x0102030405060708"`, + want: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + wantErr: false, + }, + { + name: "invalid input - not hex", + input: `"0102030405060708"`, + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: `"0x01020304"`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B8 + err := got.UnmarshalJSON([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestBytes8String(t *testing.T) { + tests := []struct { + name string + h bytes.B8 + want string + }{ + { + name: "non-empty bytes", + h: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + want: "0x0102030405060708", + }, + { + name: "empty bytes", + h: bytes.B8{}, + want: "0x0000000000000000", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.h.String() + require.Equal(t, tt.want, got) + }) + } +} + +func TestBytes8MarshalText(t *testing.T) { + tests := []struct { + name string + h bytes.B8 + want string + }{ + { + name: "valid bytes", + h: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + want: "0x0102030405060708", + }, + { + name: "empty bytes", + h: bytes.B8{}, + want: "0x0000000000000000", + }, + { + name: "all zeros", + h: bytes.B8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + want: "0x0000000000000000", + }, + { + name: "all ones", + h: bytes.B8{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + want: "0xffffffffffffffff", + }, + { + name: "mixed bytes", + h: bytes.B8{0xaa, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22}, + want: "0xaabbccddeeff1122", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.h.MarshalText() + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) + }) + } +} + +func TestBytes8UnmarshalText(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B8 + wantErr bool + }{ + { + name: "valid input", + input: "0x0102030405060708", + want: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + wantErr: false, + }, + { + name: "invalid input - not hex", + input: "0102030405060708", + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: "0x01020304", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B8 + err := got.UnmarshalText([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestToBytes8(t *testing.T) { + tests := []struct { + name string + input []byte + wantRes bytes.B8 + wantErr error + }{ + { + name: "Exact 8 bytes", + input: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + wantRes: bytes.B8{1, 2, 3, 4, 5, 6, 7, 8}, + wantErr: nil, + }, + { + name: "Less than 8 bytes", + input: []byte{1, 2, 3, 4}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Two bytes", + input: []byte{1, 2}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Empty input", + input: []byte{}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "More than 8 bytes", + input: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + wantErr: bytes.ErrIncorrectLength, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := bytes.ToBytes8(tt.input) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRes, result) + } + }) + } +} + func TestBytes8MarshalSSZ(t *testing.T) { tests := []struct { name string @@ -43,8 +235,8 @@ func TestBytes8MarshalSSZ(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalSSZ() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } @@ -64,8 +256,9 @@ func TestBytes8HashTreeRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.input.HashTreeRoot() - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + got, err := tt.input.HashTreeRoot() + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } diff --git a/mod/primitives/pkg/bytes/b96.go b/mod/primitives/pkg/bytes/b96.go index 876cc8d777..5e7d313a95 100644 --- a/mod/primitives/pkg/bytes/b96.go +++ b/mod/primitives/pkg/bytes/b96.go @@ -22,6 +22,8 @@ package bytes import ( + "fmt" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" "github.com/prysmaticlabs/gohashtree" ) @@ -36,9 +38,17 @@ const ( type B96 [96]byte // ToBytes96 is a utility function that transforms a byte slice into a fixed -// 96-byte array. If the input exceeds 96 bytes, it gets truncated. -func ToBytes96(input []byte) B96 { - return B96(ExtendToSize(input, B96Size)) +// 96-byte array. It errs if input has not the required size. +func ToBytes96(input []byte) (B96, error) { + if len(input) != B96Size { + return B96{}, fmt.Errorf( + "%w, got %d, expected %d", + ErrIncorrectLength, + len(input), + B96Size, + ) + } + return B96(input), nil } /* -------------------------------------------------------------------------- */ diff --git a/mod/primitives/pkg/bytes/b96_test.go b/mod/primitives/pkg/bytes/b96_test.go index d8e5f1f899..57c4144766 100644 --- a/mod/primitives/pkg/bytes/b96_test.go +++ b/mod/primitives/pkg/bytes/b96_test.go @@ -22,6 +22,8 @@ package bytes_test import ( + "fmt" + "strings" "testing" "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" @@ -30,6 +32,214 @@ import ( "github.com/stretchr/testify/require" ) +func TestBytes96UnmarshalText(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B96 + wantErr bool + }{ + { + name: "valid input", + input: "0x" + strings.Repeat("01", 96), + want: func() bytes.B96 { + var b bytes.B96 + for i := range b { + b[i] = 0x01 + } + return b + }(), + }, + { + name: "invalid input - not hex", + input: strings.Repeat("01", 96), + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: "0x" + strings.Repeat("01", 95), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B96 + err := got.UnmarshalText([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestBytes96UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want bytes.B96 + wantErr bool + }{ + { + name: "valid input", + input: `"0x` + strings.Repeat("01", 96) + `"`, + want: func() bytes.B96 { + var b bytes.B96 + for i := range b { + b[i] = 0x01 + } + return b + }(), + }, + { + name: "invalid input - not hex", + input: `"` + strings.Repeat("01", 96) + `"`, + wantErr: true, + }, + { + name: "invalid input - wrong length", + input: `"0x` + strings.Repeat("01", 95) + `"`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bytes.B96 + err := got.UnmarshalJSON([]byte(tt.input)) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} +func TestBytes96MarshalText(t *testing.T) { + tests := []struct { + name string + h bytes.B96 + want string + }{ + { + name: "valid bytes", + h: func() bytes.B96 { + var b bytes.B96 + for i := range b { + b[i] = 0x01 + } + return b + }(), + want: "0x" + strings.Repeat("01", 96), + }, + { + name: "empty bytes", + h: bytes.B96{}, + want: "0x" + strings.Repeat("00", 96), + }, + { + name: "mixed bytes", + h: func() bytes.B96 { + var b bytes.B96 + for i := 0; i < len(b); i++ { + b[i] = byte(i % 256) + } + return b + }(), + want: "0x" + func() string { + var s string + for i := range 96 { + s += fmt.Sprintf("%02x", i%256) + } + return s + }(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.h.MarshalText() + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) + }) + } +} + +func TestBytes96String(t *testing.T) { + tests := []struct { + name string + h bytes.B96 + want string + }{ + { + name: "non-empty bytes", + h: func() bytes.B96 { + var b bytes.B96 + for i := range b { + b[i] = 0x01 + } + return b + }(), + want: "0x" + strings.Repeat("01", 96), + }, + { + name: "empty bytes", + h: bytes.B96{}, + want: "0x" + strings.Repeat("00", 96), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.h.String() + require.Equal(t, tt.want, got) + }) + } +} + +func TestToBytes96(t *testing.T) { + tests := []struct { + name string + input []byte + wantRes bytes.B96 + wantErr error + }{ + { + name: "Input less than 96 bytes", + input: []byte{1, 2, 3}, + wantRes: bytes.B96{}, + wantErr: bytes.ErrIncorrectLength, + }, + { + name: "Input exactly 96 bytes", + input: make([]byte, 96), + wantRes: bytes.B96{}, + }, + { + name: "Input more than 96 bytes", + input: make([]byte, 100), + wantRes: bytes.B96{}, + wantErr: bytes.ErrIncorrectLength, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := bytes.ToBytes96(tt.input) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRes, result) + } + }) + } +} + func TestB96_HashTreeRoot(t *testing.T) { tests := []struct { name string @@ -107,8 +317,8 @@ func TestB96MarshalSSZ(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalSSZ() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, got) }) } } diff --git a/mod/primitives/pkg/bytes/b_test.go b/mod/primitives/pkg/bytes/b_test.go index 654b5ab291..c7cfe0a03f 100644 --- a/mod/primitives/pkg/bytes/b_test.go +++ b/mod/primitives/pkg/bytes/b_test.go @@ -18,14 +18,11 @@ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND // TITLE. -//nolint:lll // long strings. package bytes_test import ( stdhex "encoding/hex" - "fmt" "reflect" - "strings" "testing" "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" @@ -159,421 +156,7 @@ func TestReverseEndianness(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := bytes.CopyAndReverseEndianess(tt.input) - require.Equal(t, tt.expected, result, "Test case %s", tt.name) - }) - } -} - -func TestBytes4UnmarshalJSON(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B4 - wantErr bool - }{ - { - name: "valid input", - input: `"0x01020304"`, - want: bytes.B4{0x01, 0x02, 0x03, 0x04}, - wantErr: false, - }, - { - name: "invalid input - not hex", - input: `"01020304"`, - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: `"0x010203"`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B4 - err := got.UnmarshalJSON([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case %s", tt.name) - } else { - require.NoError(t, err, "Test case %s", tt.name) - require.Equal(t, tt.want, got, "Test case %s", tt.name) - } - }) - } -} - -func TestBytes4String(t *testing.T) { - tests := []struct { - name string - h bytes.B4 - want string - }{ - { - name: "non-empty bytes", - h: bytes.B4{0x01, 0x02, 0x03, 0x04}, - want: "0x01020304", - }, - { - name: "empty bytes", - h: bytes.B4{}, - want: "0x00000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.h.String() - require.Equal(t, tt.want, got, "Test case %s", tt.name) - }) - } -} - -func TestBytes4MarshalText(t *testing.T) { - tests := []struct { - name string - h bytes.B4 - want string - }{ - { - name: "valid bytes", - h: bytes.B4{0x01, 0x02, 0x03, 0x04}, - want: "0x01020304", - }, - { - name: "all zeros", - h: bytes.B4{0x00, 0x00, 0x00, 0x00}, - want: "0x00000000", - }, - { - name: "all ones", - h: bytes.B4{0xFF, 0xFF, 0xFF, 0xFF}, - want: "0xffffffff", - }, - { - name: "mixed bytes", - h: bytes.B4{0xAA, 0xBB, 0xCC, 0xDD}, - want: "0xaabbccdd", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.h.MarshalText() - require.NoError(t, err, "Test case %s", tt.name) - require.Equal(t, tt.want, string(got), "Test case %s", tt.name) - }) - } -} - -func TestBytes4UnmarshalText(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B4 - wantErr bool - }{ - { - name: "valid input", - input: "0x01020304", - want: bytes.B4{0x01, 0x02, 0x03, 0x04}, - wantErr: false, - }, - { - name: "invalid input - not hex", - input: "01020304", - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: "0x010203", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B4 - err := got.UnmarshalText([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case %s", tt.name) - } else { - require.NoError(t, err, "Test case %s", tt.name) - require.Equal(t, tt.want, got, "Test case %s", tt.name) - } - }) - } -} -func TestToBytes4(t *testing.T) { - tests := []struct { - name string - input []byte - expected bytes.B4 - }{ - { - name: "Input less than 4 bytes", - input: []byte{0x01, 0x02}, - expected: bytes.B4{0x01, 0x02, 0x00, 0x00}, - }, - { - name: "Input exactly 4 bytes", - input: []byte{0x01, 0x02, 0x03, 0x04}, - expected: bytes.B4{0x01, 0x02, 0x03, 0x04}, - }, - { - name: "Input more than 4 bytes", - input: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, - expected: bytes.B4{0x01, 0x02, 0x03, 0x04}, - }, - { - name: "Empty input", - input: []byte{}, - expected: bytes.B4{0x00, 0x00, 0x00, 0x00}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := bytes.ToBytes4(tt.input) - require.Equal(t, tt.expected, result, "Test case: %s", tt.name) - }) - } -} -func TestBytes32UnmarshalText(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B32 - wantErr bool - }{ - { - name: "valid input", - input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", - want: bytes.B32{ - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0a, - 0x0b, - 0x0c, - 0x0d, - 0x0e, - 0x0f, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1a, - 0x1b, - 0x1c, - 0x1d, - 0x1e, - 0x1f, - 0x20, - }, - wantErr: false, - }, - { - name: "invalid input - not hex", - input: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B32 - err := got.UnmarshalText([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case %s", tt.name) - } else { - require.NoError(t, err) - require.Equal(t, tt.want, got, "Test case %s", tt.name) - } - }) - } -} - -func TestBytes32UnmarshalJSON(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B32 - wantErr bool - }{ - { - name: "valid input", - input: `"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"`, - want: bytes.B32{ - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0a, - 0x0b, - 0x0c, - 0x0d, - 0x0e, - 0x0f, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1a, - 0x1b, - 0x1c, - 0x1d, - 0x1e, - 0x1f, - 0x20, - }, - wantErr: false, - }, - { - name: "invalid input - not hex", - input: `"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"`, - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: `"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"`, - wantErr: true, - }, - { - name: "invalid input - extra characters", - input: `"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122"`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B32 - err := got.UnmarshalJSON([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) - } else { - require.NoError(t, err) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - } - }) - } -} - -func TestBytes32MarshalText(t *testing.T) { - tests := []struct { - name string - input bytes.B32 - want string - }{ - { - name: "valid input", - input: bytes.B32{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, - 0x1e, 0x1f, 0x20}, - want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", - }, - { - name: "empty input", - input: bytes.B32{}, - want: "0x0000000000000000000000000000000000000000000000000000000000000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.input.MarshalText() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, string(got), "Test case: %s", tt.name) - }) - } -} - -func TestBytes32String(t *testing.T) { - tests := []struct { - name string - input bytes.B32 - want string - }{ - { - name: "valid input", - input: bytes.B32{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, - 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}, - want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", - }, - { - name: "empty input", - input: bytes.B32{}, - want: "0x0000000000000000000000000000000000000000000000000000000000000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.input.String() - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - }) - } -} - -func TestToBytes32(t *testing.T) { - tests := []struct { - name string - input []byte - expected bytes.B32 - }{ - { - name: "Input less than 32 bytes", - input: []byte{1, 2, 3}, - expected: bytes.B32{1, 2, 3}, - }, - { - name: "Input exactly 32 bytes", - input: make([]byte, 32), - expected: bytes.B32{}, - }, - { - name: "Input more than 32 bytes", - input: make([]byte, 40), - expected: bytes.B32{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := bytes.ToBytes32(tt.input) - require.Equal(t, tt.expected, result, "Test case: %s", tt.name) + require.Equal(t, tt.expected, result) }) } } @@ -599,707 +182,7 @@ func TestHashTreeRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.input.HashTreeRoot() - require.Equal(t, tt.want, result, "Test case: %s", tt.name) - }) - } -} - -func TestBytes48String(t *testing.T) { - tests := []struct { - name string - input bytes.B48 - want string - }{ - { - name: "valid input", - input: bytes.B48{ - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0a, - 0x0b, - 0x0c, - 0x0d, - 0x0e, - 0x0f, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1a, - 0x1b, - 0x1c, - 0x1d, - 0x1e, - 0x1f, - 0x20, - 0x21, - 0x22, - 0x23, - 0x24, - 0x25, - 0x26, - 0x27, - 0x28, - 0x29, - 0x2a, - 0x2b, - 0x2c, - 0x2d, - 0x2e, - 0x2f, - 0x30, - }, - want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", - }, - { - name: "empty input", - input: bytes.B48{}, - want: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.input.String() - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - }) - } -} - -func TestBytes48MarshalText(t *testing.T) { - tests := []struct { - name string - input bytes.B48 - want string - }{ - { - name: "valid input", - input: bytes.B48{ - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0a, - 0x0b, - 0x0c, - 0x0d, - 0x0e, - 0x0f, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1a, - 0x1b, - 0x1c, - 0x1d, - 0x1e, - 0x1f, - 0x20, - 0x21, - 0x22, - 0x23, - 0x24, - 0x25, - 0x26, - 0x27, - 0x28, - 0x29, - 0x2a, - 0x2b, - 0x2c, - 0x2d, - 0x2e, - 0x2f, - 0x30, - }, - want: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", - }, - { - name: "empty input", - input: bytes.B48{}, - want: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.input.MarshalText() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, string(got), "Test case: %s", tt.name) - }) - } -} - -func TestBytes48UnmarshalText(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B48 - wantErr bool - }{ - { - name: "valid input", - input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", - want: bytes.B48{ - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, - 0x09, - 0x0a, - 0x0b, - 0x0c, - 0x0d, - 0x0e, - 0x0f, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1a, - 0x1b, - 0x1c, - 0x1d, - 0x1e, - 0x1f, - 0x20, - 0x21, - 0x22, - 0x23, - 0x24, - 0x25, - 0x26, - 0x27, - 0x28, - 0x29, - 0x2a, - 0x2b, - 0x2c, - 0x2d, - 0x2e, - 0x2f, - 0x30, - }, - }, - { - name: "invalid input - not hex", - input: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30", - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B48 - err := got.UnmarshalText([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) - } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - } - }) - } -} - -func TestToBytes48(t *testing.T) { - tests := []struct { - name string - input []byte - expected bytes.B48 - }{ - { - name: "Input less than 48 bytes", - input: []byte{1, 2, 3}, - expected: bytes.B48{1, 2, 3}, - }, - { - name: "Input exactly 48 bytes", - input: make([]byte, 48), - expected: bytes.B48{}, - }, - { - name: "Input more than 48 bytes", - input: make([]byte, 60), - expected: bytes.B48{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := bytes.ToBytes48(tt.input) - require.Equal(t, tt.expected, result, "Test case: %s", tt.name) - }) - } -} - -func TestUnmarshalJSON(t *testing.T) { - tests := []struct { - name string - input string - expected bytes.B48 - wantErr bool - }{ - { - name: "Valid input", - input: `"0x010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`, - expected: bytes.B48{1, 2, 3}, - wantErr: false, - }, - { - name: "Empty input", - input: `"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"`, - expected: bytes.B48{}, - wantErr: false, - }, - { - name: "Invalid input - not hex", - input: `"invalid"`, - expected: bytes.B48{}, - wantErr: true, - }, - { - name: "Invalid input - odd length", - input: `"0x010203"`, - expected: bytes.B48{}, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B48 - err := got.UnmarshalJSON([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) - } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.expected, got, "Test case: %s", tt.name) - } - }) - } -} - -func TestBytes96UnmarshalText(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B96 - wantErr bool - }{ - { - name: "valid input", - input: "0x" + strings.Repeat("01", 96), - want: func() bytes.B96 { - var b bytes.B96 - for i := range b { - b[i] = 0x01 - } - return b - }(), - }, - { - name: "invalid input - not hex", - input: strings.Repeat("01", 96), - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: "0x" + strings.Repeat("01", 95), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B96 - err := got.UnmarshalText([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) - } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - } - }) - } -} - -func TestBytes96UnmarshalJSON(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B96 - wantErr bool - }{ - { - name: "valid input", - input: `"0x` + strings.Repeat("01", 96) + `"`, - want: func() bytes.B96 { - var b bytes.B96 - for i := range b { - b[i] = 0x01 - } - return b - }(), - }, - { - name: "invalid input - not hex", - input: `"` + strings.Repeat("01", 96) + `"`, - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: `"0x` + strings.Repeat("01", 95) + `"`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B96 - err := got.UnmarshalJSON([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) - } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - } - }) - } -} -func TestBytes96MarshalText(t *testing.T) { - tests := []struct { - name string - h bytes.B96 - want string - }{ - { - name: "valid bytes", - h: func() bytes.B96 { - var b bytes.B96 - for i := range b { - b[i] = 0x01 - } - return b - }(), - want: "0x" + strings.Repeat("01", 96), - }, - { - name: "empty bytes", - h: bytes.B96{}, - want: "0x" + strings.Repeat("00", 96), - }, - { - name: "mixed bytes", - h: func() bytes.B96 { - var b bytes.B96 - for i := 0; i < len(b); i++ { - b[i] = byte(i % 256) - } - return b - }(), - want: "0x" + func() string { - var s string - for i := range 96 { - s += fmt.Sprintf("%02x", i%256) - } - return s - }(), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.h.MarshalText() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, string(got), "Test case: %s", tt.name) - }) - } -} - -func TestBytes96String(t *testing.T) { - tests := []struct { - name string - h bytes.B96 - want string - }{ - { - name: "non-empty bytes", - h: func() bytes.B96 { - var b bytes.B96 - for i := range b { - b[i] = 0x01 - } - return b - }(), - want: "0x" + strings.Repeat("01", 96), - }, - { - name: "empty bytes", - h: bytes.B96{}, - want: "0x" + strings.Repeat("00", 96), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.h.String() - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - }) - } -} - -func TestToBytes96(t *testing.T) { - tests := []struct { - name string - input []byte - expected bytes.B96 - }{ - { - name: "Input less than 96 bytes", - input: []byte{1, 2, 3}, - expected: bytes.B96{1, 2, 3}, - }, - { - name: "Input exactly 96 bytes", - input: make([]byte, 96), - expected: bytes.B96{}, - }, - { - name: "Input more than 96 bytes", - input: make([]byte, 100), - expected: bytes.B96{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := bytes.ToBytes96(tt.input) - require.Equal(t, tt.expected, result, "Test case: %s", tt.name) - }) - } -} - -func TestBytes8UnmarshalJSON(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B8 - wantErr bool - }{ - { - name: "valid input", - input: `"0x0102030405060708"`, - want: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - wantErr: false, - }, - { - name: "invalid input - not hex", - input: `"0102030405060708"`, - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: `"0x01020304"`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B8 - err := got.UnmarshalJSON([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestBytes8String(t *testing.T) { - tests := []struct { - name string - h bytes.B8 - want string - }{ - { - name: "non-empty bytes", - h: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - want: "0x0102030405060708", - }, - { - name: "empty bytes", - h: bytes.B8{}, - want: "0x0000000000000000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.h.String() - require.Equal(t, tt.want, got, "Test case: %s", tt.name) - }) - } -} - -func TestBytes8MarshalText(t *testing.T) { - tests := []struct { - name string - h bytes.B8 - want string - }{ - { - name: "valid bytes", - h: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - want: "0x0102030405060708", - }, - { - name: "empty bytes", - h: bytes.B8{}, - want: "0x0000000000000000", - }, - { - name: "all zeros", - h: bytes.B8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - want: "0x0000000000000000", - }, - { - name: "all ones", - h: bytes.B8{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - want: "0xffffffffffffffff", - }, - { - name: "mixed bytes", - h: bytes.B8{0xaa, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22}, - want: "0xaabbccddeeff1122", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.h.MarshalText() - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, string(got), "Test case: %s", tt.name) - }) - } -} - -func TestBytes8UnmarshalText(t *testing.T) { - tests := []struct { - name string - input string - want bytes.B8 - wantErr bool - }{ - { - name: "valid input", - input: "0x0102030405060708", - want: bytes.B8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - wantErr: false, - }, - { - name: "invalid input - not hex", - input: "0102030405060708", - wantErr: true, - }, - { - name: "invalid input - wrong length", - input: "0x01020304", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got bytes.B8 - err := got.UnmarshalText([]byte(tt.input)) - if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) - } else { - require.NoError(t, err, "Test case: %s", tt.name) - } - }) - } -} -func TestToBytes8(t *testing.T) { - tests := []struct { - name string - input []byte - expected bytes.B8 - }{ - { - name: "Exact 8 bytes", - input: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - expected: bytes.B8{1, 2, 3, 4, 5, 6, 7, 8}, - }, - { - name: "Less than 8 bytes", - input: []byte{1, 2, 3, 4}, - expected: bytes.B8{1, 2, 3, 4, 0, 0, 0, 0}, - }, - { - name: "Two bytes", - input: []byte{1, 2}, - expected: bytes.B8{1, 2, 0, 0, 0, 0, 0, 0}, - }, - { - name: "Empty input", - input: []byte{}, - expected: bytes.B8{0, 0, 0, 0, 0, 0, 0, 0}, - }, - { - name: "More than 8 bytes", - input: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - expected: bytes.B8{1, 2, 3, 4, 5, 6, 7, 8}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := bytes.ToBytes8(tt.input) - require.Equal(t, tt.expected, result, "Test case: %s", tt.name) + require.Equal(t, tt.want, result) }) } } @@ -1343,10 +226,10 @@ func TestUnmarshalFixedJSON(t *testing.T) { t.Run(tt.name, func(t *testing.T) { err := bytes.UnmarshalFixedJSON(tt.input, tt.out) if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) + require.Error(t, err) } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.expected, tt.out, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.expected, tt.out) } }) } @@ -1391,10 +274,10 @@ func TestUnmarshalFixedText(t *testing.T) { t.Run(tt.name, func(t *testing.T) { err := bytes.UnmarshalFixedText(tt.input, tt.out) if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) + require.Error(t, err) } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.expected, tt.out, "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.expected, tt.out) } }) } @@ -1473,10 +356,10 @@ func TestBytes_MarshalText(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := tt.input.MarshalText() if tt.wantErr { - require.Error(t, err, "Test case: %s", tt.name) + require.Error(t, err) } else { - require.NoError(t, err, "Test case: %s", tt.name) - require.Equal(t, tt.want, string(got), "Test case: %s", tt.name) + require.NoError(t, err) + require.Equal(t, tt.want, string(got)) } }) } diff --git a/mod/primitives/pkg/bytes/utils.go b/mod/primitives/pkg/bytes/utils.go index 4a3828c75c..8152014dbe 100644 --- a/mod/primitives/pkg/bytes/utils.go +++ b/mod/primitives/pkg/bytes/utils.go @@ -25,6 +25,8 @@ import ( "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" ) +var ErrIncorrectLength = errors.New("incorrect length") + // ------------------------------ Helpers ------------------------------ // Helper function to unmarshal JSON for various byte types. @@ -34,7 +36,7 @@ func UnmarshalJSONHelper(target []byte, input []byte) error { return err } if len(bz) != len(target) { - return errors.New("incorrect length") + return ErrIncorrectLength } copy(target, bz) return nil @@ -47,7 +49,7 @@ func UnmarshalTextHelper(target []byte, text []byte) error { return err } if len(bz) != len(target) { - return errors.New("incorrect length") + return ErrIncorrectLength } copy(target, bz) return nil diff --git a/mod/primitives/pkg/common/consensus.go b/mod/primitives/pkg/common/consensus.go index 67592888a4..50bbc0ed01 100644 --- a/mod/primitives/pkg/common/consensus.go +++ b/mod/primitives/pkg/common/consensus.go @@ -46,7 +46,7 @@ type ( //nolint:lll DomainType = bytes.B4 - // Hash32 as er the Ethereum 2.0 Specification: + // Hash32 as per the Ethereum 2.0 Specification: // https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types Hash32 = bytes.B32 @@ -65,7 +65,9 @@ type ( // Root represents a 32-byte Merkle root. // We use this type to represent roots that come from the consensus layer. -type Root [32]byte +type Root [RootSize]byte + +const RootSize = 32 // NewRootFromHex creates a new root from a hex string. func NewRootFromHex(input string) (Root, error) { @@ -73,6 +75,9 @@ func NewRootFromHex(input string) (Root, error) { if err != nil { return Root{}, err } + if len(val) != RootSize { + return Root{}, bytes.ErrIncorrectLength + } return Root(val), nil } diff --git a/mod/primitives/pkg/common/consensus_test.go b/mod/primitives/pkg/common/consensus_test.go new file mode 100644 index 0000000000..1f2071391b --- /dev/null +++ b/mod/primitives/pkg/common/consensus_test.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package common_test + +import ( + "strings" + "testing" + + "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" + "github.com/berachain/beacon-kit/mod/primitives/pkg/common" + "github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/hex" + "github.com/stretchr/testify/require" +) + +func TestNewRootFromHex(t *testing.T) { + tests := []struct { + name string + input func() string + expectedErr error + }{ + { + name: "EmptyString", + input: func() string { + return "" + }, + expectedErr: hex.ErrEmptyString, + }, + { + name: "ShortSize", + input: func() string { + return hex.Prefix + strings.Repeat("f", 2*common.RootSize-2) + }, + expectedErr: bytes.ErrIncorrectLength, + }, + { + name: "RightSize", + input: func() string { + return hex.Prefix + strings.Repeat("f", 2*common.RootSize) + }, + expectedErr: nil, + }, + { + name: "LongSize", + input: func() string { + return hex.Prefix + strings.Repeat("f", 2*common.RootSize+2) + }, + expectedErr: bytes.ErrIncorrectLength, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var err error + f := func() { + input := tt.input() + _, err = common.NewRootFromHex(input) + } + require.NotPanics(t, f) + if tt.expectedErr != nil { + require.ErrorIs(t, err, tt.expectedErr) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/mod/primitives/pkg/crypto/mocks/bls_signer.mock.go b/mod/primitives/pkg/crypto/mocks/bls_signer.mock.go index 0b306d6509..4ea1d83c10 100644 --- a/mod/primitives/pkg/crypto/mocks/bls_signer.mock.go +++ b/mod/primitives/pkg/crypto/mocks/bls_signer.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/primitives/pkg/encoding/hex/bytes.go b/mod/primitives/pkg/encoding/hex/bytes.go index 2cce326c13..8aaa3b0cb4 100644 --- a/mod/primitives/pkg/encoding/hex/bytes.go +++ b/mod/primitives/pkg/encoding/hex/bytes.go @@ -32,7 +32,7 @@ var ErrInvalidHexStringLength = errors.New("invalid hex string length") // Inverse operation is ToBytes or MustToBytes. func EncodeBytes(b []byte) string { hexStr := make([]byte, len(b)*2+prefixLen) - copy(hexStr, prefix) + copy(hexStr, Prefix) hex.Encode(hexStr[prefixLen:], b) return string(hexStr) } diff --git a/mod/primitives/pkg/encoding/hex/const.go b/mod/primitives/pkg/encoding/hex/const.go index 8a273f146a..f74dc6efaf 100644 --- a/mod/primitives/pkg/encoding/hex/const.go +++ b/mod/primitives/pkg/encoding/hex/const.go @@ -21,8 +21,8 @@ package hex const ( - prefix = "0x" - prefixLen = len(prefix) + Prefix = "0x" + prefixLen = len(Prefix) badNibble = ^uint64(0) hexBase = 16 initialCapacity = 10 diff --git a/mod/primitives/pkg/encoding/hex/string.go b/mod/primitives/pkg/encoding/hex/string.go index 90e3b70908..fd9ee4cbae 100644 --- a/mod/primitives/pkg/encoding/hex/string.go +++ b/mod/primitives/pkg/encoding/hex/string.go @@ -38,11 +38,11 @@ func NewString[T []byte | string](s T) String { str := string(s) switch _, err := IsValidHex(s); { case errors.Is(err, ErrEmptyString): - return String(prefix + "0") + return String(Prefix + "0") case err == nil: return String(str) default: - return String(prefix + string(s)) + return String(Prefix + string(s)) } } @@ -68,7 +68,7 @@ func IsValidHex[T ~[]byte | ~string](s T) (T, error) { if len(s) < prefixLen { return *new(T), ErrMissingPrefix } - if strings.ToLower(string(s[:prefixLen])) != prefix { + if strings.ToLower(string(s[:prefixLen])) != Prefix { return *new(T), ErrMissingPrefix } return s[prefixLen:], nil @@ -77,7 +77,7 @@ func IsValidHex[T ~[]byte | ~string](s T) (T, error) { // FromUint64 encodes i as a hex string with 0x prefix. func FromUint64[U ~uint64](i U) String { enc := make([]byte, prefixLen, initialCapacity) - copy(enc, prefix) + copy(enc, Prefix) //#nosec:G701 // i is a uint64, so it can't overflow. return String(strconv.AppendUint(enc, uint64(i), hexBase)) } @@ -86,12 +86,12 @@ func FromUint64[U ~uint64](i U) String { // Precondition: bigint is non-negative. func FromBigInt(bigint *big.Int) String { if sign := bigint.Sign(); sign == 0 { - return NewString(prefix + "0") + return NewString(Prefix + "0") } else if sign > 0 { - return NewString(prefix + bigint.Text(hexBase)) + return NewString(Prefix + bigint.Text(hexBase)) } // this return should never reach if precondition is met - return NewString(prefix + bigint.Text(hexBase)[1:]) + return NewString(Prefix + bigint.Text(hexBase)[1:]) } // ToUint64 decodes a hex string with 0x prefix. diff --git a/mod/primitives/pkg/encoding/hex/u64.go b/mod/primitives/pkg/encoding/hex/u64.go index 77ec28dc8a..d94fcc5a8e 100644 --- a/mod/primitives/pkg/encoding/hex/u64.go +++ b/mod/primitives/pkg/encoding/hex/u64.go @@ -32,7 +32,7 @@ import ( // of uint64 input. func MarshalText(b uint64) ([]byte, error) { buf := make([]byte, prefixLen, initialCapacity) - copy(buf, prefix) + copy(buf, Prefix) buf = strconv.AppendUint(buf, b, hexBase) return buf, nil } diff --git a/mod/primitives/pkg/merkle/tree_fuzz_test.go b/mod/primitives/pkg/merkle/tree_fuzz_test.go index be36dd7ae8..7fce414d41 100644 --- a/mod/primitives/pkg/merkle/tree_fuzz_test.go +++ b/mod/primitives/pkg/merkle/tree_fuzz_test.go @@ -45,16 +45,22 @@ func FuzzTree_IsValidMerkleBranch(f *testing.F) { return proofs } - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("B")), - byteslib.ToBytes32([]byte("C")), - byteslib.ToBytes32([]byte("D")), - byteslib.ToBytes32([]byte("E")), - byteslib.ToBytes32([]byte("F")), - byteslib.ToBytes32([]byte("G")), - byteslib.ToBytes32([]byte("H")), + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("B"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("C"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("D"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("E"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("F"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("G"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(f, err) + items = append(items, item) } + require.NotEmpty(f, items) // appease nilaway + m, err := merkle.NewTreeFromLeavesWithDepth(items, depth) require.NoError(f, err) proof, err := m.MerkleProofWithMixin(0) @@ -68,16 +74,26 @@ func FuzzTree_IsValidMerkleBranch(f *testing.F) { f.Add(root[:], items[0][:], uint64(0), proofRaw, depth) f.Fuzz( - func(_ *testing.T, + func(t *testing.T, root, item []byte, merkleIndex uint64, proofRaw []byte, depth uint8, ) { + var r, leaf byteslib.B32 + + item = byteslib.ExtendToSize(item, byteslib.B32Size)[:byteslib.B32Size] + leaf, err = byteslib.ToBytes32(item) + require.NoError(t, err) + + root = byteslib.ExtendToSize(root, byteslib.B32Size)[:byteslib.B32Size] + r, err = byteslib.ToBytes32(root) + require.NoError(t, err) + merkle.IsValidMerkleBranch( - byteslib.ToBytes32(item), + leaf, splitProofs(proofRaw), depth, merkleIndex, - byteslib.ToBytes32(root), + r, ) }, ) diff --git a/mod/primitives/pkg/merkle/tree_test.go b/mod/primitives/pkg/merkle/tree_test.go index 03ef277b1a..8f4bb0e5ec 100644 --- a/mod/primitives/pkg/merkle/tree_test.go +++ b/mod/primitives/pkg/merkle/tree_test.go @@ -40,15 +40,21 @@ func TestNewTreeFromLeavesWithDepth_NoItemsProvided(t *testing.T) { } func TestNewTreeFromLeavesWithDepth_DepthSupport(t *testing.T) { - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("BB")), - byteslib.ToBytes32([]byte("CCC")), - byteslib.ToBytes32([]byte("DDDD")), - byteslib.ToBytes32([]byte("EEEEE")), - byteslib.ToBytes32([]byte("FFFFFF")), - byteslib.ToBytes32([]byte("GGGGGGG")), + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("BB"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("CCC"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("DDDD"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("EEEEE"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("FFFFFF"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("GGGGGGG"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(t, err) + items = append(items, item) } + // Supported depth m1, err := merkle.NewTreeFromLeavesWithDepth( items, @@ -68,16 +74,22 @@ func TestNewTreeFromLeavesWithDepth_DepthSupport(t *testing.T) { func TestMerkleTree_IsValidMerkleBranch(t *testing.T) { treeDepth := uint8(32) - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("B")), - byteslib.ToBytes32([]byte("C")), - byteslib.ToBytes32([]byte("D")), - byteslib.ToBytes32([]byte("E")), - byteslib.ToBytes32([]byte("F")), - byteslib.ToBytes32([]byte("G")), - byteslib.ToBytes32([]byte("H")), + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("B"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("C"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("D"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("E"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("F"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("G"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(t, err) + items = append(items, item) } + require.NotEmpty(t, items) + m, err := merkle.NewTreeFromLeavesWithDepth( items, treeDepth, @@ -108,10 +120,14 @@ func TestMerkleTree_IsValidMerkleBranch(t *testing.T) { proof, ), ) + + it := byteslib.ExtendToSize([]byte("buzz"), byteslib.B32Size) + item, err := byteslib.ToBytes32(it) + require.NoError(t, err) require.False( t, merkle.IsValidMerkleBranch( - common.Root(byteslib.ToBytes32([]byte("buzz"))), + common.Root(item), proof, treeDepth, 3, @@ -122,16 +138,22 @@ func TestMerkleTree_IsValidMerkleBranch(t *testing.T) { func TestMerkleTree_VerifyProof(t *testing.T) { treeDepth := uint8(32) - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("B")), - byteslib.ToBytes32([]byte("C")), - byteslib.ToBytes32([]byte("D")), - byteslib.ToBytes32([]byte("E")), - byteslib.ToBytes32([]byte("F")), - byteslib.ToBytes32([]byte("G")), - byteslib.ToBytes32([]byte("H")), + + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("B"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("C"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("D"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("E"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("F"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("G"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(t, err) + items = append(items, item) } + require.NotEmpty(t, items) // appease nilaway m, err := merkle.NewTreeFromLeavesWithDepth[[32]byte]( items, @@ -152,11 +174,15 @@ func TestMerkleTree_VerifyProof(t *testing.T) { proof, err = m.MerkleProofWithMixin(3) require.NoError(t, err) require.True(t, merkle.VerifyProof(root, items[3], 3, proof)) + + it := byteslib.ExtendToSize([]byte("buzz"), byteslib.B32Size) + item, err := byteslib.ToBytes32(it) + require.NoError(t, err) require.False( t, merkle.VerifyProof( root, - common.Root(byteslib.ToBytes32([]byte("buzz"))), + common.Root(item), 3, proof, ), @@ -165,22 +191,30 @@ func TestMerkleTree_VerifyProof(t *testing.T) { func TestMerkleTree_NegativeIndexes(t *testing.T) { treeDepth := uint8(32) - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("B")), - byteslib.ToBytes32([]byte("C")), - byteslib.ToBytes32([]byte("D")), - byteslib.ToBytes32([]byte("E")), - byteslib.ToBytes32([]byte("F")), - byteslib.ToBytes32([]byte("G")), - byteslib.ToBytes32([]byte("H")), + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("B"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("C"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("D"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("E"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("F"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("G"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(t, err) + items = append(items, item) } m, err := merkle.NewTreeFromLeavesWithDepth( items, treeDepth, ) require.NoError(t, err) - err = m.Insert(byteslib.ToBytes32([]byte{'J'}), -1) + + it := byteslib.ExtendToSize([]byte("J"), byteslib.B32Size) + extraItem, err := byteslib.ToBytes32(it) + require.NoError(t, err) + err = m.Insert(extraItem, -1) require.ErrorIs(t, err, merkle.ErrNegativeIndex) } @@ -210,8 +244,12 @@ func TestMerkleTree_VerifyProof_TrieUpdated(t *testing.T) { ), ) - // Now we update the merkle. - require.NoError(t, m.Insert(byteslib.ToBytes32([]byte{5}), 3)) + // Now we update the merkle + it := byteslib.ExtendToSize([]byte{5}, byteslib.B32Size) + item, err := byteslib.ToBytes32(it) + + require.NoError(t, err) + require.NoError(t, m.Insert(item, 3)) proof, err = m.MerkleProofWithMixin(3) require.NoError(t, err) root = m.HashTreeRoot() @@ -223,19 +261,27 @@ func TestMerkleTree_VerifyProof_TrieUpdated(t *testing.T) { ), "Old item should not verify") // Now we update the tree at an index larger than the number of items. - require.NoError(t, m.Insert(byteslib.ToBytes32([]byte{6}), 15)) + it = byteslib.ExtendToSize([]byte{6}, byteslib.B32Size) + item, err = byteslib.ToBytes32(it) + require.NoError(t, err) + require.NoError(t, m.Insert(item, 15)) } func BenchmarkNewTreeFromLeavesWithDepth(b *testing.B) { treeDepth := uint8(32) - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("BB")), - byteslib.ToBytes32([]byte("CCC")), - byteslib.ToBytes32([]byte("DDDD")), - byteslib.ToBytes32([]byte("EEEEE")), - byteslib.ToBytes32([]byte("FFFFFF")), - byteslib.ToBytes32([]byte("GGGGGGG")), + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("BB"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("CCC"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("DDDD"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("EEEEE"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("FFFFFF"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("GGGGGGG"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(b, err) + items = append(items, item) } for i := 0; i < b.N; i++ { _, err := merkle.NewTreeFromLeavesWithDepth( @@ -249,10 +295,17 @@ func BenchmarkNewTreeFromLeavesWithDepth(b *testing.B) { func BenchmarkInsertTrie_Optimized(b *testing.B) { treeDepth := uint8(32) b.StopTimer() - numDeposits := 16000 - items := make([][32]byte, numDeposits) + + var ( + numDeposits = 16000 + items = make([][32]byte, numDeposits) + err error + ) + for i := range numDeposits { - items[i] = byteslib.ToBytes32([]byte(strconv.Itoa(i))) + it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size) + items[i], err = byteslib.ToBytes32(it) + require.NoError(b, err) } tr, err := merkle.NewTreeFromLeavesWithDepth[[32]byte]( items, @@ -260,7 +313,10 @@ func BenchmarkInsertTrie_Optimized(b *testing.B) { ) require.NoError(b, err) - someItem := byteslib.ToBytes32([]byte("hello-world")) + it := byteslib.ExtendToSize([]byte("hello-world"), byteslib.B32Size) + someItem, err := byteslib.ToBytes32(it) + require.NoError(b, err) + b.StartTimer() for i := range b.N { require.NoError(b, tr.Insert(someItem, i%numDeposits)) @@ -270,15 +326,22 @@ func BenchmarkInsertTrie_Optimized(b *testing.B) { func BenchmarkGenerateProof(b *testing.B) { treeDepth := uint8(32) b.StopTimer() - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("BB")), - byteslib.ToBytes32([]byte("CCC")), - byteslib.ToBytes32([]byte("DDDD")), - byteslib.ToBytes32([]byte("EEEEE")), - byteslib.ToBytes32([]byte("FFFFFF")), - byteslib.ToBytes32([]byte("GGGGGGG")), + + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("BB"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("CCC"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("DDDD"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("EEEEE"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("FFFFFF"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("GGGGGGG"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(b, err) + items = append(items, item) } + goodTree, err := merkle.NewTreeFromLeavesWithDepth[[32]byte]( items, treeDepth, @@ -295,15 +358,23 @@ func BenchmarkGenerateProof(b *testing.B) { func BenchmarkIsValidMerkleBranch(b *testing.B) { treeDepth := uint8(4) b.StopTimer() - items := [][32]byte{ - byteslib.ToBytes32([]byte("A")), - byteslib.ToBytes32([]byte("BB")), - byteslib.ToBytes32([]byte("CCC")), - byteslib.ToBytes32([]byte("DDDD")), - byteslib.ToBytes32([]byte("EEEEE")), - byteslib.ToBytes32([]byte("FFFFFF")), - byteslib.ToBytes32([]byte("GGGGGGG")), + + items := make([][32]byte, 0) + for _, v := range [][]byte{ + byteslib.ExtendToSize([]byte("A"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("BB"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("CCC"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("DDDD"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("EEEEE"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("FFFFFF"), byteslib.B32Size), + byteslib.ExtendToSize([]byte("GGGGGGG"), byteslib.B32Size), + } { + item, err := byteslib.ToBytes32(v) + require.NoError(b, err) + items = append(items, item) } + require.NotEmpty(b, items) // appease nilaway + m, err := merkle.NewTreeFromLeavesWithDepth[[32]byte]( items, treeDepth, diff --git a/mod/primitives/pkg/service/status.go b/mod/primitives/pkg/service/status.go deleted file mode 100644 index 17c444f161..0000000000 --- a/mod/primitives/pkg/service/status.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -// -// Copyright (C) 2024, Berachain Foundation. All rights reserved. -// Use of this software is governed by the Business Source License included -// in the LICENSE file of this repository and at www.mariadb.com/bsl11. -// -// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY -// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER -// VERSIONS OF THE LICENSED WORK. -// -// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF -// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF -// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). -// -// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON -// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, -// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND -// TITLE. - -package service - -// StatusEvent represents a service status event. -type StatusEvent struct { - // name represents the name of the service. - name string - // healthy indicates whether the service is in a healthy state. - healthy bool -} - -// NewStatusEvent creates a new status service. -func NewStatusEvent(name string, healthy bool) *StatusEvent { - return &StatusEvent{ - name: name, - healthy: healthy, - } -} - -// Name returns the name of the service. -func (s *StatusEvent) Name() string { - return s.name -} - -// IsHealthy returns the health status of the service. -func (s *StatusEvent) IsHealthy() bool { - return s.healthy -} diff --git a/mod/storage/pkg/block/store.go b/mod/storage/pkg/block/store.go index 3e1c069ec9..a309d81b3c 100644 --- a/mod/storage/pkg/block/store.go +++ b/mod/storage/pkg/block/store.go @@ -23,6 +23,7 @@ package block import ( "fmt" + "github.com/berachain/beacon-kit/mod/errors" "github.com/berachain/beacon-kit/mod/log" "github.com/berachain/beacon-kit/mod/primitives/pkg/common" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" @@ -32,10 +33,18 @@ import ( // KVStore is a simple memory store based implementation that stores metadata of // beacon blocks. type KVStore[BeaconBlockT BeaconBlock] struct { - blockRoots *lru.Cache[common.Root, math.Slot] - executionNumbers *lru.Cache[math.U64, math.Slot] - stateRoots *lru.Cache[common.Root, math.Slot] + // Beacon block root to slot mapping is injective for finalized blocks. + blockRoots *lru.Cache[common.Root, math.Slot] + // Timestamp to slot mapping is injective for finalized blocks. This is + // guaranteed by CometBFT consensus. So each slot will be associated with a + // different timestamp (no overwriting) as we store only finalized blocks. + timestamps *lru.Cache[math.U64, math.Slot] + + // Beacon state root to slot mapping is injective for finalized blocks. + stateRoots *lru.Cache[common.Root, math.Slot] + + // Logger for the store. logger log.Logger } @@ -48,7 +57,7 @@ func NewStore[BeaconBlockT BeaconBlock]( if err != nil { panic(err) } - executionNumbers, err := lru.New[math.U64, math.Slot](availabilityWindow) + timestamps, err := lru.New[math.U64, math.Slot](availabilityWindow) if err != nil { panic(err) } @@ -57,20 +66,20 @@ func NewStore[BeaconBlockT BeaconBlock]( panic(err) } return &KVStore[BeaconBlockT]{ - blockRoots: blockRoots, - executionNumbers: executionNumbers, - stateRoots: stateRoots, - logger: logger, + blockRoots: blockRoots, + timestamps: timestamps, + stateRoots: stateRoots, + logger: logger, } } // Set sets the block by a given index in the store, storing the block root, -// execution number, and state root. Only this function may potentially evict +// timestamp, and state root. Only this function may potentially evict // entries from the store if the availability window is reached. func (kv *KVStore[BeaconBlockT]) Set(blk BeaconBlockT) error { slot := blk.GetSlot() kv.blockRoots.Add(blk.HashTreeRoot(), slot) - kv.executionNumbers.Add(blk.GetExecutionNumber(), slot) + kv.timestamps.Add(blk.GetTimestamp(), slot) kv.stateRoots.Add(blk.GetStateRoot(), slot) return nil } @@ -86,19 +95,20 @@ func (kv *KVStore[BeaconBlockT]) GetSlotByBlockRoot( return slot, nil } -// GetSlotByExecutionNumber retrieves the slot by a given execution number from +// GetParentSlotByTimestamp retrieves the parent slot by a given timestamp from // the store. -func (kv *KVStore[BeaconBlockT]) GetSlotByExecutionNumber( - executionNumber math.U64, +func (kv *KVStore[BeaconBlockT]) GetParentSlotByTimestamp( + timestamp math.U64, ) (math.Slot, error) { - slot, ok := kv.executionNumbers.Peek(executionNumber) + slot, ok := kv.timestamps.Peek(timestamp) if !ok { - return 0, fmt.Errorf( - "slot not found at execution number: %d", - executionNumber, - ) + return slot, fmt.Errorf("slot not found at timestamp: %d", timestamp) } - return slot, nil + if slot == 0 { + return slot, errors.New("parent slot not supported for genesis slot 0") + } + + return slot - 1, nil } // GetSlotByStateRoot retrieves the slot by a given state root from the store. diff --git a/mod/storage/pkg/block/store_test.go b/mod/storage/pkg/block/store_test.go index e41553b7b4..14de7a5fef 100644 --- a/mod/storage/pkg/block/store_test.go +++ b/mod/storage/pkg/block/store_test.go @@ -42,7 +42,7 @@ func (m MockBeaconBlock) HashTreeRoot() common.Root { return [32]byte{byte(m.slot)} } -func (m MockBeaconBlock) GetExecutionNumber() math.U64 { +func (m MockBeaconBlock) GetTimestamp() math.U64 { return m.slot } @@ -65,15 +65,15 @@ func TestBlockStore(t *testing.T) { require.NoError(t, err) } - // Get the slots by roots & execution numbers. + // Get the slots by roots & timestamps. for i := math.Slot(3); i <= 7; i++ { slot, err = blockStore.GetSlotByBlockRoot([32]byte{byte(i)}) require.NoError(t, err) require.Equal(t, i, slot) - slot, err = blockStore.GetSlotByExecutionNumber(i) + slot, err = blockStore.GetParentSlotByTimestamp(i) require.NoError(t, err) - require.Equal(t, i, slot) + require.Equal(t, i-1, slot) slot, err = blockStore.GetSlotByStateRoot([32]byte{byte(i)}) require.NoError(t, err) @@ -83,6 +83,6 @@ func TestBlockStore(t *testing.T) { // Try getting a slot that doesn't exist. _, err = blockStore.GetSlotByBlockRoot([32]byte{byte(8)}) require.ErrorContains(t, err, "not found") - _, err = blockStore.GetSlotByExecutionNumber(2) + _, err = blockStore.GetParentSlotByTimestamp(2) require.ErrorContains(t, err, "not found") } diff --git a/mod/storage/pkg/block/types.go b/mod/storage/pkg/block/types.go index 07afd76035..3b02a2a9ec 100644 --- a/mod/storage/pkg/block/types.go +++ b/mod/storage/pkg/block/types.go @@ -26,10 +26,10 @@ import ( ) // BeaconBlock is a block in the beacon chain that has a slot, block root (hash -// tree root), execution number, and state root. +// tree root), timestamp, and state root. type BeaconBlock interface { GetSlot() math.U64 HashTreeRoot() common.Root - GetExecutionNumber() math.U64 + GetTimestamp() math.U64 GetStateRoot() common.Root } diff --git a/mod/storage/pkg/interfaces/mocks/db.mock.go b/mod/storage/pkg/interfaces/mocks/db.mock.go index ac7b734427..97c0431b20 100644 --- a/mod/storage/pkg/interfaces/mocks/db.mock.go +++ b/mod/storage/pkg/interfaces/mocks/db.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/storage/pkg/pruner/mocks/beacon_block.mock.go b/mod/storage/pkg/pruner/mocks/beacon_block.mock.go index 3859cc6053..61f07716ae 100644 --- a/mod/storage/pkg/pruner/mocks/beacon_block.mock.go +++ b/mod/storage/pkg/pruner/mocks/beacon_block.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/storage/pkg/pruner/mocks/block_event.mock.go b/mod/storage/pkg/pruner/mocks/block_event.mock.go index 4b24eb033a..105bf64f13 100644 --- a/mod/storage/pkg/pruner/mocks/block_event.mock.go +++ b/mod/storage/pkg/pruner/mocks/block_event.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/storage/pkg/pruner/mocks/prunable.mock.go b/mod/storage/pkg/pruner/mocks/prunable.mock.go index fba09727d5..d04348a99b 100644 --- a/mod/storage/pkg/pruner/mocks/prunable.mock.go +++ b/mod/storage/pkg/pruner/mocks/prunable.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/mod/storage/pkg/pruner/mocks/pruner.mock.go b/mod/storage/pkg/pruner/mocks/pruner.mock.go index 19bb2c3f4c..b062cca253 100644 --- a/mod/storage/pkg/pruner/mocks/pruner.mock.go +++ b/mod/storage/pkg/pruner/mocks/pruner.mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.2. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks