Skip to content

Commit

Permalink
feat(): streamable program
Browse files Browse the repository at this point in the history
  • Loading branch information
n33pm committed Mar 21, 2024
1 parent 2a64d04 commit 594a17d
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 2 deletions.
15 changes: 14 additions & 1 deletion pkg/streamable/streamable.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package streamable
import (
"encoding/binary"
"fmt"
"github.com/chia-network/go-chia-libs/pkg/types"
"reflect"
"strings"
"unsafe"
Expand Down Expand Up @@ -64,7 +65,9 @@ func unmarshalStruct(bytes []byte, t reflect.Type, tv reflect.Value) ([]byte, er
// Not needed for anything else
// When recursively calling this on a wrapper type like mo.Option, pass the parent/wrapping StructField
func unmarshalField(bytes []byte, fieldType reflect.Type, fieldValue reflect.Value, structField reflect.StructField) ([]byte, error) {
if _, tagPresent := structField.Tag.Lookup(tagName); !tagPresent {
var tagValue string
var tagPresent bool
if tagValue, tagPresent = structField.Tag.Lookup(tagName); !tagPresent {
// Continuing because the tag isn't present
return bytes, nil
}
Expand Down Expand Up @@ -112,6 +115,16 @@ func unmarshalField(bytes []byte, fieldType reflect.Type, fieldValue reflect.Val
fieldValue = fieldValue.Elem()
}

if tagValue == "SerializedProgram" {
length, err := types.SerializedLengthFromBytesTrusted(bytes)
if err != nil {
return bytes, err
}
newVal, bytes = bytes[:length], bytes[length:]
fieldValue.SetBytes(newVal)
return bytes, nil
}

switch kind := fieldType.Kind(); kind {
case reflect.Uint8:
newVal, bytes, err = util.ShiftNBytes(1, bytes)
Expand Down
13 changes: 13 additions & 0 deletions pkg/streamable/streamable_test.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type FullBlock struct {
Foliage Foliage `json:"foliage" streamable:""`
FoliageTransactionBlock mo.Option[FoliageTransactionBlock] `json:"foliage_transaction_block" streamable:""`
TransactionsInfo mo.Option[TransactionsInfo] `json:"transactions_info" streamable:""`
TransactionsGenerator mo.Option[SerializedProgram] `json:"transactions_generator" streamable:""`
TransactionsGenerator mo.Option[SerializedProgram] `json:"transactions_generator" streamable:"SerializedProgram"`
TransactionsGeneratorRefList []uint32 `json:"transactions_generator_ref_list" streamable:""`
}

Expand Down
104 changes: 104 additions & 0 deletions pkg/types/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ package types

import (
"encoding/json"
"errors"
"fmt"
)

// SerializedProgram An opaque representation of a clvm program. It has a more limited interface than a full SExp
// https://github.com/Chia-Network/chia-blockchain/blob/main/chia/types/blockchain_format/program.py#L232
type SerializedProgram Bytes

const MAX_SINGLE_BYTE byte = 0x7f
const BACK_REFERENCE byte = 0xfe
const CONS_BOX_MARKER byte = 0xff

const (
badEncErr = "bad encoding"
internalErr = "internal error"
)

// MarshalJSON custom hex marshaller
func (g SerializedProgram) MarshalJSON() ([]byte, error) {
return json.Marshal(Bytes(g))
Expand All @@ -25,3 +36,96 @@ func (g *SerializedProgram) UnmarshalJSON(data []byte) error {

return nil
}

func SerializedLengthFromBytesTrusted(b []byte) (uint64, error) {
var opsCounter uint64 = 1
var position uint64 = 0
start := len(b)

for opsCounter > 0 {
opsCounter--
if len(b) == 0 {
return 0, errors.New("unexpected end of input")
}
currentByte := b[0]
b = b[1:]
position++

if currentByte == CONS_BOX_MARKER {
opsCounter += 2
} else if currentByte == BACK_REFERENCE {
if len(b) == 0 {
return 0, errors.New("unexpected end of input")
}
firstByte := b[0]
b = b[1:]
position++
if firstByte > MAX_SINGLE_BYTE {
_, length, err := decodeSize(b, firstByte)
if err != nil {
return 0, err
}
b = b[length:]
position += length
}
} else if currentByte == 0x80 || currentByte <= MAX_SINGLE_BYTE {
// This one byte we just read was the whole atom.
// or the special case of NIL
} else {
_, length, err := decodeSize(b, currentByte)
if err != nil {
return 0, err
}
b = b[length:]
position += length
}

}

fmt.Println("read bytes", start, start-len(b), position)

return position, nil
}

func decodeSize(input []byte, initialB byte) (byte, uint64, error) {

bitMask := byte(0x80)

if (initialB & bitMask) == 0 {
return 0, 0, errors.New(internalErr)
}

var atomStartOffset byte

b := initialB

for (b & bitMask) != 0 {
atomStartOffset++
b &= 0xff ^ bitMask
bitMask >>= 1
}

sizeBlob := make([]byte, atomStartOffset)
sizeBlob[0] = b

if atomStartOffset > 1 {
copy(sizeBlob[1:], input)
}

var atomSize uint64 = 0

if len(sizeBlob) > 6 {
return 0, 0, errors.New(badEncErr)
}

for _, b := range sizeBlob {
atomSize <<= 8
atomSize += uint64(b)
}

if atomSize >= 0x400000000 {
return 0, 0, errors.New(badEncErr)
}

return atomStartOffset, atomSize, nil
}
103 changes: 103 additions & 0 deletions pkg/types/program_test.go

Large diffs are not rendered by default.

0 comments on commit 594a17d

Please sign in to comment.