gecko/vms/spchainvm/codec.go

394 lines
8.9 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spchainvm
import (
"errors"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/wrappers"
)
var (
errBadCodec = errors.New("wrong or unknown codec used")
errExtraSpace = errors.New("trailing buffer space")
errNil = errors.New("nil value is invalid")
)
// CodecID is an identifier for a codec
type CodecID uint32
// Codec types
const (
NoID CodecID = iota
GenericID
CustomID
// TODO: Utilize a standard serialization library. Must have a canonical
// serialization format.
)
// Verify that the codec is a known codec value. Returns nil if the codec is
// valid.
func (c CodecID) Verify() error {
switch c {
case NoID, GenericID, CustomID:
return nil
default:
return errBadCodec
}
}
func (c CodecID) String() string {
switch c {
case NoID:
return "No Codec"
case GenericID:
return "Generic Codec"
case CustomID:
return "Custom Codec"
default:
return "Unknown Codec"
}
}
// Codec is used to serialize and de-serialize transaction objects
type Codec struct{}
/*
******************************************************************************
*********************************** Genesis **********************************
******************************************************************************
*/
/* Genesis:
* Accounts | ? Bytes
*/
// MarshalGenesis returns the byte representation of the genesis
func (c *Codec) MarshalGenesis(accounts []Account) ([]byte, error) {
return c.MarshalAccounts(accounts)
}
// UnmarshalGenesis attempts to parse the genesis
func (c *Codec) UnmarshalGenesis(b []byte) ([]Account, error) {
return c.UnmarshalAccounts(b)
}
/*
******************************************************************************
************************************ Block ***********************************
******************************************************************************
*/
/* Block:
* Codec | 04 Bytes
* ParentID | 32 Bytes
* NumTxs | 04 bytes
* Repeated (NumTxs):
* Tx | ? bytes
*/
const baseOpSize = 40
// MarshalBlock returns the byte representation of the block
func (c *Codec) MarshalBlock(block *Block) ([]byte, error) {
p := wrappers.Packer{Bytes: make([]byte, baseOpSize+signedTxSize*len(block.txs))}
c.marshalBlock(block, &p)
if p.Offset != len(p.Bytes) {
p.Add(errExtraSpace)
}
return p.Bytes, p.Err
}
func (c *Codec) marshalBlock(block *Block, p *wrappers.Packer) {
if block == nil {
p.Add(errNil)
return
}
p.PackInt(uint32(CustomID))
p.PackFixedBytes(block.parentID.Bytes())
p.PackInt(uint32(len(block.txs)))
for _, tx := range block.txs {
if tx != nil {
p.PackFixedBytes(tx.Bytes())
} else {
p.Add(errNil)
return
}
}
}
// UnmarshalBlock attempts to parse an block from a byte array
func (c *Codec) UnmarshalBlock(b []byte) (*Block, error) {
p := wrappers.Packer{Bytes: b}
block := c.unmarshalBlock(&p)
if p.Offset != len(b) {
p.Add(errExtraSpace)
}
return block, p.Err
}
func (c *Codec) unmarshalBlock(p *wrappers.Packer) *Block {
start := p.Offset
if codecID := CodecID(p.UnpackInt()); codecID != CustomID {
p.Add(errBadCodec)
}
parentID, _ := ids.ToID(p.UnpackFixedBytes(hashing.HashLen))
txs := []*Tx(nil)
for i := p.UnpackInt(); i > 0 && !p.Errored(); i-- {
txs = append(txs, c.unmarshalTx(p))
}
if p.Errored() {
return nil
}
bytes := p.Bytes[start:p.Offset]
return &Block{
id: ids.NewID(hashing.ComputeHash256Array(bytes)),
parentID: parentID,
txs: txs,
bytes: bytes,
}
}
/*
******************************************************************************
************************************* Tx *************************************
******************************************************************************
*/
/* Unsigned Tx:
* Codec | 04 Bytes
* Network ID | 04 bytes
* Chain ID | 32 bytes
* Nonce | 08 bytes
* Amount | 08 bytes
* Destination | 20 bytes
*/
const unsignedTxSize = 2*wrappers.IntLen + 2*wrappers.LongLen + hashing.AddrLen + hashing.HashLen
/* Tx:
* Unsigned Tx | 76 bytes
* Signature | 65 bytes
*/
const signedTxSize = unsignedTxSize + crypto.SECP256K1RSigLen
// MarshalUnsignedTx returns the byte representation of the unsigned tx
func (c *Codec) MarshalUnsignedTx(tx *Tx) ([]byte, error) {
p := wrappers.Packer{Bytes: make([]byte, unsignedTxSize)}
c.marshalUnsignedTx(tx, &p)
if p.Offset != len(p.Bytes) {
p.Add(errExtraSpace)
}
return p.Bytes, p.Err
}
// MarshalTx returns the byte representation of the tx
func (c *Codec) MarshalTx(tx *Tx) ([]byte, error) {
p := wrappers.Packer{Bytes: make([]byte, signedTxSize)}
c.marshalTx(tx, &p)
if p.Offset != len(p.Bytes) {
p.Add(errExtraSpace)
}
return p.Bytes, p.Err
}
func (c *Codec) marshalUnsignedTx(tx *Tx, p *wrappers.Packer) {
if tx == nil {
p.Add(errNil)
return
}
p.PackInt(uint32(CustomID))
p.PackInt(tx.networkID)
p.PackFixedBytes(tx.chainID.Bytes())
p.PackLong(tx.nonce)
p.PackLong(tx.amount)
p.PackFixedBytes(tx.to.Bytes())
}
func (c *Codec) marshalTx(tx *Tx, p *wrappers.Packer) {
c.marshalUnsignedTx(tx, p)
p.PackFixedBytes(tx.sig)
}
// UnmarshalTx attempts to convert the stream of bytes into a representation
// of a tx
func (c *Codec) UnmarshalTx(b []byte) (*Tx, error) {
p := wrappers.Packer{Bytes: b}
tx := c.unmarshalTx(&p)
if p.Offset != len(b) {
p.Add(errExtraSpace)
}
return tx, p.Err
}
func (c *Codec) unmarshalTx(p *wrappers.Packer) *Tx {
start := p.Offset
if codecID := CodecID(p.UnpackInt()); codecID != CustomID {
p.Add(errBadCodec)
}
networkID := p.UnpackInt()
chainID, _ := ids.ToID(p.UnpackFixedBytes(hashing.HashLen))
nonce := p.UnpackLong()
amount := p.UnpackLong()
destination, _ := ids.ToShortID(p.UnpackFixedBytes(hashing.AddrLen))
sig := p.UnpackFixedBytes(crypto.SECP256K1RSigLen)
if p.Errored() {
return nil
}
bytes := p.Bytes[start:p.Offset]
return &Tx{
id: ids.NewID(hashing.ComputeHash256Array(bytes)),
networkID: networkID,
chainID: chainID,
nonce: nonce,
amount: amount,
to: destination,
sig: sig,
bytes: bytes,
verification: make(chan error, 1),
}
}
/*
******************************************************************************
********************************** Accounts **********************************
******************************************************************************
*/
/* Accounts:
* NumAccounts | 04 Bytes
* Repeated (NumAccounts):
* Account | 36 bytes
*/
const baseAccountsSize = 4
// MarshalAccounts returns the byte representation of a list of accounts
func (c *Codec) MarshalAccounts(accounts []Account) ([]byte, error) {
p := wrappers.Packer{Bytes: make([]byte, baseAccountsSize+accountSize*len(accounts))}
c.marshalAccounts(accounts, &p)
if p.Offset != len(p.Bytes) {
p.Add(errExtraSpace)
}
return p.Bytes, p.Err
}
func (c *Codec) marshalAccounts(accounts []Account, p *wrappers.Packer) {
p.PackInt(uint32(len(accounts)))
for _, account := range accounts {
c.marshalAccount(account, p)
}
}
// UnmarshalAccounts attempts to parse a list of accounts from a byte array
func (c *Codec) UnmarshalAccounts(b []byte) ([]Account, error) {
p := wrappers.Packer{Bytes: b}
account := c.unmarshalAccounts(&p)
if p.Offset != len(b) {
p.Add(errExtraSpace)
}
return account, p.Err
}
func (c *Codec) unmarshalAccounts(p *wrappers.Packer) []Account {
accounts := []Account(nil)
for i := p.UnpackInt(); i > 0 && !p.Errored(); i-- {
accounts = append(accounts, c.unmarshalAccount(p))
}
return accounts
}
/*
******************************************************************************
*********************************** Account **********************************
******************************************************************************
*/
/* Account:
* ID | 20 bytes
* Nonce | 08 bytes
* Balance | 08 bytes
*/
const accountSize = 36
// MarshalAccount returns the byte representation of the account
func (c *Codec) MarshalAccount(account Account) ([]byte, error) {
p := wrappers.Packer{Bytes: make([]byte, accountSize)}
c.marshalAccount(account, &p)
if p.Offset != len(p.Bytes) {
p.Add(errExtraSpace)
}
return p.Bytes, p.Err
}
func (c *Codec) marshalAccount(account Account, p *wrappers.Packer) {
p.PackFixedBytes(account.id.Bytes())
p.PackLong(account.nonce)
p.PackLong(account.balance)
}
// UnmarshalAccount attempts to parse an account from a byte array
func (c *Codec) UnmarshalAccount(b []byte) (Account, error) {
p := wrappers.Packer{Bytes: b}
account := c.unmarshalAccount(&p)
if p.Offset != len(b) {
p.Add(errExtraSpace)
}
return account, p.Err
}
func (c *Codec) unmarshalAccount(p *wrappers.Packer) Account {
id, _ := ids.ToShortID(p.UnpackFixedBytes(hashing.AddrLen))
nonce := p.UnpackLong()
balance := p.UnpackLong()
return Account{
id: id,
nonce: nonce,
balance: balance,
}
}