Files
node/vms/xvm/static_service.go
T
Hanzo AI c3b398bc7b json: migrate every encoding/json import to json/v2 (go-json-experiment)
External (HTTP / JSON-RPC) is the only place JSON is legitimate. Every
existing encoding/json import in node/ moves to github.com/go-json-experiment/json
(v2 root, not the v1 sub-package). NewEncoder/NewDecoder rewrite to
MarshalWrite/UnmarshalRead. MarshalIndent rewrites to Marshal with
jsontext.WithIndent. json.RawMessage rewrites to jsontext.Value.
*json.SyntaxError rewrites to *jsontext.SyntacticError.

81 files migrated. LLM.md captures the rule + v1->v2 delta table.

Known v2 semantic deltas surfaced by existing tests (followups, not regressions):
- [N]byte fields with no MarshalJSON now marshal as base64 string (v1 marshalled
  as JSON array of byte numbers). Affects vms/platformvm/txs/*_test.go fixtures
  with embedded BLS proofOfPossession.
- time.Duration has no v2 default representation; configs that wire-format
  Duration as nanoseconds (vms/{xvm,platformvm}/config, config/spec) need to
  switch to string-form Duration or carry an explicit option. v2 root does not
  re-export FormatDurationAsNano.
- v2 enforces strict UTF-8 (vms/chainadapter/messaging fixture has non-UTF-8).
- json.MarshalWrite does not append a trailing '\n' (v1 NewEncoder.Encode did);
  service/auth/auth_test.go expectation updated.
- nil []byte round-trips to empty (not nil); config_test deep-equal fixtures
  surface this.

All affected sites are at the API boundary; ZAP wire envelope already covers
the internal data paths (state, P2P, consensus, MPC, threshold). Internal
JSON sites that should move to ZAP next (separate work):
- vms/da/store.go            (DA blob/cert storage as JSON)
- vms/platformvm/airdrop     (airdrop claims as JSON in db)
- vms/chainadapter/appchain  (SQLite materializer schema/data blobs)
- vms/chainadapter/messaging (conversation codec)
- staking/kms.go             (KMS HTTP client — external technically, leave)
- utils/{bimap,ips}          (small marshaler shims — low priority)
2026-06-06 22:26:02 -07:00

198 lines
6.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package xvm
import (
"errors"
"fmt"
"net/http"
"github.com/go-json-experiment/json"
"github.com/luxfi/address"
"github.com/luxfi/formatting"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/xvm/fxs"
"github.com/luxfi/node/vms/xvm/txs"
"github.com/luxfi/utils"
"github.com/luxfi/utxo/nftfx"
"github.com/luxfi/utxo/propertyfx"
"github.com/luxfi/utxo/secp256k1fx"
avajson "github.com/luxfi/node/utils/json"
)
var (
errUnknownAssetType = errors.New("unknown asset type")
_ lux.TransferableIn = (*secp256k1fx.TransferInput)(nil)
_ verify.State = (*secp256k1fx.MintOutput)(nil)
_ lux.TransferableOut = (*secp256k1fx.TransferOutput)(nil)
_ fxs.FxOperation = (*secp256k1fx.MintOperation)(nil)
_ verify.Verifiable = (*secp256k1fx.Credential)(nil)
_ verify.State = (*nftfx.MintOutput)(nil)
_ verify.State = (*nftfx.TransferOutput)(nil)
_ fxs.FxOperation = (*nftfx.MintOperation)(nil)
_ fxs.FxOperation = (*nftfx.TransferOperation)(nil)
_ verify.Verifiable = (*nftfx.Credential)(nil)
_ verify.State = (*propertyfx.MintOutput)(nil)
_ verify.State = (*propertyfx.OwnedOutput)(nil)
_ fxs.FxOperation = (*propertyfx.MintOperation)(nil)
_ fxs.FxOperation = (*propertyfx.BurnOperation)(nil)
_ verify.Verifiable = (*propertyfx.Credential)(nil)
)
// StaticService defines the base service for the asset vm
type StaticService struct{}
func CreateStaticService() *StaticService {
return &StaticService{}
}
// BuildGenesisArgs are arguments for BuildGenesis
type BuildGenesisArgs struct {
NetworkID avajson.Uint32 `json:"networkID"`
GenesisData map[string]AssetDefinition `json:"genesisData"`
Encoding formatting.Encoding `json:"encoding"`
}
type AssetDefinition struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Denomination avajson.Uint8 `json:"denomination"`
InitialState map[string][]interface{} `json:"initialState"`
Memo string `json:"memo"`
}
// BuildGenesisReply is the reply from BuildGenesis
type BuildGenesisReply struct {
Bytes string `json:"bytes"`
Encoding formatting.Encoding `json:"encoding"`
}
// BuildGenesis returns the UTXOs such that at least one address in [args.Addresses] is
// referenced in the UTXO.
func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, reply *BuildGenesisReply) error {
parser, err := txs.NewParser(
[]fxs.Fx{
&secp256k1fx.Fx{},
&nftfx.Fx{},
&propertyfx.Fx{},
},
)
if err != nil {
return err
}
g := Genesis{}
genesisCodec := parser.GenesisCodec()
for assetAlias, assetDefinition := range args.GenesisData {
assetMemo, err := formatting.Decode(args.Encoding, assetDefinition.Memo)
if err != nil {
return fmt.Errorf("problem formatting asset definition memo due to: %w", err)
}
asset := GenesisAsset{
Alias: assetAlias,
CreateAssetTx: txs.CreateAssetTx{
BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{
NetworkID: uint32(args.NetworkID),
BlockchainID: ids.Empty,
Memo: assetMemo,
}},
Name: assetDefinition.Name,
Symbol: assetDefinition.Symbol,
Denomination: byte(assetDefinition.Denomination),
},
}
if len(assetDefinition.InitialState) > 0 {
initialState := &txs.InitialState{
FxIndex: 0, // Implementation note
}
for assetType, initialStates := range assetDefinition.InitialState {
switch assetType {
case "fixedCap":
for _, state := range initialStates {
b, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("problem marshaling state: %w", err)
}
holder := Holder{}
if err := json.Unmarshal(b, &holder); err != nil {
return fmt.Errorf("problem unmarshaling holder: %w", err)
}
_, addrbuff, err := address.ParseBech32(holder.Address)
if err != nil {
return fmt.Errorf("problem parsing holder address: %w", err)
}
addr, err := ids.ToShortID(addrbuff)
if err != nil {
return fmt.Errorf("problem parsing holder address: %w", err)
}
initialState.Outs = append(initialState.Outs, &secp256k1fx.TransferOutput{
Amt: uint64(holder.Amount),
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{addr},
},
})
}
case "variableCap":
for _, state := range initialStates {
b, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("problem marshaling state: %w", err)
}
owners := Owners{}
if err := json.Unmarshal(b, &owners); err != nil {
return fmt.Errorf("problem unmarshaling Owners: %w", err)
}
out := &secp256k1fx.MintOutput{
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
},
}
for _, addrStr := range owners.Minters {
_, addrBytes, err := address.ParseBech32(addrStr)
if err != nil {
return fmt.Errorf("problem parsing minters address: %w", err)
}
addr, err := ids.ToShortID(addrBytes)
if err != nil {
return fmt.Errorf("problem parsing minters address: %w", err)
}
out.Addrs = append(out.Addrs, addr)
}
out.Sort()
initialState.Outs = append(initialState.Outs, out)
}
default:
return errUnknownAssetType
}
}
initialState.Sort(genesisCodec)
asset.States = append(asset.States, initialState)
}
utils.Sort(asset.States)
g.Txs = append(g.Txs, &asset)
}
utils.Sort(g.Txs)
b, err := genesisCodec.Marshal(txs.CodecVersion, &g)
if err != nil {
return fmt.Errorf("problem marshaling genesis: %w", err)
}
reply.Bytes, err = formatting.Encode(args.Encoding, b)
if err != nil {
return fmt.Errorf("couldn't encode genesis as string: %w", err)
}
reply.Encoding = args.Encoding
return nil
}