mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
Full-codec-kill wave 2: proposervm + xsvm + components + evm + platformvm-residual → native ZAP
Kills pcodecs from 3 complete subsystems (verified go test ./... = 155 ok / 0 FAIL):
- proposervm: block/state/summary struct-is-wire (blockwire.go + statewire.go;
deleted block/state/summary codec.go). blockKind bytes: reserved=0 signed=1
option=2. Epoch inlined in unsigned object. proposer/windower chainSource seed
= binary.LittleEndian (byte-identical to old wrappers.Packer.UnpackLong).
- example/xsvm: tx/block/genesis native Marshal (deleted 3 codec.go); create-chain
example uses genesis.Marshal().
- components: message.Tx native (deleted codec.go); keystore codec shell removed;
index pcodecs.LongLen → local const.
- evm/{predicate,lp176}: off pcodecs.
- platformvm residual: state metadata + genesis + metrics + signer + stakeable
native, vestigial serialize tags removed.
REMAINING (careful solo, agents weekly-capped): warp (reverted — agent left an
ID≠Marshal consensus inconsistency in ChainToL1Conversion; needs proper review,
not a golden-regen) + xvm (reverted — barely started). pcodecs package stays
until those 2 land. /ext/→/v1/ endpoint change also pending.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/luxfi/database/prefixdb"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/utils/wrappers"
|
||||
lux "github.com/luxfi/utxo"
|
||||
)
|
||||
|
||||
@@ -143,7 +143,7 @@ func (i *indexer) Accept(txID ids.ID, inputUTXOs []*lux.UTXO, outputUTXOs []*lux
|
||||
idx = binary.BigEndian.Uint64(idxBytes)
|
||||
case database.ErrNotFound:
|
||||
// idx not found; this must be the first entry.
|
||||
idxBytes = make([]byte, pcodecs.LongLen)
|
||||
idxBytes = make([]byte, wrappers.LongLen)
|
||||
default:
|
||||
// Unexpected error
|
||||
return fmt.Errorf("unexpected error when indexing txID %s: %w", txID, err)
|
||||
@@ -184,7 +184,7 @@ func (i *indexer) Read(address []byte, assetID ids.ID, cursor, pageSize uint64)
|
||||
assetPrefixDB := prefixdb.New(assetID[:], addressTxDB)
|
||||
|
||||
// get cursor in bytes
|
||||
cursorBytes := make([]byte, pcodecs.LongLen)
|
||||
cursorBytes := make([]byte, wrappers.LongLen)
|
||||
binary.BigEndian.PutUint64(cursorBytes, cursor)
|
||||
|
||||
// start reading from the cursor bytes, numeric keys maintain the order (see Accept)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
const CodecVersion = 0
|
||||
|
||||
var (
|
||||
Codec pcodecs.Manager
|
||||
LegacyCodec pcodecs.Manager
|
||||
)
|
||||
|
||||
func init() {
|
||||
c := pcodecs.NewLinearCodec()
|
||||
Codec = pcodecs.NewDefaultManager()
|
||||
lc := pcodecs.NewLinearCodec()
|
||||
LegacyCodec = pcodecs.NewMaxInt32Manager()
|
||||
|
||||
err := errors.Join(
|
||||
Codec.RegisterCodec(CodecVersion, c),
|
||||
LegacyCodec.RegisterCodec(CodecVersion, lc),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -71,9 +71,34 @@ func (u *user) GetAddresses() ([]ids.ShortID, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addresses []ids.ShortID
|
||||
_, err = LegacyCodec.Unmarshal(addressBytes, &addresses)
|
||||
return addresses, err
|
||||
return parseAddresses(addressBytes)
|
||||
}
|
||||
|
||||
// marshalAddresses encodes the user's controlled addresses as the flat
|
||||
// concatenation of their 20-byte values. ids.ShortID is fixed-width, so the
|
||||
// count is implied by len/ShortIDLen — no length prefix or codec is needed.
|
||||
func marshalAddresses(addresses []ids.ShortID) []byte {
|
||||
b := make([]byte, 0, len(addresses)*ids.ShortIDLen)
|
||||
for i := range addresses {
|
||||
b = append(b, addresses[i][:]...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// parseAddresses is the inverse of marshalAddresses.
|
||||
func parseAddresses(b []byte) ([]ids.ShortID, error) {
|
||||
if len(b)%ids.ShortIDLen != 0 {
|
||||
return nil, fmt.Errorf("keystore: address blob length %d is not a multiple of %d", len(b), ids.ShortIDLen)
|
||||
}
|
||||
n := len(b) / ids.ShortIDLen
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
addresses := make([]ids.ShortID, n)
|
||||
for i := 0; i < n; i++ {
|
||||
copy(addresses[i][:], b[i*ids.ShortIDLen:])
|
||||
}
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func (u *user) PutKeys(privKeys ...*secp256k1.PrivateKey) error {
|
||||
@@ -119,11 +144,7 @@ func (u *user) PutKeys(privKeys ...*secp256k1.PrivateKey) error {
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
|
||||
addressBytes, err := Codec.Marshal(CodecVersion, addresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.db.Put(addressesKey, addressBytes)
|
||||
return u.db.Put(addressesKey, marshalAddresses(addresses))
|
||||
}
|
||||
|
||||
func (u *user) GetKey(address ids.ShortID) (*secp256k1.PrivateKey, error) {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package message
|
||||
|
||||
import (
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
codecVersion = 0
|
||||
maxMessageSize = 512 * constants.KiB
|
||||
maxSliceLen = maxMessageSize
|
||||
)
|
||||
|
||||
// Codec does serialization and deserialization
|
||||
var c pcodecs.Manager
|
||||
|
||||
func init() {
|
||||
c = pcodecs.NewManager(maxMessageSize)
|
||||
lc := pcodecs.NewLinearCodec()
|
||||
|
||||
err := utils.Err(
|
||||
lc.RegisterType(&Tx{}),
|
||||
c.RegisterCodec(codecVersion, lc),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,45 @@
|
||||
|
||||
package message
|
||||
|
||||
// Native ZAP wire for gossip messages: the struct IS the wire. Each message
|
||||
// is one zap object keyed by a 1-byte kind discriminator at object offset 0 —
|
||||
// the whole dispatch. Parse reads it and returns the typed message. There is
|
||||
// no codec, no version prefix, no slot map.
|
||||
//
|
||||
// Object fixed section (offsets object-relative, little-endian):
|
||||
//
|
||||
// kind u8 @ 0 tx=1
|
||||
// Tx bytes @ 1 ptr to the gossiped tx bytes (Tx only)
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Message = (*Tx)(nil)
|
||||
|
||||
ErrUnexpectedCodecVersion = errors.New("unexpected codec version")
|
||||
// ErrUnknownMessageKind is returned when the 1-byte kind discriminator at
|
||||
// object offset 0 does not correspond to a known message type.
|
||||
ErrUnknownMessageKind = errors.New("unknown message kind")
|
||||
)
|
||||
|
||||
// msgKind is the 1-byte discriminator at object offset 0 of every message
|
||||
// buffer. Parse reads it and dispatches to the typed message.
|
||||
type msgKind uint8
|
||||
|
||||
const (
|
||||
msgKindReserved msgKind = iota
|
||||
msgKindTx
|
||||
)
|
||||
|
||||
// Fixed wire offsets (object-relative) and object size for a Tx message.
|
||||
const (
|
||||
offMsgKind = 0
|
||||
offMsgTx = 1
|
||||
sizeMsgTx = 9 // kind(1) + bytes ptr(8)
|
||||
)
|
||||
|
||||
type Message interface {
|
||||
@@ -26,6 +55,11 @@ type Message interface {
|
||||
//
|
||||
// Bytes should only be called after being initialized
|
||||
Bytes() []byte
|
||||
|
||||
// marshal encodes the message to its native ZAP wire form (kind byte at
|
||||
// object offset 0). Each concrete type writes its own kind — this is the
|
||||
// whole dispatch, no codec.
|
||||
marshal() ([]byte, error)
|
||||
}
|
||||
|
||||
type message []byte
|
||||
@@ -39,20 +73,26 @@ func (m *message) Bytes() []byte {
|
||||
}
|
||||
|
||||
func Parse(bytes []byte) (Message, error) {
|
||||
var msg Message
|
||||
version, err := c.Unmarshal(bytes, &msg)
|
||||
zmsg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != codecVersion {
|
||||
return nil, ErrUnexpectedCodecVersion
|
||||
obj := zmsg.Root()
|
||||
switch msgKind(obj.Uint8(offMsgKind)) {
|
||||
case msgKindTx:
|
||||
msg := &Tx{Tx: append([]byte(nil), obj.Bytes(offMsgTx)...)}
|
||||
msg.initialize(bytes)
|
||||
return msg, nil
|
||||
default:
|
||||
return nil, ErrUnknownMessageKind
|
||||
}
|
||||
msg.initialize(bytes)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func Build(msg Message) ([]byte, error) {
|
||||
bytes, err := c.Marshal(codecVersion, &msg)
|
||||
bytes, err := msg.marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.initialize(bytes)
|
||||
return bytes, err
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
@@ -3,16 +3,30 @@
|
||||
|
||||
package message
|
||||
|
||||
import "github.com/luxfi/ids"
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
var _ Message = (*Tx)(nil)
|
||||
|
||||
type Tx struct {
|
||||
message
|
||||
|
||||
Tx []byte `serialize:"true"`
|
||||
Tx []byte
|
||||
}
|
||||
|
||||
func (msg *Tx) Handle(handler Handler, nodeID ids.NodeID, requestID uint32) error {
|
||||
return handler.HandleTx(nodeID, requestID, msg)
|
||||
}
|
||||
|
||||
// marshal writes the Tx message as one native ZAP object: kind byte at
|
||||
// offset 0, gossiped tx bytes at offset 1.
|
||||
func (msg *Tx) marshal() ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeMsgTx + len(msg.Tx))
|
||||
ob := b.StartObject(sizeMsgTx)
|
||||
ob.SetUint8(offMsgKind, uint8(msgKindTx))
|
||||
ob.SetBytes(offMsgTx, msg.Tx)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
safemath "github.com/luxfi/math"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/utils/wrappers"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -35,7 +35,7 @@ const (
|
||||
MinMaxPerSecond = MinTargetPerSecond * TargetToMax
|
||||
MinMaxCapacity = MinMaxPerSecond * TimeToFillCapacity
|
||||
|
||||
StateSize = 3 * pcodecs.LongLen
|
||||
StateSize = 3 * wrappers.LongLen
|
||||
|
||||
maxTargetExcess = 1_024_950_627 // TargetConversion * ln(MaxUint64 / MinTargetPerSecond) + 1
|
||||
)
|
||||
@@ -63,9 +63,9 @@ func ParseState(bytes []byte) (State, error) {
|
||||
return State{
|
||||
Gas: gas.State{
|
||||
Capacity: gas.Gas(binary.BigEndian.Uint64(bytes)),
|
||||
Excess: gas.Gas(binary.BigEndian.Uint64(bytes[pcodecs.LongLen:])),
|
||||
Excess: gas.Gas(binary.BigEndian.Uint64(bytes[wrappers.LongLen:])),
|
||||
},
|
||||
TargetExcess: gas.Gas(binary.BigEndian.Uint64(bytes[2*pcodecs.LongLen:])),
|
||||
TargetExcess: gas.Gas(binary.BigEndian.Uint64(bytes[2*wrappers.LongLen:])),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -172,8 +172,8 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) {
|
||||
func (s *State) Bytes() []byte {
|
||||
bytes := make([]byte, StateSize)
|
||||
binary.BigEndian.PutUint64(bytes, uint64(s.Gas.Capacity))
|
||||
binary.BigEndian.PutUint64(bytes[pcodecs.LongLen:], uint64(s.Gas.Excess))
|
||||
binary.BigEndian.PutUint64(bytes[2*pcodecs.LongLen:], uint64(s.TargetExcess))
|
||||
binary.BigEndian.PutUint64(bytes[wrappers.LongLen:], uint64(s.Gas.Excess))
|
||||
binary.BigEndian.PutUint64(bytes[2*wrappers.LongLen:], uint64(s.TargetExcess))
|
||||
return bytes
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
safemath "github.com/luxfi/math"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/utils/wrappers"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -35,7 +35,7 @@ const (
|
||||
MinMaxPerSecond = MinTargetPerSecond * TargetToMax
|
||||
MinMaxCapacity = MinMaxPerSecond * TimeToFillCapacity
|
||||
|
||||
StateSize = 3 * pcodecs.LongLen
|
||||
StateSize = 3 * wrappers.LongLen
|
||||
|
||||
maxTargetExcess = 1_024_950_627 // TargetConversion * ln(MaxUint64 / MinTargetPerSecond) + 1
|
||||
)
|
||||
@@ -63,9 +63,9 @@ func ParseState(bytes []byte) (State, error) {
|
||||
return State{
|
||||
Gas: gas.State{
|
||||
Capacity: gas.Gas(binary.BigEndian.Uint64(bytes)),
|
||||
Excess: gas.Gas(binary.BigEndian.Uint64(bytes[pcodecs.LongLen:])),
|
||||
Excess: gas.Gas(binary.BigEndian.Uint64(bytes[wrappers.LongLen:])),
|
||||
},
|
||||
TargetExcess: gas.Gas(binary.BigEndian.Uint64(bytes[2*pcodecs.LongLen:])),
|
||||
TargetExcess: gas.Gas(binary.BigEndian.Uint64(bytes[2*wrappers.LongLen:])),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -161,8 +161,8 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) {
|
||||
func (s *State) Bytes() []byte {
|
||||
bytes := make([]byte, StateSize)
|
||||
binary.BigEndian.PutUint64(bytes, uint64(s.Gas.Capacity))
|
||||
binary.BigEndian.PutUint64(bytes[pcodecs.LongLen:], uint64(s.Gas.Excess))
|
||||
binary.BigEndian.PutUint64(bytes[2*pcodecs.LongLen:], uint64(s.TargetExcess))
|
||||
binary.BigEndian.PutUint64(bytes[wrappers.LongLen:], uint64(s.Gas.Excess))
|
||||
binary.BigEndian.PutUint64(bytes[2*wrappers.LongLen:], uint64(s.TargetExcess))
|
||||
return bytes
|
||||
}
|
||||
|
||||
|
||||
+187
-32
@@ -3,46 +3,51 @@
|
||||
|
||||
package predicate
|
||||
|
||||
// Native ZAP wire for predicate results: the nested map IS the wire. There is
|
||||
// no codec, no version prefix, no slot map. The value is a
|
||||
// map[txHash]map[precompileAddr]resultBytes; each level is encoded as a
|
||||
// (u32 per-entry length list, concatenated blob) so a variable number of
|
||||
// variable-length children packs into one zap object. Map keys are sorted by
|
||||
// their raw bytes before encoding so the output is deterministic — required
|
||||
// because these results are committed as part of block building/verification.
|
||||
//
|
||||
// Object fixed sections (offsets object-relative, little-endian):
|
||||
//
|
||||
// root txLenList ptr @0, txBlob ptr @8 size 16
|
||||
// txEntry hash 32B @0, addrLenList ptr @32, addrBlob ptr @40 size 48
|
||||
// addrEntry addr 20B @0, result bytes ptr @20 size 28
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
version = 0
|
||||
// addrEntry: precompile address + its result bytes.
|
||||
prAddrAddr = 0
|
||||
prAddrBytes = 20
|
||||
prAddrSize = 28
|
||||
|
||||
// The Results maximum size should comfortably exceed the maximum value that could happen in practice,
|
||||
// so that a correct block builder will not attempt to build a block and fail to marshal the predicate results using the codec.
|
||||
//
|
||||
// We make this easy to reason about by assigning a minimum gas cost to the `PredicateGas` function of precompiles.
|
||||
// In the case of Warp, the minimum gas cost is set to 200k gas, which can lead to at most 32 additional bytes being included in Results.
|
||||
//
|
||||
// The additional bytes come from the transaction hash (32 bytes), length of tx predicate results (4 bytes),
|
||||
// the precompile address (20 bytes), length of the bytes result (4 bytes), and the additional byte in the results bitset (1 byte).
|
||||
//
|
||||
// This results in 200k gas contributing a maximum of 61 additional bytes to Result.
|
||||
// For a block with a maximum gas limit of 100M, the block can include up to 500 validated predicates based contributing to the size of Result.
|
||||
//
|
||||
// At 61 bytes / validated predicate, this yields ~30KB, which is well short of the 1MB cap.
|
||||
maxResultsSize = constants.MiB
|
||||
// txEntry: tx hash + the per-address length list and blob.
|
||||
prTxHash = 0
|
||||
prTxAddrLen = 32
|
||||
prTxAddrBlob = 40
|
||||
prTxSize = 48
|
||||
|
||||
// root: the per-tx length list and blob.
|
||||
prRootTxLen = 0
|
||||
prRootTxBlob = 8
|
||||
prRootSize = 16
|
||||
|
||||
prLenStride = 4 // uint32 per-entry length
|
||||
)
|
||||
|
||||
var resultsCodec pcodecs.Manager
|
||||
|
||||
func init() {
|
||||
resultsCodec = pcodecs.NewManager(maxResultsSize)
|
||||
|
||||
c := pcodecs.NewLinearCodec()
|
||||
if err := resultsCodec.RegisterCodec(version, c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// PrecompileResults is a map of results for each precompile address to the
|
||||
// resulting bitset.
|
||||
@@ -58,8 +63,7 @@ type (
|
||||
|
||||
// ParseBlockResults parses bytes into predicate results.
|
||||
func ParseBlockResults(b []byte) (BlockResults, error) {
|
||||
var encodedResults encodedBlockResults
|
||||
_, err := resultsCodec.Unmarshal(b, &encodedResults)
|
||||
encodedResults, err := parseEncodedBlockResults(b)
|
||||
if err != nil {
|
||||
return BlockResults{}, fmt.Errorf("failed to unmarshal predicate results: %w", err)
|
||||
}
|
||||
@@ -102,7 +106,7 @@ func (b *BlockResults) Set(txHash common.Hash, txResults PrecompileResults) {
|
||||
// Bytes marshals the predicate results.
|
||||
func (b *BlockResults) Bytes() ([]byte, error) {
|
||||
// Convert to results representation before marshaling to avoid serializing
|
||||
// set.Bits directly, which is not supported by the codec.
|
||||
// set.Bits directly, which is not supported by the native wire.
|
||||
results := make(encodedBlockResults, len(*b))
|
||||
for txHash, addrToBits := range *b {
|
||||
encoded := make(map[common.Address][]byte, len(addrToBits))
|
||||
@@ -112,5 +116,156 @@ func (b *BlockResults) Bytes() ([]byte, error) {
|
||||
results[txHash] = encoded
|
||||
}
|
||||
|
||||
return resultsCodec.Marshal(version, results)
|
||||
return marshalEncodedBlockResults(results), nil
|
||||
}
|
||||
|
||||
// marshalEncodedBlockResults encodes the nested map as one native ZAP object
|
||||
// tree. Tx hashes are sorted so the output is byte-deterministic.
|
||||
func marshalEncodedBlockResults(results encodedBlockResults) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + prRootSize + 256)
|
||||
|
||||
hashes := make([]common.Hash, 0, len(results))
|
||||
for h := range results {
|
||||
hashes = append(hashes, h)
|
||||
}
|
||||
slices.SortFunc(hashes, func(a, b common.Hash) int { return bytes.Compare(a[:], b[:]) })
|
||||
|
||||
var (
|
||||
txBlob []byte
|
||||
lenOff int
|
||||
lenCount int
|
||||
)
|
||||
if len(hashes) > 0 {
|
||||
lb := b.StartList(prLenStride)
|
||||
for _, h := range hashes {
|
||||
entry := marshalTxEntry(h, results[h])
|
||||
lb.AddUint32(uint32(len(entry)))
|
||||
txBlob = append(txBlob, entry...)
|
||||
}
|
||||
lenOff, lenCount = lb.Finish()
|
||||
}
|
||||
|
||||
ob := b.StartObject(prRootSize)
|
||||
ob.SetList(prRootTxLen, lenOff, lenCount)
|
||||
ob.SetBytes(prRootTxBlob, txBlob)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// marshalTxEntry encodes one (txHash → addr map) as its own zap object.
|
||||
// Addresses are sorted for determinism.
|
||||
func marshalTxEntry(hash common.Hash, addrToBytes map[common.Address][]byte) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + prTxSize + 128)
|
||||
|
||||
addrs := make([]common.Address, 0, len(addrToBytes))
|
||||
for a := range addrToBytes {
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
slices.SortFunc(addrs, func(a, b common.Address) int { return bytes.Compare(a[:], b[:]) })
|
||||
|
||||
var (
|
||||
addrBlob []byte
|
||||
lenOff int
|
||||
lenCount int
|
||||
)
|
||||
if len(addrs) > 0 {
|
||||
lb := b.StartList(prLenStride)
|
||||
for _, a := range addrs {
|
||||
entry := marshalAddrEntry(a, addrToBytes[a])
|
||||
lb.AddUint32(uint32(len(entry)))
|
||||
addrBlob = append(addrBlob, entry...)
|
||||
}
|
||||
lenOff, lenCount = lb.Finish()
|
||||
}
|
||||
|
||||
ob := b.StartObject(prTxSize)
|
||||
ob.SetBytesFixed(prTxHash, hash[:])
|
||||
ob.SetList(prTxAddrLen, lenOff, lenCount)
|
||||
ob.SetBytes(prTxAddrBlob, addrBlob)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// marshalAddrEntry encodes one (precompile address → result bytes) as its own
|
||||
// zap object.
|
||||
func marshalAddrEntry(addr common.Address, result []byte) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + prAddrSize + len(result))
|
||||
ob := b.StartObject(prAddrSize)
|
||||
ob.SetBytesFixed(prAddrAddr, addr[:])
|
||||
ob.SetBytes(prAddrBytes, result)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
func parseEncodedBlockResults(bs []byte) (encodedBlockResults, error) {
|
||||
zmsg, err := zap.Parse(bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root := zmsg.Root()
|
||||
lengths := root.ListStride(prRootTxLen, prLenStride)
|
||||
n := lengths.Len()
|
||||
results := make(encodedBlockResults, n)
|
||||
if n == 0 {
|
||||
return results, nil
|
||||
}
|
||||
blob := root.Bytes(prRootTxBlob)
|
||||
cursor := 0
|
||||
for i := 0; i < n; i++ {
|
||||
size := int(lengths.Uint32(i))
|
||||
if cursor+size > len(blob) {
|
||||
return nil, fmt.Errorf("predicate: tx entry %d length %d overruns blob (%d)", i, size, len(blob))
|
||||
}
|
||||
hash, addrMap, err := parseTxEntry(blob[cursor : cursor+size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[hash] = addrMap
|
||||
cursor += size
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func parseTxEntry(bs []byte) (common.Hash, map[common.Address][]byte, error) {
|
||||
zmsg, err := zap.Parse(bs)
|
||||
if err != nil {
|
||||
return common.Hash{}, nil, err
|
||||
}
|
||||
obj := zmsg.Root()
|
||||
var hash common.Hash
|
||||
copy(hash[:], obj.BytesFixedSlice(prTxHash, len(hash)))
|
||||
|
||||
lengths := obj.ListStride(prTxAddrLen, prLenStride)
|
||||
n := lengths.Len()
|
||||
addrMap := make(map[common.Address][]byte, n)
|
||||
if n == 0 {
|
||||
return hash, addrMap, nil
|
||||
}
|
||||
blob := obj.Bytes(prTxAddrBlob)
|
||||
cursor := 0
|
||||
for i := 0; i < n; i++ {
|
||||
size := int(lengths.Uint32(i))
|
||||
if cursor+size > len(blob) {
|
||||
return common.Hash{}, nil, fmt.Errorf("predicate: addr entry %d length %d overruns blob (%d)", i, size, len(blob))
|
||||
}
|
||||
addr, result, err := parseAddrEntry(blob[cursor : cursor+size])
|
||||
if err != nil {
|
||||
return common.Hash{}, nil, err
|
||||
}
|
||||
addrMap[addr] = result
|
||||
cursor += size
|
||||
}
|
||||
return hash, addrMap, nil
|
||||
}
|
||||
|
||||
func parseAddrEntry(bs []byte) (common.Address, []byte, error) {
|
||||
zmsg, err := zap.Parse(bs)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
obj := zmsg.Root()
|
||||
var addr common.Address
|
||||
copy(addr[:], obj.BytesFixedSlice(prAddrAddr, len(addr)))
|
||||
result := append([]byte(nil), obj.Bytes(prAddrBytes)...)
|
||||
return addr, result, nil
|
||||
}
|
||||
|
||||
@@ -4,59 +4,33 @@
|
||||
package predicate
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
// Valid result parsing is tested by [TestBlockResultsBytes]
|
||||
// Valid result parsing/round-tripping is tested by [TestBlockResultsBytes]
|
||||
func TestParseBlockResultsInvalid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
b []byte
|
||||
wantErr error
|
||||
name string
|
||||
b []byte
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
b: nil,
|
||||
wantErr: pcodecs.ErrCantUnpackVersion,
|
||||
name: "nil",
|
||||
b: nil,
|
||||
},
|
||||
{
|
||||
name: "too_big",
|
||||
b: slices.Concat(
|
||||
[]byte{
|
||||
// codecID
|
||||
0x00, 0x00,
|
||||
// BlockResults length
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
// txHash
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// PrecompileResults length
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
// precompile address
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
// Length of bitset
|
||||
0x01, 0x00, 0x00, 0x00, // 2^24
|
||||
},
|
||||
make([]byte, 1<<24), // Append the bitset
|
||||
),
|
||||
wantErr: pcodecs.ErrMaxSizeExceeded,
|
||||
name: "truncated_header",
|
||||
b: []byte{0x00, 0x01, 0x02},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := ParseBlockResults(test.b)
|
||||
require.ErrorIs(t, err, test.wantErr)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -188,31 +162,21 @@ func TestBlockResultsSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockResultsBytes checks that the native ZAP encoding round-trips and is
|
||||
// deterministic (byte-identical across marshals of the same value — required
|
||||
// because the results are committed during block building/verification).
|
||||
func TestBlockResultsBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input BlockResults
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
input: nil,
|
||||
want: []byte{
|
||||
// codecID
|
||||
0x00, 0x00,
|
||||
// results length
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: make(BlockResults),
|
||||
want: []byte{
|
||||
// codecID
|
||||
0x00, 0x00,
|
||||
// results length
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single_tx_single_result",
|
||||
@@ -221,13 +185,6 @@ func TestBlockResultsBytes(t *testing.T) {
|
||||
{2}: set.NewBits(1, 2, 3),
|
||||
},
|
||||
},
|
||||
want: []byte{
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x0e,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single_tx_multiple_results",
|
||||
@@ -237,14 +194,6 @@ func TestBlockResultsBytes(t *testing.T) {
|
||||
{3}: set.NewBits(0, 1, 2, 3),
|
||||
},
|
||||
},
|
||||
want: []byte{
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple_txs_single_result",
|
||||
@@ -256,16 +205,6 @@ func TestBlockResultsBytes(t *testing.T) {
|
||||
{3}: set.NewBits(3, 2, 1),
|
||||
},
|
||||
},
|
||||
want: []byte{
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple_txs_multiple_results",
|
||||
@@ -279,20 +218,6 @@ func TestBlockResultsBytes(t *testing.T) {
|
||||
{3}: set.NewBits(3, 2, 1),
|
||||
},
|
||||
},
|
||||
want: []byte{
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x0e,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@@ -301,7 +226,11 @@ func TestBlockResultsBytes(t *testing.T) {
|
||||
|
||||
got, err := test.input.Bytes()
|
||||
require.NoError(err)
|
||||
require.Equal(test.want, got)
|
||||
|
||||
// Deterministic: a second marshal of the same value is byte-identical.
|
||||
got2, err := test.input.Bytes()
|
||||
require.NoError(err)
|
||||
require.Equal(got, got2)
|
||||
|
||||
input := test.input
|
||||
if input == nil {
|
||||
|
||||
@@ -126,7 +126,7 @@ func (c *Client) IssueTx(
|
||||
newTx *tx.Tx,
|
||||
options ...rpc.Option,
|
||||
) (ids.ID, error) {
|
||||
txBytes, err := tx.Codec.Marshal(tx.CodecVersion, newTx)
|
||||
txBytes, err := newTx.Marshal()
|
||||
if err != nil {
|
||||
return ids.Empty, err
|
||||
}
|
||||
|
||||
@@ -3,35 +3,133 @@
|
||||
|
||||
package block
|
||||
|
||||
// Native ZAP wire for xsvm blocks: the struct IS the wire. One zap object per
|
||||
// block; the txs are self-describing via their own tx.Marshal(), so a block
|
||||
// stores the per-tx byte lengths as a u32 list and the concatenated tx bytes as
|
||||
// a blob, then Parse re-splits and hands each slice to tx.Parse. There is no
|
||||
// codec, no version prefix, no slot map.
|
||||
//
|
||||
// Object fixed section (offsets object-relative, little-endian):
|
||||
//
|
||||
// ParentID 32B @ 0
|
||||
// Timestamp i64 @ 32
|
||||
// Height u64 @ 40
|
||||
// TxLengths 8B @ 48 u32 list ptr — one entry per tx
|
||||
// TxBlob 8B @ 56 bytes ptr — concat of each tx.Marshal()
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/example/xsvm/tx"
|
||||
hash "github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
offBlkParentID = 0
|
||||
offBlkTimestamp = 32
|
||||
offBlkHeight = 40
|
||||
offBlkTxLengths = 48
|
||||
offBlkTxBlob = 56
|
||||
sizeBlk = 64
|
||||
|
||||
blkTxLenStride = 4 // uint32
|
||||
blkIDLen = 32
|
||||
)
|
||||
|
||||
// Stateless blocks are blocks as they are marshalled/unmarshalled and sent over
|
||||
// the p2p network. The stateful blocks which can be executed are built from
|
||||
// Stateless blocks.
|
||||
type Stateless struct {
|
||||
ParentID ids.ID `serialize:"true" json:"parentID"`
|
||||
Timestamp int64 `serialize:"true" json:"timestamp"`
|
||||
Height uint64 `serialize:"true" json:"height"`
|
||||
Txs []*tx.Tx `serialize:"true" json:"txs"`
|
||||
ParentID ids.ID `json:"parentID"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Height uint64 `json:"height"`
|
||||
Txs []*tx.Tx `json:"txs"`
|
||||
}
|
||||
|
||||
func (b *Stateless) Time() time.Time {
|
||||
return time.Unix(b.Timestamp, 0)
|
||||
}
|
||||
|
||||
// Marshal encodes the block as one native ZAP object.
|
||||
func (b *Stateless) Marshal() ([]byte, error) {
|
||||
builder := zap.NewBuilder(zap.HeaderSize + sizeBlk + 256)
|
||||
|
||||
var (
|
||||
txBlob []byte
|
||||
lenOff int
|
||||
lenCount int
|
||||
)
|
||||
if len(b.Txs) > 0 {
|
||||
lb := builder.StartList(blkTxLenStride)
|
||||
for i, t := range b.Txs {
|
||||
raw, err := t.Marshal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xsvm/block: marshal tx %d: %w", i, err)
|
||||
}
|
||||
lb.AddUint32(uint32(len(raw)))
|
||||
txBlob = append(txBlob, raw...)
|
||||
}
|
||||
lenOff, lenCount = lb.Finish()
|
||||
}
|
||||
|
||||
ob := builder.StartObject(sizeBlk)
|
||||
ob.SetBytesFixed(offBlkParentID, b.ParentID[:])
|
||||
ob.SetInt64(offBlkTimestamp, b.Timestamp)
|
||||
ob.SetUint64(offBlkHeight, b.Height)
|
||||
ob.SetList(offBlkTxLengths, lenOff, lenCount)
|
||||
ob.SetBytes(offBlkTxBlob, txBlob)
|
||||
ob.FinishAsRoot()
|
||||
return builder.Finish(), nil
|
||||
}
|
||||
|
||||
func (b *Stateless) ID() (ids.ID, error) {
|
||||
bytes, err := Codec.Marshal(CodecVersion, b)
|
||||
bytes, err := b.Marshal()
|
||||
return hash.ComputeHash256Array(bytes), err
|
||||
}
|
||||
|
||||
func Parse(bytes []byte) (*Stateless, error) {
|
||||
blk := &Stateless{}
|
||||
_, err := Codec.Unmarshal(bytes, blk)
|
||||
return blk, err
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj := msg.Root()
|
||||
blk := &Stateless{
|
||||
Timestamp: obj.Int64(offBlkTimestamp),
|
||||
Height: obj.Uint64(offBlkHeight),
|
||||
}
|
||||
copy(blk.ParentID[:], obj.BytesFixedSlice(offBlkParentID, blkIDLen))
|
||||
|
||||
txs, err := parseTxs(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blk.Txs = txs
|
||||
return blk, nil
|
||||
}
|
||||
|
||||
func parseTxs(obj zap.Object) ([]*tx.Tx, error) {
|
||||
lengths := obj.ListStride(offBlkTxLengths, blkTxLenStride)
|
||||
n := lengths.Len()
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
blob := obj.Bytes(offBlkTxBlob)
|
||||
out := make([]*tx.Tx, n)
|
||||
cursor := 0
|
||||
for i := 0; i < n; i++ {
|
||||
size := int(lengths.Uint32(i))
|
||||
if cursor+size > len(blob) {
|
||||
return nil, fmt.Errorf("xsvm/block: tx %d length %d overruns blob (%d)", i, size, len(blob))
|
||||
}
|
||||
t, err := tx.Parse(blob[cursor : cursor+size])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xsvm/block: parse tx %d: %w", i, err)
|
||||
}
|
||||
out[i] = t
|
||||
cursor += size
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
|
||||
import "github.com/luxfi/node/vms/example/xsvm/tx"
|
||||
|
||||
const CodecVersion = tx.CodecVersion
|
||||
|
||||
var Codec = tx.Codec
|
||||
@@ -84,7 +84,7 @@ func (c *chain) NewBlock(blk *xsblock.Stateless) (Block, error) {
|
||||
return blk, nil
|
||||
}
|
||||
|
||||
blkBytes, err := xsblock.Codec.Marshal(xsblock.CodecVersion, blk)
|
||||
blkBytes, err := blk.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func createFunc(c *cobra.Command, args []string) error {
|
||||
}
|
||||
log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime))
|
||||
|
||||
genesisBytes, err := genesis.Codec.Marshal(genesis.CodecVersion, &genesis.Genesis{
|
||||
genesisBytes, err := (&genesis.Genesis{
|
||||
Timestamp: 0,
|
||||
Allocations: []genesis.Allocation{
|
||||
{
|
||||
@@ -63,7 +63,7 @@ func createFunc(c *cobra.Command, args []string) error {
|
||||
Balance: config.Balance,
|
||||
},
|
||||
},
|
||||
})
|
||||
}).Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/luxfi/formatting"
|
||||
"github.com/luxfi/node/vms/example/xsvm/genesis"
|
||||
)
|
||||
|
||||
var errUnknownEncoding = errors.New("unknown encoding")
|
||||
@@ -34,7 +33,7 @@ func genesisFunc(c *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
genesisBytes, err := genesis.Codec.Marshal(genesis.CodecVersion, config.Genesis)
|
||||
genesisBytes, err := config.Genesis.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func Block(
|
||||
return err
|
||||
}
|
||||
|
||||
blkBytes, err := xsblock.Codec.Marshal(xsblock.CodecVersion, blk)
|
||||
blkBytes, err := blk.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package execute
|
||||
import (
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/example/xsvm/block"
|
||||
"github.com/luxfi/node/vms/example/xsvm/genesis"
|
||||
"github.com/luxfi/node/vms/example/xsvm/state"
|
||||
)
|
||||
@@ -36,7 +35,7 @@ func Genesis(db database.KeyValueReaderWriterDeleter, chainID ids.ID, g *genesis
|
||||
return err
|
||||
}
|
||||
|
||||
blkBytes, err := block.Codec.Marshal(block.CodecVersion, blk)
|
||||
blkBytes, err := blk.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/example/xsvm/state"
|
||||
"github.com/luxfi/node/vms/example/xsvm/tx"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/node/vms/platformvm/warp"
|
||||
"github.com/luxfi/runtime"
|
||||
"github.com/luxfi/utils/wrappers"
|
||||
validators "github.com/luxfi/validators"
|
||||
)
|
||||
|
||||
@@ -91,7 +91,7 @@ func (t *Tx) Export(e *tx.Export) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs pcodecs.Errs
|
||||
var errs wrappers.Errs
|
||||
errs.Add(
|
||||
state.IncrementNonce(t.Database, t.Sender, e.Nonce),
|
||||
state.DecreaseBalance(t.Database, t.Sender, e.ChainID, t.ExportFee),
|
||||
@@ -127,7 +127,7 @@ func (t *Tx) Import(i *tx.Import) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs pcodecs.Errs
|
||||
var errs wrappers.Errs
|
||||
errs.Add(
|
||||
state.IncrementNonce(t.Database, t.Sender, i.Nonce),
|
||||
state.DecreaseBalance(t.Database, t.Sender, t.Runtime.ChainID, t.ImportFee),
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package genesis
|
||||
|
||||
import "github.com/luxfi/node/vms/example/xsvm/block"
|
||||
|
||||
const CodecVersion = block.CodecVersion
|
||||
|
||||
var Codec = block.Codec
|
||||
@@ -3,30 +3,96 @@
|
||||
|
||||
package genesis
|
||||
|
||||
// Native ZAP wire for the xsvm genesis: the struct IS the wire. One zap object;
|
||||
// the allocations are a fixed-stride list (28-byte records: 20-byte address +
|
||||
// u64 balance). There is no codec, no version prefix, no slot map.
|
||||
//
|
||||
// Object fixed section (offsets object-relative, little-endian):
|
||||
//
|
||||
// Timestamp i64 @ 0
|
||||
// Allocations 8B @ 8 fixed-stride list ptr (stride 28)
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/example/xsvm/block"
|
||||
hash "github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
offGenesisTimestamp = 0
|
||||
offGenesisAllocs = 8
|
||||
sizeGenesis = 16
|
||||
|
||||
// Allocation record: address 20B @0, balance u64 @20.
|
||||
allocAddr = 0
|
||||
allocBalance = 20
|
||||
allocStride = 28
|
||||
|
||||
genShortLen = 20
|
||||
)
|
||||
|
||||
type Genesis struct {
|
||||
Timestamp int64 `serialize:"true" json:"timestamp"`
|
||||
Allocations []Allocation `serialize:"true" json:"allocations"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Allocations []Allocation `json:"allocations"`
|
||||
}
|
||||
|
||||
type Allocation struct {
|
||||
Address ids.ShortID `serialize:"true" json:"address"`
|
||||
Balance uint64 `serialize:"true" json:"balance"`
|
||||
Address ids.ShortID `json:"address"`
|
||||
Balance uint64 `json:"balance"`
|
||||
}
|
||||
|
||||
// Marshal encodes the genesis as one native ZAP object.
|
||||
func (g *Genesis) Marshal() ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeGenesis + len(g.Allocations)*allocStride)
|
||||
|
||||
var allocOff, allocCount int
|
||||
if len(g.Allocations) > 0 {
|
||||
lb := b.StartList(allocStride)
|
||||
rec := make([]byte, allocStride)
|
||||
for i := range g.Allocations {
|
||||
copy(rec[allocAddr:], g.Allocations[i].Address[:])
|
||||
binary.LittleEndian.PutUint64(rec[allocBalance:], g.Allocations[i].Balance)
|
||||
lb.AddBytes(rec)
|
||||
}
|
||||
// AddBytes counts bytes, not elements — use the real element count.
|
||||
allocOff, _ = lb.Finish()
|
||||
allocCount = len(g.Allocations)
|
||||
}
|
||||
|
||||
ob := b.StartObject(sizeGenesis)
|
||||
ob.SetInt64(offGenesisTimestamp, g.Timestamp)
|
||||
ob.SetList(offGenesisAllocs, allocOff, allocCount)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func Parse(bytes []byte) (*Genesis, error) {
|
||||
genesis := &Genesis{}
|
||||
_, err := Codec.Unmarshal(bytes, genesis)
|
||||
return genesis, err
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj := msg.Root()
|
||||
g := &Genesis{
|
||||
Timestamp: obj.Int64(offGenesisTimestamp),
|
||||
}
|
||||
arr := obj.ListStride(offGenesisAllocs, allocStride)
|
||||
n := arr.Len()
|
||||
if n > 0 {
|
||||
g.Allocations = make([]Allocation, n)
|
||||
for i := 0; i < n; i++ {
|
||||
e := arr.Object(i, allocStride)
|
||||
copy(g.Allocations[i].Address[:], e.BytesFixedSlice(allocAddr, genShortLen))
|
||||
g.Allocations[i].Balance = e.Uint64(allocBalance)
|
||||
}
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func Block(genesis *Genesis) (*block.Stateless, error) {
|
||||
bytes, err := Codec.Marshal(CodecVersion, genesis)
|
||||
bytes, err := genesis.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestGenesis(t *testing.T) {
|
||||
{Address: id2, Balance: 3000000000},
|
||||
},
|
||||
}
|
||||
bytes, err := Codec.Marshal(CodecVersion, genesis)
|
||||
bytes, err := genesis.Marshal()
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := Parse(bytes)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package tx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
const CodecVersion = 0
|
||||
|
||||
var Codec pcodecs.Manager
|
||||
|
||||
func init() {
|
||||
c := pcodecs.NewLinearCodec()
|
||||
Codec = pcodecs.NewMaxInt32Manager()
|
||||
|
||||
err := errors.Join(
|
||||
c.RegisterType(&Transfer{}),
|
||||
c.RegisterType(&Export{}),
|
||||
c.RegisterType(&Import{}),
|
||||
Codec.RegisterCodec(CodecVersion, c),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,55 @@
|
||||
|
||||
package tx
|
||||
|
||||
import "github.com/luxfi/ids"
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
var _ Unsigned = (*Export)(nil)
|
||||
|
||||
type Export struct {
|
||||
// ChainID provides cross chain replay protection
|
||||
ChainID ids.ID `serialize:"true" json:"chainID"`
|
||||
ChainID ids.ID `json:"chainID"`
|
||||
// Nonce provides internal chain replay protection
|
||||
Nonce uint64 `serialize:"true" json:"nonce"`
|
||||
MaxFee uint64 `serialize:"true" json:"maxFee"`
|
||||
PeerChainID ids.ID `serialize:"true" json:"peerChainID"`
|
||||
IsReturn bool `serialize:"true" json:"isReturn"`
|
||||
Amount uint64 `serialize:"true" json:"amount"`
|
||||
To ids.ShortID `serialize:"true" json:"to"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
MaxFee uint64 `json:"maxFee"`
|
||||
PeerChainID ids.ID `json:"peerChainID"`
|
||||
IsReturn bool `json:"isReturn"`
|
||||
Amount uint64 `json:"amount"`
|
||||
To ids.ShortID `json:"to"`
|
||||
}
|
||||
|
||||
func (e *Export) Visit(v Visitor) error {
|
||||
return v.Export(e)
|
||||
}
|
||||
|
||||
// Marshal encodes the Export as one native ZAP object with the tx kind byte at
|
||||
// offset 0.
|
||||
func (e *Export) Marshal() ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeExport)
|
||||
ob := b.StartObject(sizeExport)
|
||||
ob.SetUint8(offKind, uint8(kindExport))
|
||||
ob.SetBytesFixed(offExportChainID, e.ChainID[:])
|
||||
ob.SetUint64(offExportNonce, e.Nonce)
|
||||
ob.SetUint64(offExportMaxFee, e.MaxFee)
|
||||
ob.SetBytesFixed(offExportPeerChainID, e.PeerChainID[:])
|
||||
ob.SetBool(offExportIsReturn, e.IsReturn)
|
||||
ob.SetUint64(offExportAmount, e.Amount)
|
||||
ob.SetBytesFixed(offExportTo, e.To[:])
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func parseExport(obj zap.Object) *Export {
|
||||
e := &Export{
|
||||
Nonce: obj.Uint64(offExportNonce),
|
||||
MaxFee: obj.Uint64(offExportMaxFee),
|
||||
IsReturn: obj.Bool(offExportIsReturn),
|
||||
Amount: obj.Uint64(offExportAmount),
|
||||
}
|
||||
copy(e.ChainID[:], obj.BytesFixedSlice(offExportChainID, idLen))
|
||||
copy(e.PeerChainID[:], obj.BytesFixedSlice(offExportPeerChainID, idLen))
|
||||
copy(e.To[:], obj.BytesFixedSlice(offExportTo, shortLen))
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -3,16 +3,42 @@
|
||||
|
||||
package tx
|
||||
|
||||
import "github.com/luxfi/zap"
|
||||
|
||||
var _ Unsigned = (*Import)(nil)
|
||||
|
||||
type Import struct {
|
||||
// Nonce provides internal chain replay protection
|
||||
Nonce uint64 `serialize:"true" json:"nonce"`
|
||||
MaxFee uint64 `serialize:"true" json:"maxFee"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
MaxFee uint64 `json:"maxFee"`
|
||||
// Message includes the chainIDs to provide cross chain replay protection
|
||||
Message []byte `serialize:"true" json:"message"`
|
||||
Message []byte `json:"message"`
|
||||
}
|
||||
|
||||
func (i *Import) Visit(v Visitor) error {
|
||||
return v.Import(i)
|
||||
}
|
||||
|
||||
// Marshal encodes the Import as one native ZAP object with the tx kind byte at
|
||||
// offset 0.
|
||||
func (i *Import) Marshal() ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeImport + len(i.Message))
|
||||
ob := b.StartObject(sizeImport)
|
||||
ob.SetUint8(offKind, uint8(kindImport))
|
||||
ob.SetUint64(offImportNonce, i.Nonce)
|
||||
ob.SetUint64(offImportMaxFee, i.MaxFee)
|
||||
ob.SetBytes(offImportMessage, i.Message)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func parseImport(obj zap.Object) *Import {
|
||||
i := &Import{
|
||||
Nonce: obj.Uint64(offImportNonce),
|
||||
MaxFee: obj.Uint64(offImportMaxFee),
|
||||
}
|
||||
if m := obj.Bytes(offImportMessage); len(m) > 0 {
|
||||
i.Message = append([]byte(nil), m...)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package tx
|
||||
|
||||
// Native ZAP wire for xsvm txs: the struct IS the wire. Each Unsigned tx is one
|
||||
// zap object keyed by a 1-byte kind discriminator at object offset 0 — the
|
||||
// whole dispatch. parseUnsigned reads it and returns the typed tx. There is no
|
||||
// codec, no version prefix, no slot map.
|
||||
//
|
||||
// Object fixed sections (offsets object-relative, little-endian):
|
||||
//
|
||||
// Transfer kind@0 ChainID 32B@1 Nonce u64@33 MaxFee u64@41 AssetID 32B@49
|
||||
// Amount u64@81 To 20B@89 size 109
|
||||
// Export kind@0 ChainID 32B@1 Nonce u64@33 MaxFee u64@41 PeerChainID 32B@49
|
||||
// IsReturn bool@81 Amount u64@82 To 20B@90 size 110
|
||||
// Import kind@0 Nonce u64@1 MaxFee u64@9 Message bytes@17 size 25
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// kind is the 1-byte tx discriminator at object offset 0 of every Unsigned tx
|
||||
// buffer.
|
||||
type kind uint8
|
||||
|
||||
const (
|
||||
kindReserved kind = iota
|
||||
kindTransfer
|
||||
kindExport
|
||||
kindImport
|
||||
)
|
||||
|
||||
// Shared id widths (ids.IDLen / ids.ShortIDLen / secp256k1.SignatureLen).
|
||||
const (
|
||||
idLen = 32
|
||||
shortLen = 20
|
||||
sigLen = 65
|
||||
)
|
||||
|
||||
// offKind is the fixed wire position of the discriminator (object offset 0).
|
||||
const offKind = 0
|
||||
|
||||
// Transfer object layout.
|
||||
const (
|
||||
offTransferChainID = 1
|
||||
offTransferNonce = 33
|
||||
offTransferMaxFee = 41
|
||||
offTransferAssetID = 49
|
||||
offTransferAmount = 81
|
||||
offTransferTo = 89
|
||||
sizeTransfer = 109
|
||||
)
|
||||
|
||||
// Export object layout.
|
||||
const (
|
||||
offExportChainID = 1
|
||||
offExportNonce = 33
|
||||
offExportMaxFee = 41
|
||||
offExportPeerChainID = 49
|
||||
offExportIsReturn = 81
|
||||
offExportAmount = 82
|
||||
offExportTo = 90
|
||||
sizeExport = 110
|
||||
)
|
||||
|
||||
// Import object layout.
|
||||
const (
|
||||
offImportNonce = 1
|
||||
offImportMaxFee = 9
|
||||
offImportMessage = 17
|
||||
sizeImport = 25
|
||||
)
|
||||
|
||||
// parseUnsigned reads the kind byte at object offset 0 and returns the typed
|
||||
// Unsigned tx.
|
||||
func parseUnsigned(bytes []byte) (Unsigned, error) {
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj := msg.Root()
|
||||
switch k := kind(obj.Uint8(offKind)); k {
|
||||
case kindTransfer:
|
||||
return parseTransfer(obj), nil
|
||||
case kindExport:
|
||||
return parseExport(obj), nil
|
||||
case kindImport:
|
||||
return parseImport(obj), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("xsvm/tx: unknown tx kind %d", k)
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,30 @@
|
||||
|
||||
package tx
|
||||
|
||||
import "github.com/luxfi/ids"
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// Payload object layout: Sender 20B @0, Nonce u64 @20, IsReturn bool @28,
|
||||
// Amount u64 @29, To 20B @37. size 57. Payload is a single type (not
|
||||
// polymorphic), so it carries no kind byte.
|
||||
const (
|
||||
offPayloadSender = 0
|
||||
offPayloadNonce = 20
|
||||
offPayloadIsReturn = 28
|
||||
offPayloadAmount = 29
|
||||
offPayloadTo = 37
|
||||
sizePayload = 57
|
||||
)
|
||||
|
||||
type Payload struct {
|
||||
// Sender + Nonce provides replay protection
|
||||
Sender ids.ShortID `serialize:"true" json:"sender"`
|
||||
Nonce uint64 `serialize:"true" json:"nonce"`
|
||||
IsReturn bool `serialize:"true" json:"isReturn"`
|
||||
Amount uint64 `serialize:"true" json:"amount"`
|
||||
To ids.ShortID `serialize:"true" json:"to"`
|
||||
Sender ids.ShortID `json:"sender"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
IsReturn bool `json:"isReturn"`
|
||||
Amount uint64 `json:"amount"`
|
||||
To ids.ShortID `json:"to"`
|
||||
|
||||
bytes []byte
|
||||
}
|
||||
@@ -20,6 +35,18 @@ func (p *Payload) Bytes() []byte {
|
||||
return p.bytes
|
||||
}
|
||||
|
||||
func (p *Payload) marshal() ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizePayload)
|
||||
ob := b.StartObject(sizePayload)
|
||||
ob.SetBytesFixed(offPayloadSender, p.Sender[:])
|
||||
ob.SetUint64(offPayloadNonce, p.Nonce)
|
||||
ob.SetBool(offPayloadIsReturn, p.IsReturn)
|
||||
ob.SetUint64(offPayloadAmount, p.Amount)
|
||||
ob.SetBytesFixed(offPayloadTo, p.To[:])
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func NewPayload(
|
||||
sender ids.ShortID,
|
||||
nonce uint64,
|
||||
@@ -34,15 +61,24 @@ func NewPayload(
|
||||
Amount: amount,
|
||||
To: to,
|
||||
}
|
||||
bytes, err := Codec.Marshal(CodecVersion, p)
|
||||
bytes, err := p.marshal()
|
||||
p.bytes = bytes
|
||||
return p, err
|
||||
}
|
||||
|
||||
func ParsePayload(bytes []byte) (*Payload, error) {
|
||||
p := &Payload{
|
||||
bytes: bytes,
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err := Codec.Unmarshal(bytes, p)
|
||||
return p, err
|
||||
obj := msg.Root()
|
||||
p := &Payload{
|
||||
Nonce: obj.Uint64(offPayloadNonce),
|
||||
IsReturn: obj.Bool(offPayloadIsReturn),
|
||||
Amount: obj.Uint64(offPayloadAmount),
|
||||
bytes: bytes,
|
||||
}
|
||||
copy(p.Sender[:], obj.BytesFixedSlice(offPayloadSender, shortLen))
|
||||
copy(p.To[:], obj.BytesFixedSlice(offPayloadTo, shortLen))
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -3,21 +3,52 @@
|
||||
|
||||
package tx
|
||||
|
||||
import "github.com/luxfi/ids"
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
var _ Unsigned = (*Transfer)(nil)
|
||||
|
||||
type Transfer struct {
|
||||
// ChainID provides cross chain replay protection
|
||||
ChainID ids.ID `serialize:"true" json:"chainID"`
|
||||
ChainID ids.ID `json:"chainID"`
|
||||
// Nonce provides internal chain replay protection
|
||||
Nonce uint64 `serialize:"true" json:"nonce"`
|
||||
MaxFee uint64 `serialize:"true" json:"maxFee"`
|
||||
AssetID ids.ID `serialize:"true" json:"assetID"`
|
||||
Amount uint64 `serialize:"true" json:"amount"`
|
||||
To ids.ShortID `serialize:"true" json:"to"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
MaxFee uint64 `json:"maxFee"`
|
||||
AssetID ids.ID `json:"assetID"`
|
||||
Amount uint64 `json:"amount"`
|
||||
To ids.ShortID `json:"to"`
|
||||
}
|
||||
|
||||
func (t *Transfer) Visit(v Visitor) error {
|
||||
return v.Transfer(t)
|
||||
}
|
||||
|
||||
// Marshal encodes the Transfer as one native ZAP object with the tx kind byte
|
||||
// at offset 0.
|
||||
func (t *Transfer) Marshal() ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeTransfer)
|
||||
ob := b.StartObject(sizeTransfer)
|
||||
ob.SetUint8(offKind, uint8(kindTransfer))
|
||||
ob.SetBytesFixed(offTransferChainID, t.ChainID[:])
|
||||
ob.SetUint64(offTransferNonce, t.Nonce)
|
||||
ob.SetUint64(offTransferMaxFee, t.MaxFee)
|
||||
ob.SetBytesFixed(offTransferAssetID, t.AssetID[:])
|
||||
ob.SetUint64(offTransferAmount, t.Amount)
|
||||
ob.SetBytesFixed(offTransferTo, t.To[:])
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func parseTransfer(obj zap.Object) *Transfer {
|
||||
t := &Transfer{
|
||||
Nonce: obj.Uint64(offTransferNonce),
|
||||
MaxFee: obj.Uint64(offTransferMaxFee),
|
||||
Amount: obj.Uint64(offTransferAmount),
|
||||
}
|
||||
copy(t.ChainID[:], obj.BytesFixedSlice(offTransferChainID, idLen))
|
||||
copy(t.AssetID[:], obj.BytesFixedSlice(offTransferAssetID, idLen))
|
||||
copy(t.To[:], obj.BytesFixedSlice(offTransferTo, shortLen))
|
||||
return t
|
||||
}
|
||||
|
||||
@@ -4,24 +4,56 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/crypto/secp256k1"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// Tx object layout: unsigned bytes ptr @0, signature 65B @8. size 73.
|
||||
const (
|
||||
offTxUnsigned = 0
|
||||
offTxSignature = 8
|
||||
sizeTx = 73
|
||||
)
|
||||
|
||||
type Tx struct {
|
||||
Unsigned `serialize:"true" json:"unsigned"`
|
||||
Signature [secp256k1.SignatureLen]byte `serialize:"true" json:"signature"`
|
||||
Unsigned `json:"unsigned"`
|
||||
Signature [secp256k1.SignatureLen]byte `json:"signature"`
|
||||
}
|
||||
|
||||
// Marshal encodes the signed tx: the unsigned tx bytes (self-describing via its
|
||||
// own kind byte) plus the fixed signature.
|
||||
func (tx *Tx) Marshal() ([]byte, error) {
|
||||
unsignedBytes, err := tx.Unsigned.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeTx + len(unsignedBytes))
|
||||
ob := b.StartObject(sizeTx)
|
||||
ob.SetBytes(offTxUnsigned, unsignedBytes)
|
||||
ob.SetBytesFixed(offTxSignature, tx.Signature[:])
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func Parse(bytes []byte) (*Tx, error) {
|
||||
tx := &Tx{}
|
||||
_, err := Codec.Unmarshal(bytes, tx)
|
||||
return tx, err
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj := msg.Root()
|
||||
unsigned, err := parseUnsigned(obj.Bytes(offTxUnsigned))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx := &Tx{Unsigned: unsigned}
|
||||
copy(tx.Signature[:], obj.BytesFixedSlice(offTxSignature, sigLen))
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func Sign(utx Unsigned, key *secp256k1.PrivateKey) (*Tx, error) {
|
||||
unsignedBytes, err := Codec.Marshal(CodecVersion, &utx)
|
||||
unsignedBytes, err := utx.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,12 +71,12 @@ func Sign(utx Unsigned, key *secp256k1.PrivateKey) (*Tx, error) {
|
||||
}
|
||||
|
||||
func (tx *Tx) ID() (ids.ID, error) {
|
||||
bytes, err := Codec.Marshal(CodecVersion, tx)
|
||||
bytes, err := tx.Marshal()
|
||||
return hash.ComputeHash256Array(bytes), err
|
||||
}
|
||||
|
||||
func (tx *Tx) SenderID() (ids.ShortID, error) {
|
||||
unsignedBytes, err := Codec.Marshal(CodecVersion, &tx.Unsigned)
|
||||
unsignedBytes, err := tx.Unsigned.Marshal()
|
||||
if err != nil {
|
||||
return ids.ShortEmpty, err
|
||||
}
|
||||
|
||||
@@ -5,4 +5,9 @@ package tx
|
||||
|
||||
type Unsigned interface {
|
||||
Visit(Visitor) error
|
||||
|
||||
// Marshal encodes the unsigned tx to its native ZAP wire form. The first
|
||||
// object byte is the tx kind discriminator — this is the whole dispatch,
|
||||
// no codec.
|
||||
Marshal() ([]byte, error)
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
"github.com/luxfi/address"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
lux "github.com/luxfi/utxo"
|
||||
"github.com/luxfi/math"
|
||||
"github.com/luxfi/node/vms/platformvm/signer"
|
||||
"github.com/luxfi/node/vms/platformvm/stakeable"
|
||||
"github.com/luxfi/node/vms/platformvm/txs"
|
||||
"github.com/luxfi/node/vms/platformvm/txs/txheap"
|
||||
"github.com/luxfi/utils"
|
||||
"github.com/luxfi/math"
|
||||
"github.com/luxfi/node/vms/platformvm/signer"
|
||||
lux "github.com/luxfi/utxo"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
|
||||
var (
|
||||
errUTXOHasNoValue = errors.New("genesis UTXO has no value")
|
||||
errValidatorHasZeroWeight = errors.New("validator has zero weight")
|
||||
errValidatorHasZeroWeight = errors.New("validator has zero weight")
|
||||
errValidatorAlreadyExited = errors.New("validator would have already unstaked")
|
||||
errStakeOverflow = errors.New("validator stake exceeds limit")
|
||||
|
||||
@@ -38,18 +38,18 @@ var (
|
||||
|
||||
// UTXO adds messages to UTXOs
|
||||
type UTXO struct {
|
||||
lux.UTXO `serialize:"true"`
|
||||
Message []byte `serialize:"true" json:"message"`
|
||||
lux.UTXO
|
||||
Message []byte `json:"message"`
|
||||
}
|
||||
|
||||
// Genesis represents a genesis state of the platform chain
|
||||
type Genesis struct {
|
||||
UTXOs []*UTXO `serialize:"true"`
|
||||
Validators []*txs.Tx `serialize:"true"`
|
||||
Chains []*txs.Tx `serialize:"true"`
|
||||
Timestamp uint64 `serialize:"true"`
|
||||
InitialSupply uint64 `serialize:"true"`
|
||||
Message string `serialize:"true"`
|
||||
UTXOs []*UTXO
|
||||
Validators []*txs.Tx
|
||||
Chains []*txs.Tx
|
||||
Timestamp uint64
|
||||
InitialSupply uint64
|
||||
Message string
|
||||
}
|
||||
|
||||
// Parse deserializes a P-Chain genesis blob from its native-ZAP wire form
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/luxfi/ids"
|
||||
utilmetric "github.com/luxfi/node/utils/metric"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/node/vms/platformvm/block"
|
||||
"github.com/luxfi/utils/wrappers"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -145,7 +145,7 @@ func New(registerer metric.Registerer) (Metrics, error) {
|
||||
}),
|
||||
}
|
||||
|
||||
errs := pcodecs.Errs{Err: err}
|
||||
errs := wrappers.Errs{Err: err}
|
||||
registry, ok := registerer.(metric.Registry)
|
||||
if !ok {
|
||||
return nil, errors.New("registerer must be a Registry")
|
||||
|
||||
@@ -48,8 +48,8 @@ type HybridSigner interface {
|
||||
// - RT PublicKey: 1952 bytes (ML-DSA-65)
|
||||
// - RT ProofOfPossession: 3309 bytes (ML-DSA-65 signature)
|
||||
type HybridProofOfPossession struct {
|
||||
BLS *ProofOfPossession `serialize:"true" json:"bls"`
|
||||
RT *RTProofOfPossession `serialize:"true" json:"rt"`
|
||||
BLS *ProofOfPossession `json:"bls"`
|
||||
RT *RTProofOfPossession `json:"rt"`
|
||||
}
|
||||
|
||||
// NewHybridProofOfPossession creates a new hybrid proof combining BLS and RT.
|
||||
|
||||
@@ -18,10 +18,10 @@ var (
|
||||
)
|
||||
|
||||
type ProofOfPossession struct {
|
||||
PublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"publicKey"`
|
||||
PublicKey [bls.PublicKeyLen]byte `json:"publicKey"`
|
||||
// BLS signature proving ownership of [PublicKey]. The signed message is the
|
||||
// [PublicKey].
|
||||
ProofOfPossession [bls.SignatureLen]byte `serialize:"true" json:"proofOfPossession"`
|
||||
ProofOfPossession [bls.SignatureLen]byte `json:"proofOfPossession"`
|
||||
|
||||
// publicKey is the parsed version of [PublicKey]. It is populated in
|
||||
// [Verify].
|
||||
|
||||
@@ -43,9 +43,9 @@ type RTSigner interface {
|
||||
// by requiring a signature over the public key itself.
|
||||
type RTProofOfPossession struct {
|
||||
// PublicKey is the ML-DSA-65 public key (1952 bytes)
|
||||
PublicKey []byte `serialize:"true" json:"publicKey"`
|
||||
PublicKey []byte `json:"publicKey"`
|
||||
// ProofOfPossession is the ML-DSA signature over PublicKey, proving ownership
|
||||
ProofOfPossession []byte `serialize:"true" json:"proofOfPossession"`
|
||||
ProofOfPossession []byte `json:"proofOfPossession"`
|
||||
}
|
||||
|
||||
// NewRTProofOfPossession creates a new RTProofOfPossession from a private key.
|
||||
|
||||
@@ -55,8 +55,8 @@ func init() {
|
||||
}
|
||||
|
||||
type LockOut struct {
|
||||
Locktime uint64 `serialize:"true" json:"locktime"`
|
||||
lux.TransferableOut `serialize:"true" json:"output"`
|
||||
Locktime uint64 `json:"locktime"`
|
||||
lux.TransferableOut `json:"output"`
|
||||
}
|
||||
|
||||
func (s *LockOut) InitRuntime(rt *runtime.Runtime) {
|
||||
@@ -106,8 +106,8 @@ func (s *LockOut) Bytes() []byte {
|
||||
}
|
||||
|
||||
type LockIn struct {
|
||||
Locktime uint64 `serialize:"true" json:"locktime"`
|
||||
lux.TransferableIn `serialize:"true" json:"input"`
|
||||
Locktime uint64 `json:"locktime"`
|
||||
lux.TransferableIn `json:"input"`
|
||||
}
|
||||
|
||||
func (s *LockIn) Verify() error {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
const (
|
||||
CodecVersion0Tag = "v0"
|
||||
CodecVersion0 uint16 = 0
|
||||
|
||||
CodecVersion1Tag = "v1"
|
||||
CodecVersion1 uint16 = 1
|
||||
)
|
||||
|
||||
var MetadataCodec pcodecs.Manager
|
||||
|
||||
func init() {
|
||||
c0 := pcodecs.NewLinearCodec()
|
||||
c1 := pcodecs.NewLinearCodec()
|
||||
MetadataCodec = pcodecs.NewMaxInt32Manager()
|
||||
|
||||
err := errors.Join(
|
||||
MetadataCodec.RegisterCodec(CodecVersion0, c0),
|
||||
MetadataCodec.RegisterCodec(CodecVersion1, c1),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -9,33 +9,24 @@ import (
|
||||
)
|
||||
|
||||
type delegatorMetadata struct {
|
||||
PotentialReward uint64 `serialize:"true"`
|
||||
StakerStartTime uint64 `serialize:"true"`
|
||||
PotentialReward uint64
|
||||
StakerStartTime uint64
|
||||
|
||||
txID ids.ID
|
||||
}
|
||||
|
||||
// parseDelegatorMetadata overlays metadata's persisted fields (native
|
||||
// delegatorMetadata wire) from bytes. Empty bytes means nothing was persisted —
|
||||
// the caller's tx-derived StakerStartTime default is kept.
|
||||
func parseDelegatorMetadata(bytes []byte, metadata *delegatorMetadata) error {
|
||||
var err error
|
||||
switch len(bytes) {
|
||||
case database.Uint64Size:
|
||||
// only potential reward was stored
|
||||
metadata.PotentialReward, err = database.ParseUInt64(bytes)
|
||||
default:
|
||||
_, err = MetadataCodec.Unmarshal(bytes, metadata)
|
||||
if len(bytes) == 0 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
return unmarshalDelegatorMetadata(bytes, metadata)
|
||||
}
|
||||
|
||||
func writeDelegatorMetadata(db database.KeyValueWriter, metadata *delegatorMetadata, codecVersion uint16) error {
|
||||
// The "0" codec is skipped for [delegatorMetadata]. This is to ensure the
|
||||
// [validatorMetadata] codec version is the same as the [delegatorMetadata]
|
||||
// codec version.
|
||||
//
|
||||
if codecVersion == 0 {
|
||||
return database.PutUInt64(db, metadata.txID[:], metadata.PotentialReward)
|
||||
}
|
||||
metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata)
|
||||
func writeDelegatorMetadata(db database.KeyValueWriter, metadata *delegatorMetadata) error {
|
||||
metadataBytes, err := marshalDelegatorMetadata(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,132 +10,73 @@ import (
|
||||
|
||||
"github.com/luxfi/database/memdb"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
func TestParseDelegatorMetadata(t *testing.T) {
|
||||
full := &delegatorMetadata{PotentialReward: 123, StakerStartTime: 456}
|
||||
fullBytes, err := marshalDelegatorMetadata(full)
|
||||
require.NoError(t, err)
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
bytes []byte
|
||||
expected *delegatorMetadata
|
||||
expectedErr error
|
||||
name string
|
||||
bytes []byte
|
||||
initial *delegatorMetadata // caller-supplied defaults before parse
|
||||
want *delegatorMetadata
|
||||
wantErr bool
|
||||
}
|
||||
tests := []test{
|
||||
{
|
||||
name: "potential reward only no codec",
|
||||
bytes: []byte{
|
||||
// potential reward via database.ParseUInt64 (BE — database layer, not codec)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b,
|
||||
},
|
||||
expected: &delegatorMetadata{
|
||||
PotentialReward: 123,
|
||||
StakerStartTime: 0,
|
||||
},
|
||||
expectedErr: nil,
|
||||
// Empty ⇒ nothing persisted; the caller's tx-derived StakerStartTime
|
||||
// default is kept.
|
||||
name: "empty keeps defaults",
|
||||
bytes: nil,
|
||||
initial: &delegatorMetadata{StakerStartTime: 456},
|
||||
want: &delegatorMetadata{StakerStartTime: 456},
|
||||
},
|
||||
{
|
||||
name: "potential reward + staker start time with codec v1",
|
||||
bytes: []byte{
|
||||
// codec version (LE) — 1 = 0x01 0x00
|
||||
0x01, 0x00,
|
||||
// potential reward (LE) — 123
|
||||
0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// staker start time (LE) — 456 = 0xC8_01_00_...
|
||||
0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: &delegatorMetadata{
|
||||
PotentialReward: 123,
|
||||
StakerStartTime: 456,
|
||||
},
|
||||
expectedErr: nil,
|
||||
name: "full native round-trip",
|
||||
bytes: fullBytes,
|
||||
initial: &delegatorMetadata{StakerStartTime: 999},
|
||||
want: &delegatorMetadata{PotentialReward: 123, StakerStartTime: 456},
|
||||
},
|
||||
{
|
||||
name: "invalid codec version",
|
||||
bytes: []byte{
|
||||
// codec version (LE) — 2 = 0x02 0x00
|
||||
0x02, 0x00,
|
||||
// potential reward (LE)
|
||||
0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// staker start time (LE)
|
||||
0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: nil,
|
||||
expectedErr: pcodecs.ErrUnknownVersion,
|
||||
},
|
||||
{
|
||||
name: "short byte len",
|
||||
bytes: []byte{
|
||||
// codec version (LE)
|
||||
0x01, 0x00,
|
||||
// potential reward (LE)
|
||||
0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// staker start time (truncated)
|
||||
0xc8, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: nil,
|
||||
expectedErr: pcodecs.ErrInsufficientLength,
|
||||
name: "truncated buffer errors",
|
||||
bytes: fullBytes[:len(fullBytes)-1],
|
||||
initial: &delegatorMetadata{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
var metadata delegatorMetadata
|
||||
err := parseDelegatorMetadata(tt.bytes, &metadata)
|
||||
require.ErrorIs(err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
metadata := tt.initial
|
||||
err := parseDelegatorMetadata(tt.bytes, metadata)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.Equal(tt.expected, &metadata)
|
||||
require.NoError(err)
|
||||
require.Equal(tt.want, metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteDelegatorMetadata(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
version uint16
|
||||
metadata *delegatorMetadata
|
||||
expected []byte
|
||||
}
|
||||
tests := []test{
|
||||
{
|
||||
name: CodecVersion0Tag,
|
||||
version: CodecVersion0,
|
||||
metadata: &delegatorMetadata{
|
||||
PotentialReward: 123,
|
||||
StakerStartTime: 456,
|
||||
},
|
||||
expected: []byte{
|
||||
// potential reward via database.PutUInt64 (BE — database layer, not codec)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CodecVersion1Tag,
|
||||
version: CodecVersion1,
|
||||
metadata: &delegatorMetadata{
|
||||
PotentialReward: 123,
|
||||
StakerStartTime: 456,
|
||||
},
|
||||
expected: []byte{
|
||||
// codec version (LE)
|
||||
0x01, 0x00,
|
||||
// potential reward (LE)
|
||||
0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// staker start time (LE)
|
||||
0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
db := memdb.New()
|
||||
tt.metadata.txID = ids.GenerateTestID()
|
||||
require.NoError(writeDelegatorMetadata(db, tt.metadata, tt.version))
|
||||
bytes, err := db.Get(tt.metadata.txID[:])
|
||||
require.NoError(err)
|
||||
require.Equal(tt.expected, bytes)
|
||||
})
|
||||
require := require.New(t)
|
||||
db := memdb.New()
|
||||
|
||||
metadata := &delegatorMetadata{
|
||||
PotentialReward: 123,
|
||||
StakerStartTime: 456,
|
||||
txID: ids.GenerateTestID(),
|
||||
}
|
||||
require.NoError(writeDelegatorMetadata(db, metadata))
|
||||
|
||||
bytes, err := db.Get(metadata.txID[:])
|
||||
require.NoError(err)
|
||||
|
||||
// The persisted bytes round-trip back to the same serialized fields.
|
||||
got := &delegatorMetadata{txID: metadata.txID}
|
||||
require.NoError(parseDelegatorMetadata(bytes, got))
|
||||
require.Equal(metadata, got)
|
||||
}
|
||||
|
||||
@@ -10,91 +10,29 @@ import (
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
// preDelegateeRewardSize is the size of codec marshalling
|
||||
// [preDelegateeRewardMetadata].
|
||||
//
|
||||
// CodecVersionLen + UpDurationLen + LastUpdatedLen + PotentialRewardLen
|
||||
const preDelegateeRewardSize = pcodecs.VersionSize + 3*pcodecs.LongLen
|
||||
|
||||
// preStakerStartTimeSize is the size of codec marshalling
|
||||
// [preStakerStartTimeMetadata].
|
||||
//
|
||||
// CodecVersionLen + UpDurationLen + LastUpdatedLen + PotentialRewardLen + PotentialDelegateeRewardLen
|
||||
const preStakerStartTimeSize = pcodecs.VersionSize + 4*pcodecs.LongLen
|
||||
|
||||
var _ validatorState = (*metadata)(nil)
|
||||
|
||||
type preDelegateeRewardMetadata struct {
|
||||
UpDuration time.Duration `serialize:"true"`
|
||||
LastUpdated uint64 `serialize:"true"` // Unix time in seconds
|
||||
PotentialReward uint64 `serialize:"true"`
|
||||
}
|
||||
|
||||
// preStakerStartTimeMetadata is used for backward compatibility with data
|
||||
// that was written before StakerStartTime was added.
|
||||
type preStakerStartTimeMetadata struct {
|
||||
UpDuration time.Duration `serialize:"true"`
|
||||
LastUpdated uint64 `serialize:"true"` // Unix time in seconds
|
||||
PotentialReward uint64 `serialize:"true"`
|
||||
PotentialDelegateeReward uint64 `serialize:"true"`
|
||||
}
|
||||
|
||||
type validatorMetadata struct {
|
||||
UpDuration time.Duration `serialize:"true"`
|
||||
LastUpdated uint64 `serialize:"true"` // Unix time in seconds
|
||||
PotentialReward uint64 `serialize:"true"`
|
||||
PotentialDelegateeReward uint64 `serialize:"true"`
|
||||
StakerStartTime uint64 `serialize:"true"`
|
||||
UpDuration time.Duration
|
||||
LastUpdated uint64 // Unix time in seconds
|
||||
PotentialReward uint64
|
||||
PotentialDelegateeReward uint64
|
||||
StakerStartTime uint64
|
||||
|
||||
txID ids.ID
|
||||
lastUpdated time.Time
|
||||
}
|
||||
|
||||
// Permissioned validators originally wrote their values as nil.
|
||||
// We now write the uptime, reward, and delegatee reward together.
|
||||
// parseValidatorMetadata overlays metadata's persisted fields (native
|
||||
// validatorMetadata wire) from bytes. Empty bytes means nothing was persisted
|
||||
// for this staker — the caller pre-populates tx-derived defaults
|
||||
// (StakerStartTime/LastUpdated), which are kept as-is. lastUpdated is always
|
||||
// derived from the resulting LastUpdated.
|
||||
func parseValidatorMetadata(bytes []byte, metadata *validatorMetadata) error {
|
||||
switch len(bytes) {
|
||||
case 0:
|
||||
// nothing was stored
|
||||
|
||||
case database.Uint64Size:
|
||||
// only potential reward was stored
|
||||
var err error
|
||||
metadata.PotentialReward, err = database.ParseUInt64(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case preDelegateeRewardSize:
|
||||
// potential reward and uptime was stored but potential delegatee reward
|
||||
// was not
|
||||
tmp := preDelegateeRewardMetadata{}
|
||||
if _, err := MetadataCodec.Unmarshal(bytes, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metadata.UpDuration = tmp.UpDuration
|
||||
metadata.LastUpdated = tmp.LastUpdated
|
||||
metadata.PotentialReward = tmp.PotentialReward
|
||||
|
||||
case preStakerStartTimeSize:
|
||||
// All fields except StakerStartTime were stored (pre-v1 format)
|
||||
tmp := preStakerStartTimeMetadata{}
|
||||
if _, err := MetadataCodec.Unmarshal(bytes, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metadata.UpDuration = tmp.UpDuration
|
||||
metadata.LastUpdated = tmp.LastUpdated
|
||||
metadata.PotentialReward = tmp.PotentialReward
|
||||
metadata.PotentialDelegateeReward = tmp.PotentialDelegateeReward
|
||||
|
||||
default:
|
||||
// everything was stored (v1+ format with StakerStartTime)
|
||||
if _, err := MetadataCodec.Unmarshal(bytes, metadata); err != nil {
|
||||
if len(bytes) > 0 {
|
||||
if err := unmarshalValidatorMetadata(bytes, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -157,7 +95,6 @@ type validatorState interface {
|
||||
WriteValidatorMetadata(
|
||||
dbPrimary database.KeyValueWriter,
|
||||
dbNet database.KeyValueWriter,
|
||||
codecVersion uint16,
|
||||
) error
|
||||
}
|
||||
|
||||
@@ -258,14 +195,13 @@ func (m *metadata) DeleteValidatorMetadata(vdrID ids.NodeID, netID ids.ID) {
|
||||
func (m *metadata) WriteValidatorMetadata(
|
||||
dbPrimary database.KeyValueWriter,
|
||||
dbNet database.KeyValueWriter,
|
||||
codecVersion uint16,
|
||||
) error {
|
||||
for vdrID, updatedNets := range m.updatedMetadata {
|
||||
for netID := range updatedNets {
|
||||
metadata := m.metadata[vdrID][netID]
|
||||
metadata.LastUpdated = uint64(metadata.lastUpdated.Unix())
|
||||
|
||||
metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata)
|
||||
metadataBytes, err := marshalValidatorMetadata(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/database/memdb"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
func TestValidatorUptimes(t *testing.T) {
|
||||
@@ -82,7 +81,7 @@ func TestWriteValidatorMetadata(t *testing.T) {
|
||||
chainDB := memdb.New()
|
||||
|
||||
// write empty uptimes
|
||||
require.NoError(state.WriteValidatorMetadata(primaryDB, chainDB, CodecVersion1))
|
||||
require.NoError(state.WriteValidatorMetadata(primaryDB, chainDB))
|
||||
|
||||
// load uptime
|
||||
nodeID := ids.GenerateTestNodeID()
|
||||
@@ -96,7 +95,7 @@ func TestWriteValidatorMetadata(t *testing.T) {
|
||||
state.LoadValidatorMetadata(nodeID, netID, testUptimeReward)
|
||||
|
||||
// write state, should not reflect to DB yet
|
||||
require.NoError(state.WriteValidatorMetadata(primaryDB, chainDB, CodecVersion1))
|
||||
require.NoError(state.WriteValidatorMetadata(primaryDB, chainDB))
|
||||
require.False(primaryDB.Has(testUptimeReward.txID[:]))
|
||||
require.False(chainDB.Has(testUptimeReward.txID[:]))
|
||||
|
||||
@@ -112,7 +111,7 @@ func TestWriteValidatorMetadata(t *testing.T) {
|
||||
require.NoError(state.SetUptime(nodeID, netID, newUpDuration, newLastUpdated))
|
||||
|
||||
// write uptimes, should reflect to net DB
|
||||
require.NoError(state.WriteValidatorMetadata(primaryDB, chainDB, CodecVersion1))
|
||||
require.NoError(state.WriteValidatorMetadata(primaryDB, chainDB))
|
||||
require.False(primaryDB.Has(testUptimeReward.txID[:]))
|
||||
require.True(chainDB.Has(testUptimeReward.txID[:]))
|
||||
}
|
||||
@@ -171,130 +170,76 @@ func TestValidatorDelegateeRewards(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseValidatorMetadata(t *testing.T) {
|
||||
// full is a fully-populated record round-tripped through the native wire.
|
||||
full := &validatorMetadata{
|
||||
UpDuration: 6000000,
|
||||
LastUpdated: 900000,
|
||||
PotentialReward: 100000,
|
||||
PotentialDelegateeReward: 20000,
|
||||
StakerStartTime: 12345,
|
||||
}
|
||||
fullBytes, err := marshalValidatorMetadata(full)
|
||||
require.NoError(t, err)
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
bytes []byte
|
||||
expected *validatorMetadata
|
||||
expectedErr error
|
||||
name string
|
||||
bytes []byte
|
||||
initial *validatorMetadata // caller-supplied defaults before parse
|
||||
want *validatorMetadata
|
||||
wantErr bool
|
||||
}
|
||||
tests := []test{
|
||||
{
|
||||
name: "nil",
|
||||
bytes: nil,
|
||||
expected: &validatorMetadata{
|
||||
lastUpdated: time.Unix(0, 0),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
bytes: []byte{},
|
||||
expected: &validatorMetadata{
|
||||
lastUpdated: time.Unix(0, 0),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "potential reward only",
|
||||
bytes: []byte{
|
||||
// potential reward via database.ParseUInt64 (BE — database layer, not codec)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xA0,
|
||||
},
|
||||
expected: &validatorMetadata{
|
||||
PotentialReward: 100000,
|
||||
lastUpdated: time.Unix(0, 0),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "uptime + potential reward",
|
||||
bytes: []byte{
|
||||
// codec version (LE)
|
||||
0x00, 0x00,
|
||||
// up duration (LE) — 6000000 = 0x808D5B_00_00_00_00_00
|
||||
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// last updated (LE) — 900000 = 0xA0BB0D_00_00_00_00_00
|
||||
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential reward (LE) — 100000 = 0xA08601_00_00_00_00_00
|
||||
0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: &validatorMetadata{
|
||||
UpDuration: 6000000,
|
||||
// Empty ⇒ nothing persisted; caller's tx-derived defaults are kept,
|
||||
// only lastUpdated is derived from LastUpdated.
|
||||
name: "nil keeps defaults",
|
||||
bytes: nil,
|
||||
initial: &validatorMetadata{StakerStartTime: 900000, LastUpdated: 900000},
|
||||
want: &validatorMetadata{
|
||||
StakerStartTime: 900000,
|
||||
LastUpdated: 900000,
|
||||
PotentialReward: 100000,
|
||||
lastUpdated: time.Unix(900000, 0),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "uptime + potential reward + potential delegatee reward",
|
||||
bytes: []byte{
|
||||
// codec version (LE)
|
||||
0x00, 0x00,
|
||||
// up duration (LE) — 6000000
|
||||
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// last updated (LE) — 900000
|
||||
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential reward (LE) — 100000
|
||||
0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential delegatee reward (LE) — 20000 = 0x204E_00_00_00_00_00_00
|
||||
0x20, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: &validatorMetadata{
|
||||
name: "empty keeps defaults",
|
||||
bytes: []byte{},
|
||||
initial: &validatorMetadata{},
|
||||
want: &validatorMetadata{lastUpdated: time.Unix(0, 0)},
|
||||
},
|
||||
{
|
||||
// Full native buffer overwrites every serialized field, including the
|
||||
// caller's tx-default StakerStartTime.
|
||||
name: "full native round-trip",
|
||||
bytes: fullBytes,
|
||||
initial: &validatorMetadata{StakerStartTime: 999},
|
||||
want: &validatorMetadata{
|
||||
UpDuration: 6000000,
|
||||
LastUpdated: 900000,
|
||||
PotentialReward: 100000,
|
||||
PotentialDelegateeReward: 20000,
|
||||
StakerStartTime: 12345,
|
||||
lastUpdated: time.Unix(900000, 0),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid codec version",
|
||||
bytes: []byte{
|
||||
// codec version (LE) — 2 = 0x02 0x00, but reading LE that means we want value 2
|
||||
// so encode as 0x02 0x00 (low byte first)
|
||||
0x02, 0x00,
|
||||
// up duration (LE)
|
||||
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// last updated (LE)
|
||||
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential reward (LE)
|
||||
0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential delegatee reward (LE)
|
||||
0x20, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: nil,
|
||||
expectedErr: pcodecs.ErrUnknownVersion,
|
||||
},
|
||||
{
|
||||
name: "short byte len",
|
||||
bytes: []byte{
|
||||
// codec version (LE)
|
||||
0x00, 0x00,
|
||||
// up duration (LE)
|
||||
0x80, 0x8D, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// last updated (LE)
|
||||
0xA0, 0xBB, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential reward (LE)
|
||||
0xA0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// potential delegatee reward (truncated)
|
||||
0x20, 0x4E, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
expected: nil,
|
||||
expectedErr: pcodecs.ErrInsufficientLength,
|
||||
name: "truncated buffer errors",
|
||||
bytes: fullBytes[:len(fullBytes)-1],
|
||||
initial: &validatorMetadata{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
var metadata validatorMetadata
|
||||
err := parseValidatorMetadata(tt.bytes, &metadata)
|
||||
require.ErrorIs(err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
metadata := tt.initial
|
||||
err := parseValidatorMetadata(tt.bytes, metadata)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.Equal(tt.expected, &metadata)
|
||||
require.NoError(err)
|
||||
require.Equal(tt.want, metadata)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/google/btree"
|
||||
"github.com/luxfi/metric"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/runtime"
|
||||
validators "github.com/luxfi/validators"
|
||||
"github.com/luxfi/validators/uptime"
|
||||
@@ -521,7 +520,7 @@ func txAndStatusSize(_ ids.ID, t *txAndStatus) int {
|
||||
if t == nil {
|
||||
return ids.IDLen + constants.PointerOverhead
|
||||
}
|
||||
return ids.IDLen + len(t.tx.Bytes()) + pcodecs.IntLen + 2*constants.PointerOverhead
|
||||
return ids.IDLen + len(t.tx.Bytes()) + 4 /* status (uint32) */ + 2*constants.PointerOverhead
|
||||
}
|
||||
|
||||
func blockSize(_ ids.ID, blk block.Block) int {
|
||||
@@ -595,7 +594,7 @@ func New(
|
||||
"l1_validator_weights_cache",
|
||||
reg,
|
||||
lru.NewSizedCache(execCfg.L1WeightsCacheSize, func(ids.ID, uint64) int {
|
||||
return ids.IDLen + pcodecs.LongLen
|
||||
return ids.IDLen + database.Uint64Size
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -609,8 +608,8 @@ func New(
|
||||
execCfg.L1InactiveValidatorsCacheSize,
|
||||
func(_ ids.ID, maybeL1Validator maybe.Maybe[L1Validator]) int {
|
||||
const (
|
||||
l1ValidatorOverhead = ids.IDLen + ids.NodeIDLen + 4*pcodecs.LongLen + 3*constants.PointerOverhead
|
||||
maybeL1ValidatorOverhead = pcodecs.BoolLen + l1ValidatorOverhead
|
||||
l1ValidatorOverhead = ids.IDLen + ids.NodeIDLen + 4*database.Uint64Size + 3*constants.PointerOverhead
|
||||
maybeL1ValidatorOverhead = database.BoolSize + l1ValidatorOverhead
|
||||
entryOverhead = ids.IDLen + maybeL1ValidatorOverhead
|
||||
)
|
||||
if maybeL1Validator.IsNothing() {
|
||||
@@ -630,7 +629,7 @@ func New(
|
||||
"l1_validator_chain_id_node_id_cache",
|
||||
reg,
|
||||
lru.NewSizedCache(execCfg.L1ChainIDNodeIDCacheSize, func(chainIDNodeID, bool) int {
|
||||
return ids.IDLen + ids.NodeIDLen + pcodecs.BoolLen
|
||||
return ids.IDLen + ids.NodeIDLen + database.BoolSize
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -62,16 +62,14 @@ func (s *state) Close() error {
|
||||
}
|
||||
|
||||
func (s *state) write(updateValidators bool, height uint64) error {
|
||||
const codecVersion = CodecVersion1
|
||||
|
||||
return errors.Join(
|
||||
s.writeBlocks(),
|
||||
s.writeExpiry(),
|
||||
s.updateValidatorManager(updateValidators),
|
||||
s.writeValidatorDiffs(height),
|
||||
s.writeCurrentStakers(codecVersion),
|
||||
s.writeCurrentStakers(),
|
||||
s.writePendingStakers(),
|
||||
s.WriteValidatorMetadata(s.currentValidatorList, s.currentNetValidatorList, codecVersion), // Must be called after writeCurrentStakers
|
||||
s.WriteValidatorMetadata(s.currentValidatorList, s.currentNetValidatorList), // Must be called after writeCurrentStakers
|
||||
s.writeL1Validators(),
|
||||
s.writeTXs(),
|
||||
s.writeRewardUTXOs(),
|
||||
|
||||
@@ -607,7 +607,7 @@ func (s *state) initValidatorSets() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state) writeCurrentStakers(codecVersion uint16) error {
|
||||
func (s *state) writeCurrentStakers() error {
|
||||
for chainID, validatorDiffs := range s.currentStakers.validatorDiffs {
|
||||
// Select db to write to
|
||||
validatorDB := s.currentNetValidatorList
|
||||
@@ -639,7 +639,7 @@ func (s *state) writeCurrentStakers(codecVersion uint16) error {
|
||||
PotentialDelegateeReward: 0,
|
||||
}
|
||||
|
||||
metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata)
|
||||
metadataBytes, err := marshalValidatorMetadata(metadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize current validator: %w", err)
|
||||
}
|
||||
@@ -660,7 +660,6 @@ func (s *state) writeCurrentStakers(codecVersion uint16) error {
|
||||
err := writeCurrentDelegatorDiff(
|
||||
delegatorDB,
|
||||
validatorDiff,
|
||||
codecVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -674,7 +673,6 @@ func (s *state) writeCurrentStakers(codecVersion uint16) error {
|
||||
func writeCurrentDelegatorDiff(
|
||||
currentDelegatorList linkeddb.LinkedDB,
|
||||
validatorDiff *diffValidator,
|
||||
codecVersion uint16,
|
||||
) error {
|
||||
addedDelegatorIterator := iterator.FromTree(validatorDiff.addedDelegators)
|
||||
defer addedDelegatorIterator.Release()
|
||||
@@ -687,7 +685,7 @@ func writeCurrentDelegatorDiff(
|
||||
PotentialReward: staker.PotentialReward,
|
||||
StakerStartTime: uint64(staker.StartTime.Unix()),
|
||||
}
|
||||
if err := writeDelegatorMetadata(currentDelegatorList, metadata, codecVersion); err != nil {
|
||||
if err := writeDelegatorMetadata(currentDelegatorList, metadata); err != nil {
|
||||
return fmt.Errorf("failed to write current delegator to list: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
@@ -309,3 +310,74 @@ func parseTxStatus(b []byte) (txBytesAndStatus, error) {
|
||||
Status: status.Status(obj.Uint32(txsStatus)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ---- validatorMetadata (staker uptime/reward record; txID is the DB key, not
|
||||
// serialized). Fixed object of five uint64 fields. ----
|
||||
|
||||
const (
|
||||
vmdUpDuration = 0
|
||||
vmdLastUpdated = 8
|
||||
vmdPotentialReward = 16
|
||||
vmdDelegateeReward = 24
|
||||
vmdStakerStartTime = 32
|
||||
vmdSize = 40
|
||||
)
|
||||
|
||||
func marshalValidatorMetadata(m *validatorMetadata) ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + vmdSize)
|
||||
ob := b.StartObject(vmdSize)
|
||||
ob.SetUint64(vmdUpDuration, uint64(m.UpDuration))
|
||||
ob.SetUint64(vmdLastUpdated, m.LastUpdated)
|
||||
ob.SetUint64(vmdPotentialReward, m.PotentialReward)
|
||||
ob.SetUint64(vmdDelegateeReward, m.PotentialDelegateeReward)
|
||||
ob.SetUint64(vmdStakerStartTime, m.StakerStartTime)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
// unmarshalValidatorMetadata overlays m's five serialized fields from b. All
|
||||
// five are always written, so a full buffer overwrites any caller-supplied
|
||||
// tx-default values (StakerStartTime/LastUpdated); the derived lastUpdated and
|
||||
// the DB-key txID are handled by the parseValidatorMetadata wrapper.
|
||||
func unmarshalValidatorMetadata(b []byte, m *validatorMetadata) error {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj := msg.Root()
|
||||
m.UpDuration = time.Duration(obj.Uint64(vmdUpDuration))
|
||||
m.LastUpdated = obj.Uint64(vmdLastUpdated)
|
||||
m.PotentialReward = obj.Uint64(vmdPotentialReward)
|
||||
m.PotentialDelegateeReward = obj.Uint64(vmdDelegateeReward)
|
||||
m.StakerStartTime = obj.Uint64(vmdStakerStartTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- delegatorMetadata (PotentialReward + StakerStartTime; txID is the DB
|
||||
// key, not serialized). ----
|
||||
|
||||
const (
|
||||
dmdPotentialReward = 0
|
||||
dmdStakerStartTime = 8
|
||||
dmdSize = 16
|
||||
)
|
||||
|
||||
func marshalDelegatorMetadata(m *delegatorMetadata) ([]byte, error) {
|
||||
b := zap.NewBuilder(zap.HeaderSize + dmdSize)
|
||||
ob := b.StartObject(dmdSize)
|
||||
ob.SetUint64(dmdPotentialReward, m.PotentialReward)
|
||||
ob.SetUint64(dmdStakerStartTime, m.StakerStartTime)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish(), nil
|
||||
}
|
||||
|
||||
func unmarshalDelegatorMetadata(b []byte, m *delegatorMetadata) error {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj := msg.Root()
|
||||
m.PotentialReward = obj.Uint64(dmdPotentialReward)
|
||||
m.StakerStartTime = obj.Uint64(dmdStakerStartTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/luxfi/math"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
"github.com/luxfi/node/vms/components/verify"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/node/vms/platformvm/fx"
|
||||
"github.com/luxfi/node/vms/platformvm/signer"
|
||||
"github.com/luxfi/node/vms/platformvm/stakeable"
|
||||
@@ -24,57 +23,69 @@ import (
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
)
|
||||
|
||||
// Wire byte-lengths of the primitive fields the intrinsic-bandwidth fee
|
||||
// schedule prices. The schedule sums these per-field sizes to charge bandwidth
|
||||
// gas; they are the fee model's own constants (no codec dependency). Values are
|
||||
// the fixed on-wire sizes of each primitive, so the schedule stays stable and
|
||||
// self-contained.
|
||||
const (
|
||||
wireShortLen = 2 // uint16
|
||||
wireIntLen = 4 // uint32
|
||||
wireLongLen = 8 // uint64
|
||||
wireVersionLen = 2 // fixed per-tx serialization-header budget
|
||||
)
|
||||
|
||||
// Signature verification costs were conservatively based on benchmarks run on
|
||||
// an AWS c5.xlarge instance.
|
||||
const (
|
||||
intrinsicValidatorBandwidth = ids.NodeIDLen + // nodeID
|
||||
pcodecs.LongLen + // start
|
||||
pcodecs.LongLen + // end
|
||||
pcodecs.LongLen // weight
|
||||
wireLongLen + // start
|
||||
wireLongLen + // end
|
||||
wireLongLen // weight
|
||||
|
||||
intrinsicNetValidatorBandwidth = intrinsicValidatorBandwidth + // validator
|
||||
ids.IDLen // subchainID
|
||||
|
||||
intrinsicOutputBandwidth = ids.IDLen + // assetID
|
||||
pcodecs.IntLen // output typeID
|
||||
wireIntLen // output typeID
|
||||
|
||||
intrinsicStakeableLockedOutputBandwidth = pcodecs.LongLen + // locktime
|
||||
pcodecs.IntLen // output typeID
|
||||
intrinsicStakeableLockedOutputBandwidth = wireLongLen + // locktime
|
||||
wireIntLen // output typeID
|
||||
|
||||
intrinsicSECP256k1FxOutputOwnersBandwidth = pcodecs.LongLen + // locktime
|
||||
pcodecs.IntLen + // threshold
|
||||
pcodecs.IntLen // num addresses
|
||||
intrinsicSECP256k1FxOutputOwnersBandwidth = wireLongLen + // locktime
|
||||
wireIntLen + // threshold
|
||||
wireIntLen // num addresses
|
||||
|
||||
intrinsicSECP256k1FxOutputBandwidth = pcodecs.LongLen + // amount
|
||||
intrinsicSECP256k1FxOutputBandwidth = wireLongLen + // amount
|
||||
intrinsicSECP256k1FxOutputOwnersBandwidth
|
||||
|
||||
intrinsicInputBandwidth = ids.IDLen + // txID
|
||||
pcodecs.IntLen + // output index
|
||||
wireIntLen + // output index
|
||||
ids.IDLen + // assetID
|
||||
pcodecs.IntLen + // input typeID
|
||||
pcodecs.IntLen // credential typeID
|
||||
wireIntLen + // input typeID
|
||||
wireIntLen // credential typeID
|
||||
|
||||
intrinsicStakeableLockedInputBandwidth = pcodecs.LongLen + // locktime
|
||||
pcodecs.IntLen // input typeID
|
||||
intrinsicStakeableLockedInputBandwidth = wireLongLen + // locktime
|
||||
wireIntLen // input typeID
|
||||
|
||||
intrinsicSECP256k1FxInputBandwidth = pcodecs.IntLen + // num indices
|
||||
pcodecs.IntLen // num signatures
|
||||
intrinsicSECP256k1FxInputBandwidth = wireIntLen + // num indices
|
||||
wireIntLen // num signatures
|
||||
|
||||
intrinsicSECP256k1FxTransferableInputBandwidth = pcodecs.LongLen + // amount
|
||||
intrinsicSECP256k1FxTransferableInputBandwidth = wireLongLen + // amount
|
||||
intrinsicSECP256k1FxInputBandwidth
|
||||
|
||||
intrinsicSECP256k1FxSignatureBandwidth = pcodecs.IntLen + // signature index
|
||||
intrinsicSECP256k1FxSignatureBandwidth = wireIntLen + // signature index
|
||||
secp256k1.SignatureLen // signature length
|
||||
|
||||
intrinsicSECP256k1FxSignatureCompute = 200 // secp256k1 signature verification time is around 200us
|
||||
|
||||
intrinsicConvertNetworkToL1ValidatorBandwidth = pcodecs.IntLen + // nodeID length
|
||||
pcodecs.LongLen + // weight
|
||||
pcodecs.LongLen + // balance
|
||||
pcodecs.IntLen + // remaining balance owner threshold
|
||||
pcodecs.IntLen + // remaining balance owner num addresses
|
||||
pcodecs.IntLen + // deactivation owner threshold
|
||||
pcodecs.IntLen // deactivation owner num addresses
|
||||
intrinsicConvertNetworkToL1ValidatorBandwidth = wireIntLen + // nodeID length
|
||||
wireLongLen + // weight
|
||||
wireLongLen + // balance
|
||||
wireIntLen + // remaining balance owner threshold
|
||||
wireIntLen + // remaining balance owner num addresses
|
||||
wireIntLen + // deactivation owner threshold
|
||||
wireIntLen // deactivation owner num addresses
|
||||
|
||||
intrinsicBLSAggregateCompute = 5 // BLS public key aggregation time is around 5us
|
||||
intrinsicBLSVerifyCompute = 1_000 // BLS verification time is around 1000us
|
||||
@@ -99,44 +110,44 @@ var (
|
||||
IntrinsicAddChainValidatorTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
intrinsicNetValidatorBandwidth + // netValidator
|
||||
pcodecs.IntLen + // netAuth typeID
|
||||
pcodecs.IntLen, // netAuthCredential typeID
|
||||
wireIntLen + // netAuth typeID
|
||||
wireIntLen, // netAuthCredential typeID
|
||||
gas.DBRead: 3, // get net auth + check for net transformation + check for net conversion
|
||||
gas.DBWrite: 3, // put current staker + write weight diff + write pk diff
|
||||
}
|
||||
IntrinsicCreateChainTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // subchainID
|
||||
pcodecs.ShortLen + // chainName length
|
||||
wireShortLen + // chainName length
|
||||
ids.IDLen + // vmID
|
||||
pcodecs.IntLen + // num fxIDs
|
||||
pcodecs.IntLen + // genesis length
|
||||
pcodecs.IntLen + // chainAuth typeID
|
||||
pcodecs.IntLen, // chainAuthCredential typeID
|
||||
wireIntLen + // num fxIDs
|
||||
wireIntLen + // genesis length
|
||||
wireIntLen + // chainAuth typeID
|
||||
wireIntLen, // chainAuthCredential typeID
|
||||
gas.DBRead: 3, // get chain auth + check for chain transformation + check for chain conversion
|
||||
gas.DBWrite: 1, // put chain
|
||||
}
|
||||
IntrinsicCreateNetworkTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
pcodecs.IntLen, // owner typeID
|
||||
wireIntLen, // owner typeID
|
||||
gas.DBWrite: 1, // write chain owner
|
||||
}
|
||||
IntrinsicImportTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // source chainID
|
||||
pcodecs.IntLen, // num importing inputs
|
||||
wireIntLen, // num importing inputs
|
||||
}
|
||||
IntrinsicExportTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // destination chainID
|
||||
pcodecs.IntLen, // num exported outputs
|
||||
wireIntLen, // num exported outputs
|
||||
}
|
||||
IntrinsicRemoveChainValidatorTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.NodeIDLen + // nodeID
|
||||
ids.IDLen + // netID
|
||||
pcodecs.IntLen + // netAuth typeID
|
||||
pcodecs.IntLen, // netAuthCredential typeID
|
||||
wireIntLen + // netAuth typeID
|
||||
wireIntLen, // netAuthCredential typeID
|
||||
gas.DBRead: 1, // read net auth
|
||||
gas.DBWrite: 3, // delete validator + write weight diff + write pk diff
|
||||
}
|
||||
@@ -144,11 +155,11 @@ var (
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
intrinsicValidatorBandwidth + // validator
|
||||
ids.IDLen + // subchainID
|
||||
pcodecs.IntLen + // signer typeID
|
||||
pcodecs.IntLen + // num stake outs
|
||||
pcodecs.IntLen + // validator rewards typeID
|
||||
pcodecs.IntLen + // delegator rewards typeID
|
||||
pcodecs.IntLen, // delegation shares
|
||||
wireIntLen + // signer typeID
|
||||
wireIntLen + // num stake outs
|
||||
wireIntLen + // validator rewards typeID
|
||||
wireIntLen + // delegator rewards typeID
|
||||
wireIntLen, // delegation shares
|
||||
gas.DBRead: 1, // get staking config
|
||||
gas.DBWrite: 3, // put current staker + write weight diff + write pk diff
|
||||
}
|
||||
@@ -156,17 +167,17 @@ var (
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
intrinsicValidatorBandwidth + // validator
|
||||
ids.IDLen + // subchainID
|
||||
pcodecs.IntLen + // num stake outs
|
||||
pcodecs.IntLen, // delegator rewards typeID
|
||||
wireIntLen + // num stake outs
|
||||
wireIntLen, // delegator rewards typeID
|
||||
gas.DBRead: 1, // get staking config
|
||||
gas.DBWrite: 2, // put current staker + write weight diff
|
||||
}
|
||||
IntrinsicTransferChainOwnershipTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // netID
|
||||
pcodecs.IntLen + // netAuth typeID
|
||||
pcodecs.IntLen + // owner typeID
|
||||
pcodecs.IntLen, // netAuthCredential typeID
|
||||
wireIntLen + // netAuth typeID
|
||||
wireIntLen + // owner typeID
|
||||
wireIntLen, // netAuthCredential typeID
|
||||
gas.DBRead: 1, // read net auth
|
||||
gas.DBWrite: 1, // set net owner
|
||||
}
|
||||
@@ -174,71 +185,71 @@ var (
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // netID
|
||||
ids.IDLen + // assetID
|
||||
pcodecs.IntLen + // initialSupply
|
||||
pcodecs.IntLen + // maximumSupply
|
||||
pcodecs.IntLen + // minConsumptionRate
|
||||
pcodecs.IntLen + // maxConsumptionRate
|
||||
pcodecs.LongLen + // minValidatorStake
|
||||
pcodecs.LongLen + // maxValidatorStake
|
||||
pcodecs.IntLen + // minStakeDuration
|
||||
pcodecs.IntLen + // maxStakeDuration
|
||||
pcodecs.IntLen + // minDelegationFee
|
||||
pcodecs.IntLen + // minDelegatorStake
|
||||
pcodecs.IntLen + // maxValidatorWeightFactor
|
||||
pcodecs.IntLen + // uptimeRequirement
|
||||
pcodecs.IntLen + // netAuth typeID
|
||||
pcodecs.IntLen, // netAuthCredential typeID
|
||||
wireIntLen + // initialSupply
|
||||
wireIntLen + // maximumSupply
|
||||
wireIntLen + // minConsumptionRate
|
||||
wireIntLen + // maxConsumptionRate
|
||||
wireLongLen + // minValidatorStake
|
||||
wireLongLen + // maxValidatorStake
|
||||
wireIntLen + // minStakeDuration
|
||||
wireIntLen + // maxStakeDuration
|
||||
wireIntLen + // minDelegationFee
|
||||
wireIntLen + // minDelegatorStake
|
||||
wireIntLen + // maxValidatorWeightFactor
|
||||
wireIntLen + // uptimeRequirement
|
||||
wireIntLen + // netAuth typeID
|
||||
wireIntLen, // netAuthCredential typeID
|
||||
gas.DBRead: 2, // get net auth + check for net transformation
|
||||
gas.DBWrite: 1, // write net transformation
|
||||
}
|
||||
IntrinsicBaseTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: pcodecs.VersionSize + // codecVersion
|
||||
pcodecs.IntLen + // typeID
|
||||
pcodecs.IntLen + // networkID
|
||||
gas.Bandwidth: wireVersionLen + // codecVersion
|
||||
wireIntLen + // typeID
|
||||
wireIntLen + // networkID
|
||||
ids.IDLen + // blockchainID
|
||||
pcodecs.IntLen + // number of outputs
|
||||
pcodecs.IntLen + // number of inputs
|
||||
pcodecs.IntLen + // length of memo
|
||||
pcodecs.IntLen, // number of credentials
|
||||
wireIntLen + // number of outputs
|
||||
wireIntLen + // number of inputs
|
||||
wireIntLen + // length of memo
|
||||
wireIntLen, // number of credentials
|
||||
}
|
||||
IntrinsicConvertNetworkToL1TxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // subchainID
|
||||
ids.IDLen + // chainID
|
||||
pcodecs.IntLen + // address length
|
||||
pcodecs.IntLen + // validators length
|
||||
pcodecs.IntLen + // chainAuth typeID
|
||||
pcodecs.IntLen, // chainAuthCredential typeID
|
||||
wireIntLen + // address length
|
||||
wireIntLen + // validators length
|
||||
wireIntLen + // chainAuth typeID
|
||||
wireIntLen, // chainAuthCredential typeID
|
||||
gas.DBRead: 3, // chain auth + transformation lookup + conversion lookup
|
||||
gas.DBWrite: 2, // write conversion manager + total weight
|
||||
}
|
||||
IntrinsicRegisterL1ValidatorTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
pcodecs.LongLen + // balance
|
||||
wireLongLen + // balance
|
||||
bls.SignatureLen + // proof of possession
|
||||
pcodecs.IntLen, // message length
|
||||
wireIntLen, // message length
|
||||
gas.DBRead: 5, // conversion owner + expiry lookup + sov lookup + subchainID/nodeID lookup + weight lookup
|
||||
gas.DBWrite: 6, // write current staker + expiry + write weight diff + write pk diff + subchainID/nodeID lookup + weight lookup
|
||||
gas.Compute: intrinsicBLSPoPVerifyCompute,
|
||||
}
|
||||
IntrinsicSetL1ValidatorWeightTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
pcodecs.IntLen, // message length
|
||||
wireIntLen, // message length
|
||||
gas.DBRead: 3, // read staker + read conversion + read weight
|
||||
gas.DBWrite: 5, // remaining balance utxo + write weight diff + write pk diff + weights lookup + validator write
|
||||
}
|
||||
IntrinsicIncreaseL1ValidatorBalanceTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // validationID
|
||||
pcodecs.LongLen, // balance
|
||||
wireLongLen, // balance
|
||||
gas.DBRead: 1, // read staker
|
||||
gas.DBWrite: 5, // weight diff + deactivated weight diff + public key diff + delete staker + write staker
|
||||
}
|
||||
IntrinsicDisableL1ValidatorTxComplexities = gas.Dimensions{
|
||||
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
||||
ids.IDLen + // validationID
|
||||
pcodecs.IntLen + // auth typeID
|
||||
pcodecs.IntLen, // authCredential typeID
|
||||
wireIntLen + // auth typeID
|
||||
wireIntLen, // authCredential typeID
|
||||
gas.DBRead: 1, // read staker
|
||||
gas.DBWrite: 6, // write remaining balance utxo + weight diff + deactivated weight diff + public key diff + delete staker + write staker
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/luxfi/node/cache/lru"
|
||||
"github.com/luxfi/node/utils/json"
|
||||
"github.com/luxfi/node/version"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/node/vms/platformvm/block"
|
||||
"github.com/luxfi/node/vms/platformvm/config"
|
||||
"github.com/luxfi/node/vms/platformvm/fx"
|
||||
@@ -101,8 +100,7 @@ type VM struct {
|
||||
chainID ids.ID
|
||||
state state.State
|
||||
|
||||
fx fx.Fx
|
||||
codecRegistry pcodecs.Registry
|
||||
fx fx.Fx
|
||||
|
||||
// Bootstrapped remembers if this chain has finished bootstrapping or not
|
||||
bootstrappedConsensus utils.Atomic[bool]
|
||||
@@ -231,8 +229,6 @@ func (vm *VM) Initialize(
|
||||
// Since DBManager is now an interface{}, we need to handle it differently
|
||||
// logic simplified as we now just trust init.DB or fallback to memdb if nil above
|
||||
|
||||
// Note: this codec is never used to serialize anything
|
||||
vm.codecRegistry = pcodecs.NewLinearCodec()
|
||||
vm.fx = &secp256k1fx.Fx{}
|
||||
if err := vm.fx.Initialize(vm); err != nil {
|
||||
return fmt.Errorf("failed to initialize fx: %w", err)
|
||||
@@ -777,10 +773,6 @@ func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
|
||||
return vm.Network.Disconnected(ctx, nodeID)
|
||||
}
|
||||
|
||||
func (vm *VM) CodecRegistry() pcodecs.Registry {
|
||||
return vm.codecRegistry
|
||||
}
|
||||
|
||||
func (vm *VM) Clock() *mockable.Clock {
|
||||
return &vm.nodeClock
|
||||
}
|
||||
|
||||
@@ -10,12 +10,15 @@ import (
|
||||
chain "github.com/luxfi/vm/chain"
|
||||
"github.com/luxfi/ids"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
statelessblock "github.com/luxfi/node/vms/proposervm/block"
|
||||
)
|
||||
|
||||
var _ chain.BatchedChainVM = (*VM)(nil)
|
||||
|
||||
// containerLenPrefix is the 4-byte length prefix each block occupies in an
|
||||
// ancestors response container; counted toward the response size budget.
|
||||
const containerLenPrefix = 4
|
||||
|
||||
func (vm *VM) GetAncestors(
|
||||
ctx context.Context,
|
||||
blkID ids.ID,
|
||||
@@ -42,10 +45,10 @@ func (vm *VM) GetAncestors(
|
||||
|
||||
blkBytes := blk.Bytes()
|
||||
|
||||
// Ensure response size isn't too large. Include pcodecs.IntLen because
|
||||
// the size of the message is included with each container, and the size
|
||||
// is repr. by an int.
|
||||
currentByteLength += pcodecs.IntLen + len(blkBytes)
|
||||
// Ensure response size isn't too large. Include containerLenPrefix
|
||||
// because the size of the message is included with each container, and
|
||||
// the size is repr. by an int.
|
||||
currentByteLength += containerLenPrefix + len(blkBytes)
|
||||
elapsedTime := vm.Clock.Time().Sub(startTime)
|
||||
if len(res) > 0 && (currentByteLength >= maxBlocksSize || maxBlocksRetrievalTime <= elapsedTime) {
|
||||
return res, nil // reached maximum size or ran out of time
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/staking"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,9 +23,9 @@ var (
|
||||
|
||||
// Epoch represents a P-Chain epoch for validator set coordination
|
||||
type Epoch struct {
|
||||
PChainHeight uint64 `serialize:"true" json:"pChainHeight"`
|
||||
Number uint64 `serialize:"true" json:"number"`
|
||||
StartTime int64 `serialize:"true" json:"startTime"`
|
||||
PChainHeight uint64 `json:"pChainHeight"`
|
||||
Number uint64 `json:"number"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
}
|
||||
|
||||
type Block interface {
|
||||
@@ -56,30 +56,19 @@ type SignedBlock interface {
|
||||
BlobCount() uint32 // Number of DA blobs in block
|
||||
}
|
||||
|
||||
type statelessUnsignedBlock struct {
|
||||
ParentID ids.ID `serialize:"true"`
|
||||
Timestamp int64 `serialize:"true"`
|
||||
PChainHeight uint64 `serialize:"true"`
|
||||
Epoch Epoch `serialize:"true"`
|
||||
Certificate []byte `serialize:"true"`
|
||||
Block []byte `serialize:"true"`
|
||||
|
||||
// Data Availability fields (v1.1 spec)
|
||||
DARoot [32]byte `serialize:"true"` // Root of DA commitments
|
||||
WitnessRoot [32]byte `serialize:"true"` // Root of witnesses/proofs
|
||||
MessagesOutRoot [32]byte `serialize:"true"` // Root of outgoing cross-chain messages
|
||||
BlobCount uint32 `serialize:"true"` // Number of DA blobs in block
|
||||
}
|
||||
|
||||
// statelessBlock is zap-backed: msg is the unsigned body buffer (a
|
||||
// self-delimiting zap message with blkSigned at offset 0), Signature is the
|
||||
// optional proposer signature carried in the appended suffix buffer. All block
|
||||
// fields are read from msg via fixed offsets — the struct IS the wire.
|
||||
type statelessBlock struct {
|
||||
StatelessBlock statelessUnsignedBlock `serialize:"true"`
|
||||
Signature []byte `serialize:"true"`
|
||||
msg *zap.Message // unsigned body buffer
|
||||
Signature []byte // proposer signature (appended suffix); exported for tests
|
||||
|
||||
id ids.ID
|
||||
timestamp time.Time
|
||||
cert *staking.Certificate
|
||||
proposer ids.NodeID
|
||||
bytes []byte
|
||||
bytes []byte // full signed bytes = unsigned ‖ sig
|
||||
}
|
||||
|
||||
func (b *statelessBlock) ID() ids.ID {
|
||||
@@ -87,34 +76,55 @@ func (b *statelessBlock) ID() ids.ID {
|
||||
}
|
||||
|
||||
func (b *statelessBlock) ParentID() ids.ID {
|
||||
return b.StatelessBlock.ParentID
|
||||
return ids.ID(read32(b.msg.Root(), offParentID))
|
||||
}
|
||||
|
||||
func (b *statelessBlock) Block() []byte {
|
||||
return b.StatelessBlock.Block
|
||||
return b.msg.Root().Bytes(offBlock)
|
||||
}
|
||||
|
||||
func (b *statelessBlock) Bytes() []byte {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
// initialize binds the block's fields from its signed bytes. This is the one
|
||||
// bytes→fields entry, shared by the Build* constructors (fresh buffer) and
|
||||
// Parse (wire/disk buffer); the bytes are authoritative and never re-encoded.
|
||||
func (b *statelessBlock) initialize(bytes []byte) error {
|
||||
b.bytes = bytes
|
||||
|
||||
// The serialized form of the block is the unsignedBytes followed by the
|
||||
// signature, which is prefixed by a uint32. So, we need to strip off the
|
||||
// signature as well as it's length prefix to get the unsigned bytes.
|
||||
lenUnsignedBytes := len(bytes) - pcodecs.IntLen - len(b.Signature)
|
||||
unsignedBytes := bytes[:lenUnsignedBytes]
|
||||
b.id = hash.ComputeHash256Array(unsignedBytes)
|
||||
// The signed form is unsigned_buffer ‖ sig_buffer (both self-delimiting).
|
||||
// Split on the leading message length; the ID is the hash of that prefix.
|
||||
n, err := zapLen(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg, err := zap.Parse(bytes[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.msg = msg
|
||||
b.id = hash.ComputeHash256Array(bytes[:n])
|
||||
|
||||
b.timestamp = time.Unix(b.StatelessBlock.Timestamp, 0)
|
||||
if len(b.StatelessBlock.Certificate) == 0 {
|
||||
if len(bytes) > n {
|
||||
sigMsg, err := zap.Parse(bytes[n:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s := sigMsg.Root().Bytes(offSig); len(s) > 0 {
|
||||
b.Signature = append([]byte(nil), s...)
|
||||
}
|
||||
}
|
||||
|
||||
root := b.msg.Root()
|
||||
b.timestamp = time.Unix(root.Int64(offTimestamp), 0)
|
||||
|
||||
certBytes := root.Bytes(offCert)
|
||||
if len(certBytes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
b.cert, err = staking.ParseCertificate(b.StatelessBlock.Certificate)
|
||||
b.cert, err = staking.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errInvalidCertificate, err)
|
||||
}
|
||||
@@ -127,14 +137,14 @@ func (b *statelessBlock) initialize(bytes []byte) error {
|
||||
}
|
||||
|
||||
func (b *statelessBlock) verify(chainID ids.ID) error {
|
||||
if len(b.StatelessBlock.Certificate) == 0 {
|
||||
if b.cert == nil {
|
||||
if len(b.Signature) > 0 {
|
||||
return errUnexpectedSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := BuildHeader(chainID, b.StatelessBlock.ParentID, b.id)
|
||||
header, err := BuildHeader(chainID, b.ParentID(), b.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,11 +158,16 @@ func (b *statelessBlock) verify(chainID ids.ID) error {
|
||||
}
|
||||
|
||||
func (b *statelessBlock) PChainHeight() uint64 {
|
||||
return b.StatelessBlock.PChainHeight
|
||||
return b.msg.Root().Uint64(offPChainHt)
|
||||
}
|
||||
|
||||
func (b *statelessBlock) PChainEpoch() Epoch {
|
||||
return b.StatelessBlock.Epoch
|
||||
root := b.msg.Root()
|
||||
return Epoch{
|
||||
PChainHeight: root.Uint64(offEpochHt),
|
||||
Number: root.Uint64(offEpochNum),
|
||||
StartTime: root.Int64(offEpochStart),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *statelessBlock) Timestamp() time.Time {
|
||||
@@ -164,17 +179,17 @@ func (b *statelessBlock) Proposer() ids.NodeID {
|
||||
}
|
||||
|
||||
func (b *statelessBlock) DARoot() [32]byte {
|
||||
return b.StatelessBlock.DARoot
|
||||
return read32(b.msg.Root(), offDARoot)
|
||||
}
|
||||
|
||||
func (b *statelessBlock) WitnessRoot() [32]byte {
|
||||
return b.StatelessBlock.WitnessRoot
|
||||
return read32(b.msg.Root(), offWitnessRoot)
|
||||
}
|
||||
|
||||
func (b *statelessBlock) MessagesOutRoot() [32]byte {
|
||||
return b.StatelessBlock.MessagesOutRoot
|
||||
return read32(b.msg.Root(), offMsgOutRoot)
|
||||
}
|
||||
|
||||
func (b *statelessBlock) BlobCount() uint32 {
|
||||
return b.StatelessBlock.BlobCount
|
||||
return b.msg.Root().Uint32(offBlobCount)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
|
||||
// Native ZAP block wire: the struct IS the wire. There is no codec, no version
|
||||
// prefix, no slot map. A 1-byte blockKind at object offset 0 is the whole
|
||||
// dispatch — Parse reads it and returns the typed block (signed body vs option).
|
||||
//
|
||||
// A signed block is unsigned_buffer ‖ sig_buffer (both self-delimiting zap
|
||||
// messages), so the unsigned bytes are a genuine byte-prefix of the signed
|
||||
// bytes and the block ID = hash(unsigned prefix). An unsigned block (no
|
||||
// certificate, no signature) is just the unsigned buffer with no suffix. An
|
||||
// option is a single self-delimiting message.
|
||||
//
|
||||
// Unsigned-block object (kind = blkSigned), all offsets object-relative, LE:
|
||||
//
|
||||
// kind u8 @ 0
|
||||
// ParentID 32B @ 1
|
||||
// Timestamp i64 @ 33
|
||||
// PChainHeight u64 @ 41
|
||||
// Epoch.PChainHt u64 @ 49
|
||||
// Epoch.Number u64 @ 57
|
||||
// Epoch.StartTime i64 @ 65
|
||||
// Certificate 8B @ 73 bytes ptr (opaque DER; part of the ID hash)
|
||||
// Block 8B @ 81 bytes ptr (inner block bytes)
|
||||
// DARoot 32B @ 89
|
||||
// WitnessRoot 32B @ 121
|
||||
// MessagesOutRoot 32B @ 153
|
||||
// BlobCount u32 @ 185
|
||||
// size 189
|
||||
//
|
||||
// Sig buffer object: Signature bytes ptr @ 0 size 8
|
||||
// Header object: Chain 32B @0, Parent 32B @32, Body 32B @64 size 96
|
||||
// Option object (kind = blkOption): kind u8 @0, ParentID 32B @1,
|
||||
// InnerBytes bytes ptr @33 size 41
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// blockKind is the 1-byte discriminator at object offset 0 of every parseable
|
||||
// proposervm block buffer. Parse reads it and returns the typed block.
|
||||
type blockKind uint8
|
||||
|
||||
const (
|
||||
blkReserved blockKind = iota
|
||||
blkSigned // statelessBlock (may carry cert + appended signature)
|
||||
blkOption // option block
|
||||
)
|
||||
|
||||
var errUnknownBlockType = errors.New("proposervm block: unknown block kind")
|
||||
|
||||
// Unsigned-block object offsets + size.
|
||||
const (
|
||||
offKind = 0
|
||||
offParentID = 1
|
||||
offTimestamp = 33
|
||||
offPChainHt = 41
|
||||
|
||||
offEpochHt = 49
|
||||
offEpochNum = 57
|
||||
offEpochStart = 65
|
||||
|
||||
offCert = 73
|
||||
offBlock = 81
|
||||
|
||||
offDARoot = 89
|
||||
offWitnessRoot = 121
|
||||
offMsgOutRoot = 153
|
||||
offBlobCount = 185
|
||||
|
||||
sizeUnsigned = 189
|
||||
|
||||
idLen = 32
|
||||
)
|
||||
|
||||
// Sig buffer object.
|
||||
const (
|
||||
offSig = 0
|
||||
sizeSig = 8
|
||||
)
|
||||
|
||||
// Header object.
|
||||
const (
|
||||
hdrChain = 0
|
||||
hdrParent = 32
|
||||
hdrBody = 64
|
||||
sizeHeader = 96
|
||||
)
|
||||
|
||||
// Option object.
|
||||
const (
|
||||
offOptParent = 1
|
||||
offOptInner = 33
|
||||
sizeOption = 41
|
||||
)
|
||||
|
||||
// zapLen returns the total length of the leading self-delimiting zap message in
|
||||
// b (its header size field @[12:16]). This is the split point between the
|
||||
// unsigned prefix and the signature suffix of a signed block.
|
||||
func zapLen(b []byte) (int, error) {
|
||||
if len(b) < zap.HeaderSize {
|
||||
return 0, zap.ErrBufferTooSmall
|
||||
}
|
||||
n := int(binary.LittleEndian.Uint32(b[12:16]))
|
||||
if n < zap.HeaderSize || n > len(b) {
|
||||
return 0, zap.ErrBufferTooSmall
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// buildUnsignedBuffer writes the unsigned block body as one zap object. cert is
|
||||
// the opaque DER certificate (nil for an unsigned block). The v1.1 DA fields
|
||||
// (DARoot/WitnessRoot/MessagesOutRoot/BlobCount) are written as zeros — no
|
||||
// current constructor populates them, but they stay in the wire so the
|
||||
// SignedBlock accessor surface round-trips if a future builder sets them.
|
||||
func buildUnsignedBuffer(parentID ids.ID, timestamp int64, pChainHeight uint64, epoch Epoch, cert, blockBytes []byte) []byte {
|
||||
var zero [idLen]byte
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeUnsigned + len(cert) + len(blockBytes) + 64)
|
||||
ob := b.StartObject(sizeUnsigned)
|
||||
ob.SetUint8(offKind, uint8(blkSigned))
|
||||
ob.SetBytesFixed(offParentID, parentID[:])
|
||||
ob.SetInt64(offTimestamp, timestamp)
|
||||
ob.SetUint64(offPChainHt, pChainHeight)
|
||||
ob.SetUint64(offEpochHt, epoch.PChainHeight)
|
||||
ob.SetUint64(offEpochNum, epoch.Number)
|
||||
ob.SetInt64(offEpochStart, epoch.StartTime)
|
||||
ob.SetBytes(offCert, cert)
|
||||
ob.SetBytes(offBlock, blockBytes)
|
||||
ob.SetBytesFixed(offDARoot, zero[:])
|
||||
ob.SetBytesFixed(offWitnessRoot, zero[:])
|
||||
ob.SetBytesFixed(offMsgOutRoot, zero[:])
|
||||
ob.SetUint32(offBlobCount, 0)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// buildSigBuffer wraps a proposer signature as a standalone zap message,
|
||||
// appended after the unsigned prefix of a signed block.
|
||||
func buildSigBuffer(sig []byte) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeSig + len(sig))
|
||||
ob := b.StartObject(sizeSig)
|
||||
ob.SetBytes(offSig, sig)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// buildHeaderBuffer encodes the (chain, parent, body) header signed by the
|
||||
// proposer. Deterministic fixed-shape object — Build and verify both hash it.
|
||||
func buildHeaderBuffer(chainID, parentID, bodyID ids.ID) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeHeader)
|
||||
ob := b.StartObject(sizeHeader)
|
||||
ob.SetBytesFixed(hdrChain, chainID[:])
|
||||
ob.SetBytesFixed(hdrParent, parentID[:])
|
||||
ob.SetBytesFixed(hdrBody, bodyID[:])
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// buildOptionBuffer encodes an option block as one zap object.
|
||||
func buildOptionBuffer(parentID ids.ID, innerBytes []byte) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + sizeOption + len(innerBytes) + 32)
|
||||
ob := b.StartObject(sizeOption)
|
||||
ob.SetUint8(offKind, uint8(blkOption))
|
||||
ob.SetBytesFixed(offOptParent, parentID[:])
|
||||
ob.SetBytes(offOptInner, innerBytes)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// read32 reads a 32-byte fixed field at the given object offset.
|
||||
func read32(o zap.Object, off int) [idLen]byte {
|
||||
var r [idLen]byte
|
||||
copy(r[:], o.BytesFixedSlice(off, idLen))
|
||||
return r
|
||||
}
|
||||
|
||||
// concat returns a ‖ b as a fresh slice.
|
||||
func concat(a, b []byte) []byte {
|
||||
out := make([]byte, 0, len(a)+len(b))
|
||||
out = append(out, a...)
|
||||
out = append(out, b...)
|
||||
return out
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/staking"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
func BuildUnsigned(
|
||||
@@ -21,24 +20,14 @@ func BuildUnsigned(
|
||||
epoch Epoch,
|
||||
blockBytes []byte,
|
||||
) (SignedBlock, error) {
|
||||
var block SignedBlock = &statelessBlock{
|
||||
StatelessBlock: statelessUnsignedBlock{
|
||||
ParentID: parentID,
|
||||
Timestamp: timestamp.Unix(),
|
||||
PChainHeight: pChainHeight,
|
||||
Epoch: epoch,
|
||||
Certificate: nil,
|
||||
Block: blockBytes,
|
||||
},
|
||||
timestamp: timestamp,
|
||||
}
|
||||
// No certificate, no signature: the block bytes are just the unsigned body.
|
||||
unsignedBytes := buildUnsignedBuffer(parentID, timestamp.Unix(), pChainHeight, epoch, nil, blockBytes)
|
||||
|
||||
bytes, err := Codec.Marshal(CodecVersion, &block)
|
||||
if err != nil {
|
||||
block := &statelessBlock{}
|
||||
if err := block.initialize(unsignedBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, block.initialize(bytes)
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func Build(
|
||||
@@ -51,50 +40,30 @@ func Build(
|
||||
chainID ids.ID,
|
||||
key crypto.Signer,
|
||||
) (SignedBlock, error) {
|
||||
block := &statelessBlock{
|
||||
StatelessBlock: statelessUnsignedBlock{
|
||||
ParentID: parentID,
|
||||
Timestamp: timestamp.Unix(),
|
||||
PChainHeight: pChainHeight,
|
||||
Epoch: epoch,
|
||||
Certificate: cert.Raw,
|
||||
Block: blockBytes,
|
||||
},
|
||||
timestamp: timestamp,
|
||||
cert: cert,
|
||||
proposer: ids.NodeIDFromCert(&ids.Certificate{
|
||||
Raw: cert.Raw,
|
||||
PublicKey: cert.PublicKey,
|
||||
}),
|
||||
}
|
||||
var blockIntf SignedBlock = block
|
||||
unsignedBytes := buildUnsignedBuffer(parentID, timestamp.Unix(), pChainHeight, epoch, cert.Raw, blockBytes)
|
||||
|
||||
unsignedBytesWithEmptySignature, err := Codec.Marshal(CodecVersion, &blockIntf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The block ID is the hash of the unsigned prefix; the proposer signs a
|
||||
// header binding (chainID, parentID, blockID).
|
||||
id := hash.ComputeHash256Array(unsignedBytes)
|
||||
|
||||
// The serialized form of the block is the unsignedBytes followed by the
|
||||
// signature, which is prefixed by a uint32. Because we are marshalling the
|
||||
// block with an empty signature, we only need to strip off the length
|
||||
// prefix to get the unsigned bytes.
|
||||
lenUnsignedBytes := len(unsignedBytesWithEmptySignature) - pcodecs.IntLen
|
||||
unsignedBytes := unsignedBytesWithEmptySignature[:lenUnsignedBytes]
|
||||
block.id = hash.ComputeHash256Array(unsignedBytes)
|
||||
|
||||
header, err := BuildHeader(chainID, parentID, block.id)
|
||||
header, err := BuildHeader(chainID, parentID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
headerHash := hash.ComputeHash256(header.Bytes())
|
||||
block.Signature, err = key.Sign(rand.Reader, headerHash, crypto.SHA256)
|
||||
sig, err := key.Sign(rand.Reader, headerHash, crypto.SHA256)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block.bytes, err = Codec.Marshal(CodecVersion, &blockIntf)
|
||||
return block, err
|
||||
signedBytes := concat(unsignedBytes, buildSigBuffer(sig))
|
||||
|
||||
block := &statelessBlock{}
|
||||
if err := block.initialize(signedBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func BuildHeader(
|
||||
@@ -102,15 +71,12 @@ func BuildHeader(
|
||||
parentID ids.ID,
|
||||
bodyID ids.ID,
|
||||
) (Header, error) {
|
||||
header := statelessHeader{
|
||||
return &statelessHeader{
|
||||
Chain: chainID,
|
||||
Parent: parentID,
|
||||
Body: bodyID,
|
||||
}
|
||||
|
||||
bytes, err := Codec.Marshal(CodecVersion, &header)
|
||||
header.bytes = bytes
|
||||
return &header, err
|
||||
bytes: buildHeaderBuffer(chainID, parentID, bodyID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildOption the option block
|
||||
@@ -120,15 +86,9 @@ func BuildOption(
|
||||
parentID ids.ID,
|
||||
innerBytes []byte,
|
||||
) (Block, error) {
|
||||
var block Block = &option{
|
||||
PrntID: parentID,
|
||||
InnerBytes: innerBytes,
|
||||
}
|
||||
|
||||
bytes, err := Codec.Marshal(CodecVersion, &block)
|
||||
if err != nil {
|
||||
block := &option{}
|
||||
if err := block.initialize(buildOptionBuffer(parentID, innerBytes)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, block.initialize(bytes)
|
||||
return block, nil
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
const CodecVersion = 0
|
||||
|
||||
var Codec pcodecs.Manager
|
||||
|
||||
func init() {
|
||||
lc := pcodecs.NewLinearCodec()
|
||||
// The maximum block size is enforced by the p2p message size limit.
|
||||
// See: [constants.DefaultMaxMessageSize]
|
||||
Codec = pcodecs.NewMaxIntManager()
|
||||
|
||||
err := errors.Join(
|
||||
lc.RegisterType(&statelessBlock{}),
|
||||
lc.RegisterType(&option{}),
|
||||
Codec.RegisterCodec(CodecVersion, lc),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
@@ -12,10 +12,13 @@ type Header interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
// statelessHeader is the (chain, parent, body) binding the proposer signs. Its
|
||||
// bytes are a single fixed-shape zap object (see buildHeaderBuffer); the struct
|
||||
// caches the three IDs plus the encoded bytes.
|
||||
type statelessHeader struct {
|
||||
Chain ids.ID `serialize:"true"`
|
||||
Parent ids.ID `serialize:"true"`
|
||||
Body ids.ID `serialize:"true"`
|
||||
Chain ids.ID
|
||||
Parent ids.ID
|
||||
Body ids.ID
|
||||
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// option is zap-backed: msg is the single self-delimiting message with
|
||||
// blkOption at offset 0. ID = hash(bytes); options carry no signature.
|
||||
type option struct {
|
||||
PrntID ids.ID `serialize:"true"`
|
||||
InnerBytes []byte `serialize:"true"`
|
||||
|
||||
msg *zap.Message
|
||||
id ids.ID
|
||||
bytes []byte
|
||||
}
|
||||
@@ -21,11 +22,11 @@ func (b *option) ID() ids.ID {
|
||||
}
|
||||
|
||||
func (b *option) ParentID() ids.ID {
|
||||
return b.PrntID
|
||||
return ids.ID(read32(b.msg.Root(), offOptParent))
|
||||
}
|
||||
|
||||
func (b *option) Block() []byte {
|
||||
return b.InnerBytes
|
||||
return b.msg.Root().Bytes(offOptInner)
|
||||
}
|
||||
|
||||
func (b *option) Bytes() []byte {
|
||||
@@ -33,8 +34,13 @@ func (b *option) Bytes() []byte {
|
||||
}
|
||||
|
||||
func (b *option) initialize(bytes []byte) error {
|
||||
b.id = hash.ComputeHash256Array(bytes)
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.msg = msg
|
||||
b.bytes = bytes
|
||||
b.id = hash.ComputeHash256Array(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
type ParseResult struct {
|
||||
@@ -46,28 +47,40 @@ func Parse(bytes []byte, chainID ids.ID) (Block, error) {
|
||||
}
|
||||
|
||||
// ParseWithoutVerification parses a block without verifying that the signature
|
||||
// on the block is correct.
|
||||
// on the block is correct. Dispatch is the 1-byte blockKind at object offset 0
|
||||
// of the leading self-delimiting zap message — no codec, no version.
|
||||
func ParseWithoutVerification(bytes []byte) (Block, error) {
|
||||
var block Block
|
||||
parsedVersion, err := Codec.Unmarshal(bytes, &block)
|
||||
n, err := zapLen(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsedVersion != CodecVersion {
|
||||
return nil, fmt.Errorf("expected codec version %d but got %d", CodecVersion, parsedVersion)
|
||||
msg, err := zap.Parse(bytes[:n])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch k := blockKind(msg.Root().Uint8(offKind)); k {
|
||||
case blkSigned:
|
||||
block := &statelessBlock{}
|
||||
return block, block.initialize(bytes)
|
||||
case blkOption:
|
||||
block := &option{}
|
||||
return block, block.initialize(bytes[:n])
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %d", errUnknownBlockType, k)
|
||||
}
|
||||
return block, block.initialize(bytes)
|
||||
}
|
||||
|
||||
func ParseHeader(bytes []byte) (Header, error) {
|
||||
header := statelessHeader{}
|
||||
parsedVersion, err := Codec.Unmarshal(bytes, &header)
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsedVersion != CodecVersion {
|
||||
return nil, fmt.Errorf("expected codec version %d but got %d", CodecVersion, parsedVersion)
|
||||
}
|
||||
header.bytes = bytes
|
||||
return &header, nil
|
||||
root := msg.Root()
|
||||
return &statelessHeader{
|
||||
Chain: ids.ID(read32(root, hdrChain)),
|
||||
Parent: ids.ID(read32(root, hdrParent)),
|
||||
Body: ids.ID(read32(root, hdrBody)),
|
||||
bytes: bytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package block
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/staking"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
func TestParseBlocks(t *testing.T) {
|
||||
@@ -54,12 +54,12 @@ func TestParseBlocks(t *testing.T) {
|
||||
{
|
||||
name: "ValidThenInvalid",
|
||||
input: [][]byte{signedBlockBytes, malformedBlockBytes},
|
||||
output: []ParseResult{{Block: &statelessBlock{bytes: signedBlockBytes}}, {Err: pcodecs.ErrInsufficientLength}},
|
||||
output: []ParseResult{{Block: &statelessBlock{bytes: signedBlockBytes}}, {Err: zap.ErrBufferTooSmall}},
|
||||
},
|
||||
{
|
||||
name: "InvalidThenValid",
|
||||
input: [][]byte{malformedBlockBytes, signedBlockBytes},
|
||||
output: []ParseResult{{Err: pcodecs.ErrInsufficientLength}, {Block: &statelessBlock{bytes: signedBlockBytes}}},
|
||||
output: []ParseResult{{Err: zap.ErrBufferTooSmall}, {Block: &statelessBlock{bytes: signedBlockBytes}}},
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
@@ -106,13 +106,12 @@ func TestParse(t *testing.T) {
|
||||
unsignedBlock, err := BuildUnsigned(parentID, timestamp, pChainHeight, Epoch{}, innerBlockBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// A block that carries no certificate but does carry a signature suffix:
|
||||
// verify must reject it with errUnexpectedSignature.
|
||||
signedWithoutCertBlockIntf, err := BuildUnsigned(parentID, timestamp, pChainHeight, Epoch{}, innerBlockBytes)
|
||||
require.NoError(t, err)
|
||||
signedWithoutCertBlock := signedWithoutCertBlockIntf.(*statelessBlock)
|
||||
signedWithoutCertBlock.Signature = []byte{5}
|
||||
|
||||
signedWithoutCertBlock.bytes, err = Codec.Marshal(CodecVersion, &signedWithoutCertBlockIntf)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, signedWithoutCertBlock.initialize(concat(signedWithoutCertBlock.Bytes(), buildSigBuffer([]byte{5}))))
|
||||
|
||||
optionBlock, err := BuildOption(parentID, innerBlockBytes)
|
||||
require.NoError(t, err)
|
||||
@@ -175,24 +174,22 @@ func TestParse(t *testing.T) {
|
||||
func TestParseBytes(t *testing.T) {
|
||||
chainID := ids.ID{4}
|
||||
tests := []struct {
|
||||
name string
|
||||
hex string
|
||||
expectedErr error
|
||||
expectedErrMsg string // when set, match by ErrorContains instead of ErrorIs
|
||||
name string
|
||||
hex string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
// Malformed cert: extension count length-prefix overflows int32. Under the
|
||||
// ZAP-native parser this trips the max-slice-length guard. The exact sentinel
|
||||
// can vary between proto/zap_codec and the standalone luxfi/zapcodec module,
|
||||
// so match on the canonical error message instead of a typed sentinel.
|
||||
name: "duplicate extensions in certificate",
|
||||
hex: "0000000000000100000000000000000000000000000000000000000000000000000000000000000000000000007b0000000000000002000004bd308204b9308202a1a003020102020100300d06092a864886f70d01010b050030003020170d3939313233313030303030305a180f32313232303830333233323835335a300030820222300d06092a864886f70d01010105000382020f003082020a0282020100c2b2de1c16924d9b9254a0d5b80a4bc5f9beaa4f4f40a0e4efb69eb9b55d7d37f8c82328c237d7c5b451f5427b487284fa3f365f9caa53c7fcfef8d7a461d743bd7d88129f2da62b877ebe9d6feabf1bd12923e6c12321382c782fc3bb6b6cb4986a937a1edc3814f4e621e1a62053deea8c7649e43edd97ab6b56315b00d9ab5026bb9c31fb042dc574ba83c54e720e0120fcba2e8a66b77839be3ece0d4a6383ef3f76aac952b49a15b65e18674cd1340c32cecbcbaf80ae45be001366cb56836575fb0ab51ea44bf7278817e99b6b180fdd110a49831a132968489822c56692161bbd372cf89d9b8ee5a734cff15303b3a960ee78d79e76662a701941d9ec084429f26707f767e9b1d43241c0e4f96655d95c1f4f4aa00add78eff6bf0a6982766a035bf0b465786632c5bb240788ca0fdf032d8815899353ea4bec5848fd30118711e5b356bde8a0da074cc25709623225e734ff5bd0cf65c40d9fd8fccf746d8f8f35145bcebcf378d2b086e57d78b11e84f47fa467c4d037f92bff6dd4e934e0189b58193f24c4222ffb72b5c06361cf68ca64345bc3e230cc0f40063ad5f45b1659c643662996328c2eeddcd760d6f7c9cbae081ccc065844f7ea78c858564a408979764de882793706acc67d88092790dff567ed914b03355330932616a0f26f994b963791f0b1dbd8df979db86d1ea490700a3120293c3c2b10bef10203010001a33c303a300e0603551d0f0101ff0404030204b030130603551d25040c300a06082b0601050507030230130603551d25040c300a06082b06010505070302300d06092a864886f70d01010b05000382020100a21a0d73ec9ef4eb39f810557ac70b0b775772b8bae5f42c98565bc50b5b2c57317aa9cb1da12f55d0aac7bb36a00cd4fd0d7384c4efa284b53520c5a3c4b8a65240b393eeab02c802ea146c0728c3481c9e8d3aaad9d4dd7607103dcfaa96da83460adbe18174ed5b71bde7b0a93d4fb52234a9ff54e3fd25c5b74790dfb090f2e59dc5907357f510cc3a0b70ccdb87aee214def794b316224f318b471ffa13b66e44b467670e881cb1628c99c048a503376d9b6d7b8eef2e7be47ff7d5c1d56221f4cf7fa2519b594cb5917815c64dc75d8d281bcc99b5a12899b08f2ca0f189857b64a1afc5963337f3dd6e79390e85221569f6dbbb13aadce06a3dfb5032f0cc454809627872cd7cd0cea5eba187723f07652c8abc3fc42bd62136fc66287f2cc19a7cb416923ad1862d7f820b55cacb65e43731cb6df780e2651e457a3438456aeeeb278ad9c0ad2e760f6c1cbe276eeb621c8a4e609b5f2d902beb3212e3e45df99497021ff536d0b56390c5d785a8bf7909f6b61bdc705d7d92ae22f58e7b075f164a0450d82d8286bf449072751636ab5185f59f518b845a75d112d6f7b65223479202cff67635e2ad88106bc8a0cc9352d87c5b182ac19a4680a958d814a093acf46730f87da0df6926291d02590f215041b44a0a1a32eeb3a52cddabc3d256689bace18a8d85e644cf9137cce3718f7caac1cb16ae06e874f4c701000000010300000200b8e3a4d9a4394bac714cb597f5ba1a81865185e35c782d0317e7abc0b52d49ff8e10f787bedf86f08148e3dbd2d2d478caa2a2893d31db7d5ee51339883fe84d3004440f16cb3797a7fab0f627d3ebd79217e995488e785cd6bb7b96b9d306f8109daa9cfc4162f9839f60fb965bcb3b56a5fa787549c153a4c80027398f73a617b90b7f24f437b140cd3ac832c0b75ec98b9423b275782988a9fd426937b8f82fbb0e88a622934643fb6335c1a080a4d13125544b04585d5f5295be7cd2c8be364246ea3d5df3e837b39a85074575a1fa2f4799050460110bdfb20795c8a9172a20f61b95e1c5c43eccd0c2c155b67385366142c63409cb3fb488e7aba6c8930f7f151abf1c24a54bd21c3f7a06856ea9db35beddecb30d2c61f533a3d0590bdbb438c6f2a2286dfc3c71b383354f0abad72771c2cc3687b50c2298783e53857cf26058ed78d0c1cf53786eb8d006a058ee3c85a7b2b836b5d03ef782709ce8f2725548e557b3de45a395a669a15f1d910e97015d22ac70020cab7e2531e8b1f739b023b49e742203e9e19a7fe0053826a9a2fe2e118d3b83498c2cb308573202ad41aa4a390aee4b6b5dd2164e5c5cd1b5f68b7d5632cf7dbb9a9139663c9aac53a74b2c6fc73cad80e228a186ba027f6f32f0182d62503e04fcced385f2e7d2e11c00940622ebd533b4d144689082f9777e5b16c36f9af9066e0ad6564d43",
|
||||
expectedErrMsg: "max slice length",
|
||||
// Fewer than zap.HeaderSize bytes: rejected at the wire boundary.
|
||||
name: "too short",
|
||||
hex: "000102030405",
|
||||
expectedErr: zap.ErrBufferTooSmall,
|
||||
},
|
||||
{
|
||||
name: "gibberish",
|
||||
hex: "000102030405",
|
||||
expectedErr: pcodecs.ErrUnknownVersion,
|
||||
// A 16-byte frame with a valid size field (=16) but the wrong magic
|
||||
// bytes: rejected by zap.Parse.
|
||||
name: "invalid magic",
|
||||
hex: "00000000000000000000000010000000",
|
||||
expectedErr: zap.ErrInvalidMagic,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@@ -203,11 +200,7 @@ func TestParseBytes(t *testing.T) {
|
||||
require.NoError(err)
|
||||
|
||||
_, err = Parse(bytes, chainID)
|
||||
if test.expectedErrMsg != "" {
|
||||
require.ErrorContains(err, test.expectedErrMsg)
|
||||
} else {
|
||||
require.ErrorIs(err, test.expectedErr)
|
||||
}
|
||||
require.ErrorIs(err, test.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package proposer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
"time"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/luxfi/container/sampler"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/math"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/utils"
|
||||
)
|
||||
|
||||
@@ -106,11 +106,13 @@ type windower struct {
|
||||
}
|
||||
|
||||
func New(state validators.State, netID, chainID ids.ID) Windower {
|
||||
w := pcodecs.Packer{Bytes: chainID[:]}
|
||||
// chainSource seeds the per-chain PRNG for proposer selection: the first 8
|
||||
// bytes of the chainID read little-endian (byte-for-byte the value the
|
||||
// retired pcodecs.Packer.UnpackLong produced).
|
||||
return &windower{
|
||||
state: state,
|
||||
netID: netID,
|
||||
chainSource: w.UnpackLong(),
|
||||
chainSource: binary.LittleEndian.Uint64(chainID[:8]),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,16 @@ import (
|
||||
"github.com/luxfi/node/cache"
|
||||
"github.com/luxfi/node/cache/lru"
|
||||
"github.com/luxfi/node/cache/metercacher"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/node/vms/proposervm/block"
|
||||
)
|
||||
|
||||
const blockCacheSize = 64 * constants.MiB
|
||||
|
||||
var (
|
||||
errBlockWrongVersion = errors.New("wrong version")
|
||||
// statusIntLen is the wire width of the serialized status uint32; used only for
|
||||
// cache-size accounting.
|
||||
const statusIntLen = 4
|
||||
|
||||
_ BlockState = (*blockState)(nil)
|
||||
)
|
||||
var _ BlockState = (*blockState)(nil)
|
||||
|
||||
type BlockState interface {
|
||||
GetBlock(blkID ids.ID) (block.Block, error)
|
||||
@@ -41,8 +40,8 @@ type blockState struct {
|
||||
}
|
||||
|
||||
type blockWrapper struct {
|
||||
Block []byte `serialize:"true"`
|
||||
StatusInt uint32 `serialize:"true"` // Store status as uint32 for serialization
|
||||
Block []byte
|
||||
StatusInt uint32 // Store status as uint32 for serialization
|
||||
|
||||
block block.Block
|
||||
status choices.Status // Keep the actual status here
|
||||
@@ -52,7 +51,7 @@ func cachedBlockSize(_ ids.ID, bw *blockWrapper) int {
|
||||
if bw == nil {
|
||||
return ids.IDLen + constants.PointerOverhead
|
||||
}
|
||||
return ids.IDLen + len(bw.Block) + pcodecs.IntLen + 2*constants.PointerOverhead
|
||||
return ids.IDLen + len(bw.Block) + statusIntLen + 2*constants.PointerOverhead
|
||||
}
|
||||
|
||||
func NewBlockState(db database.Database) BlockState {
|
||||
@@ -97,13 +96,9 @@ func (s *blockState) GetBlock(blkID ids.ID) (block.Block, error) {
|
||||
}
|
||||
|
||||
blkWrapper := blockWrapper{}
|
||||
parsedVersion, err := Codec.Unmarshal(blkWrapperBytes, &blkWrapper)
|
||||
if err != nil {
|
||||
if err := parseBlockWrapper(blkWrapperBytes, &blkWrapper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsedVersion != CodecVersion {
|
||||
return nil, errBlockWrongVersion
|
||||
}
|
||||
|
||||
// The key was in the database
|
||||
blk, err := block.ParseWithoutVerification(blkWrapper.Block)
|
||||
@@ -124,10 +119,7 @@ func (s *blockState) PutBlock(blk block.Block) error {
|
||||
block: blk,
|
||||
}
|
||||
|
||||
bytes, err := Codec.Marshal(CodecVersion, &blkWrapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bytes := marshalBlockWrapper(&blkWrapper)
|
||||
|
||||
blkID := blk.ID()
|
||||
s.blkCache.Put(blkID, &blkWrapper)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
const CodecVersion = 0
|
||||
|
||||
var Codec pcodecs.Manager
|
||||
|
||||
func init() {
|
||||
lc := pcodecs.NewLinearCodec()
|
||||
Codec = pcodecs.NewMaxInt32Manager()
|
||||
|
||||
err := Codec.RegisterCodec(CodecVersion, lc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package state
|
||||
|
||||
// Native-ZAP wire for the on-disk block store value (blockWrapper). The struct
|
||||
// IS the wire: one fixed-shape zap object, no codec, no version prefix, no slot
|
||||
// registry. Re-genesis means the on-disk format is free to change, so this
|
||||
// layout is the canonical one.
|
||||
//
|
||||
// object (size 16): StatusInt u32 @0, Block bytes ptr @8.
|
||||
// The stored block bytes are opaque — a self-delimiting zap block buffer
|
||||
// re-parsed via block.ParseWithoutVerification, so the block ID is preserved
|
||||
// with no re-encoding. Block bytes are copied out of the transient DB read
|
||||
// buffer so a cached value never aliases it.
|
||||
|
||||
import "github.com/luxfi/zap"
|
||||
|
||||
const (
|
||||
bwStatus = 0
|
||||
bwBlock = 8
|
||||
bwSize = 16
|
||||
)
|
||||
|
||||
func marshalBlockWrapper(bw *blockWrapper) []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + bwSize + len(bw.Block))
|
||||
ob := b.StartObject(bwSize)
|
||||
ob.SetUint32(bwStatus, bw.StatusInt)
|
||||
ob.SetBytes(bwBlock, bw.Block)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
func parseBlockWrapper(bytes []byte, bw *blockWrapper) error {
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root := msg.Root()
|
||||
bw.StatusInt = root.Uint32(bwStatus)
|
||||
if blk := root.Bytes(bwBlock); len(blk) > 0 {
|
||||
bw.Block = append([]byte(nil), blk...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package summary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
)
|
||||
|
||||
@@ -14,18 +12,13 @@ func Build(
|
||||
block []byte,
|
||||
coreSummary []byte,
|
||||
) (StateSummary, error) {
|
||||
summary := stateSummary{
|
||||
summary := &stateSummary{
|
||||
Height: forkHeight,
|
||||
Block: block,
|
||||
InnerSummary: coreSummary,
|
||||
}
|
||||
|
||||
bytes, err := Codec.Marshal(CodecVersion, &summary)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot marshal proposer summary due to: %w", err)
|
||||
}
|
||||
|
||||
summary.id = hash.ComputeHash256Array(bytes)
|
||||
summary.bytes = bytes
|
||||
return &summary, nil
|
||||
summary.bytes = summary.marshal()
|
||||
summary.id = hash.ComputeHash256Array(summary.bytes)
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package summary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
)
|
||||
|
||||
const CodecVersion = 0
|
||||
|
||||
var (
|
||||
Codec pcodecs.Manager
|
||||
|
||||
errWrongCodecVersion = errors.New("wrong codec version")
|
||||
)
|
||||
|
||||
func init() {
|
||||
lc := pcodecs.NewLinearCodec()
|
||||
Codec = pcodecs.NewMaxInt32Manager()
|
||||
if err := Codec.RegisterCodec(CodecVersion, lc); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package summary
|
||||
@@ -10,16 +10,12 @@ import (
|
||||
)
|
||||
|
||||
func Parse(bytes []byte) (StateSummary, error) {
|
||||
summary := stateSummary{
|
||||
summary := &stateSummary{
|
||||
id: hash.ComputeHash256Array(bytes),
|
||||
bytes: bytes,
|
||||
}
|
||||
version, err := Codec.Unmarshal(bytes, &summary)
|
||||
if err != nil {
|
||||
if err := summary.unmarshal(bytes); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal summary due to: %w", err)
|
||||
}
|
||||
if version != CodecVersion {
|
||||
return nil, errWrongCodecVersion
|
||||
}
|
||||
return &summary, nil
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@@ -37,5 +37,5 @@ func TestParseGibberish(t *testing.T) {
|
||||
bytes := []byte{0, 1, 2, 3, 4, 5}
|
||||
|
||||
_, err := Parse(bytes)
|
||||
require.ErrorIs(err, pcodecs.ErrUnknownVersion)
|
||||
require.ErrorIs(err, zap.ErrBufferTooSmall)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
package summary
|
||||
|
||||
import "github.com/luxfi/ids"
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
var _ StateSummary = (*stateSummary)(nil)
|
||||
|
||||
@@ -15,18 +18,55 @@ type StateSummary interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
// stateSummary is zap-backed: the struct IS the wire. One fixed-shape zap
|
||||
// object, no codec, no version prefix.
|
||||
//
|
||||
// object (size 24): Height u64 @0, Block bytes ptr @8, InnerSummary bytes ptr @16.
|
||||
type stateSummary struct {
|
||||
Height uint64 `serialize:"true"`
|
||||
Height uint64
|
||||
// proposervm information. We would then modify the StateSummary
|
||||
// interface to expose the required information to generate the full
|
||||
// block.
|
||||
Block []byte `serialize:"true"`
|
||||
InnerSummary []byte `serialize:"true"`
|
||||
Block []byte
|
||||
InnerSummary []byte
|
||||
|
||||
id ids.ID
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
const (
|
||||
ssHeight = 0
|
||||
ssBlock = 8
|
||||
ssInner = 16
|
||||
ssSize = 24
|
||||
)
|
||||
|
||||
func (s *stateSummary) marshal() []byte {
|
||||
b := zap.NewBuilder(zap.HeaderSize + ssSize + len(s.Block) + len(s.InnerSummary))
|
||||
ob := b.StartObject(ssSize)
|
||||
ob.SetUint64(ssHeight, s.Height)
|
||||
ob.SetBytes(ssBlock, s.Block)
|
||||
ob.SetBytes(ssInner, s.InnerSummary)
|
||||
ob.FinishAsRoot()
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
func (s *stateSummary) unmarshal(bytes []byte) error {
|
||||
msg, err := zap.Parse(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root := msg.Root()
|
||||
s.Height = root.Uint64(ssHeight)
|
||||
if blk := root.Bytes(ssBlock); len(blk) > 0 {
|
||||
s.Block = append([]byte(nil), blk...)
|
||||
}
|
||||
if inner := root.Bytes(ssInner); len(inner) > 0 {
|
||||
s.InnerSummary = append([]byte(nil), inner...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stateSummary) ID() ids.ID {
|
||||
return s.id
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/node/vms/pcodecs"
|
||||
"github.com/luxfi/node/vms/rpcchainvm/runtime"
|
||||
"github.com/luxfi/utils/wrappers"
|
||||
)
|
||||
|
||||
func NewCmd(path string, args ...string) *exec.Cmd {
|
||||
@@ -31,7 +31,7 @@ func stop(ctx context.Context, log log.Logger, cmd *exec.Cmd) {
|
||||
waitChan := make(chan error)
|
||||
go func() {
|
||||
// attempt graceful shutdown
|
||||
errs := pcodecs.Errs{}
|
||||
errs := wrappers.Errs{}
|
||||
err := cmd.Process.Signal(syscall.SIGTERM)
|
||||
errs.Add(err)
|
||||
_, err = cmd.Process.Wait()
|
||||
|
||||
@@ -43,7 +43,7 @@ func main() {
|
||||
log.Fatalf("failed to parse net ID: %s\n", err)
|
||||
}
|
||||
|
||||
genesisBytes, err := xsgenesis.Codec.Marshal(xsgenesis.CodecVersion, genesis)
|
||||
genesisBytes, err := genesis.Marshal()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create genesis bytes: %s\n", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user