gecko/snow/engine/avalanche/state/vertex.go

145 lines
3.4 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package state
import (
"bytes"
"errors"
"sort"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
"github.com/ava-labs/gecko/snow/engine/avalanche"
"github.com/ava-labs/gecko/utils"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/wrappers"
)
// maxSize is the maximum allowed vertex size. It is necessary to deter DoS.
const maxSize = 1 << 20
var (
errBadCodec = errors.New("invalid codec")
errExtraSpace = errors.New("trailing buffer space")
errInvalidParents = errors.New("vertex contains non-sorted or duplicated parentIDs")
errInvalidTxs = errors.New("vertex contains non-sorted or duplicated transactions")
)
type vertex struct {
id ids.ID
chainID ids.ID
height uint64
parentIDs []ids.ID
txs []snowstorm.Tx
bytes []byte
}
func (vtx *vertex) ID() ids.ID { return vtx.id }
func (vtx *vertex) Bytes() []byte { return vtx.bytes }
func (vtx *vertex) Verify() error {
switch {
case !ids.IsSortedAndUniqueIDs(vtx.parentIDs):
return errInvalidParents
case !isSortedAndUniqueTxs(vtx.txs):
return errInvalidTxs
default:
return nil
}
}
/*
* Vertex:
* Codec | 04 Bytes
* Chain | 32 Bytes
* Height | 08 Bytes
* NumParents | 04 Bytes
* Repeated (NumParents):
* ParentID | 32 bytes
* NumTxs | 04 Bytes
* Repeated (NumTxs):
* TxSize | 04 bytes
* Tx | ?? bytes
*/
// Marshal creates the byte representation of the vertex
func (vtx *vertex) Marshal() ([]byte, error) {
p := wrappers.Packer{MaxSize: maxSize}
p.PackInt(uint32(CustomID))
p.PackFixedBytes(vtx.chainID.Bytes())
p.PackLong(vtx.height)
p.PackInt(uint32(len(vtx.parentIDs)))
for _, parentID := range vtx.parentIDs {
p.PackFixedBytes(parentID.Bytes())
}
p.PackInt(uint32(len(vtx.txs)))
for _, tx := range vtx.txs {
p.PackBytes(tx.Bytes())
}
return p.Bytes, p.Err
}
// Unmarshal attempts to set the contents of this vertex to the value encoded in
// the stream of bytes.
func (vtx *vertex) Unmarshal(b []byte, vm avalanche.DAGVM) error {
p := wrappers.Packer{Bytes: b}
if codecID := ID(p.UnpackInt()); codecID != CustomID {
p.Add(errBadCodec)
}
chainID, _ := ids.ToID(p.UnpackFixedBytes(hashing.HashLen))
height := p.UnpackLong()
parentIDs := []ids.ID(nil)
for i := p.UnpackInt(); i > 0 && !p.Errored(); i-- {
parentID, _ := ids.ToID(p.UnpackFixedBytes(hashing.HashLen))
parentIDs = append(parentIDs, parentID)
}
txs := []snowstorm.Tx(nil)
for i := p.UnpackInt(); i > 0 && !p.Errored(); i-- {
tx, err := vm.ParseTx(p.UnpackBytes())
p.Add(err)
txs = append(txs, tx)
}
if p.Offset != len(b) {
p.Add(errExtraSpace)
}
if p.Errored() {
return p.Err
}
*vtx = vertex{
id: ids.NewID(hashing.ComputeHash256Array(b)),
parentIDs: parentIDs,
chainID: chainID,
height: height,
txs: txs,
bytes: b,
}
return nil
}
type sortTxsData []snowstorm.Tx
func (txs sortTxsData) Less(i, j int) bool {
return bytes.Compare(txs[i].ID().Bytes(), txs[j].ID().Bytes()) == -1
}
func (txs sortTxsData) Len() int { return len(txs) }
func (txs sortTxsData) Swap(i, j int) { txs[j], txs[i] = txs[i], txs[j] }
func sortTxs(txs []snowstorm.Tx) { sort.Sort(sortTxsData(txs)) }
func isSortedAndUniqueTxs(txs []snowstorm.Tx) bool {
return utils.IsSortedAndUnique(sortTxsData(txs))
}