mirror of
https://github.com/luxfi/proto.git
synced 2026-10-30 15:21:09 +00:00
proto required luxfi/node and luxfi/node required proto, with no replace directive on either side. The cycle was not load-bearing: most of it was proto reaching into the node for packages proto already owns — p/warp, p/txs, p/signer, p/stakeable, p/security, x/txs — which now name themselves. Two were real. The UTXO components moved here, where the chains that use them already live, and the node is the only other caller, so that dependency now runs one way. And p/config held the node's chain manager to queue a chain the P-Chain had accepted a CreateChainTx for; it calls one method, so it declares one method. A P-Chain that imports the node to describe QueueChainCreation is what made two modules require each other. Dropping the requirement dropped a registration with it: lux.ParseUTXO is a registry filled by an init, which the node used to link in on proto's behalf. The packages that call it now say so themselves. Co-authored-by: Hanzo Dev <[email protected]>
249 lines
6.9 KiB
Go
249 lines
6.9 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package lux
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"sort"
|
|
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/vm/components/verify"
|
|
"github.com/luxfi/runtime"
|
|
"github.com/luxfi/util"
|
|
)
|
|
|
|
var (
|
|
ErrNilTransferableOutput = errors.New("nil transferable output is not valid")
|
|
ErrNilTransferableFxOutput = errors.New("nil transferable feature extension output is not valid")
|
|
ErrOutputsNotSorted = errors.New("outputs not sorted")
|
|
|
|
ErrNilTransferableInput = errors.New("nil transferable input is not valid")
|
|
ErrNilTransferableFxInput = errors.New("nil transferable feature extension input is not valid")
|
|
ErrInputsNotSortedUnique = errors.New("inputs not sorted and unique")
|
|
|
|
_ verify.Verifiable = (*TransferableOutput)(nil)
|
|
_ verify.Verifiable = (*TransferableInput)(nil)
|
|
_ utils.Sortable[*TransferableInput] = (*TransferableInput)(nil)
|
|
)
|
|
|
|
// Amounter is a data structure that has an amount of something associated with it
|
|
type Amounter interface {
|
|
// Amount returns how much value this element represents of the asset in its
|
|
// transaction.
|
|
Amount() uint64
|
|
}
|
|
|
|
// Coster is a data structure that has a cost associated with it
|
|
type Coster interface {
|
|
// Cost returns how much this element costs to be included in its
|
|
// transaction.
|
|
Cost() (uint64, error)
|
|
}
|
|
|
|
// TransferableIn is the interface a feature extension must provide to transfer
|
|
// value between features extensions.
|
|
type TransferableIn interface {
|
|
verify.Verifiable
|
|
Amounter
|
|
Coster
|
|
}
|
|
|
|
// TransferableOut is the interface a feature extension must provide to transfer
|
|
// value between features extensions.
|
|
type TransferableOut interface {
|
|
verify.State
|
|
Amounter
|
|
InitRuntime(*runtime.Runtime)
|
|
}
|
|
|
|
type TransferableOutput struct {
|
|
Asset `serialize:"true"`
|
|
// FxID has serialize false because we don't want this to be encoded in bytes
|
|
FxID ids.ID `serialize:"false" json:"fxID"`
|
|
Out TransferableOut `serialize:"true" json:"output"`
|
|
}
|
|
|
|
func (out *TransferableOutput) InitRuntime(rt *runtime.Runtime) {
|
|
out.Out.InitRuntime(rt)
|
|
}
|
|
|
|
// Output returns the feature extension output that this Output is using.
|
|
func (out *TransferableOutput) Output() TransferableOut {
|
|
return out.Out
|
|
}
|
|
|
|
func (out *TransferableOutput) Verify() error {
|
|
switch {
|
|
case out == nil:
|
|
return ErrNilTransferableOutput
|
|
case out.Out == nil:
|
|
return ErrNilTransferableFxOutput
|
|
default:
|
|
return verify.All(&out.Asset, out.Out)
|
|
}
|
|
}
|
|
|
|
// wireBytesOrNil returns the native ZAP wire envelope of a TransferableOut
|
|
// when the inner fx primitive carries a Bytes() adapter (every production
|
|
// fx does). This is the single source of truth for canonical ordering —
|
|
// the same bytes that hit disk and the wire, no separate codec marshal.
|
|
func wireBytesOrNil(out TransferableOut) []byte {
|
|
if ws, ok := out.(interface{ Bytes() []byte }); ok {
|
|
return ws.Bytes()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type innerSortTransferableOutputs struct {
|
|
outs []*TransferableOutput
|
|
}
|
|
|
|
func (outs *innerSortTransferableOutputs) Less(i, j int) bool {
|
|
iOut := outs.outs[i]
|
|
jOut := outs.outs[j]
|
|
|
|
iAssetID := iOut.AssetID()
|
|
jAssetID := jOut.AssetID()
|
|
|
|
switch bytes.Compare(iAssetID[:], jAssetID[:]) {
|
|
case -1:
|
|
return true
|
|
case 1:
|
|
return false
|
|
}
|
|
|
|
return bytes.Compare(wireBytesOrNil(iOut.Out), wireBytesOrNil(jOut.Out)) == -1
|
|
}
|
|
|
|
func (outs *innerSortTransferableOutputs) Len() int {
|
|
return len(outs.outs)
|
|
}
|
|
|
|
func (outs *innerSortTransferableOutputs) Swap(i, j int) {
|
|
o := outs.outs
|
|
o[j], o[i] = o[i], o[j]
|
|
}
|
|
|
|
// SortTransferableOutputs sorts output objects by (AssetID, inner-output
|
|
// ZAP wire bytes). ZAP-native — no codec.Manager needed.
|
|
func SortTransferableOutputs(outs []*TransferableOutput) {
|
|
sort.Sort(&innerSortTransferableOutputs{outs: outs})
|
|
}
|
|
|
|
// IsSortedTransferableOutputs returns true if output objects are sorted
|
|
func IsSortedTransferableOutputs(outs []*TransferableOutput) bool {
|
|
return sort.IsSorted(&innerSortTransferableOutputs{outs: outs})
|
|
}
|
|
|
|
type TransferableInput struct {
|
|
UTXOID `serialize:"true"`
|
|
Asset `serialize:"true"`
|
|
// FxID has serialize false because we don't want this to be encoded in bytes
|
|
FxID ids.ID `serialize:"false" json:"fxID"`
|
|
In TransferableIn `serialize:"true" json:"input"`
|
|
}
|
|
|
|
// Input returns the feature extension input that this Input is using.
|
|
func (in *TransferableInput) Input() TransferableIn {
|
|
return in.In
|
|
}
|
|
|
|
func (in *TransferableInput) Verify() error {
|
|
switch {
|
|
case in == nil:
|
|
return ErrNilTransferableInput
|
|
case in.In == nil:
|
|
return ErrNilTransferableFxInput
|
|
default:
|
|
return verify.All(&in.UTXOID, &in.Asset, in.In)
|
|
}
|
|
}
|
|
|
|
func (in *TransferableInput) InitRuntime(rt *runtime.Runtime) {
|
|
if contextInput, ok := in.In.(interface{ InitRuntime(*runtime.Runtime) }); ok {
|
|
contextInput.InitRuntime(rt)
|
|
}
|
|
}
|
|
|
|
func (in *TransferableInput) Compare(other *TransferableInput) int {
|
|
return in.UTXOID.Compare(&other.UTXOID)
|
|
}
|
|
|
|
type innerSortTransferableInputsWithSigners struct {
|
|
ins []*TransferableInput
|
|
signers [][]*secp256k1.PrivateKey
|
|
}
|
|
|
|
func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool {
|
|
iID, iIndex := ins.ins[i].InputSource()
|
|
jID, jIndex := ins.ins[j].InputSource()
|
|
|
|
switch bytes.Compare(iID[:], jID[:]) {
|
|
case -1:
|
|
return true
|
|
case 0:
|
|
return iIndex < jIndex
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (ins *innerSortTransferableInputsWithSigners) Len() int {
|
|
return len(ins.ins)
|
|
}
|
|
|
|
func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
|
|
ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j]
|
|
ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
|
|
}
|
|
|
|
// SortTransferableInputsWithSigners sorts the inputs and signers based on the
|
|
// input's utxo ID
|
|
func SortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*secp256k1.PrivateKey) {
|
|
sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
|
|
}
|
|
|
|
// VerifyTx verifies that the inputs and outputs flowcheck, including a fee.
|
|
// Additionally, this verifies that the inputs and outputs are sorted.
|
|
func VerifyTx(
|
|
feeAmount uint64,
|
|
feeAssetID ids.ID,
|
|
allIns [][]*TransferableInput,
|
|
allOuts [][]*TransferableOutput,
|
|
) error {
|
|
fc := NewFlowChecker()
|
|
|
|
fc.Produce(feeAssetID, feeAmount) // The txFee must be burned
|
|
|
|
// Add all the outputs to the flow checker and make sure they are sorted
|
|
for _, outs := range allOuts {
|
|
for _, out := range outs {
|
|
if err := out.Verify(); err != nil {
|
|
return err
|
|
}
|
|
fc.Produce(out.AssetID(), out.Output().Amount())
|
|
}
|
|
if !IsSortedTransferableOutputs(outs) {
|
|
return ErrOutputsNotSorted
|
|
}
|
|
}
|
|
|
|
// Add all the inputs to the flow checker and make sure they are sorted
|
|
for _, ins := range allIns {
|
|
for _, in := range ins {
|
|
if err := in.Verify(); err != nil {
|
|
return err
|
|
}
|
|
fc.Consume(in.AssetID(), in.Input().Amount())
|
|
}
|
|
if !utils.IsSortedAndUnique(ins) {
|
|
return ErrInputsNotSortedUnique
|
|
}
|
|
}
|
|
|
|
return fc.Verify()
|
|
}
|