ceremonyclient/node/execution/fees/matcher.go
2025-09-30 02:46:49 -05:00

159 lines
4.4 KiB
Go

package fees
import (
"bytes"
"math/big"
"github.com/pkg/errors"
"source.quilibrium.com/quilibrium/monorepo/protobufs"
)
// Policy controls which ops PRODUCE fees and which ops CONSUME a single fee.
type Policy struct {
// Domain whose tx/mint/pending PRODUCE fee outputs (flattened FIFO queue).
// Typically token.QUIL_TOKEN_ADDRESS. Alt fee markets configure this
// differently.
ProducerDomain []byte
// Consumers (each consumes exactly one fee, FIFO).
ConsumeDeploy bool
ConsumeUpdate bool
ConsumeTx bool
ConsumePendingTx bool
ConsumeMintTx bool // MintTransaction (usually false; mints are free)
// Compute ops (consumption)
ConsumeComputeDeploy bool
ConsumeComputeUpdate bool
ConsumeCodeDeploy bool
ConsumeCodeExecute bool
ConsumeCodeFinalize bool
// Hypergraph ops (consumption)
ConsumeHypergraphDeploy bool
ConsumeHypergraphUpdate bool
ConsumeVertexAdd bool
ConsumeVertexRemove bool
ConsumeHyperedgeAdd bool
ConsumeHyperedgeRemove bool
}
// CollectBundleFees flattens fee outputs produced by ops in ProducerDomain.
// Operations that do not consume fees may still produce fee outputs for others.
func CollectBundleFees(
bundle *protobufs.MessageBundle,
pol *Policy,
) []*big.Int {
feeQueue := []*big.Int{}
push := func(raw [][]byte) {
for _, b := range raw {
if len(b) == 0 {
continue
}
feeQueue = append(feeQueue, new(big.Int).SetBytes(b))
}
}
for _, op := range bundle.Requests {
switch t := op.Request.(type) {
case *protobufs.MessageRequest_PendingTransaction:
if t.PendingTransaction != nil &&
bytes.Equal(t.PendingTransaction.Domain, pol.ProducerDomain) &&
len(t.PendingTransaction.Fees) > 0 {
push(t.PendingTransaction.Fees)
}
case *protobufs.MessageRequest_Transaction:
if t.Transaction != nil &&
bytes.Equal(t.Transaction.Domain, pol.ProducerDomain) &&
len(t.Transaction.Fees) > 0 {
push(t.Transaction.Fees)
}
case *protobufs.MessageRequest_MintTransaction:
if t.MintTransaction != nil &&
bytes.Equal(t.MintTransaction.Domain, pol.ProducerDomain) &&
len(t.MintTransaction.Fees) > 0 {
push(t.MintTransaction.Fees)
}
}
}
return feeQueue
}
// CountFeeConsumers counts how many ops in the bundle must consume a single fee
func CountFeeConsumers(bundle *protobufs.MessageBundle, pol *Policy) int {
n := 0
for _, op := range bundle.Requests {
if NeedsOneFee(op, pol) {
n++
}
}
return n
}
// SanityCheck ensures there are enough fee outputs to satisfy all consumers.
func SanityCheck(feeQueue []*big.Int, consumers int) error {
if len(feeQueue) < consumers {
return errors.Wrap(
errors.Wrapf(
errors.New("insufficient fees"),
"have %d fee outputs, need %d",
len(feeQueue), consumers,
),
"sanity check",
)
}
return nil
}
// NeedsOneFee says whether the given request consumes a fee under the policy.
func NeedsOneFee(req *protobufs.MessageRequest, pol *Policy) bool {
switch req.Request.(type) {
case *protobufs.MessageRequest_TokenDeploy:
return pol.ConsumeDeploy
case *protobufs.MessageRequest_TokenUpdate:
return pol.ConsumeUpdate
case *protobufs.MessageRequest_Transaction:
return pol.ConsumeTx
case *protobufs.MessageRequest_PendingTransaction:
return pol.ConsumePendingTx
case *protobufs.MessageRequest_MintTransaction:
return pol.ConsumeMintTx
case *protobufs.MessageRequest_ComputeDeploy:
return pol.ConsumeComputeDeploy
case *protobufs.MessageRequest_ComputeUpdate:
return pol.ConsumeComputeUpdate
case *protobufs.MessageRequest_CodeDeploy:
return pol.ConsumeCodeDeploy
case *protobufs.MessageRequest_CodeExecute:
return pol.ConsumeCodeExecute
case *protobufs.MessageRequest_CodeFinalize:
return pol.ConsumeCodeFinalize
case *protobufs.MessageRequest_HypergraphDeploy:
return pol.ConsumeHypergraphDeploy
case *protobufs.MessageRequest_HypergraphUpdate:
return pol.ConsumeHypergraphUpdate
case *protobufs.MessageRequest_VertexAdd:
return pol.ConsumeVertexAdd
case *protobufs.MessageRequest_VertexRemove:
return pol.ConsumeVertexRemove
case *protobufs.MessageRequest_HyperedgeAdd:
return pol.ConsumeHyperedgeAdd
case *protobufs.MessageRequest_HyperedgeRemove:
return pol.ConsumeHyperedgeRemove
default:
return false
}
}
// PopFee pops the next fee from the queue (caller should ensure availability).
func PopFee(queue *[]*big.Int) *big.Int {
if len(*queue) == 0 {
return big.NewInt(0)
}
f := (*queue)[0]
*queue = (*queue)[1:]
return f
}