From ca84f1da53dbe162f61b72d5cd8c4de2235cd238 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 4 Dec 2025 10:27:48 -0800 Subject: [PATCH] feat(commp): add binary unmarshall/marshall --- commp.go | 184 +++++++++++++++++++++++++++++++----- commp_test.go | 127 +++++++++++++++++++++++++ go.mod | 11 +++ go.sum | 29 ++++++ internal/gen/main.go | 16 ++++ internal/serialized.go | 7 ++ internal/serialized_gen.go | 189 +++++++++++++++++++++++++++++++++++++ 7 files changed, 538 insertions(+), 25 deletions(-) create mode 100644 internal/gen/main.go create mode 100644 internal/serialized.go create mode 100644 internal/serialized_gen.go diff --git a/commp.go b/commp.go index e05b7ce..fdf9b1f 100644 --- a/commp.go +++ b/commp.go @@ -10,10 +10,13 @@ package commp import ( + "bytes" + "encoding" "hash" "math/bits" "sync" + "github.com/filecoin-project/go-fil-commp-hashhash/internal" sha256simd "github.com/minio/sha256-simd" "golang.org/x/xerrors" ) @@ -25,14 +28,21 @@ type Calc struct { state mu sync.Mutex } +type message interface{} +type marshall struct { + twinHolds [][]byte +} + type state struct { quadsEnqueued uint64 - layerQueues [MaxLayers + 2]chan []byte // one extra layer for the initial leaves, one more for the dummy never-to-use channel - resultCommP chan []byte + layerQueues [MaxLayers + 2]chan message // one extra layer for the initial leaves, one more for the dummy never-to-use channel + result chan message buffer []byte } var _ hash.Hash = &Calc{} // make sure we are hash.Hash compliant +var _ encoding.BinaryMarshaler = &Calc{} +var _ encoding.BinaryUnmarshaler = &Calc{} // MaxLayers is the current maximum height of the rust-fil-proofs proving tree. const MaxLayers = uint(31) // result of log2( 64 GiB / 32 ) @@ -93,7 +103,7 @@ func (cp *Calc) Reset() { // we are resetting without digesting: close everything out to terminate // the layer workers close(cp.layerQueues[0]) - <-cp.resultCommP + <-cp.result } cp.state = state{} // reset cp.mu.Unlock() @@ -158,7 +168,7 @@ func (cp *Calc) Digest() (commP []byte, paddedPieceSize uint64, err error) { paddedPieceSize = 1 << uint(64-bits.LeadingZeros64(paddedPieceSize)) } - return <-cp.resultCommP, paddedPieceSize, nil + return (<-cp.result).([]byte), paddedPieceSize, nil } // Write adds bytes to the accumulator, for a subsequent Digest(). Upon the @@ -189,8 +199,8 @@ func (cp *Calc) Write(input []byte) (int, error) { // just starting: initialize internal state, start first background layer-goroutine if cp.buffer == nil { cp.buffer = make([]byte, 0, bufferSize) - cp.resultCommP = make(chan []byte, 1) - cp.layerQueues[0] = make(chan []byte, layerQueueDepth) + cp.result = make(chan message, 1) + cp.layerQueues[0] = make(chan message, layerQueueDepth) cp.addLayer(0) } @@ -278,18 +288,20 @@ func (cp *Calc) digestQuads(inSlab []byte) { } func (cp *Calc) addLayer(myIdx uint) { + cp.addLayerWithTwinhold(myIdx, nil) +} + +func (cp *Calc) addLayerWithTwinhold(myIdx uint, twinHold []byte) { // the next layer channel, which we might *not* use if cp.layerQueues[myIdx+1] != nil { panic("addLayer called more than once with identical idx argument") } - cp.layerQueues[myIdx+1] = make(chan []byte, layerQueueDepth) + cp.layerQueues[myIdx+1] = make(chan message, layerQueueDepth) go func() { s256 := sha256simd.New() - var twinHold []byte - for { - slab, queueIsOpen := <-cp.layerQueues[myIdx] + message, queueIsOpen := <-cp.layerQueues[myIdx] // the dream is collapsing if !queueIsOpen { @@ -297,7 +309,12 @@ func (cp *Calc) addLayer(myIdx uint) { // I am last if myIdx == MaxLayers || cp.layerQueues[myIdx+2] == nil { - cp.resultCommP <- append(make([]byte, 0, 32), twinHold[0:32]...) + if twinHold == nil { + // just send empty 32-byte slice -- this is a reset + cp.result <- make([]byte, 32) + return + } + cp.result <- append(make([]byte, 0, 32), twinHold[0:32]...) return } @@ -311,22 +328,56 @@ func (cp *Calc) addLayer(myIdx uint) { close(cp.layerQueues[myIdx+1]) return } - - switch { - case uint64(len(slab)) > uint64(1<<(5+myIdx)): // uint64 cast needed on 32-bit systems - cp.hashSlab254(s256, myIdx, slab) - cp.layerQueues[myIdx+1] <- slab - case twinHold != nil: - copy(twinHold[32:64], slab[0:32]) - cp.hashSlab254(s256, 0, twinHold[0:64]) - cp.layerQueues[myIdx+1] <- twinHold[0:32:64] - twinHold = nil - default: - twinHold = slab[0:32:64] - // avoid code below + switch typed := message.(type) { + case []byte: + slab := typed + switch { + case uint64(len(slab)) > uint64(1<<(5+myIdx)): // uint64 cast needed on 32-bit systems + // check if we need to pull off beginning for twinHold + if twinHold != nil { + copy(twinHold[32:64], slab[0:32]) + cp.hashSlab254(s256, 0, twinHold[0:64]) + cp.layerQueues[myIdx+1] <- twinHold[0:32:64] + slab = slab[(1 << (5 + myIdx)):] + twinHold = nil + // do we still have a larger block or just a remaining twinhold? + if uint64(len(slab)) <= uint64(1<<(5+myIdx)) { + twinHold = slab[0:32:64] + continue + } + } + // check if we need to pull twinHold off the end + if len(slab)%(1<<(6+myIdx)) != 0 { + twinHold = slab[len(slab)-(len(slab)%(1<<(6+myIdx))):] + slab = slab[:len(slab)-(len(slab)%(1<<(6+myIdx)))] + twinHold = append(make([]byte, 0, 64), twinHold...) + } + cp.hashSlab254(s256, myIdx, slab) + cp.layerQueues[myIdx+1] <- slab + case twinHold != nil: + copy(twinHold[32:64], slab[0:32]) + cp.hashSlab254(s256, 0, twinHold[0:64]) + cp.layerQueues[myIdx+1] <- twinHold[0:32:64] + twinHold = nil + default: + twinHold = slab[0:32:64] + // avoid code below + continue + } + case marshall: + marshallMsg := marshall{ + twinHolds: append(typed.twinHolds, twinHold), + } + // am I last? + if myIdx == MaxLayers || cp.layerQueues[myIdx+2] == nil { + cp.result <- marshallMsg + } else { + cp.layerQueues[myIdx+1] <- marshallMsg + } continue + default: + panic("unexpected message type received in layer worker") } - // Check whether we need another worker for what we just pushed // // n.b. we will not blow out of the preallocated layerQueues array, @@ -348,6 +399,89 @@ func (cp *Calc) hashSlab254(h hash.Hash, layerIdx uint, slab []byte) { } } +// MarshalBinary is an experimental function that allows marshalling +// intermediate commP calculation state into a byte slice, which can later +// be restored with UnmarshalBinary(). This is useful for pausing and +// resuming commP calculations. +func (cp *Calc) MarshalBinary() ([]byte, error) { + cp.mu.Lock() + defer cp.mu.Unlock() + + if cp.buffer == nil { + return nil, xerrors.New("cannot marshall uninitialized commP calculator") + } + + // Flush bytes in buffer till there are less than 127 remaining + if len(cp.buffer) > 0 { + for len(cp.buffer) >= 127 { + // FIXME: there is a smarter way to do this instead of 127-at-a-time, + // but that's for another PR + cp.digestQuads(cp.buffer[:127]) + cp.buffer = cp.buffer[127:] + } + } + + cp.layerQueues[0] <- marshall{} + + msg := (<-cp.result).(marshall) + + serializedState := internal.SerializedState{ + QuadsEnqueued: cp.quadsEnqueued, + Buffer: cp.buffer, + LayerQueueTwinholds: msg.twinHolds, + } + + buf := &bytes.Buffer{} + err := serializedState.MarshalCBOR(buf) + if err != nil { + return nil, xerrors.Errorf("failed to marshall commP state: %w", err) + } + return buf.Bytes(), nil +} + +// UnmarshalBinary is an experimental function that allows restoring +// intermediate commP calculation state from a byte slice previously produced +// by MarshalBinary(). This is useful for pausing and resuming commP +// calculations. It will reset any existing state in the Calc object. +func (cp *Calc) UnmarshalBinary(data []byte) error { + cp.mu.Lock() + defer cp.mu.Unlock() + + // reset any existing state in the Calc object + if cp.buffer != nil { + // we are resetting without digesting: close everything out to terminate + // the layer workers + close(cp.layerQueues[0]) + <-cp.result + } + cp.state = state{} // reset + + var serializedState internal.SerializedState + err := serializedState.UnmarshalCBOR(bytes.NewReader(data)) + if err != nil { + return xerrors.Errorf("failed to unmarshall commP state: %w", err) + } + + cp.quadsEnqueued = serializedState.QuadsEnqueued + cp.buffer = make([]byte, 0, bufferSize) + cp.buffer = append(cp.buffer, serializedState.Buffer...) + + // re-initialize the layer 0 queue and worker + cp.layerQueues[0] = make(chan message, layerQueueDepth) + for i := uint(0); i < uint(len(serializedState.LayerQueueTwinholds)); i++ { + if len(serializedState.LayerQueueTwinholds[i]) == 0 { + cp.addLayerWithTwinhold(i, nil) + } else { + twinHold := make([]byte, 0, 64) + twinHold = append(twinHold, serializedState.LayerQueueTwinholds[i]...) + cp.addLayerWithTwinhold(i, twinHold) + } + } + cp.result = make(chan message, 1) + + return nil +} + // PadCommP is experimental, do not use it. func PadCommP(sourceCommP []byte, sourcePaddedSize, targetPaddedSize uint64) ([]byte, error) { diff --git a/commp_test.go b/commp_test.go index e659480..7d0e10f 100644 --- a/commp_test.go +++ b/commp_test.go @@ -110,7 +110,72 @@ func TestCommP(t *testing.T) { } }) } +} +func TestCommPMarshalUnmarshal(t *testing.T) { + //t.Parallel() + + tests, err := getTestCases("testdata/random.txt", testing.Short()) + if err != nil { + t.Fatal(err) + } + + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%d", test.PayloadSize), func(t *testing.T) { + //t.Parallel() + pr, pw := io.Pipe() + var randErr error + go func() { + defer pw.Close() + + // This go-routine logic is the same as in + // jbenet/go-random. It's copied here to allow doing + // parallel tests, since that library uses a singleton seed + // for the random source. + // + // Recall that go-random is used to deterministically generate + // the data in Lotus or any other source of truth for these + // tests. So it's important to have the same logic here for + // deterministic data generation. + rand := randmath.New(randmath.NewSource(1337)) + + bufsize := int64(1024 * 1024 * 4) + b := make([]byte, bufsize) + count := test.PayloadSize + for count > 0 { + if bufsize > count { + bufsize = count + b = b[:bufsize] + } + + var n uint32 + for i := int64(0); i < bufsize; { + n = rand.Uint32() + for j := 0; j < 4 && i < bufsize; j++ { + b[i] = byte(n & 0xff) + n >>= 8 + i++ + } + } + count = count - bufsize + + r := bytes.NewReader(b) + _, err := io.Copy(pw, r) + if err != nil { + randErr = err + return + } + } + }() + if err := verifyReaderSizeAndCommPMarshalUnmarshal(t, pr, test); err != nil { + t.Fatal(err) + } + if randErr != nil { + t.Fatal(err) + } + }) + } } type repeatedReader struct { @@ -215,6 +280,68 @@ func verifyReaderSizeAndCommP(t *testing.T, r io.Reader, test testCase) error { return nil } +func verifyReaderSizeAndCommPMarshalUnmarshal(t *testing.T, r io.Reader, test testCase) error { + cp := &Calc{} + + readers := make([]io.Reader, 0, 5) + // assorted readsizes stress-test + // break up the reading into 127, 25%, 254, 33%, 25%, rest + { + remaining := test.PayloadSize + + if remaining >= 127 { + readers = append(readers, io.LimitReader(r, 127)) + remaining -= 127 + } + if frac := test.PayloadSize / 4; frac >= remaining { + readers = append(readers, io.LimitReader(r, frac)) + remaining -= frac + } + if remaining >= 254 { + readers = append(readers, io.LimitReader(r, 254)) + remaining -= 254 + } + if frac := test.PayloadSize / 3; frac >= remaining { + readers = append(readers, io.LimitReader(r, frac)) + remaining -= frac + } + if frac := test.PayloadSize / 4; frac >= remaining { + readers = append(readers, io.LimitReader(r, frac)) + remaining -= frac + } + if remaining > 0 { + readers = append(readers, r) + } + } + + for _, reader := range readers { + if _, err := io.Copy(cp, reader); err != nil { + t.Fatal(err) + } + state, err := cp.MarshalBinary() + if err != nil { + t.Fatal(err) + } + cp.Reset() + newCP := &Calc{} + if err := newCP.UnmarshalBinary(state); err != nil { + t.Fatal(err) + } + cp = newCP + } + rawCommP, paddedSize, err := cp.Digest() + if err != nil { + t.Fatal(err) + } + if paddedSize != test.PieceSize { + return fmt.Errorf("produced padded size %d doesn't match expected size %d", paddedSize, test.PieceSize) + } + if !bytes.Equal(rawCommP, test.RawCommP) { + return fmt.Errorf("produced piececid 0x%X doesn't match expected 0x%X", rawCommP, test.RawCommP) + } + return nil +} + var b32dec = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) func getTestCases(path string, shortOnly bool) ([]testCase, error) { diff --git a/go.mod b/go.mod index 2f30d86..424284c 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,22 @@ module github.com/filecoin-project/go-fil-commp-hashhash go 1.24 require ( + github.com/ipfs/go-cid v0.0.6 github.com/minio/sha256-simd v1.0.1-0.20230130105256-d9c3aea9e949 + github.com/whyrusleeping/cbor-gen v0.3.1 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 ) require ( github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect + github.com/mr-tron/base58 v1.1.3 // indirect + github.com/multiformats/go-base32 v0.0.3 // indirect + github.com/multiformats/go-base36 v0.1.0 // indirect + github.com/multiformats/go-multibase v0.0.3 // indirect + github.com/multiformats/go-multihash v0.0.13 // indirect + github.com/multiformats/go-varint v0.0.5 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect golang.org/x/sys v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index ccba87c..cd876dc 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,39 @@ +github.com/ipfs/go-cid v0.0.6 h1:go0y+GcDOGeJIV01FeBsta4FHngoA4Wz7KMeLkXAhMs= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1-0.20230130105256-d9c3aea9e949 h1:/wWTRC45sBSB8czmeKwl14WL8Pd3Z+Bd3FXPPrDyPuw= github.com/minio/sha256-simd v1.0.1-0.20230130105256-d9c3aea9e949/go.mod h1:svsp3c9I8SlWYKpIFAZMgdvmFn8DIN5C9ktYpzZEj80= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0= +github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= diff --git a/internal/gen/main.go b/internal/gen/main.go new file mode 100644 index 0000000..82b6e3e --- /dev/null +++ b/internal/gen/main.go @@ -0,0 +1,16 @@ +package main + +import ( + commp "github.com/filecoin-project/go-fil-commp-hashhash/internal" + cbg "github.com/whyrusleeping/cbor-gen" +) + +func main() { + // Generate tuple-style encoders (list of fields)... + if err := cbg.WriteTupleEncodersToFile("serialized_gen.go", "internal", + commp.SerializedState{}, + // Add other types here... + ); err != nil { + panic(err) + } +} diff --git a/internal/serialized.go b/internal/serialized.go new file mode 100644 index 0000000..efa0911 --- /dev/null +++ b/internal/serialized.go @@ -0,0 +1,7 @@ +package internal + +type SerializedState struct { + QuadsEnqueued uint64 + Buffer []byte + LayerQueueTwinholds [][]byte // each layer's queued messages concatenated +} diff --git a/internal/serialized_gen.go b/internal/serialized_gen.go new file mode 100644 index 0000000..4a50761 --- /dev/null +++ b/internal/serialized_gen.go @@ -0,0 +1,189 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package internal + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +var lengthBufSerializedState = []byte{131} + +func (t *SerializedState) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufSerializedState); err != nil { + return err + } + + // t.QuadsEnqueued (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.QuadsEnqueued)); err != nil { + return err + } + + // t.Buffer ([]uint8) (slice) + if len(t.Buffer) > 2097152 { + return xerrors.Errorf("Byte array in field t.Buffer was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Buffer))); err != nil { + return err + } + + if _, err := cw.Write(t.Buffer); err != nil { + return err + } + + // t.LayerQueueTwinholds ([][]uint8) (slice) + if len(t.LayerQueueTwinholds) > 8192 { + return xerrors.Errorf("Slice value in field t.LayerQueueTwinholds was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.LayerQueueTwinholds))); err != nil { + return err + } + for _, v := range t.LayerQueueTwinholds { + if len(v) > 2097152 { + return xerrors.Errorf("Byte array in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(v))); err != nil { + return err + } + + if _, err := cw.Write(v); err != nil { + return err + } + + } + return nil +} + +func (t *SerializedState) UnmarshalCBOR(r io.Reader) (err error) { + *t = SerializedState{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.QuadsEnqueued (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.QuadsEnqueued = uint64(extra) + + } + // t.Buffer ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 2097152 { + return fmt.Errorf("t.Buffer: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Buffer = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Buffer); err != nil { + return err + } + + // t.LayerQueueTwinholds ([][]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 8192 { + return fmt.Errorf("t.LayerQueueTwinholds: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.LayerQueueTwinholds = make([][]uint8, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 2097152 { + return fmt.Errorf("t.LayerQueueTwinholds[i]: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.LayerQueueTwinholds[i] = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.LayerQueueTwinholds[i]); err != nil { + return err + } + + } + } + return nil +}