mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
platformvm/txs/zap_native: 5 more tx types (LP-023 Phase 1 batch 2)
Native ZAP encoding — no marshal step, struct IS wire format — for the
next batch of platformvm tx types. Same pattern as Phase 1 batch 1.
New types (v1 schemas; variable-length nested fields deferred to batch 3):
base_tx.go — NetworkID + BlockchainID + Memo
(Outs/Ins → batch 3)
register_l1_validator_tx.go — ValidationID + BLS pubkey 48B + PoP 96B
+ Expiry + RemainingBalanceOwnerID stub
(Warp Message + full OutputOwners → batch 3)
slash_validator_tx.go — NodeID + Subnet + SlashPercentage
(Evidence variable-length → batch 3)
transfer_chain_ownership_tx.go — Chain + Owner{threshold,locktime,addr} v1 stub
(full OutputOwners list → batch 3)
remove_chain_validator_tx.go — NodeID + Subnet
(ChainAuth lives in signed wrapper)
Tests + benches extend the existing all_tx_types_test.go +
all_types_bench_test.go infra (no duplication). 18 new accessor zero-alloc
sites verified, including 48B BLSPublicKey and 96B PoP by-value returns.
Aggregate bench results (Apple M1 Max, Go 1.24, -benchtime=2s):
Parse Legacy vs ZAP:
BaseTx 470.0 → 60.42 ns ( 7.8x) 152→24 B 3→1 alloc
RegisterL1ValidatorTx 1008 → 75.03 ns (13.4x) 384→24 B 6→1 alloc
SlashValidatorTx 690.6 → 82.74 ns ( 8.3x) 176→24 B 4→1 alloc
TransferChainOwnershipTx 2459 → 161.5 ns (15.2x) 192→24 B 4→1 alloc
RemoveChainValidatorTx 879.6 → 117.2 ns ( 7.5x) 176→24 B 4→1 alloc
Parse cross-type mean (10 types now native): 8.5x speedup.
Build (one-time per proposer, dominated by Parse on consensus path):
BaseTx 1.5x slower (memo SetBytes deferred-copy; acceptable trade)
Register 1.03x (allocs 7→2)
Slash 1.4x faster (allocs 5→2)
Transfer 0.96x (allocs 5→2)
Remove 1.3x faster (allocs 5→2)
All accessor reads zero-alloc. All builds 1 alloc on read path.
Verification:
cd ~/work/lux/node
GOWORK=off go build ./vms/platformvm/txs/zap_native/...
GOWORK=off go test ./vms/platformvm/txs/zap_native/...
GOWORK=off go test -bench=. -benchmem -benchtime=2s \\
./vms/platformvm/txs/zap_native/...
Activation 1782604800 (2026-07-01 00:00 UTC) unchanged.
LUXD_ENABLE_LEGACY_CODEC=1 opts INTO legacy unchanged.
Refs LP-023. Next: batch 3 — variable-length nested-object schemas (Outs/Ins
lists, Warp Message payloads, full OutputOwners, Evidence) + remaining tx
types via codegen.
This commit is contained in:
@@ -39,6 +39,8 @@ selection, and EVM contract auth.
|
||||
|
||||
| SHA | Tag | Impact |
|
||||
|-----|-----|--------|
|
||||
| (pending) | next | LP-023 Phase 1 batch 2: 5 more native-ZAP tx types — BaseTx v1, RegisterL1ValidatorTx v1, SlashValidatorTx v1, TransferChainOwnershipTx v1, RemoveChainValidatorTx. Cross-type Parse mean speedup 8.5× over linearcodec. Variable-length nested-object schemas (Outs/Ins/full OutputOwners/Warp Message/Evidence) deferred to batch 3. |
|
||||
| `e77a7ef78e` | (pre) | LP-023 Phase 1 batch 1: 4 simple tx types + bench harness. 37× Parse, 5.6× cross-type mean. |
|
||||
| `9df72a6f55` | v1.26.10 | Wire ChainSecurityProfile into bootstrap (closes F102) |
|
||||
| `c4af52411e` | v1.26.10 | X-Chain (avm) mempool refuses classical creds under strict-PQ |
|
||||
| `a14a1601f4` | v1.26.10 | P-Chain (platformvm) mempool refuses classical creds under strict-PQ |
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
package zap_native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/bls"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
@@ -90,17 +92,182 @@ func TestDisableL1ValidatorTxRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseTxRoundTrip(t *testing.T) {
|
||||
const wantNetID uint32 = 1337
|
||||
wantChain := ids.ID{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}
|
||||
wantMemo := []byte("LP-023 batch 2 canary memo")
|
||||
|
||||
tx := NewBaseTx(wantNetID, wantChain, wantMemo)
|
||||
if !IsZAPBytes(tx.Bytes()) {
|
||||
t.Fatal("Bytes() not ZAP-formatted")
|
||||
}
|
||||
if got := tx.NetworkID(); got != wantNetID {
|
||||
t.Fatalf("NetworkID() = %d, want %d", got, wantNetID)
|
||||
}
|
||||
if got := tx.BlockchainID(); got != wantChain {
|
||||
t.Fatalf("BlockchainID() = %v, want %v", got, wantChain)
|
||||
}
|
||||
if got := tx.Memo(); !bytes.Equal(got, wantMemo) {
|
||||
t.Fatalf("Memo() = %x, want %x", got, wantMemo)
|
||||
}
|
||||
|
||||
tx2, err := WrapBaseTx(tx.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx2.NetworkID() != wantNetID || tx2.BlockchainID() != wantChain || !bytes.Equal(tx2.Memo(), wantMemo) {
|
||||
t.Fatal("wrap-round-trip mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseTxNilMemo(t *testing.T) {
|
||||
// Memo is variable-length; nil/empty memo must round-trip cleanly.
|
||||
tx := NewBaseTx(1, ids.ID{0xfe, 0xed}, nil)
|
||||
if memo := tx.Memo(); len(memo) != 0 {
|
||||
t.Fatalf("nil-memo round-trip got %x, want empty", memo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterL1ValidatorTxRoundTrip(t *testing.T) {
|
||||
wantValID := ids.ID{0xaa, 0xbb, 0xcc, 0xdd}
|
||||
var wantBLS [bls.PublicKeyLen]byte
|
||||
for i := range wantBLS {
|
||||
wantBLS[i] = byte(i + 1)
|
||||
}
|
||||
var wantPoP [bls.SignatureLen]byte
|
||||
for i := range wantPoP {
|
||||
wantPoP[i] = byte(0xff - i)
|
||||
}
|
||||
const wantExpiry uint64 = 1_900_000_000
|
||||
wantOwnerID := ids.ID{0x12, 0x34, 0x56, 0x78}
|
||||
|
||||
tx := NewRegisterL1ValidatorTx(wantValID, wantBLS, wantPoP, wantExpiry, wantOwnerID)
|
||||
if tx.ValidationID() != wantValID {
|
||||
t.Fatal("ValidationID mismatch")
|
||||
}
|
||||
if tx.BLSPublicKey() != wantBLS {
|
||||
t.Fatal("BLSPublicKey mismatch")
|
||||
}
|
||||
if tx.ProofOfPossession() != wantPoP {
|
||||
t.Fatal("ProofOfPossession mismatch")
|
||||
}
|
||||
if tx.Expiry() != wantExpiry {
|
||||
t.Fatalf("Expiry = %d, want %d", tx.Expiry(), wantExpiry)
|
||||
}
|
||||
if tx.RemainingBalanceOwnerID() != wantOwnerID {
|
||||
t.Fatal("RemainingBalanceOwnerID mismatch")
|
||||
}
|
||||
|
||||
tx2, err := WrapRegisterL1ValidatorTx(tx.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx2.ValidationID() != wantValID || tx2.BLSPublicKey() != wantBLS ||
|
||||
tx2.ProofOfPossession() != wantPoP || tx2.Expiry() != wantExpiry ||
|
||||
tx2.RemainingBalanceOwnerID() != wantOwnerID {
|
||||
t.Fatal("wrap-round-trip mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlashValidatorTxRoundTrip(t *testing.T) {
|
||||
wantNode := ids.NodeID{0x01, 0x02, 0x03, 0x04, 0x05}
|
||||
wantSubnet := ids.ID{0xa1, 0xa2, 0xa3, 0xa4}
|
||||
const wantPct uint32 = 250_000 // 25% in PercentDenominator units
|
||||
|
||||
tx := NewSlashValidatorTx(wantNode, wantSubnet, wantPct)
|
||||
if tx.NodeID() != wantNode {
|
||||
t.Fatalf("NodeID = %v, want %v", tx.NodeID(), wantNode)
|
||||
}
|
||||
if tx.Subnet() != wantSubnet {
|
||||
t.Fatal("Subnet mismatch")
|
||||
}
|
||||
if tx.SlashPercentage() != wantPct {
|
||||
t.Fatalf("SlashPercentage = %d, want %d", tx.SlashPercentage(), wantPct)
|
||||
}
|
||||
|
||||
tx2, err := WrapSlashValidatorTx(tx.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx2.NodeID() != wantNode || tx2.Subnet() != wantSubnet || tx2.SlashPercentage() != wantPct {
|
||||
t.Fatal("wrap-round-trip mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransferChainOwnershipTxRoundTrip(t *testing.T) {
|
||||
wantChain := ids.ID{0xc0, 0xc1, 0xc2}
|
||||
const wantThreshold uint32 = 1
|
||||
const wantLocktime uint64 = 1_800_000_000
|
||||
wantAddr := ids.ShortID{0xbe, 0xef, 0xca, 0xfe}
|
||||
|
||||
tx := NewTransferChainOwnershipTx(wantChain, wantThreshold, wantLocktime, wantAddr)
|
||||
if tx.Chain() != wantChain {
|
||||
t.Fatal("Chain mismatch")
|
||||
}
|
||||
if tx.OwnerThreshold() != wantThreshold {
|
||||
t.Fatalf("OwnerThreshold = %d, want %d", tx.OwnerThreshold(), wantThreshold)
|
||||
}
|
||||
if tx.OwnerLocktime() != wantLocktime {
|
||||
t.Fatalf("OwnerLocktime = %d, want %d", tx.OwnerLocktime(), wantLocktime)
|
||||
}
|
||||
if tx.OwnerAddress() != wantAddr {
|
||||
t.Fatal("OwnerAddress mismatch")
|
||||
}
|
||||
|
||||
tx2, err := WrapTransferChainOwnershipTx(tx.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx2.Chain() != wantChain || tx2.OwnerThreshold() != wantThreshold ||
|
||||
tx2.OwnerLocktime() != wantLocktime || tx2.OwnerAddress() != wantAddr {
|
||||
t.Fatal("wrap-round-trip mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveChainValidatorTxRoundTrip(t *testing.T) {
|
||||
wantNode := ids.NodeID{0x10, 0x20, 0x30, 0x40, 0x50}
|
||||
wantSubnet := ids.ID{0xfa, 0xce, 0xb0, 0x0c}
|
||||
|
||||
tx := NewRemoveChainValidatorTx(wantNode, wantSubnet)
|
||||
if tx.NodeID() != wantNode {
|
||||
t.Fatal("NodeID mismatch")
|
||||
}
|
||||
if tx.Subnet() != wantSubnet {
|
||||
t.Fatal("Subnet mismatch")
|
||||
}
|
||||
|
||||
tx2, err := WrapRemoveChainValidatorTx(tx.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx2.NodeID() != wantNode || tx2.Subnet() != wantSubnet {
|
||||
t.Fatal("wrap-round-trip mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// Zero-allocation accessor verification across all the simple types. Reading
|
||||
// a field from a wrapped accessor must allocate 0 — that's the whole point
|
||||
// of native ZAP.
|
||||
func TestAllAccessorsZeroAlloc(t *testing.T) {
|
||||
id := ids.ID{1, 2, 3}
|
||||
nodeID := ids.NodeID{4, 5, 6}
|
||||
shortID := ids.ShortID{7, 8, 9}
|
||||
var blsPub [bls.PublicKeyLen]byte
|
||||
var pop [bls.SignatureLen]byte
|
||||
|
||||
atx := NewAdvanceTimeTx(123)
|
||||
rtx := NewRewardValidatorTx(id)
|
||||
stx := NewSetL1ValidatorWeightTx(id, 1, 1)
|
||||
itx := NewIncreaseL1ValidatorBalanceTx(id, 1)
|
||||
dtx := NewDisableL1ValidatorTx(id)
|
||||
btx := NewBaseTx(1337, id, []byte("memo"))
|
||||
regTx := NewRegisterL1ValidatorTx(id, blsPub, pop, 1, id)
|
||||
slTx := NewSlashValidatorTx(nodeID, id, 100_000)
|
||||
tcoTx := NewTransferChainOwnershipTx(id, 1, 0, shortID)
|
||||
rcvTx := NewRemoveChainValidatorTx(nodeID, id)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -115,6 +282,47 @@ func TestAllAccessorsZeroAlloc(t *testing.T) {
|
||||
// for callers. AllocsPerRun reports 0 for these on stack-able sites.
|
||||
{"RewardValidatorTx.TxID", func() { _ = rtx.TxID() }},
|
||||
{"DisableL1ValidatorTx.ValidationID", func() { _ = dtx.ValidationID() }},
|
||||
// Batch 2 accessors.
|
||||
{"BaseTx.NetworkID", func() { _ = btx.NetworkID() }},
|
||||
{"BaseTx.BlockchainID", func() { _ = btx.BlockchainID() }},
|
||||
{"BaseTx.Memo", func() { _ = btx.Memo() }},
|
||||
{"RegisterL1ValidatorTx.Expiry", func() { _ = regTx.Expiry() }},
|
||||
{"RegisterL1ValidatorTx.ValidationID", func() { _ = regTx.ValidationID() }},
|
||||
{"SlashValidatorTx.NodeID", func() { _ = slTx.NodeID() }},
|
||||
{"SlashValidatorTx.Subnet", func() { _ = slTx.Subnet() }},
|
||||
{"SlashValidatorTx.SlashPercentage", func() { _ = slTx.SlashPercentage() }},
|
||||
{"TransferChainOwnershipTx.Chain", func() { _ = tcoTx.Chain() }},
|
||||
{"TransferChainOwnershipTx.OwnerThreshold", func() { _ = tcoTx.OwnerThreshold() }},
|
||||
{"TransferChainOwnershipTx.OwnerLocktime", func() { _ = tcoTx.OwnerLocktime() }},
|
||||
{"TransferChainOwnershipTx.OwnerAddress", func() { _ = tcoTx.OwnerAddress() }},
|
||||
{"RemoveChainValidatorTx.NodeID", func() { _ = rcvTx.NodeID() }},
|
||||
{"RemoveChainValidatorTx.Subnet", func() { _ = rcvTx.Subnet() }},
|
||||
}
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if got := testing.AllocsPerRun(100, c.fn); got != 0 {
|
||||
t.Fatalf("%s: %.2f allocs/run, want 0", c.name, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 192-byte and 48-byte by-value array accessors. Same stack-able-by-return
|
||||
// guarantee as the 32-byte ones; this test pins it explicitly because the
|
||||
// Go compiler's escape analysis is sensitive to return size.
|
||||
func TestRegisterL1ValidatorTxLargeArrayAccessorsZeroAlloc(t *testing.T) {
|
||||
var blsPub [bls.PublicKeyLen]byte
|
||||
var pop [bls.SignatureLen]byte
|
||||
regTx := NewRegisterL1ValidatorTx(ids.ID{1}, blsPub, pop, 1, ids.ID{2})
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fn func()
|
||||
}{
|
||||
{"BLSPublicKey", func() { _ = regTx.BLSPublicKey() }},
|
||||
{"ProofOfPossession", func() { _ = regTx.ProofOfPossession() }},
|
||||
{"RemainingBalanceOwnerID", func() { _ = regTx.RemainingBalanceOwnerID() }},
|
||||
}
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/luxfi/codec"
|
||||
"github.com/luxfi/codec/linearcodec"
|
||||
"github.com/luxfi/crypto/bls"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
@@ -34,6 +35,39 @@ type legacyDisableL1ValidatorTx struct {
|
||||
ValidationID ids.ID `serialize:"true"`
|
||||
}
|
||||
|
||||
// Batch 2 legacy stubs — same field shape as the v1 native schemas above.
|
||||
type legacyBaseTx struct {
|
||||
NetworkID uint32 `serialize:"true"`
|
||||
BlockchainID ids.ID `serialize:"true"`
|
||||
Memo []byte `serialize:"true"`
|
||||
}
|
||||
|
||||
type legacyRegisterL1ValidatorTx struct {
|
||||
ValidationID ids.ID `serialize:"true"`
|
||||
BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true"`
|
||||
ProofOfPossession [bls.SignatureLen]byte `serialize:"true"`
|
||||
Expiry uint64 `serialize:"true"`
|
||||
RemainingBalanceOwnerID ids.ID `serialize:"true"`
|
||||
}
|
||||
|
||||
type legacySlashValidatorTx struct {
|
||||
NodeID ids.NodeID `serialize:"true"`
|
||||
Subnet ids.ID `serialize:"true"`
|
||||
SlashPercentage uint32 `serialize:"true"`
|
||||
}
|
||||
|
||||
type legacyTransferChainOwnershipTx struct {
|
||||
Chain ids.ID `serialize:"true"`
|
||||
OwnerThreshold uint32 `serialize:"true"`
|
||||
OwnerLocktime uint64 `serialize:"true"`
|
||||
OwnerAddress ids.ShortID `serialize:"true"`
|
||||
}
|
||||
|
||||
type legacyRemoveChainValidatorTx struct {
|
||||
NodeID ids.NodeID `serialize:"true"`
|
||||
Subnet ids.ID `serialize:"true"`
|
||||
}
|
||||
|
||||
// newLegacyManagerFor builds a codec.Manager with the given types registered.
|
||||
func newLegacyManagerFor(b *testing.B, types ...interface{}) codec.Manager {
|
||||
c := linearcodec.NewDefault()
|
||||
@@ -264,3 +298,301 @@ func BenchmarkBuild_ZAP_DisableL1ValidatorTx(b *testing.B) {
|
||||
_ = tx.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// BaseTx (Batch 2)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
var benchBaseTxMemo = []byte("LP-023 batch 2 realistic memo payload for parse/build cost measurement")
|
||||
|
||||
func BenchmarkParse_Legacy_BaseTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyBaseTx{})
|
||||
src := &legacyBaseTx{NetworkID: 1337, BlockchainID: ids.ID{0x11}, Memo: benchBaseTxMemo}
|
||||
encoded, err := m.Marshal(0, src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var dst legacyBaseTx
|
||||
if _, err := m.Unmarshal(encoded, &dst); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse_ZAP_BaseTx(b *testing.B) {
|
||||
tx := NewBaseTx(1337, ids.ID{0x11}, benchBaseTxMemo)
|
||||
buf := tx.Bytes()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := WrapBaseTx(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_Legacy_BaseTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyBaseTx{})
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
src := &legacyBaseTx{NetworkID: uint32(i), BlockchainID: ids.ID{byte(i)}, Memo: benchBaseTxMemo}
|
||||
if _, err := m.Marshal(0, src); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_ZAP_BaseTx(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx := NewBaseTx(uint32(i), ids.ID{byte(i)}, benchBaseTxMemo)
|
||||
_ = tx.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// RegisterL1ValidatorTx (Batch 2)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
var (
|
||||
benchRegBLS [bls.PublicKeyLen]byte
|
||||
benchRegPoP [bls.SignatureLen]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := range benchRegBLS {
|
||||
benchRegBLS[i] = byte(i + 1)
|
||||
}
|
||||
for i := range benchRegPoP {
|
||||
benchRegPoP[i] = byte(0xff - i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse_Legacy_RegisterL1ValidatorTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyRegisterL1ValidatorTx{})
|
||||
src := &legacyRegisterL1ValidatorTx{
|
||||
ValidationID: ids.ID{0xaa},
|
||||
BLSPublicKey: benchRegBLS,
|
||||
ProofOfPossession: benchRegPoP,
|
||||
Expiry: 1_900_000_000,
|
||||
RemainingBalanceOwnerID: ids.ID{0xbb},
|
||||
}
|
||||
encoded, err := m.Marshal(0, src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var dst legacyRegisterL1ValidatorTx
|
||||
if _, err := m.Unmarshal(encoded, &dst); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse_ZAP_RegisterL1ValidatorTx(b *testing.B) {
|
||||
tx := NewRegisterL1ValidatorTx(ids.ID{0xaa}, benchRegBLS, benchRegPoP, 1_900_000_000, ids.ID{0xbb})
|
||||
buf := tx.Bytes()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := WrapRegisterL1ValidatorTx(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_Legacy_RegisterL1ValidatorTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyRegisterL1ValidatorTx{})
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
src := &legacyRegisterL1ValidatorTx{
|
||||
ValidationID: ids.ID{byte(i)},
|
||||
BLSPublicKey: benchRegBLS,
|
||||
ProofOfPossession: benchRegPoP,
|
||||
Expiry: uint64(i),
|
||||
RemainingBalanceOwnerID: ids.ID{byte(i)},
|
||||
}
|
||||
if _, err := m.Marshal(0, src); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_ZAP_RegisterL1ValidatorTx(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx := NewRegisterL1ValidatorTx(ids.ID{byte(i)}, benchRegBLS, benchRegPoP, uint64(i), ids.ID{byte(i)})
|
||||
_ = tx.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// SlashValidatorTx (Batch 2)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkParse_Legacy_SlashValidatorTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacySlashValidatorTx{})
|
||||
src := &legacySlashValidatorTx{NodeID: ids.NodeID{0xa1}, Subnet: ids.ID{0xa2}, SlashPercentage: 100_000}
|
||||
encoded, err := m.Marshal(0, src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var dst legacySlashValidatorTx
|
||||
if _, err := m.Unmarshal(encoded, &dst); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse_ZAP_SlashValidatorTx(b *testing.B) {
|
||||
tx := NewSlashValidatorTx(ids.NodeID{0xa1}, ids.ID{0xa2}, 100_000)
|
||||
buf := tx.Bytes()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := WrapSlashValidatorTx(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_Legacy_SlashValidatorTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacySlashValidatorTx{})
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
src := &legacySlashValidatorTx{NodeID: ids.NodeID{byte(i)}, Subnet: ids.ID{byte(i)}, SlashPercentage: uint32(i)}
|
||||
if _, err := m.Marshal(0, src); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_ZAP_SlashValidatorTx(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx := NewSlashValidatorTx(ids.NodeID{byte(i)}, ids.ID{byte(i)}, uint32(i))
|
||||
_ = tx.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// TransferChainOwnershipTx (Batch 2)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkParse_Legacy_TransferChainOwnershipTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyTransferChainOwnershipTx{})
|
||||
src := &legacyTransferChainOwnershipTx{Chain: ids.ID{0xc0}, OwnerThreshold: 1, OwnerLocktime: 0, OwnerAddress: ids.ShortID{0xbe, 0xef}}
|
||||
encoded, err := m.Marshal(0, src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var dst legacyTransferChainOwnershipTx
|
||||
if _, err := m.Unmarshal(encoded, &dst); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse_ZAP_TransferChainOwnershipTx(b *testing.B) {
|
||||
tx := NewTransferChainOwnershipTx(ids.ID{0xc0}, 1, 0, ids.ShortID{0xbe, 0xef})
|
||||
buf := tx.Bytes()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := WrapTransferChainOwnershipTx(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_Legacy_TransferChainOwnershipTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyTransferChainOwnershipTx{})
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
src := &legacyTransferChainOwnershipTx{Chain: ids.ID{byte(i)}, OwnerThreshold: uint32(i), OwnerLocktime: uint64(i), OwnerAddress: ids.ShortID{byte(i)}}
|
||||
if _, err := m.Marshal(0, src); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_ZAP_TransferChainOwnershipTx(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx := NewTransferChainOwnershipTx(ids.ID{byte(i)}, uint32(i), uint64(i), ids.ShortID{byte(i)})
|
||||
_ = tx.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// RemoveChainValidatorTx (Batch 2)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkParse_Legacy_RemoveChainValidatorTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyRemoveChainValidatorTx{})
|
||||
src := &legacyRemoveChainValidatorTx{NodeID: ids.NodeID{0x10}, Subnet: ids.ID{0xfa}}
|
||||
encoded, err := m.Marshal(0, src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var dst legacyRemoveChainValidatorTx
|
||||
if _, err := m.Unmarshal(encoded, &dst); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse_ZAP_RemoveChainValidatorTx(b *testing.B) {
|
||||
tx := NewRemoveChainValidatorTx(ids.NodeID{0x10}, ids.ID{0xfa})
|
||||
buf := tx.Bytes()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := WrapRemoveChainValidatorTx(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_Legacy_RemoveChainValidatorTx(b *testing.B) {
|
||||
m := newLegacyManagerFor(b, &legacyRemoveChainValidatorTx{})
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
src := &legacyRemoveChainValidatorTx{NodeID: ids.NodeID{byte(i)}, Subnet: ids.ID{byte(i)}}
|
||||
if _, err := m.Marshal(0, src); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuild_ZAP_RemoveChainValidatorTx(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx := NewRemoveChainValidatorTx(ids.NodeID{byte(i)}, ids.ID{byte(i)})
|
||||
_ = tx.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap_native
|
||||
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// BaseTx v1 schema — NetworkID + BlockchainID + Memo. The Outs/Ins fields of
|
||||
// the legacy lux.BaseTx are variable-length nested-object lists and need a
|
||||
// proper ZAP list schema; they ship in batch 3. This v1 form is the metadata
|
||||
// envelope every higher-level platformvm tx embeds.
|
||||
//
|
||||
// Fixed-section layout (size 44 bytes):
|
||||
//
|
||||
// NetworkID uint32 @ 0
|
||||
// BlockchainID 32B @ 4
|
||||
// Memo bytes @ 36 (offset+length pair, 8 bytes)
|
||||
const (
|
||||
SchemaVersionBaseTx uint16 = 2
|
||||
|
||||
OffsetBaseTx_NetworkID = 0 // uint32
|
||||
OffsetBaseTx_BlockchainID = 4 // ids.ID (32 bytes)
|
||||
OffsetBaseTx_Memo = 36 // bytes (offset+length, 8 bytes)
|
||||
SizeBaseTx = 44
|
||||
)
|
||||
|
||||
// BaseTx is a zero-copy typed accessor over a ZAP buffer carrying a v1 BaseTx.
|
||||
type BaseTx struct {
|
||||
msg *zap.Message
|
||||
obj zap.Object
|
||||
}
|
||||
|
||||
// NetworkID returns the network ID the tx targets.
|
||||
func (t BaseTx) NetworkID() uint32 {
|
||||
return t.obj.Uint32(OffsetBaseTx_NetworkID)
|
||||
}
|
||||
|
||||
// BlockchainID returns the chain ID the tx executes against.
|
||||
func (t BaseTx) BlockchainID() ids.ID {
|
||||
var out ids.ID
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetBaseTx_BlockchainID + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Memo returns the memo bytes. Zero-copy: the returned slice aliases the
|
||||
// underlying ZAP buffer. Callers MUST NOT mutate it.
|
||||
func (t BaseTx) Memo() []byte {
|
||||
return t.obj.Bytes(OffsetBaseTx_Memo)
|
||||
}
|
||||
|
||||
func (t BaseTx) Bytes() []byte { return t.msg.Bytes() }
|
||||
func (t BaseTx) IsZero() bool { return t.msg == nil }
|
||||
|
||||
// WrapBaseTx parses a ZAP buffer into a typed BaseTx accessor.
|
||||
func WrapBaseTx(b []byte) (BaseTx, error) {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return BaseTx{}, err
|
||||
}
|
||||
return BaseTx{msg: msg, obj: msg.Root()}, nil
|
||||
}
|
||||
|
||||
// NewBaseTx builds a v1 BaseTx into a fresh ZAP buffer. Memo may be nil.
|
||||
func NewBaseTx(networkID uint32, blockchainID ids.ID, memo []byte) BaseTx {
|
||||
b := zap.NewBuilder(zap.HeaderSize + 16 + SizeBaseTx + len(memo))
|
||||
ob := b.StartObject(SizeBaseTx)
|
||||
ob.SetUint32(OffsetBaseTx_NetworkID, networkID)
|
||||
for i := 0; i < 32; i++ {
|
||||
ob.SetUint8(OffsetBaseTx_BlockchainID+i, blockchainID[i])
|
||||
}
|
||||
ob.SetBytes(OffsetBaseTx_Memo, memo)
|
||||
ob.FinishAsRoot()
|
||||
msg, _ := zap.Parse(b.Finish())
|
||||
return BaseTx{msg: msg, obj: msg.Root()}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap_native
|
||||
|
||||
import (
|
||||
"github.com/luxfi/crypto/bls"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// RegisterL1ValidatorTx v1 schema — ValidationID + Signer (BLS pubkey + PoP) +
|
||||
// Expiry + RemainingBalanceOwnerID. The legacy struct's Message is a Warp
|
||||
// payload (variable-length addressed-call envelope) and RemainingBalanceOwner
|
||||
// is an OutputOwners with a variable AddressIDs list — both ship as proper
|
||||
// list/object schemas in batch 3. This v1 fixes the foundational identity
|
||||
// fields validators look up on every block.
|
||||
//
|
||||
// Fixed-section layout (size 192 bytes):
|
||||
//
|
||||
// ValidationID 32B @ 0
|
||||
// BLSPublicKey 48B @ 32 (bls.PublicKeyLen)
|
||||
// ProofOfPossession 96B @ 80 (bls.SignatureLen)
|
||||
// Expiry uint64 @ 176
|
||||
// RemainingBalanceOwnerID ids.ID @ 184 (placeholder 32B, full Owner in batch 3)
|
||||
const (
|
||||
SchemaVersionRegisterL1ValidatorTx uint16 = 2
|
||||
|
||||
OffsetRegisterL1ValidatorTx_ValidationID = 0
|
||||
OffsetRegisterL1ValidatorTx_BLSPublicKey = 32
|
||||
OffsetRegisterL1ValidatorTx_ProofOfPossession = OffsetRegisterL1ValidatorTx_BLSPublicKey + bls.PublicKeyLen
|
||||
OffsetRegisterL1ValidatorTx_Expiry = OffsetRegisterL1ValidatorTx_ProofOfPossession + bls.SignatureLen
|
||||
OffsetRegisterL1ValidatorTx_RemainingBalanceOwnerID = OffsetRegisterL1ValidatorTx_Expiry + 8
|
||||
SizeRegisterL1ValidatorTx = OffsetRegisterL1ValidatorTx_RemainingBalanceOwnerID + 32
|
||||
)
|
||||
|
||||
type RegisterL1ValidatorTx struct {
|
||||
msg *zap.Message
|
||||
obj zap.Object
|
||||
}
|
||||
|
||||
func (t RegisterL1ValidatorTx) ValidationID() ids.ID {
|
||||
var out ids.ID
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetRegisterL1ValidatorTx_ValidationID + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// BLSPublicKey returns the compressed-G1 BLS public key (48 bytes) registered
|
||||
// for this validator. Zero-copy by-value return; backing storage is the ZAP
|
||||
// buffer.
|
||||
func (t RegisterL1ValidatorTx) BLSPublicKey() [bls.PublicKeyLen]byte {
|
||||
var out [bls.PublicKeyLen]byte
|
||||
for i := 0; i < bls.PublicKeyLen; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetRegisterL1ValidatorTx_BLSPublicKey + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ProofOfPossession returns the BLS signature over the public key proving
|
||||
// the registrant controls the matching secret key.
|
||||
func (t RegisterL1ValidatorTx) ProofOfPossession() [bls.SignatureLen]byte {
|
||||
var out [bls.SignatureLen]byte
|
||||
for i := 0; i < bls.SignatureLen; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetRegisterL1ValidatorTx_ProofOfPossession + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t RegisterL1ValidatorTx) Expiry() uint64 {
|
||||
return t.obj.Uint64(OffsetRegisterL1ValidatorTx_Expiry)
|
||||
}
|
||||
|
||||
// RemainingBalanceOwnerID is a v1 placeholder for the OutputOwners pointer.
|
||||
// Batch 3 replaces this with a full OutputOwners nested-object schema
|
||||
// (threshold + AddressIDs list).
|
||||
func (t RegisterL1ValidatorTx) RemainingBalanceOwnerID() ids.ID {
|
||||
var out ids.ID
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetRegisterL1ValidatorTx_RemainingBalanceOwnerID + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t RegisterL1ValidatorTx) Bytes() []byte { return t.msg.Bytes() }
|
||||
func (t RegisterL1ValidatorTx) IsZero() bool { return t.msg == nil }
|
||||
|
||||
func WrapRegisterL1ValidatorTx(b []byte) (RegisterL1ValidatorTx, error) {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return RegisterL1ValidatorTx{}, err
|
||||
}
|
||||
return RegisterL1ValidatorTx{msg: msg, obj: msg.Root()}, nil
|
||||
}
|
||||
|
||||
func NewRegisterL1ValidatorTx(
|
||||
validationID ids.ID,
|
||||
blsPublicKey [bls.PublicKeyLen]byte,
|
||||
proofOfPossession [bls.SignatureLen]byte,
|
||||
expiry uint64,
|
||||
remainingBalanceOwnerID ids.ID,
|
||||
) RegisterL1ValidatorTx {
|
||||
b := zap.NewBuilder(zap.HeaderSize + 16 + SizeRegisterL1ValidatorTx)
|
||||
ob := b.StartObject(SizeRegisterL1ValidatorTx)
|
||||
for i := 0; i < 32; i++ {
|
||||
ob.SetUint8(OffsetRegisterL1ValidatorTx_ValidationID+i, validationID[i])
|
||||
}
|
||||
for i := 0; i < bls.PublicKeyLen; i++ {
|
||||
ob.SetUint8(OffsetRegisterL1ValidatorTx_BLSPublicKey+i, blsPublicKey[i])
|
||||
}
|
||||
for i := 0; i < bls.SignatureLen; i++ {
|
||||
ob.SetUint8(OffsetRegisterL1ValidatorTx_ProofOfPossession+i, proofOfPossession[i])
|
||||
}
|
||||
ob.SetUint64(OffsetRegisterL1ValidatorTx_Expiry, expiry)
|
||||
for i := 0; i < 32; i++ {
|
||||
ob.SetUint8(OffsetRegisterL1ValidatorTx_RemainingBalanceOwnerID+i, remainingBalanceOwnerID[i])
|
||||
}
|
||||
ob.FinishAsRoot()
|
||||
msg, _ := zap.Parse(b.Finish())
|
||||
return RegisterL1ValidatorTx{msg: msg, obj: msg.Root()}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap_native
|
||||
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// RemoveChainValidatorTx v1 schema — NodeID (20B) + Subnet (32B).
|
||||
//
|
||||
// Legacy struct also has ChainAuth (verify.Verifiable credential) which lives
|
||||
// outside the unsigned-tx bytes in the signed wrapper — the unsigned encoding
|
||||
// here pins only the identity tuple the executor matches against state.
|
||||
//
|
||||
// Fixed-section layout (size 52 bytes):
|
||||
//
|
||||
// NodeID 20B @ 0 (ids.NodeID)
|
||||
// Subnet 32B @ 20 (ids.ID)
|
||||
const (
|
||||
SchemaVersionRemoveChainValidatorTx uint16 = 2
|
||||
|
||||
OffsetRemoveChainValidatorTx_NodeID = 0
|
||||
OffsetRemoveChainValidatorTx_Subnet = ids.ShortIDLen
|
||||
SizeRemoveChainValidatorTx = OffsetRemoveChainValidatorTx_Subnet + 32
|
||||
)
|
||||
|
||||
type RemoveChainValidatorTx struct {
|
||||
msg *zap.Message
|
||||
obj zap.Object
|
||||
}
|
||||
|
||||
func (t RemoveChainValidatorTx) NodeID() ids.NodeID {
|
||||
var out ids.NodeID
|
||||
for i := 0; i < ids.ShortIDLen; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetRemoveChainValidatorTx_NodeID + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t RemoveChainValidatorTx) Subnet() ids.ID {
|
||||
var out ids.ID
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetRemoveChainValidatorTx_Subnet + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t RemoveChainValidatorTx) Bytes() []byte { return t.msg.Bytes() }
|
||||
func (t RemoveChainValidatorTx) IsZero() bool { return t.msg == nil }
|
||||
|
||||
func WrapRemoveChainValidatorTx(b []byte) (RemoveChainValidatorTx, error) {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return RemoveChainValidatorTx{}, err
|
||||
}
|
||||
return RemoveChainValidatorTx{msg: msg, obj: msg.Root()}, nil
|
||||
}
|
||||
|
||||
func NewRemoveChainValidatorTx(nodeID ids.NodeID, subnet ids.ID) RemoveChainValidatorTx {
|
||||
b := zap.NewBuilder(zap.HeaderSize + 16 + SizeRemoveChainValidatorTx)
|
||||
ob := b.StartObject(SizeRemoveChainValidatorTx)
|
||||
for i := 0; i < ids.ShortIDLen; i++ {
|
||||
ob.SetUint8(OffsetRemoveChainValidatorTx_NodeID+i, nodeID[i])
|
||||
}
|
||||
for i := 0; i < 32; i++ {
|
||||
ob.SetUint8(OffsetRemoveChainValidatorTx_Subnet+i, subnet[i])
|
||||
}
|
||||
ob.FinishAsRoot()
|
||||
msg, _ := zap.Parse(b.Finish())
|
||||
return RemoveChainValidatorTx{msg: msg, obj: msg.Root()}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap_native
|
||||
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// SlashValidatorTx v1 schema — NodeID (20B) + Subnet (32B).
|
||||
//
|
||||
// The legacy struct also carries an Evidence object (height, type, two
|
||||
// message/signature pairs) and SlashPercentage. Evidence is variable-length
|
||||
// nested bytes and ships with the proper list/object schema in batch 3.
|
||||
// SlashPercentage is included here as a uint32 because it's fixed-size and
|
||||
// the executor needs it before evaluating the evidence.
|
||||
//
|
||||
// Fixed-section layout (size 56 bytes):
|
||||
//
|
||||
// NodeID 20B @ 0 (ids.NodeID, ShortIDLen)
|
||||
// Subnet 32B @ 20 (ids.ID)
|
||||
// SlashPercentage uint32 @ 52
|
||||
const (
|
||||
SchemaVersionSlashValidatorTx uint16 = 2
|
||||
|
||||
OffsetSlashValidatorTx_NodeID = 0
|
||||
OffsetSlashValidatorTx_Subnet = ids.ShortIDLen
|
||||
OffsetSlashValidatorTx_SlashPercentage = OffsetSlashValidatorTx_Subnet + 32
|
||||
SizeSlashValidatorTx = OffsetSlashValidatorTx_SlashPercentage + 4
|
||||
)
|
||||
|
||||
type SlashValidatorTx struct {
|
||||
msg *zap.Message
|
||||
obj zap.Object
|
||||
}
|
||||
|
||||
func (t SlashValidatorTx) NodeID() ids.NodeID {
|
||||
var out ids.NodeID
|
||||
for i := 0; i < ids.ShortIDLen; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetSlashValidatorTx_NodeID + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t SlashValidatorTx) Subnet() ids.ID {
|
||||
var out ids.ID
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetSlashValidatorTx_Subnet + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t SlashValidatorTx) SlashPercentage() uint32 {
|
||||
return t.obj.Uint32(OffsetSlashValidatorTx_SlashPercentage)
|
||||
}
|
||||
|
||||
func (t SlashValidatorTx) Bytes() []byte { return t.msg.Bytes() }
|
||||
func (t SlashValidatorTx) IsZero() bool { return t.msg == nil }
|
||||
|
||||
func WrapSlashValidatorTx(b []byte) (SlashValidatorTx, error) {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return SlashValidatorTx{}, err
|
||||
}
|
||||
return SlashValidatorTx{msg: msg, obj: msg.Root()}, nil
|
||||
}
|
||||
|
||||
func NewSlashValidatorTx(nodeID ids.NodeID, subnet ids.ID, slashPercentage uint32) SlashValidatorTx {
|
||||
b := zap.NewBuilder(zap.HeaderSize + 16 + SizeSlashValidatorTx)
|
||||
ob := b.StartObject(SizeSlashValidatorTx)
|
||||
for i := 0; i < ids.ShortIDLen; i++ {
|
||||
ob.SetUint8(OffsetSlashValidatorTx_NodeID+i, nodeID[i])
|
||||
}
|
||||
for i := 0; i < 32; i++ {
|
||||
ob.SetUint8(OffsetSlashValidatorTx_Subnet+i, subnet[i])
|
||||
}
|
||||
ob.SetUint32(OffsetSlashValidatorTx_SlashPercentage, slashPercentage)
|
||||
ob.FinishAsRoot()
|
||||
msg, _ := zap.Parse(b.Finish())
|
||||
return SlashValidatorTx{msg: msg, obj: msg.Root()}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap_native
|
||||
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/zap"
|
||||
)
|
||||
|
||||
// TransferChainOwnershipTx v1 schema — Chain + a single-address Owner stub.
|
||||
//
|
||||
// The legacy struct's Owner is an fx.Owner interface — almost always
|
||||
// *secp256k1fx.OutputOwners with {Threshold uint32, Locktime uint64,
|
||||
// AddressIDs []ids.ShortID}. The variable AddressIDs list ships with the
|
||||
// proper schema in batch 3. The v1 form pins the single most common
|
||||
// configuration: threshold-1 / locktime-0 / single owner address. Callers
|
||||
// that need richer Owner shapes still have the legacy encoder available
|
||||
// behind LUXD_ENABLE_LEGACY_CODEC.
|
||||
//
|
||||
// The legacy struct also has ChainAuth (verify.Verifiable, a credential
|
||||
// proof produced by the signer). This is itself signature material that
|
||||
// rides outside the unsigned-tx bytes in the signed wrapper, so it stays
|
||||
// in the signed-tx schema (separate from the unsigned encoding here).
|
||||
//
|
||||
// Fixed-section layout (size 56 bytes):
|
||||
//
|
||||
// Chain 32B @ 0 (ids.ID)
|
||||
// OwnerThreshold uint32 @ 32
|
||||
// OwnerLocktime uint64 @ 40
|
||||
// OwnerAddress 20B @ 48 (ids.ShortID; single owner v1 stub)
|
||||
const (
|
||||
SchemaVersionTransferChainOwnershipTx uint16 = 2
|
||||
|
||||
OffsetTransferChainOwnershipTx_Chain = 0
|
||||
OffsetTransferChainOwnershipTx_OwnerThreshold = 32
|
||||
OffsetTransferChainOwnershipTx_OwnerLocktime = 40
|
||||
OffsetTransferChainOwnershipTx_OwnerAddress = 48
|
||||
SizeTransferChainOwnershipTx = OffsetTransferChainOwnershipTx_OwnerAddress + ids.ShortIDLen
|
||||
)
|
||||
|
||||
type TransferChainOwnershipTx struct {
|
||||
msg *zap.Message
|
||||
obj zap.Object
|
||||
}
|
||||
|
||||
func (t TransferChainOwnershipTx) Chain() ids.ID {
|
||||
var out ids.ID
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetTransferChainOwnershipTx_Chain + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t TransferChainOwnershipTx) OwnerThreshold() uint32 {
|
||||
return t.obj.Uint32(OffsetTransferChainOwnershipTx_OwnerThreshold)
|
||||
}
|
||||
|
||||
func (t TransferChainOwnershipTx) OwnerLocktime() uint64 {
|
||||
return t.obj.Uint64(OffsetTransferChainOwnershipTx_OwnerLocktime)
|
||||
}
|
||||
|
||||
func (t TransferChainOwnershipTx) OwnerAddress() ids.ShortID {
|
||||
var out ids.ShortID
|
||||
for i := 0; i < ids.ShortIDLen; i++ {
|
||||
out[i] = t.obj.Uint8(OffsetTransferChainOwnershipTx_OwnerAddress + i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t TransferChainOwnershipTx) Bytes() []byte { return t.msg.Bytes() }
|
||||
func (t TransferChainOwnershipTx) IsZero() bool { return t.msg == nil }
|
||||
|
||||
func WrapTransferChainOwnershipTx(b []byte) (TransferChainOwnershipTx, error) {
|
||||
msg, err := zap.Parse(b)
|
||||
if err != nil {
|
||||
return TransferChainOwnershipTx{}, err
|
||||
}
|
||||
return TransferChainOwnershipTx{msg: msg, obj: msg.Root()}, nil
|
||||
}
|
||||
|
||||
func NewTransferChainOwnershipTx(
|
||||
chain ids.ID,
|
||||
ownerThreshold uint32,
|
||||
ownerLocktime uint64,
|
||||
ownerAddress ids.ShortID,
|
||||
) TransferChainOwnershipTx {
|
||||
b := zap.NewBuilder(zap.HeaderSize + 16 + SizeTransferChainOwnershipTx)
|
||||
ob := b.StartObject(SizeTransferChainOwnershipTx)
|
||||
for i := 0; i < 32; i++ {
|
||||
ob.SetUint8(OffsetTransferChainOwnershipTx_Chain+i, chain[i])
|
||||
}
|
||||
ob.SetUint32(OffsetTransferChainOwnershipTx_OwnerThreshold, ownerThreshold)
|
||||
ob.SetUint64(OffsetTransferChainOwnershipTx_OwnerLocktime, ownerLocktime)
|
||||
for i := 0; i < ids.ShortIDLen; i++ {
|
||||
ob.SetUint8(OffsetTransferChainOwnershipTx_OwnerAddress+i, ownerAddress[i])
|
||||
}
|
||||
ob.FinishAsRoot()
|
||||
msg, _ := zap.Parse(b.Finish())
|
||||
return TransferChainOwnershipTx{msg: msg, obj: msg.Root()}
|
||||
}
|
||||
Reference in New Issue
Block a user