mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
G-Chain indexes what other chains have already agreed, so it holds no state of its own to agree on and never advances past genesis. That was written down in a comment on its state and nowhere the code could be read from: BuildBlock answered a generic "not implemented", which reads as an omission, and WaitForEvent's comment said it would one day wait for queued schema updates, which invites exactly the latch the other chains just got. Both now name the reason. A block here would carry a query result, which a peer recomputes from state, or a schema, which is local configuration; neither is something the network votes on. A test pins it, so the difference between a chain with nothing to say and a chain with no way to say it stays visible. BridgeRequest.Status was a string whose valid values lived in a comment beside it, while the same lifecycle already had a type and eight constants on BridgeRequestRecord. One concept, two representations, and the one the watcher wrote to was the untyped one. It now uses the type. Co-authored-by: Hanzo Dev <[email protected]>
286 lines
7.6 KiB
Go
286 lines
7.6 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package bridgevm
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/zap"
|
|
)
|
|
|
|
// Native-ZAP struct-is-wire for B-Chain. No pcodecs, no reflection, no codec
|
|
// registry — each type owns its Marshal/Parse over a zap object. Re-genesis
|
|
// authorized, so the on-disk/wire format is these offsets (canonical: Parse
|
|
// rejects trailing bytes).
|
|
|
|
// Note: Genesis is JSON at the genesis/config boundary (see vm.go), not a
|
|
// binary wire type — it is intentionally not encoded here.
|
|
|
|
// ---- BridgeRequest ----
|
|
//
|
|
// Fixed object (variable fields are bytes/list ptrs):
|
|
//
|
|
// ID 32B @ 0
|
|
// Asset 32B @ 32
|
|
// SourceTxID 32B @ 64
|
|
// SrcChainID u32 @ 96
|
|
// DstChainID u32 @ 100
|
|
// Confirmations u32 @ 104
|
|
// Nonce u64 @ 108
|
|
// Amount u64 @ 116
|
|
// CreatedAt i64 @ 124 (unix nanos)
|
|
// SourceChain bytes@ 132
|
|
// DestChain bytes@ 140
|
|
// Status bytes@ 148
|
|
// Recipient bytes@ 156
|
|
// MPCSigLens list @ 164 (u32 per sig)
|
|
// MPCSigBlob bytes@ 172
|
|
const (
|
|
brID = 0
|
|
brAsset = 32
|
|
brSourceTx = 64
|
|
brSrcChain = 96
|
|
brDstChain = 100
|
|
brConfirm = 104
|
|
brNonce = 108
|
|
brAmount = 116
|
|
brCreated = 124
|
|
brSrcName = 132
|
|
brDstName = 140
|
|
brStatus = 148
|
|
brRecip = 156
|
|
brSigLens = 164
|
|
brSigBlob = 172
|
|
brSize = 180
|
|
)
|
|
|
|
// marshalBridgeRequest encodes r into a standalone wire object.
|
|
func marshalBridgeRequest(r *BridgeRequest) []byte {
|
|
b := zap.NewBuilder(zap.HeaderSize + brSize + len(r.SourceChain) + len(r.DestChain) +
|
|
len(r.Status) + len(r.Recipient) + sigBlobLen(r.MPCSignatures) + 4*len(r.MPCSignatures) + 128)
|
|
sigLensOff := writeU32List(b, sigLens(r.MPCSignatures))
|
|
|
|
ob := b.StartObject(brSize)
|
|
ob.SetBytesFixed(brID, r.ID[:])
|
|
ob.SetBytesFixed(brAsset, r.Asset[:])
|
|
ob.SetBytesFixed(brSourceTx, r.SourceTxID[:])
|
|
ob.SetUint32(brSrcChain, r.SrcChainID)
|
|
ob.SetUint32(brDstChain, r.DstChainID)
|
|
ob.SetUint32(brConfirm, r.Confirmations)
|
|
ob.SetUint64(brNonce, r.Nonce)
|
|
ob.SetUint64(brAmount, r.Amount)
|
|
ob.SetInt64(brCreated, r.CreatedAt.UnixNano())
|
|
ob.SetBytes(brSrcName, []byte(r.SourceChain))
|
|
ob.SetBytes(brDstName, []byte(r.DestChain))
|
|
ob.SetBytes(brStatus, []byte(r.Status))
|
|
ob.SetBytes(brRecip, r.Recipient)
|
|
ob.SetList(brSigLens, sigLensOff, len(r.MPCSignatures))
|
|
ob.SetBytes(brSigBlob, flattenSigs(r.MPCSignatures))
|
|
ob.FinishAsRoot()
|
|
return b.Finish()
|
|
}
|
|
|
|
func readBridgeRequest(o zap.Object) *BridgeRequest {
|
|
r := &BridgeRequest{
|
|
SrcChainID: o.Uint32(brSrcChain),
|
|
DstChainID: o.Uint32(brDstChain),
|
|
Confirmations: o.Uint32(brConfirm),
|
|
Nonce: o.Uint64(brNonce),
|
|
Amount: o.Uint64(brAmount),
|
|
CreatedAt: time.Unix(0, o.Int64(brCreated)).UTC(),
|
|
SourceChain: string(o.Bytes(brSrcName)),
|
|
DestChain: string(o.Bytes(brDstName)),
|
|
Status: BridgeRequestStatus(o.Bytes(brStatus)),
|
|
Recipient: appendBytes(o.Bytes(brRecip)),
|
|
MPCSignatures: readSigs(o, brSigLens, brSigBlob),
|
|
}
|
|
copy(r.ID[:], o.BytesFixedSlice(brID, 32))
|
|
copy(r.Asset[:], o.BytesFixedSlice(brAsset, 32))
|
|
copy(r.SourceTxID[:], o.BytesFixedSlice(brSourceTx, 32))
|
|
return r
|
|
}
|
|
|
|
// ---- Block ----
|
|
//
|
|
// ParentID 32B @ 0
|
|
// BlockHeight u64 @ 32
|
|
// BlockTimestamp i64 @ 40
|
|
// ReqLens list @ 48 (u32 per BridgeRequest wire length)
|
|
// ReqBlob bytes@ 56 (concatenated BridgeRequest wire objects)
|
|
// SigNodeIDs bytes@ 64 (concatenated 20-byte NodeIDs, sorted)
|
|
// SigLens list @ 72 (u32 per MPC sig)
|
|
// SigBlob bytes@ 80
|
|
const (
|
|
blkParent = 0
|
|
blkHeight = 32
|
|
blkTime = 40
|
|
blkReqLens = 48
|
|
blkReqBlob = 56
|
|
blkSigNodes = 64
|
|
blkSigLens = 72
|
|
blkSigBlob = 80
|
|
blkSize = 88
|
|
)
|
|
|
|
func (b *Block) Marshal() ([]byte, error) {
|
|
// per-request wire objects
|
|
var reqBlob []byte
|
|
reqLens := make([]uint32, 0, len(b.BridgeRequests))
|
|
for _, r := range b.BridgeRequests {
|
|
rb := marshalBridgeRequest(r)
|
|
reqLens = append(reqLens, uint32(len(rb)))
|
|
reqBlob = append(reqBlob, rb...)
|
|
}
|
|
// sorted map -> (nodeIDs blob, sigs)
|
|
nodeIDs := make([]ids.NodeID, 0, len(b.MPCSignatures))
|
|
for id := range b.MPCSignatures {
|
|
nodeIDs = append(nodeIDs, id)
|
|
}
|
|
sort.Slice(nodeIDs, func(i, j int) bool { return nodeIDs[i].Compare(nodeIDs[j]) < 0 })
|
|
var nodeBlob []byte
|
|
sigs := make([][]byte, 0, len(nodeIDs))
|
|
for _, id := range nodeIDs {
|
|
nodeBlob = append(nodeBlob, id[:]...)
|
|
sigs = append(sigs, b.MPCSignatures[id])
|
|
}
|
|
|
|
bld := zap.NewBuilder(zap.HeaderSize + blkSize + len(reqBlob) + len(nodeBlob) +
|
|
sigBlobLen(sigs) + 4*(len(reqLens)+len(sigs)) + 256)
|
|
reqLensOff := writeU32List(bld, reqLens)
|
|
sigLensOff := writeU32List(bld, sigLens(sigs))
|
|
|
|
ob := bld.StartObject(blkSize)
|
|
ob.SetBytesFixed(blkParent, b.ParentID_[:])
|
|
ob.SetUint64(blkHeight, b.BlockHeight)
|
|
ob.SetInt64(blkTime, b.BlockTimestamp)
|
|
ob.SetList(blkReqLens, reqLensOff, len(reqLens))
|
|
ob.SetBytes(blkReqBlob, reqBlob)
|
|
ob.SetBytes(blkSigNodes, nodeBlob)
|
|
ob.SetList(blkSigLens, sigLensOff, len(sigs))
|
|
ob.SetBytes(blkSigBlob, flattenSigs(sigs))
|
|
ob.FinishAsRoot()
|
|
return bld.Finish(), nil
|
|
}
|
|
|
|
func parseBlockBytes(data []byte, blk *Block) error {
|
|
msg, err := zap.Parse(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if msg.Size() != len(data) {
|
|
return fmt.Errorf("bridgevm block: trailing bytes")
|
|
}
|
|
o := msg.Root()
|
|
copy(blk.ParentID_[:], o.BytesFixedSlice(blkParent, 32))
|
|
blk.BlockHeight = o.Uint64(blkHeight)
|
|
blk.BlockTimestamp = o.Int64(blkTime)
|
|
|
|
// requests
|
|
reqLens := readU32List(o, blkReqLens)
|
|
reqBlob := o.Bytes(blkReqBlob)
|
|
blk.BridgeRequests = make([]*BridgeRequest, 0, len(reqLens))
|
|
pos := 0
|
|
for _, l := range reqLens {
|
|
if pos+int(l) > len(reqBlob) {
|
|
return fmt.Errorf("bridgevm block: request blob out of bounds")
|
|
}
|
|
rmsg, err := zap.Parse(reqBlob[pos : pos+int(l)])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
blk.BridgeRequests = append(blk.BridgeRequests, readBridgeRequest(rmsg.Root()))
|
|
pos += int(l)
|
|
}
|
|
|
|
// mpc signatures map
|
|
nodeBlob := o.Bytes(blkSigNodes)
|
|
sigLens := readU32List(o, blkSigLens)
|
|
sigBlob := o.Bytes(blkSigBlob)
|
|
blk.MPCSignatures = make(map[ids.NodeID][]byte, len(sigLens))
|
|
sp := 0
|
|
for i, l := range sigLens {
|
|
var id ids.NodeID
|
|
if (i+1)*20 > len(nodeBlob) {
|
|
return fmt.Errorf("bridgevm block: nodeID blob out of bounds")
|
|
}
|
|
copy(id[:], nodeBlob[i*20:(i+1)*20])
|
|
if sp+int(l) > len(sigBlob) {
|
|
return fmt.Errorf("bridgevm block: sig blob out of bounds")
|
|
}
|
|
blk.MPCSignatures[id] = appendBytes(sigBlob[sp : sp+int(l)])
|
|
sp += int(l)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ---- shared helpers ----
|
|
|
|
func writeU32List(b *zap.Builder, xs []uint32) int {
|
|
lb := b.StartList(4)
|
|
for _, x := range xs {
|
|
lb.AddUint32(x)
|
|
}
|
|
off, _ := lb.Finish()
|
|
return off
|
|
}
|
|
|
|
func readU32List(o zap.Object, ptrOff int) []uint32 {
|
|
l := o.ListStride(ptrOff, 4)
|
|
n := l.Len()
|
|
out := make([]uint32, n)
|
|
for i := 0; i < n; i++ {
|
|
out[i] = l.Uint32(i)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func sigLens(sigs [][]byte) []uint32 {
|
|
out := make([]uint32, len(sigs))
|
|
for i, s := range sigs {
|
|
out[i] = uint32(len(s))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func flattenSigs(sigs [][]byte) []byte {
|
|
var out []byte
|
|
for _, s := range sigs {
|
|
out = append(out, s...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func sigBlobLen(sigs [][]byte) int {
|
|
n := 0
|
|
for _, s := range sigs {
|
|
n += len(s)
|
|
}
|
|
return n
|
|
}
|
|
|
|
func readSigs(o zap.Object, lensOff, blobOff int) [][]byte {
|
|
lens := readU32List(o, lensOff)
|
|
blob := o.Bytes(blobOff)
|
|
out := make([][]byte, 0, len(lens))
|
|
pos := 0
|
|
for _, l := range lens {
|
|
if pos+int(l) > len(blob) {
|
|
break
|
|
}
|
|
out = append(out, appendBytes(blob[pos:pos+int(l)]))
|
|
pos += int(l)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendBytes(b []byte) []byte {
|
|
if len(b) == 0 {
|
|
return nil
|
|
}
|
|
return append([]byte(nil), b...)
|
|
}
|