feat:add state nibbles type

Signed-off-by: Chen Kai <281165273grape@gmail.com>
This commit is contained in:
Chen Kai 2024-04-17 18:28:19 +08:00
parent f1a812fcad
commit 5805416966
3 changed files with 314 additions and 1 deletions

View file

@ -208,7 +208,6 @@ func (c *ConsensusLightClient) Advance() error {
return nil
}
//lint:ignore U1000 placeholder function
func (c *ConsensusLightClient) bootstrap() error {
bootstrap, err := c.API.GetCheckpointData(c.InitialCheckpoint)
if err != nil {

View file

@ -0,0 +1,116 @@
package state
import (
"errors"
"fmt"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
)
var _ common.SSZObj = (*Nibbles)(nil)
type Nibbles struct {
Nibbles []byte
}
func (n *Nibbles) Serialize(w *codec.EncodingWriter) error {
if len(n.Nibbles)%2 == 0 {
err := w.WriteByte(0)
if err != nil {
return err
}
for i := 0; i < len(n.Nibbles); i += 2 {
err = w.WriteByte(n.Nibbles[i]<<4 | n.Nibbles[i+1])
if err != nil {
return err
}
}
} else {
err := w.WriteByte(0x10 | n.Nibbles[0])
if err != nil {
return err
}
for i := 1; i < len(n.Nibbles); i += 2 {
err = w.WriteByte(n.Nibbles[i]<<4 | n.Nibbles[i+1])
if err != nil {
return err
}
}
}
return nil
}
func (n *Nibbles) ByteLength() uint64 {
return uint64(len(n.Nibbles)/2 + 1)
}
func (n *Nibbles) FixedLength() uint64 {
return 0
}
func (n *Nibbles) Deserialize(dr *codec.DecodingReader) error {
firstByte, err := dr.ReadByte()
if err != nil {
return err
}
packedNibbles := make([]byte, dr.Scope())
_, err = dr.Read(packedNibbles)
if err != nil {
return err
}
flag, first := unpackNibblePair(firstByte)
nibbles := make([]byte, 1+2*len(packedNibbles))
if flag == 0 {
if first != 0 {
return fmt.Errorf("nibbles: The lowest 4 bits of the first byte must be 0, but was: %x", first)
}
} else if flag == 1 {
nibbles = append(nibbles, first)
} else {
return fmt.Errorf("nibbles: The highest 4 bits of the first byte must be 0 or 1, but was: %x", flag)
}
for i, b := range packedNibbles {
left, right := unpackNibblePair(b)
nibbles[1+2*i] = left
nibbles[1+2*i+1] = right
}
unpackedNibbles, err := FromUnpackedNibbles(nibbles)
if err != nil {
return err
}
*n = *unpackedNibbles
return nil
}
func (n *Nibbles) HashTreeRoot(h tree.HashFn) tree.Root {
//TODO implement me
panic("implement me")
}
func FromUnpackedNibbles(nibbles []byte) (*Nibbles, error) {
if len(nibbles) > 64 {
return nil, errors.New("too many nibbles")
}
for _, nibble := range nibbles {
if nibble > 0xf {
return nil, errors.New("nibble out of range")
}
}
return &Nibbles{Nibbles: nibbles}, nil
}
func unpackNibblePair(pair byte) (byte, byte) {
return pair >> 4, pair & 0xf
}

View file

@ -0,0 +1,198 @@
package state
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/protolambda/ztyp/codec"
"github.com/stretchr/testify/assert"
)
func TestNibblesEncodeDecode(t *testing.T) {
type fields struct {
Nibbles []byte
}
type args struct {
buf bytes.Buffer
}
tests := []struct {
name string
fields fields
args args
encodeds string
}{
{
name: "emptyNibbles",
fields: fields{
Nibbles: []byte{},
},
args: args{
bytes.Buffer{},
},
encodeds: "0x00",
},
{
name: "singleNibble",
fields: fields{
Nibbles: []byte{10},
},
args: args{
bytes.Buffer{},
},
encodeds: "0x1a",
},
{
name: "evenNumberNibbles",
fields: fields{
Nibbles: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
},
args: args{
bytes.Buffer{},
},
encodeds: "0x00123456789abc",
},
{
name: "oddNumberNibbles",
fields: fields{
Nibbles: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13},
},
args: args{
bytes.Buffer{},
},
encodeds: "0x1123456789abcd",
},
{
name: "maxNumberNibbles",
fields: fields{
Nibbles: initSlice(64, 10),
},
args: args{
bytes.Buffer{},
},
encodeds: "0x00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
n, err := FromUnpackedNibbles(tt.fields.Nibbles)
assert.NoError(t, err)
err = n.Serialize(codec.NewEncodingWriter(&tt.args.buf))
assert.NoError(t, err)
assert.Equal(t, tt.encodeds, hexutil.Encode(tt.args.buf.Bytes()))
})
}
}
func TestFromUnpackedShouldFailForInvalidNibbles(t *testing.T) {
type fields struct {
Nibbles []byte
}
tests := []struct {
name string
fields fields
encodeds string
}{
{
name: "singleNibble",
fields: fields{
Nibbles: []byte{0x10},
},
},
{
name: "firstOutOfTwo",
fields: fields{
Nibbles: []byte{0x11, 0x01},
},
},
{
name: "secondOutOfTwo",
fields: fields{
Nibbles: []byte{0x01, 0x12},
},
},
{
name: "firstOutOfThree",
fields: fields{
Nibbles: []byte{0x11, 0x02, 0x03},
},
},
{
name: "secondOutOfThree",
fields: fields{
Nibbles: []byte{0x01, 0x12, 0x03},
},
},
{
name: "thirdOutOfThree",
fields: fields{
Nibbles: []byte{0x01, 0x02, 0x13},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := FromUnpackedNibbles(tt.fields.Nibbles)
assert.Error(t, err)
})
}
}
func TestDecodeShouldFailForInvalidBytes(t *testing.T) {
type fields struct {
Nibbles string
}
tests := []struct {
name string
fields fields
encodeds string
}{
{
name: "empty",
fields: fields{
Nibbles: "0x",
},
},
{
name: "invalid flag",
fields: fields{
Nibbles: "0x20",
},
},
{
name: "low bits not empty for even length",
fields: fields{
Nibbles: "0x01",
},
},
{
name: "too long",
fields: fields{
Nibbles: "0x1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
nibbles := hexutil.MustDecode(tt.fields.Nibbles)
var n Nibbles
err := n.Deserialize(codec.NewDecodingReader(bytes.NewReader(nibbles), uint64(len(nibbles))))
assert.Error(t, err)
})
}
}
func TestFromUnpackedShouldFailForTooManyNibbles(t *testing.T) {
_, err := FromUnpackedNibbles(initSlice(65, 10))
assert.Error(t, err)
}
func initSlice(n int, v byte) []byte {
s := make([]byte, n)
for i := range s {
s[i] = v
}
return s
}