mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
* feat(platformvm): CreateSovereignL1Tx — single-tx sovereign L1 launch
Adds a new platformvm tx type that atomically registers a sovereign L1
in one P-chain commit. Replaces what is today the four-step flow:
CreateNetworkTx + AddChainValidatorTx ×N + CreateChainTx ×K + ConvertNetworkToL1Tx
with one signed tx. After commit, the primary network has a permanent
record of the L1's network ID + initial validator set + chain manifest
+ on-chain validator-manager contract — but it does NOT track-chains
or validate the L1's blocks. The L1 runs its own consensus from
genesis. L2/L3/L4 follow the same pattern recursively.
Type shape:
type CreateSovereignL1Tx struct {
BaseTx
Owner fx.Owner // CreateNetworkTx parity
Validators []*ConvertNetworkToL1Validator // genesis validator set
Chains []*SovereignL1Chain // VM ID + genesis blob per chain
ManagerChainIdx uint32 // index into Chains[]
ManagerAddress types.JSONByteSlice // validator-manager contract
}
type SovereignL1Chain struct {
BlockchainName string
VMID ids.ID
FxIDs []ids.ID
GenesisData []byte
}
SyntacticVerify enforces:
- at least one validator (sorted, unique)
- at least one chain, ≤ MaxSovereignL1Chains (16)
- ManagerChainIdx is in range of Chains[]
- ManagerAddress ≤ MaxChainAddressLength
- per-chain name + VMID + FxIDs + genesis bounds
- BaseTx + Owner + each Validator each verify
Wired into:
- Visitor interface
- codec (registered as the next tx type after ConvertNetworkToL1Tx)
- signer + complexity + metrics + executor stubs across all visitor
implementations
Executor body is stubbed with a TODO. The atomic state transition
(mint new networkID from tx hash, seed validator-manager state,
register each Chain, charge fee) lands in a follow-up PR. This PR is
the type definition + interface plumbing so downstream tools (wallet,
CLI, fee calc, metrics) can target the tx type while the executor is
implemented.
* chore(node): kill subnet — chain/network vocabulary across node
Zero remaining `subnet|Subnet|SUBNET` in node Go source. Per canonical
no-subnet rule.
## Wire types (Go fields only; byte-level wire encoding unchanged)
message/wire/types.go TrackedSubnets → TrackedChains
message/wire/zap.go same on Read/Write
proto/p2p/p2p_zap.go SubnetUptime alias → ChainUptime
(consumes luxfi/proto rename in companion PR)
## Comment scrub
message/wire/types.go "(chain, subnet) pair" → "(chain, network) pair"
network/peer/handshake.go "primary-network or subnet" → "primary-network or per-chain"
node/node.go "per-subnet" → "per-chain"
"P→subnet warp" → "P→chain warp"
genesis/builder/builder.go "their own subnets" → "their own chains"
vms/platformvm/client.go dropped "subnet jargon" reference
vms/platformvm/service.go dropped "net / subnet jargon" reference
vms/platformvm/config/internal.go "subnet-spawned blockchain" → "per-chain blockchain"
## ICPSubnet (Internet Computer adapter)
ICPSubnet → ICPNet (type rename — unrelated to platform subnet,
but still a subnet word; killed for consistency)
map field `subnets` → `nets`
## Examples
wallet/network/primary/examples/bootstrap-hanzo/main.go:
--subnet-id → --network-id (CLI flag)
existingSubnetID local var → existingNetID
"subnet ID" log lines → "network ID"
"SUBNET_ID=" output → "NETWORK_ID="
"subnet-evm VM ID" → "EVM VM ID"
All comments rephrased.
wallet/network/primary/examples/heartbeat-tx/main.go: one comment
rephrase.
go.work workspace cleanup:
- Removed ./operator/go entry (placeholder; lux/operator polyglot
layout pending the cross-repo migration)
- Removed ./operator entry (mid-migration; nothing to build)
Build clean. Zero `grep -rIn "subnet|Subnet" --include="*.go"` matches.
998 lines
30 KiB
Go
998 lines
30 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package fee implements gas complexity calculations for platform transactions.
|
|
// LP-103 compliance is enforced by the dynamic fee calculator.
|
|
package fee
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/luxfi/codec"
|
|
"github.com/luxfi/crypto/bls"
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/vms/components/gas"
|
|
lux "github.com/luxfi/utxo"
|
|
"github.com/luxfi/node/vms/components/verify"
|
|
"github.com/luxfi/node/vms/platformvm/stakeable"
|
|
"github.com/luxfi/node/vms/platformvm/txs"
|
|
"github.com/luxfi/node/vms/platformvm/warp"
|
|
"github.com/luxfi/math"
|
|
"github.com/luxfi/codec/wrappers"
|
|
"github.com/luxfi/node/vms/platformvm/fx"
|
|
"github.com/luxfi/node/vms/platformvm/signer"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
)
|
|
|
|
// Signature verification costs were conservatively based on benchmarks run on
|
|
// an AWS c5.xlarge instance.
|
|
const (
|
|
intrinsicValidatorBandwidth = ids.NodeIDLen + // nodeID
|
|
wrappers.LongLen + // start
|
|
wrappers.LongLen + // end
|
|
wrappers.LongLen // weight
|
|
|
|
intrinsicNetValidatorBandwidth = intrinsicValidatorBandwidth + // validator
|
|
ids.IDLen // subchainID
|
|
|
|
intrinsicOutputBandwidth = ids.IDLen + // assetID
|
|
wrappers.IntLen // output typeID
|
|
|
|
intrinsicStakeableLockedOutputBandwidth = wrappers.LongLen + // locktime
|
|
wrappers.IntLen // output typeID
|
|
|
|
intrinsicSECP256k1FxOutputOwnersBandwidth = wrappers.LongLen + // locktime
|
|
wrappers.IntLen + // threshold
|
|
wrappers.IntLen // num addresses
|
|
|
|
intrinsicSECP256k1FxOutputBandwidth = wrappers.LongLen + // amount
|
|
intrinsicSECP256k1FxOutputOwnersBandwidth
|
|
|
|
intrinsicInputBandwidth = ids.IDLen + // txID
|
|
wrappers.IntLen + // output index
|
|
ids.IDLen + // assetID
|
|
wrappers.IntLen + // input typeID
|
|
wrappers.IntLen // credential typeID
|
|
|
|
intrinsicStakeableLockedInputBandwidth = wrappers.LongLen + // locktime
|
|
wrappers.IntLen // input typeID
|
|
|
|
intrinsicSECP256k1FxInputBandwidth = wrappers.IntLen + // num indices
|
|
wrappers.IntLen // num signatures
|
|
|
|
intrinsicSECP256k1FxTransferableInputBandwidth = wrappers.LongLen + // amount
|
|
intrinsicSECP256k1FxInputBandwidth
|
|
|
|
intrinsicSECP256k1FxSignatureBandwidth = wrappers.IntLen + // signature index
|
|
secp256k1.SignatureLen // signature length
|
|
|
|
intrinsicSECP256k1FxSignatureCompute = 200 // secp256k1 signature verification time is around 200us
|
|
|
|
intrinsicConvertNetworkToL1ValidatorBandwidth = wrappers.IntLen + // nodeID length
|
|
wrappers.LongLen + // weight
|
|
wrappers.LongLen + // balance
|
|
wrappers.IntLen + // remaining balance owner threshold
|
|
wrappers.IntLen + // remaining balance owner num addresses
|
|
wrappers.IntLen + // deactivation owner threshold
|
|
wrappers.IntLen // deactivation owner num addresses
|
|
|
|
intrinsicBLSAggregateCompute = 5 // BLS public key aggregation time is around 5us
|
|
intrinsicBLSVerifyCompute = 1_000 // BLS verification time is around 1000us
|
|
intrinsicBLSPublicKeyValidationCompute = 50 // BLS public key validation time is around 50us
|
|
intrinsicBLSPoPVerifyCompute = intrinsicBLSPublicKeyValidationCompute + intrinsicBLSVerifyCompute
|
|
|
|
intrinsicWarpDBReads = 3 + 20 // chainID -> subchainID mapping + apply weight diffs + apply pk diffs + diff application reads
|
|
|
|
intrinsicPoPBandwidth = bls.PublicKeyLen + // public key
|
|
bls.SignatureLen // signature
|
|
|
|
intrinsicInputDBRead = 1
|
|
|
|
intrinsicInputDBWrite = 1
|
|
intrinsicOutputDBWrite = 1
|
|
intrinsicConvertNetworkToL1ValidatorDBWrite = 4 // weight diff + pub key diff + chainID/nodeID + validationID
|
|
)
|
|
|
|
var (
|
|
_ txs.Visitor = (*complexityVisitor)(nil)
|
|
|
|
IntrinsicAddChainValidatorTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
intrinsicNetValidatorBandwidth + // netValidator
|
|
wrappers.IntLen + // netAuth typeID
|
|
wrappers.IntLen, // 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
|
|
wrappers.ShortLen + // chainName length
|
|
ids.IDLen + // vmID
|
|
wrappers.IntLen + // num fxIDs
|
|
wrappers.IntLen + // genesis length
|
|
wrappers.IntLen + // chainAuth typeID
|
|
wrappers.IntLen, // 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] +
|
|
wrappers.IntLen, // owner typeID
|
|
gas.DBWrite: 1, // write chain owner
|
|
}
|
|
IntrinsicImportTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
ids.IDLen + // source chainID
|
|
wrappers.IntLen, // num importing inputs
|
|
}
|
|
IntrinsicExportTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
ids.IDLen + // destination chainID
|
|
wrappers.IntLen, // num exported outputs
|
|
}
|
|
IntrinsicRemoveChainValidatorTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
ids.NodeIDLen + // nodeID
|
|
ids.IDLen + // netID
|
|
wrappers.IntLen + // netAuth typeID
|
|
wrappers.IntLen, // netAuthCredential typeID
|
|
gas.DBRead: 1, // read net auth
|
|
gas.DBWrite: 3, // delete validator + write weight diff + write pk diff
|
|
}
|
|
IntrinsicAddPermissionlessValidatorTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
intrinsicValidatorBandwidth + // validator
|
|
ids.IDLen + // subchainID
|
|
wrappers.IntLen + // signer typeID
|
|
wrappers.IntLen + // num stake outs
|
|
wrappers.IntLen + // validator rewards typeID
|
|
wrappers.IntLen + // delegator rewards typeID
|
|
wrappers.IntLen, // delegation shares
|
|
gas.DBRead: 1, // get staking config
|
|
gas.DBWrite: 3, // put current staker + write weight diff + write pk diff
|
|
}
|
|
IntrinsicAddPermissionlessDelegatorTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
intrinsicValidatorBandwidth + // validator
|
|
ids.IDLen + // subchainID
|
|
wrappers.IntLen + // num stake outs
|
|
wrappers.IntLen, // 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
|
|
wrappers.IntLen + // netAuth typeID
|
|
wrappers.IntLen + // owner typeID
|
|
wrappers.IntLen, // netAuthCredential typeID
|
|
gas.DBRead: 1, // read net auth
|
|
gas.DBWrite: 1, // set net owner
|
|
}
|
|
IntrinsicTransformChainTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
ids.IDLen + // netID
|
|
ids.IDLen + // assetID
|
|
wrappers.IntLen + // initialSupply
|
|
wrappers.IntLen + // maximumSupply
|
|
wrappers.IntLen + // minConsumptionRate
|
|
wrappers.IntLen + // maxConsumptionRate
|
|
wrappers.LongLen + // minValidatorStake
|
|
wrappers.LongLen + // maxValidatorStake
|
|
wrappers.IntLen + // minStakeDuration
|
|
wrappers.IntLen + // maxStakeDuration
|
|
wrappers.IntLen + // minDelegationFee
|
|
wrappers.IntLen + // minDelegatorStake
|
|
wrappers.IntLen + // maxValidatorWeightFactor
|
|
wrappers.IntLen + // uptimeRequirement
|
|
wrappers.IntLen + // netAuth typeID
|
|
wrappers.IntLen, // netAuthCredential typeID
|
|
gas.DBRead: 2, // get net auth + check for net transformation
|
|
gas.DBWrite: 1, // write net transformation
|
|
}
|
|
IntrinsicBaseTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: codec.VersionSize + // codecVersion
|
|
wrappers.IntLen + // typeID
|
|
wrappers.IntLen + // networkID
|
|
ids.IDLen + // blockchainID
|
|
wrappers.IntLen + // number of outputs
|
|
wrappers.IntLen + // number of inputs
|
|
wrappers.IntLen + // length of memo
|
|
wrappers.IntLen, // number of credentials
|
|
}
|
|
IntrinsicConvertNetworkToL1TxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
ids.IDLen + // subchainID
|
|
ids.IDLen + // chainID
|
|
wrappers.IntLen + // address length
|
|
wrappers.IntLen + // validators length
|
|
wrappers.IntLen + // chainAuth typeID
|
|
wrappers.IntLen, // 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] +
|
|
wrappers.LongLen + // balance
|
|
bls.SignatureLen + // proof of possession
|
|
wrappers.IntLen, // 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] +
|
|
wrappers.IntLen, // 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
|
|
wrappers.LongLen, // 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
|
|
wrappers.IntLen + // auth typeID
|
|
wrappers.IntLen, // 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
|
|
}
|
|
IntrinsicSlashValidatorTxComplexities = gas.Dimensions{
|
|
gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] +
|
|
ids.NodeIDLen + // nodeID
|
|
wrappers.LongLen + // evidence height
|
|
wrappers.ByteLen + // evidence type
|
|
wrappers.IntLen + // messageA length prefix
|
|
wrappers.IntLen + // messageB length prefix
|
|
2*bls.SignatureLen + // two BLS signatures
|
|
wrappers.IntLen, // slashPercentage
|
|
gas.DBRead: 1, // read validator
|
|
gas.DBWrite: 2, // delete + re-insert validator (or just delete if below minimum)
|
|
}
|
|
|
|
errUnsupportedOutput = errors.New("unsupported output type")
|
|
errUnsupportedInput = errors.New("unsupported input type")
|
|
errUnsupportedOwner = errors.New("unsupported owner type")
|
|
errUnsupportedAuth = errors.New("unsupported auth type")
|
|
errUnsupportedSigner = errors.New("unsupported signer type")
|
|
)
|
|
|
|
func TxComplexity(txs ...txs.UnsignedTx) (gas.Dimensions, error) {
|
|
var (
|
|
c complexityVisitor
|
|
complexity gas.Dimensions
|
|
)
|
|
for _, tx := range txs {
|
|
c = complexityVisitor{}
|
|
err := tx.Visit(&c)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
complexity, err = complexity.Add(&c.output)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
}
|
|
return complexity, nil
|
|
}
|
|
|
|
// OutputComplexity returns the complexity outputs add to a transaction.
|
|
func OutputComplexity(outs ...*lux.TransferableOutput) (gas.Dimensions, error) {
|
|
var complexity gas.Dimensions
|
|
for _, out := range outs {
|
|
outputComplexity, err := outputComplexity(out)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
complexity, err = complexity.Add(&outputComplexity)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
}
|
|
return complexity, nil
|
|
}
|
|
|
|
func outputComplexity(out *lux.TransferableOutput) (gas.Dimensions, error) {
|
|
complexity := gas.Dimensions{
|
|
gas.Bandwidth: intrinsicOutputBandwidth + intrinsicSECP256k1FxOutputBandwidth,
|
|
gas.DBWrite: intrinsicOutputDBWrite,
|
|
}
|
|
|
|
outIntf := out.Out
|
|
if stakeableOut, ok := outIntf.(*stakeable.LockOut); ok {
|
|
complexity[gas.Bandwidth] += intrinsicStakeableLockedOutputBandwidth
|
|
outIntf = stakeableOut.TransferableOut
|
|
}
|
|
|
|
secp256k1Out, ok := outIntf.(*secp256k1fx.TransferOutput)
|
|
if !ok {
|
|
return gas.Dimensions{}, errUnsupportedOutput
|
|
}
|
|
|
|
numAddresses := uint64(len(secp256k1Out.Addrs))
|
|
addressBandwidth, err := math.Mul(numAddresses, ids.ShortIDLen)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
complexity[gas.Bandwidth], err = math.Add(complexity[gas.Bandwidth], addressBandwidth)
|
|
return complexity, err
|
|
}
|
|
|
|
// InputComplexity returns the complexity inputs add to a transaction.
|
|
// It includes the complexity that the corresponding credentials will add.
|
|
func InputComplexity(ins ...*lux.TransferableInput) (gas.Dimensions, error) {
|
|
var complexity gas.Dimensions
|
|
for _, in := range ins {
|
|
inputComplexity, err := inputComplexity(in)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
complexity, err = complexity.Add(&inputComplexity)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
}
|
|
return complexity, nil
|
|
}
|
|
|
|
func inputComplexity(in *lux.TransferableInput) (gas.Dimensions, error) {
|
|
complexity := gas.Dimensions{
|
|
gas.Bandwidth: intrinsicInputBandwidth + intrinsicSECP256k1FxTransferableInputBandwidth,
|
|
gas.DBRead: intrinsicInputDBRead,
|
|
gas.DBWrite: intrinsicInputDBWrite,
|
|
}
|
|
|
|
inIntf := in.In
|
|
if stakeableIn, ok := inIntf.(*stakeable.LockIn); ok {
|
|
complexity[gas.Bandwidth] += intrinsicStakeableLockedInputBandwidth
|
|
inIntf = stakeableIn.TransferableIn
|
|
}
|
|
|
|
secp256k1In, ok := inIntf.(*secp256k1fx.TransferInput)
|
|
if !ok {
|
|
return gas.Dimensions{}, errUnsupportedInput
|
|
}
|
|
|
|
numSignatures := uint64(len(secp256k1In.SigIndices))
|
|
// Add signature bandwidth
|
|
signatureBandwidth, err := math.Mul(numSignatures, intrinsicSECP256k1FxSignatureBandwidth)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
complexity[gas.Bandwidth], err = math.Add(complexity[gas.Bandwidth], signatureBandwidth)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
// Add signature compute
|
|
complexity[gas.Compute], err = math.Mul(numSignatures, intrinsicSECP256k1FxSignatureCompute)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
return complexity, err
|
|
}
|
|
|
|
// ConvertNetworkToL1ValidatorComplexity returns the complexity the validators
|
|
// add to a transaction.
|
|
func ConvertNetworkToL1ValidatorComplexity(l1Validators ...*txs.ConvertNetworkToL1Validator) (gas.Dimensions, error) {
|
|
var complexity gas.Dimensions
|
|
for _, l1Validator := range l1Validators {
|
|
l1ValidatorComplexity, err := convertNetToL1ValidatorComplexity(l1Validator)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
complexity, err = complexity.Add(&l1ValidatorComplexity)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
}
|
|
return complexity, nil
|
|
}
|
|
|
|
func convertNetToL1ValidatorComplexity(l1Validator *txs.ConvertNetworkToL1Validator) (gas.Dimensions, error) {
|
|
complexity := gas.Dimensions{
|
|
gas.Bandwidth: intrinsicConvertNetworkToL1ValidatorBandwidth,
|
|
gas.DBWrite: intrinsicConvertNetworkToL1ValidatorDBWrite,
|
|
}
|
|
|
|
signerComplexity, err := SignerComplexity(&l1Validator.Signer)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
numAddresses := uint64(len(l1Validator.RemainingBalanceOwner.Addresses) + len(l1Validator.DeactivationOwner.Addresses))
|
|
addressBandwidth, err := math.Mul(numAddresses, ids.ShortIDLen)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
return complexity.Add(
|
|
&gas.Dimensions{
|
|
gas.Bandwidth: uint64(len(l1Validator.NodeID)),
|
|
},
|
|
&signerComplexity,
|
|
&gas.Dimensions{
|
|
gas.Bandwidth: addressBandwidth,
|
|
},
|
|
)
|
|
}
|
|
|
|
// OwnerComplexity returns the complexity an owner adds to a transaction.
|
|
// It does not include the typeID of the owner.
|
|
func OwnerComplexity(ownerIntf fx.Owner) (gas.Dimensions, error) {
|
|
owner, ok := ownerIntf.(*secp256k1fx.OutputOwners)
|
|
if !ok {
|
|
return gas.Dimensions{}, errUnsupportedOwner
|
|
}
|
|
|
|
numAddresses := uint64(len(owner.Addrs))
|
|
addressBandwidth, err := math.Mul(numAddresses, ids.ShortIDLen)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
bandwidth, err := math.Add(addressBandwidth, intrinsicSECP256k1FxOutputOwnersBandwidth)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
return gas.Dimensions{
|
|
gas.Bandwidth: bandwidth,
|
|
}, nil
|
|
}
|
|
|
|
// AuthComplexity returns the complexity an authorization adds to a transaction.
|
|
// It does not include the typeID of the authorization.
|
|
// It does includes the complexity that the corresponding credential will add.
|
|
// It does not include the typeID of the credential.
|
|
func AuthComplexity(authIntf verify.Verifiable) (gas.Dimensions, error) {
|
|
auth, ok := authIntf.(*secp256k1fx.Input)
|
|
if !ok {
|
|
return gas.Dimensions{}, errUnsupportedAuth
|
|
}
|
|
|
|
numSignatures := uint64(len(auth.SigIndices))
|
|
signatureBandwidth, err := math.Mul(numSignatures, intrinsicSECP256k1FxSignatureBandwidth)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
bandwidth, err := math.Add(signatureBandwidth, intrinsicSECP256k1FxInputBandwidth)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
signatureCompute, err := math.Mul(numSignatures, intrinsicSECP256k1FxSignatureCompute)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
return gas.Dimensions{
|
|
gas.Bandwidth: bandwidth,
|
|
gas.Compute: signatureCompute,
|
|
}, nil
|
|
}
|
|
|
|
// SignerComplexity returns the complexity a signer adds to a transaction.
|
|
// It does not include the typeID of the signer.
|
|
func SignerComplexity(s signer.Signer) (gas.Dimensions, error) {
|
|
switch s.(type) {
|
|
case *signer.Empty:
|
|
return gas.Dimensions{}, nil
|
|
case *signer.ProofOfPossession:
|
|
return gas.Dimensions{
|
|
gas.Bandwidth: intrinsicPoPBandwidth,
|
|
gas.Compute: intrinsicBLSPoPVerifyCompute,
|
|
}, nil
|
|
default:
|
|
return gas.Dimensions{}, errUnsupportedSigner
|
|
}
|
|
}
|
|
|
|
// WarpComplexity returns the complexity a warp message adds to a transaction.
|
|
func WarpComplexity(message []byte) (gas.Dimensions, error) {
|
|
msg, err := warp.ParseMessage(message)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
numSigners, err := msg.Signature.NumSigners()
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
aggregationCompute, err := math.Mul(uint64(numSigners), intrinsicBLSAggregateCompute)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
compute, err := math.Add(aggregationCompute, intrinsicBLSVerifyCompute)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
|
|
return gas.Dimensions{
|
|
gas.Bandwidth: uint64(len(message)),
|
|
gas.DBRead: intrinsicWarpDBReads,
|
|
gas.Compute: compute,
|
|
}, nil
|
|
}
|
|
|
|
type complexityVisitor struct {
|
|
output gas.Dimensions
|
|
}
|
|
|
|
func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error {
|
|
return ErrUnsupportedTx
|
|
}
|
|
|
|
func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error {
|
|
return ErrUnsupportedTx
|
|
}
|
|
|
|
func (*complexityVisitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error {
|
|
return ErrUnsupportedTx
|
|
}
|
|
|
|
func (*complexityVisitor) RewardValidatorTx(*txs.RewardValidatorTx) error {
|
|
return ErrUnsupportedTx
|
|
}
|
|
|
|
// Removed in regenesis
|
|
// func (*complexityVisitor) TransformChainTx(*txs.TransformChainTx) error {
|
|
// return ErrUnsupportedTx
|
|
// }
|
|
|
|
// Removed in regenesis
|
|
// func (c *complexityVisitor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) error {
|
|
// baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// c.output, err = IntrinsicAddChainValidatorTxComplexities.Add(
|
|
// &baseTxComplexity,
|
|
// &authComplexity,
|
|
// )
|
|
// return err
|
|
// }
|
|
|
|
func (c *complexityVisitor) CreateChainTx(tx *txs.CreateChainTx) error {
|
|
bandwidth, err := math.Mul(uint64(len(tx.FxIDs)), ids.IDLen)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bandwidth, err = math.Add(bandwidth, uint64(len(tx.BlockchainName)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bandwidth, err = math.Add(bandwidth, uint64(len(tx.GenesisData)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dynamicComplexity := gas.Dimensions{
|
|
gas.Bandwidth: bandwidth,
|
|
}
|
|
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicCreateChainTxComplexities.Add(
|
|
&dynamicComplexity,
|
|
&baseTxComplexity,
|
|
&authComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// Removed in regenesis
|
|
// func (c *complexityVisitor) CreateNetTx(tx *txs.CreateNetTx) error {
|
|
// baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// ownerComplexity, err := OwnerComplexity(tx.Owner)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// c.output, err = IntrinsicCreateNetTxComplexities.Add(
|
|
// &baseTxComplexity,
|
|
// &ownerComplexity,
|
|
// )
|
|
// return err
|
|
// }
|
|
|
|
func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inputsComplexity, err := InputComplexity(tx.ImportedInputs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicImportTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&inputsComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outputsComplexity, err := OutputComplexity(tx.ExportedOutputs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicExportTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&outputsComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// Removed in regenesis
|
|
// func (c *complexityVisitor) RemoveChainValidatorTx(tx *txs.RemoveChainValidatorTx) error {
|
|
// baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// c.output, err = IntrinsicRemoveChainValidatorTxComplexities.Add(
|
|
// &baseTxComplexity,
|
|
// &authComplexity,
|
|
// )
|
|
// return err
|
|
// }
|
|
|
|
func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signerComplexity, err := SignerComplexity(tx.Signer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outputsComplexity, err := OutputComplexity(tx.StakeOuts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&signerComplexity,
|
|
&outputsComplexity,
|
|
&validatorOwnerComplexity,
|
|
&delegatorOwnerComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outputsComplexity, err := OutputComplexity(tx.StakeOuts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&ownerComplexity,
|
|
&outputsComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// Removed in regenesis
|
|
// func (c *complexityVisitor) TransferChainOwnershipTx(tx *txs.TransferChainOwnershipTx) error {
|
|
// baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// ownerComplexity, err := OwnerComplexity(tx.Owner)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// c.output, err = IntrinsicTransferChainOwnershipTxComplexities.Add(
|
|
// &baseTxComplexity,
|
|
// &authComplexity,
|
|
// &ownerComplexity,
|
|
// )
|
|
// return err
|
|
// }
|
|
|
|
func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validatorComplexity, err := ConvertNetworkToL1ValidatorComplexity(tx.Validators...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicConvertNetworkToL1TxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&validatorComplexity,
|
|
&authComplexity,
|
|
&gas.Dimensions{
|
|
gas.Bandwidth: uint64(len(tx.Address)),
|
|
},
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
|
|
// Sovereign L1 = network + N validators + K chains. Fee complexity
|
|
// is approximately the sum: baseTx + ConvertNetworkToL1 (validators)
|
|
// + sum(CreateChain) (chains). Charge as composite.
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validatorComplexity, err := ConvertNetworkToL1ValidatorComplexity(tx.Validators...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = baseTxComplexity.Add(&validatorComplexity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Per-chain genesis adds to the bandwidth dimension.
|
|
for _, ch := range tx.Chains {
|
|
chainBytes := gas.Dimensions{
|
|
gas.Bandwidth: uint64(len(ch.GenesisData)) + uint64(len(ch.BlockchainName)),
|
|
}
|
|
c.output, err = c.output.Add(&chainBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *complexityVisitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
warpComplexity, err := WarpComplexity(tx.Message)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicRegisterL1ValidatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&warpComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
warpComplexity, err := WarpComplexity(tx.Message)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicSetL1ValidatorWeightTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&warpComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1ValidatorBalanceTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicIncreaseL1ValidatorBalanceTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authComplexity, err := AuthComplexity(tx.DisableAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicDisableL1ValidatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&authComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) SlashValidatorTx(tx *txs.SlashValidatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Add bandwidth for the variable-length evidence messages
|
|
evidenceBandwidth := uint64(len(tx.Evidence.MessageA) + len(tx.Evidence.MessageB))
|
|
c.output, err = IntrinsicSlashValidatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output[gas.Bandwidth], err = math.Add(c.output[gas.Bandwidth], evidenceBandwidth)
|
|
return err
|
|
}
|
|
|
|
func baseTxComplexity(tx *txs.BaseTx) (gas.Dimensions, error) {
|
|
outputsComplexity, err := OutputComplexity(tx.Outs...)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
inputsComplexity, err := InputComplexity(tx.Ins...)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
complexity, err := outputsComplexity.Add(&inputsComplexity)
|
|
if err != nil {
|
|
return gas.Dimensions{}, err
|
|
}
|
|
complexity[gas.Bandwidth], err = math.Add(
|
|
complexity[gas.Bandwidth],
|
|
uint64(len(tx.Memo)),
|
|
)
|
|
return complexity, err
|
|
}
|
|
|
|
func (c *complexityVisitor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicAddChainValidatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&authComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ownerComplexity, err := OwnerComplexity(tx.Owner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicCreateNetworkTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&ownerComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (c *complexityVisitor) RemoveChainValidatorTx(tx *txs.RemoveChainValidatorTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicRemoveChainValidatorTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&authComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (*complexityVisitor) TransformChainTx(*txs.TransformChainTx) error {
|
|
return ErrUnsupportedTx
|
|
}
|
|
|
|
func (c *complexityVisitor) TransferChainOwnershipTx(tx *txs.TransferChainOwnershipTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authComplexity, err := AuthComplexity(tx.ChainAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ownerComplexity, err := OwnerComplexity(tx.Owner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicTransferChainOwnershipTxComplexities.Add(
|
|
&baseTxComplexity,
|
|
&authComplexity,
|
|
&ownerComplexity,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// CreateAssetTx complexity is base + the initial-state output complexity
|
|
// (each InitialState carries TransferableOut-shaped outs that mint asset
|
|
// UTXOs). The fee charged is CreateAssetTxFee, applied by the fee calc.
|
|
func (c *complexityVisitor) CreateAssetTx(tx *txs.CreateAssetTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity)
|
|
return err
|
|
}
|
|
|
|
// OperationTx complexity is base + per-operation overhead. We bound the
|
|
// per-op cost via the BaseTx complexity envelope on P-Chain (no Fx-specific
|
|
// op state cost path).
|
|
func (c *complexityVisitor) OperationTx(tx *txs.OperationTx) error {
|
|
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity)
|
|
return err
|
|
}
|