github.com/MetalBlockchain/metalgo@v1.11.9/wallet/chain/p/builder/builder.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package builder 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "time" 11 12 "github.com/MetalBlockchain/metalgo/ids" 13 "github.com/MetalBlockchain/metalgo/utils" 14 "github.com/MetalBlockchain/metalgo/utils/constants" 15 "github.com/MetalBlockchain/metalgo/utils/math" 16 "github.com/MetalBlockchain/metalgo/utils/set" 17 "github.com/MetalBlockchain/metalgo/vms/components/avax" 18 "github.com/MetalBlockchain/metalgo/vms/platformvm/fx" 19 "github.com/MetalBlockchain/metalgo/vms/platformvm/signer" 20 "github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable" 21 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 22 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 23 "github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common" 24 ) 25 26 var ( 27 ErrNoChangeAddress = errors.New("no possible change address") 28 ErrUnknownOutputType = errors.New("unknown output type") 29 ErrUnknownOwnerType = errors.New("unknown owner type") 30 ErrInsufficientAuthorization = errors.New("insufficient authorization") 31 ErrInsufficientFunds = errors.New("insufficient funds") 32 33 _ Builder = (*builder)(nil) 34 ) 35 36 // Builder provides a convenient interface for building unsigned P-chain 37 // transactions. 38 type Builder interface { 39 // Context returns the configuration of the chain that this builder uses to 40 // create transactions. 41 Context() *Context 42 43 // GetBalance calculates the amount of each asset that this builder has 44 // control over. 45 GetBalance( 46 options ...common.Option, 47 ) (map[ids.ID]uint64, error) 48 49 // GetImportableBalance calculates the amount of each asset that this 50 // builder could import from the provided chain. 51 // 52 // - [chainID] specifies the chain the funds are from. 53 GetImportableBalance( 54 chainID ids.ID, 55 options ...common.Option, 56 ) (map[ids.ID]uint64, error) 57 58 // NewBaseTx creates a new simple value transfer. 59 // 60 // - [outputs] specifies all the recipients and amounts that should be sent 61 // from this transaction. 62 NewBaseTx( 63 outputs []*avax.TransferableOutput, 64 options ...common.Option, 65 ) (*txs.BaseTx, error) 66 67 // NewAddValidatorTx creates a new validator of the primary network. 68 // 69 // - [vdr] specifies all the details of the validation period such as the 70 // startTime, endTime, stake weight, and nodeID. 71 // - [rewardsOwner] specifies the owner of all the rewards this validator 72 // may accrue during its validation period. 73 // - [shares] specifies the fraction (out of 1,000,000) that this validator 74 // will take from delegation rewards. If 1,000,000 is provided, 100% of 75 // the delegation reward will be sent to the validator's [rewardsOwner]. 76 NewAddValidatorTx( 77 vdr *txs.Validator, 78 rewardsOwner *secp256k1fx.OutputOwners, 79 shares uint32, 80 options ...common.Option, 81 ) (*txs.AddValidatorTx, error) 82 83 // NewAddSubnetValidatorTx creates a new validator of a subnet. 84 // 85 // - [vdr] specifies all the details of the validation period such as the 86 // startTime, endTime, sampling weight, nodeID, and subnetID. 87 NewAddSubnetValidatorTx( 88 vdr *txs.SubnetValidator, 89 options ...common.Option, 90 ) (*txs.AddSubnetValidatorTx, error) 91 92 // NewRemoveSubnetValidatorTx removes [nodeID] from the validator 93 // set [subnetID]. 94 NewRemoveSubnetValidatorTx( 95 nodeID ids.NodeID, 96 subnetID ids.ID, 97 options ...common.Option, 98 ) (*txs.RemoveSubnetValidatorTx, error) 99 100 // NewAddDelegatorTx creates a new delegator to a validator on the primary 101 // network. 102 // 103 // - [vdr] specifies all the details of the delegation period such as the 104 // startTime, endTime, stake weight, and validator's nodeID. 105 // - [rewardsOwner] specifies the owner of all the rewards this delegator 106 // may accrue at the end of its delegation period. 107 NewAddDelegatorTx( 108 vdr *txs.Validator, 109 rewardsOwner *secp256k1fx.OutputOwners, 110 options ...common.Option, 111 ) (*txs.AddDelegatorTx, error) 112 113 // NewCreateChainTx creates a new chain in the named subnet. 114 // 115 // - [subnetID] specifies the subnet to launch the chain in. 116 // - [genesis] specifies the initial state of the new chain. 117 // - [vmID] specifies the vm that the new chain will run. 118 // - [fxIDs] specifies all the feature extensions that the vm should be 119 // running with. 120 // - [chainName] specifies a human readable name for the chain. 121 NewCreateChainTx( 122 subnetID ids.ID, 123 genesis []byte, 124 vmID ids.ID, 125 fxIDs []ids.ID, 126 chainName string, 127 options ...common.Option, 128 ) (*txs.CreateChainTx, error) 129 130 // NewCreateSubnetTx creates a new subnet with the specified owner. 131 // 132 // - [owner] specifies who has the ability to create new chains and add new 133 // validators to the subnet. 134 NewCreateSubnetTx( 135 owner *secp256k1fx.OutputOwners, 136 options ...common.Option, 137 ) (*txs.CreateSubnetTx, error) 138 139 // NewTransferSubnetOwnershipTx changes the owner of the named subnet. 140 // 141 // - [subnetID] specifies the subnet to be modified 142 // - [owner] specifies who has the ability to create new chains and add new 143 // validators to the subnet. 144 NewTransferSubnetOwnershipTx( 145 subnetID ids.ID, 146 owner *secp256k1fx.OutputOwners, 147 options ...common.Option, 148 ) (*txs.TransferSubnetOwnershipTx, error) 149 150 // NewImportTx creates an import transaction that attempts to consume all 151 // the available UTXOs and import the funds to [to]. 152 // 153 // - [chainID] specifies the chain to be importing funds from. 154 // - [to] specifies where to send the imported funds to. 155 NewImportTx( 156 chainID ids.ID, 157 to *secp256k1fx.OutputOwners, 158 options ...common.Option, 159 ) (*txs.ImportTx, error) 160 161 // NewExportTx creates an export transaction that attempts to send all the 162 // provided [outputs] to the requested [chainID]. 163 // 164 // - [chainID] specifies the chain to be exporting the funds to. 165 // - [outputs] specifies the outputs to send to the [chainID]. 166 NewExportTx( 167 chainID ids.ID, 168 outputs []*avax.TransferableOutput, 169 options ...common.Option, 170 ) (*txs.ExportTx, error) 171 172 // NewTransformSubnetTx creates a transform subnet transaction that attempts 173 // to convert the provided [subnetID] from a permissioned subnet to a 174 // permissionless subnet. This transaction will convert 175 // [maxSupply] - [initialSupply] of [assetID] to staking rewards. 176 // 177 // - [subnetID] specifies the subnet to transform. 178 // - [assetID] specifies the asset to use to reward stakers on the subnet. 179 // - [initialSupply] is the amount of [assetID] that will be in circulation 180 // after this transaction is accepted. 181 // - [maxSupply] is the maximum total amount of [assetID] that should ever 182 // exist. 183 // - [minConsumptionRate] is the rate that a staker will receive rewards 184 // if they stake with a duration of 0. 185 // - [maxConsumptionRate] is the maximum rate that staking rewards should be 186 // consumed from the reward pool per year. 187 // - [minValidatorStake] is the minimum amount of funds required to become a 188 // validator. 189 // - [maxValidatorStake] is the maximum amount of funds a single validator 190 // can be allocated, including delegated funds. 191 // - [minStakeDuration] is the minimum number of seconds a staker can stake 192 // for. 193 // - [maxStakeDuration] is the maximum number of seconds a staker can stake 194 // for. 195 // - [minValidatorStake] is the minimum amount of funds required to become a 196 // delegator. 197 // - [maxValidatorWeightFactor] is the factor which calculates the maximum 198 // amount of delegation a validator can receive. A value of 1 effectively 199 // disables delegation. 200 // - [uptimeRequirement] is the minimum percentage a validator must be 201 // online and responsive to receive a reward. 202 NewTransformSubnetTx( 203 subnetID ids.ID, 204 assetID ids.ID, 205 initialSupply uint64, 206 maxSupply uint64, 207 minConsumptionRate uint64, 208 maxConsumptionRate uint64, 209 minValidatorStake uint64, 210 maxValidatorStake uint64, 211 minStakeDuration time.Duration, 212 maxStakeDuration time.Duration, 213 minDelegationFee uint32, 214 minDelegatorStake uint64, 215 maxValidatorWeightFactor byte, 216 uptimeRequirement uint32, 217 options ...common.Option, 218 ) (*txs.TransformSubnetTx, error) 219 220 // NewAddPermissionlessValidatorTx creates a new validator of the specified 221 // subnet. 222 // 223 // - [vdr] specifies all the details of the validation period such as the 224 // subnetID, startTime, endTime, stake weight, and nodeID. 225 // - [signer] if the subnetID is the primary network, this is the BLS key 226 // for this validator. Otherwise, this value should be the empty signer. 227 // - [assetID] specifies the asset to stake. 228 // - [validationRewardsOwner] specifies the owner of all the rewards this 229 // validator earns for its validation period. 230 // - [delegationRewardsOwner] specifies the owner of all the rewards this 231 // validator earns for delegations during its validation period. 232 // - [shares] specifies the fraction (out of 1,000,000) that this validator 233 // will take from delegation rewards. If 1,000,000 is provided, 100% of 234 // the delegation reward will be sent to the validator's [rewardsOwner]. 235 NewAddPermissionlessValidatorTx( 236 vdr *txs.SubnetValidator, 237 signer signer.Signer, 238 assetID ids.ID, 239 validationRewardsOwner *secp256k1fx.OutputOwners, 240 delegationRewardsOwner *secp256k1fx.OutputOwners, 241 shares uint32, 242 options ...common.Option, 243 ) (*txs.AddPermissionlessValidatorTx, error) 244 245 // NewAddPermissionlessDelegatorTx creates a new delegator of the specified 246 // subnet on the specified nodeID. 247 // 248 // - [vdr] specifies all the details of the delegation period such as the 249 // subnetID, startTime, endTime, stake weight, and nodeID. 250 // - [assetID] specifies the asset to stake. 251 // - [rewardsOwner] specifies the owner of all the rewards this delegator 252 // earns during its delegation period. 253 NewAddPermissionlessDelegatorTx( 254 vdr *txs.SubnetValidator, 255 assetID ids.ID, 256 rewardsOwner *secp256k1fx.OutputOwners, 257 options ...common.Option, 258 ) (*txs.AddPermissionlessDelegatorTx, error) 259 } 260 261 type Backend interface { 262 UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) 263 GetSubnetOwner(ctx context.Context, subnetID ids.ID) (fx.Owner, error) 264 } 265 266 type builder struct { 267 addrs set.Set[ids.ShortID] 268 context *Context 269 backend Backend 270 } 271 272 // New returns a new transaction builder. 273 // 274 // - [addrs] is the set of addresses that the builder assumes can be used when 275 // signing the transactions in the future. 276 // - [context] provides the chain's configuration. 277 // - [backend] provides the chain's state. 278 func New( 279 addrs set.Set[ids.ShortID], 280 context *Context, 281 backend Backend, 282 ) Builder { 283 return &builder{ 284 addrs: addrs, 285 context: context, 286 backend: backend, 287 } 288 } 289 290 func (b *builder) Context() *Context { 291 return b.context 292 } 293 294 func (b *builder) GetBalance( 295 options ...common.Option, 296 ) (map[ids.ID]uint64, error) { 297 ops := common.NewOptions(options) 298 return b.getBalance(constants.PlatformChainID, ops) 299 } 300 301 func (b *builder) GetImportableBalance( 302 chainID ids.ID, 303 options ...common.Option, 304 ) (map[ids.ID]uint64, error) { 305 ops := common.NewOptions(options) 306 return b.getBalance(chainID, ops) 307 } 308 309 func (b *builder) NewBaseTx( 310 outputs []*avax.TransferableOutput, 311 options ...common.Option, 312 ) (*txs.BaseTx, error) { 313 toBurn := map[ids.ID]uint64{ 314 b.context.AVAXAssetID: b.context.BaseTxFee, 315 } 316 for _, out := range outputs { 317 assetID := out.AssetID() 318 amountToBurn, err := math.Add64(toBurn[assetID], out.Out.Amount()) 319 if err != nil { 320 return nil, err 321 } 322 toBurn[assetID] = amountToBurn 323 } 324 toStake := map[ids.ID]uint64{} 325 326 ops := common.NewOptions(options) 327 inputs, changeOutputs, _, err := b.spend(toBurn, toStake, ops) 328 if err != nil { 329 return nil, err 330 } 331 outputs = append(outputs, changeOutputs...) 332 avax.SortTransferableOutputs(outputs, txs.Codec) // sort the outputs 333 334 tx := &txs.BaseTx{BaseTx: avax.BaseTx{ 335 NetworkID: b.context.NetworkID, 336 BlockchainID: constants.PlatformChainID, 337 Ins: inputs, 338 Outs: outputs, 339 Memo: ops.Memo(), 340 }} 341 return tx, b.initCtx(tx) 342 } 343 344 func (b *builder) NewAddValidatorTx( 345 vdr *txs.Validator, 346 rewardsOwner *secp256k1fx.OutputOwners, 347 shares uint32, 348 options ...common.Option, 349 ) (*txs.AddValidatorTx, error) { 350 avaxAssetID := b.context.AVAXAssetID 351 toBurn := map[ids.ID]uint64{ 352 avaxAssetID: b.context.AddPrimaryNetworkValidatorFee, 353 } 354 toStake := map[ids.ID]uint64{ 355 avaxAssetID: vdr.Wght, 356 } 357 ops := common.NewOptions(options) 358 inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops) 359 if err != nil { 360 return nil, err 361 } 362 363 utils.Sort(rewardsOwner.Addrs) 364 tx := &txs.AddValidatorTx{ 365 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 366 NetworkID: b.context.NetworkID, 367 BlockchainID: constants.PlatformChainID, 368 Ins: inputs, 369 Outs: baseOutputs, 370 Memo: ops.Memo(), 371 }}, 372 Validator: *vdr, 373 StakeOuts: stakeOutputs, 374 RewardsOwner: rewardsOwner, 375 DelegationShares: shares, 376 } 377 return tx, b.initCtx(tx) 378 } 379 380 func (b *builder) NewAddSubnetValidatorTx( 381 vdr *txs.SubnetValidator, 382 options ...common.Option, 383 ) (*txs.AddSubnetValidatorTx, error) { 384 toBurn := map[ids.ID]uint64{ 385 b.context.AVAXAssetID: b.context.AddSubnetValidatorFee, 386 } 387 toStake := map[ids.ID]uint64{} 388 ops := common.NewOptions(options) 389 inputs, outputs, _, err := b.spend(toBurn, toStake, ops) 390 if err != nil { 391 return nil, err 392 } 393 394 subnetAuth, err := b.authorizeSubnet(vdr.Subnet, ops) 395 if err != nil { 396 return nil, err 397 } 398 399 tx := &txs.AddSubnetValidatorTx{ 400 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 401 NetworkID: b.context.NetworkID, 402 BlockchainID: constants.PlatformChainID, 403 Ins: inputs, 404 Outs: outputs, 405 Memo: ops.Memo(), 406 }}, 407 SubnetValidator: *vdr, 408 SubnetAuth: subnetAuth, 409 } 410 return tx, b.initCtx(tx) 411 } 412 413 func (b *builder) NewRemoveSubnetValidatorTx( 414 nodeID ids.NodeID, 415 subnetID ids.ID, 416 options ...common.Option, 417 ) (*txs.RemoveSubnetValidatorTx, error) { 418 toBurn := map[ids.ID]uint64{ 419 b.context.AVAXAssetID: b.context.BaseTxFee, 420 } 421 toStake := map[ids.ID]uint64{} 422 ops := common.NewOptions(options) 423 inputs, outputs, _, err := b.spend(toBurn, toStake, ops) 424 if err != nil { 425 return nil, err 426 } 427 428 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 429 if err != nil { 430 return nil, err 431 } 432 433 tx := &txs.RemoveSubnetValidatorTx{ 434 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 435 NetworkID: b.context.NetworkID, 436 BlockchainID: constants.PlatformChainID, 437 Ins: inputs, 438 Outs: outputs, 439 Memo: ops.Memo(), 440 }}, 441 Subnet: subnetID, 442 NodeID: nodeID, 443 SubnetAuth: subnetAuth, 444 } 445 return tx, b.initCtx(tx) 446 } 447 448 func (b *builder) NewAddDelegatorTx( 449 vdr *txs.Validator, 450 rewardsOwner *secp256k1fx.OutputOwners, 451 options ...common.Option, 452 ) (*txs.AddDelegatorTx, error) { 453 avaxAssetID := b.context.AVAXAssetID 454 toBurn := map[ids.ID]uint64{ 455 avaxAssetID: b.context.AddPrimaryNetworkDelegatorFee, 456 } 457 toStake := map[ids.ID]uint64{ 458 avaxAssetID: vdr.Wght, 459 } 460 ops := common.NewOptions(options) 461 inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops) 462 if err != nil { 463 return nil, err 464 } 465 466 utils.Sort(rewardsOwner.Addrs) 467 tx := &txs.AddDelegatorTx{ 468 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 469 NetworkID: b.context.NetworkID, 470 BlockchainID: constants.PlatformChainID, 471 Ins: inputs, 472 Outs: baseOutputs, 473 Memo: ops.Memo(), 474 }}, 475 Validator: *vdr, 476 StakeOuts: stakeOutputs, 477 DelegationRewardsOwner: rewardsOwner, 478 } 479 return tx, b.initCtx(tx) 480 } 481 482 func (b *builder) NewCreateChainTx( 483 subnetID ids.ID, 484 genesis []byte, 485 vmID ids.ID, 486 fxIDs []ids.ID, 487 chainName string, 488 options ...common.Option, 489 ) (*txs.CreateChainTx, error) { 490 toBurn := map[ids.ID]uint64{ 491 b.context.AVAXAssetID: b.context.CreateBlockchainTxFee, 492 } 493 toStake := map[ids.ID]uint64{} 494 ops := common.NewOptions(options) 495 inputs, outputs, _, err := b.spend(toBurn, toStake, ops) 496 if err != nil { 497 return nil, err 498 } 499 500 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 501 if err != nil { 502 return nil, err 503 } 504 505 utils.Sort(fxIDs) 506 tx := &txs.CreateChainTx{ 507 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 508 NetworkID: b.context.NetworkID, 509 BlockchainID: constants.PlatformChainID, 510 Ins: inputs, 511 Outs: outputs, 512 Memo: ops.Memo(), 513 }}, 514 SubnetID: subnetID, 515 ChainName: chainName, 516 VMID: vmID, 517 FxIDs: fxIDs, 518 GenesisData: genesis, 519 SubnetAuth: subnetAuth, 520 } 521 return tx, b.initCtx(tx) 522 } 523 524 func (b *builder) NewCreateSubnetTx( 525 owner *secp256k1fx.OutputOwners, 526 options ...common.Option, 527 ) (*txs.CreateSubnetTx, error) { 528 toBurn := map[ids.ID]uint64{ 529 b.context.AVAXAssetID: b.context.CreateSubnetTxFee, 530 } 531 toStake := map[ids.ID]uint64{} 532 ops := common.NewOptions(options) 533 inputs, outputs, _, err := b.spend(toBurn, toStake, ops) 534 if err != nil { 535 return nil, err 536 } 537 538 utils.Sort(owner.Addrs) 539 tx := &txs.CreateSubnetTx{ 540 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 541 NetworkID: b.context.NetworkID, 542 BlockchainID: constants.PlatformChainID, 543 Ins: inputs, 544 Outs: outputs, 545 Memo: ops.Memo(), 546 }}, 547 Owner: owner, 548 } 549 return tx, b.initCtx(tx) 550 } 551 552 func (b *builder) NewTransferSubnetOwnershipTx( 553 subnetID ids.ID, 554 owner *secp256k1fx.OutputOwners, 555 options ...common.Option, 556 ) (*txs.TransferSubnetOwnershipTx, error) { 557 toBurn := map[ids.ID]uint64{ 558 b.context.AVAXAssetID: b.context.BaseTxFee, 559 } 560 toStake := map[ids.ID]uint64{} 561 ops := common.NewOptions(options) 562 inputs, outputs, _, err := b.spend(toBurn, toStake, ops) 563 if err != nil { 564 return nil, err 565 } 566 567 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 568 if err != nil { 569 return nil, err 570 } 571 572 utils.Sort(owner.Addrs) 573 tx := &txs.TransferSubnetOwnershipTx{ 574 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 575 NetworkID: b.context.NetworkID, 576 BlockchainID: constants.PlatformChainID, 577 Ins: inputs, 578 Outs: outputs, 579 Memo: ops.Memo(), 580 }}, 581 Subnet: subnetID, 582 Owner: owner, 583 SubnetAuth: subnetAuth, 584 } 585 return tx, b.initCtx(tx) 586 } 587 588 func (b *builder) NewImportTx( 589 sourceChainID ids.ID, 590 to *secp256k1fx.OutputOwners, 591 options ...common.Option, 592 ) (*txs.ImportTx, error) { 593 ops := common.NewOptions(options) 594 utxos, err := b.backend.UTXOs(ops.Context(), sourceChainID) 595 if err != nil { 596 return nil, err 597 } 598 599 var ( 600 addrs = ops.Addresses(b.addrs) 601 minIssuanceTime = ops.MinIssuanceTime() 602 avaxAssetID = b.context.AVAXAssetID 603 txFee = b.context.BaseTxFee 604 605 importedInputs = make([]*avax.TransferableInput, 0, len(utxos)) 606 importedAmounts = make(map[ids.ID]uint64) 607 ) 608 // Iterate over the unlocked UTXOs 609 for _, utxo := range utxos { 610 out, ok := utxo.Out.(*secp256k1fx.TransferOutput) 611 if !ok { 612 continue 613 } 614 615 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 616 if !ok { 617 // We couldn't spend this UTXO, so we skip to the next one 618 continue 619 } 620 621 importedInputs = append(importedInputs, &avax.TransferableInput{ 622 UTXOID: utxo.UTXOID, 623 Asset: utxo.Asset, 624 In: &secp256k1fx.TransferInput{ 625 Amt: out.Amt, 626 Input: secp256k1fx.Input{ 627 SigIndices: inputSigIndices, 628 }, 629 }, 630 }) 631 632 assetID := utxo.AssetID() 633 newImportedAmount, err := math.Add64(importedAmounts[assetID], out.Amt) 634 if err != nil { 635 return nil, err 636 } 637 importedAmounts[assetID] = newImportedAmount 638 } 639 utils.Sort(importedInputs) // sort imported inputs 640 641 if len(importedInputs) == 0 { 642 return nil, fmt.Errorf( 643 "%w: no UTXOs available to import", 644 ErrInsufficientFunds, 645 ) 646 } 647 648 var ( 649 inputs []*avax.TransferableInput 650 outputs = make([]*avax.TransferableOutput, 0, len(importedAmounts)) 651 importedAVAX = importedAmounts[avaxAssetID] 652 ) 653 if importedAVAX > txFee { 654 importedAmounts[avaxAssetID] -= txFee 655 } else { 656 if importedAVAX < txFee { // imported amount goes toward paying tx fee 657 toBurn := map[ids.ID]uint64{ 658 avaxAssetID: txFee - importedAVAX, 659 } 660 toStake := map[ids.ID]uint64{} 661 var err error 662 inputs, outputs, _, err = b.spend(toBurn, toStake, ops) 663 if err != nil { 664 return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) 665 } 666 } 667 delete(importedAmounts, avaxAssetID) 668 } 669 670 for assetID, amount := range importedAmounts { 671 outputs = append(outputs, &avax.TransferableOutput{ 672 Asset: avax.Asset{ID: assetID}, 673 Out: &secp256k1fx.TransferOutput{ 674 Amt: amount, 675 OutputOwners: *to, 676 }, 677 }) 678 } 679 680 avax.SortTransferableOutputs(outputs, txs.Codec) // sort imported outputs 681 tx := &txs.ImportTx{ 682 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 683 NetworkID: b.context.NetworkID, 684 BlockchainID: constants.PlatformChainID, 685 Ins: inputs, 686 Outs: outputs, 687 Memo: ops.Memo(), 688 }}, 689 SourceChain: sourceChainID, 690 ImportedInputs: importedInputs, 691 } 692 return tx, b.initCtx(tx) 693 } 694 695 func (b *builder) NewExportTx( 696 chainID ids.ID, 697 outputs []*avax.TransferableOutput, 698 options ...common.Option, 699 ) (*txs.ExportTx, error) { 700 toBurn := map[ids.ID]uint64{ 701 b.context.AVAXAssetID: b.context.BaseTxFee, 702 } 703 for _, out := range outputs { 704 assetID := out.AssetID() 705 amountToBurn, err := math.Add64(toBurn[assetID], out.Out.Amount()) 706 if err != nil { 707 return nil, err 708 } 709 toBurn[assetID] = amountToBurn 710 } 711 712 toStake := map[ids.ID]uint64{} 713 ops := common.NewOptions(options) 714 inputs, changeOutputs, _, err := b.spend(toBurn, toStake, ops) 715 if err != nil { 716 return nil, err 717 } 718 719 avax.SortTransferableOutputs(outputs, txs.Codec) // sort exported outputs 720 tx := &txs.ExportTx{ 721 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 722 NetworkID: b.context.NetworkID, 723 BlockchainID: constants.PlatformChainID, 724 Ins: inputs, 725 Outs: changeOutputs, 726 Memo: ops.Memo(), 727 }}, 728 DestinationChain: chainID, 729 ExportedOutputs: outputs, 730 } 731 return tx, b.initCtx(tx) 732 } 733 734 func (b *builder) NewTransformSubnetTx( 735 subnetID ids.ID, 736 assetID ids.ID, 737 initialSupply uint64, 738 maxSupply uint64, 739 minConsumptionRate uint64, 740 maxConsumptionRate uint64, 741 minValidatorStake uint64, 742 maxValidatorStake uint64, 743 minStakeDuration time.Duration, 744 maxStakeDuration time.Duration, 745 minDelegationFee uint32, 746 minDelegatorStake uint64, 747 maxValidatorWeightFactor byte, 748 uptimeRequirement uint32, 749 options ...common.Option, 750 ) (*txs.TransformSubnetTx, error) { 751 toBurn := map[ids.ID]uint64{ 752 b.context.AVAXAssetID: b.context.TransformSubnetTxFee, 753 assetID: maxSupply - initialSupply, 754 } 755 toStake := map[ids.ID]uint64{} 756 ops := common.NewOptions(options) 757 inputs, outputs, _, err := b.spend(toBurn, toStake, ops) 758 if err != nil { 759 return nil, err 760 } 761 762 subnetAuth, err := b.authorizeSubnet(subnetID, ops) 763 if err != nil { 764 return nil, err 765 } 766 767 tx := &txs.TransformSubnetTx{ 768 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 769 NetworkID: b.context.NetworkID, 770 BlockchainID: constants.PlatformChainID, 771 Ins: inputs, 772 Outs: outputs, 773 Memo: ops.Memo(), 774 }}, 775 Subnet: subnetID, 776 AssetID: assetID, 777 InitialSupply: initialSupply, 778 MaximumSupply: maxSupply, 779 MinConsumptionRate: minConsumptionRate, 780 MaxConsumptionRate: maxConsumptionRate, 781 MinValidatorStake: minValidatorStake, 782 MaxValidatorStake: maxValidatorStake, 783 MinStakeDuration: uint32(minStakeDuration / time.Second), 784 MaxStakeDuration: uint32(maxStakeDuration / time.Second), 785 MinDelegationFee: minDelegationFee, 786 MinDelegatorStake: minDelegatorStake, 787 MaxValidatorWeightFactor: maxValidatorWeightFactor, 788 UptimeRequirement: uptimeRequirement, 789 SubnetAuth: subnetAuth, 790 } 791 return tx, b.initCtx(tx) 792 } 793 794 func (b *builder) NewAddPermissionlessValidatorTx( 795 vdr *txs.SubnetValidator, 796 signer signer.Signer, 797 assetID ids.ID, 798 validationRewardsOwner *secp256k1fx.OutputOwners, 799 delegationRewardsOwner *secp256k1fx.OutputOwners, 800 shares uint32, 801 options ...common.Option, 802 ) (*txs.AddPermissionlessValidatorTx, error) { 803 avaxAssetID := b.context.AVAXAssetID 804 toBurn := map[ids.ID]uint64{} 805 if vdr.Subnet == constants.PrimaryNetworkID { 806 toBurn[avaxAssetID] = b.context.AddPrimaryNetworkValidatorFee 807 } else { 808 toBurn[avaxAssetID] = b.context.AddSubnetValidatorFee 809 } 810 toStake := map[ids.ID]uint64{ 811 assetID: vdr.Wght, 812 } 813 ops := common.NewOptions(options) 814 inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops) 815 if err != nil { 816 return nil, err 817 } 818 819 utils.Sort(validationRewardsOwner.Addrs) 820 utils.Sort(delegationRewardsOwner.Addrs) 821 tx := &txs.AddPermissionlessValidatorTx{ 822 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 823 NetworkID: b.context.NetworkID, 824 BlockchainID: constants.PlatformChainID, 825 Ins: inputs, 826 Outs: baseOutputs, 827 Memo: ops.Memo(), 828 }}, 829 Validator: vdr.Validator, 830 Subnet: vdr.Subnet, 831 Signer: signer, 832 StakeOuts: stakeOutputs, 833 ValidatorRewardsOwner: validationRewardsOwner, 834 DelegatorRewardsOwner: delegationRewardsOwner, 835 DelegationShares: shares, 836 } 837 return tx, b.initCtx(tx) 838 } 839 840 func (b *builder) NewAddPermissionlessDelegatorTx( 841 vdr *txs.SubnetValidator, 842 assetID ids.ID, 843 rewardsOwner *secp256k1fx.OutputOwners, 844 options ...common.Option, 845 ) (*txs.AddPermissionlessDelegatorTx, error) { 846 avaxAssetID := b.context.AVAXAssetID 847 toBurn := map[ids.ID]uint64{} 848 if vdr.Subnet == constants.PrimaryNetworkID { 849 toBurn[avaxAssetID] = b.context.AddPrimaryNetworkDelegatorFee 850 } else { 851 toBurn[avaxAssetID] = b.context.AddSubnetDelegatorFee 852 } 853 toStake := map[ids.ID]uint64{ 854 assetID: vdr.Wght, 855 } 856 ops := common.NewOptions(options) 857 inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops) 858 if err != nil { 859 return nil, err 860 } 861 862 utils.Sort(rewardsOwner.Addrs) 863 tx := &txs.AddPermissionlessDelegatorTx{ 864 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 865 NetworkID: b.context.NetworkID, 866 BlockchainID: constants.PlatformChainID, 867 Ins: inputs, 868 Outs: baseOutputs, 869 Memo: ops.Memo(), 870 }}, 871 Validator: vdr.Validator, 872 Subnet: vdr.Subnet, 873 StakeOuts: stakeOutputs, 874 DelegationRewardsOwner: rewardsOwner, 875 } 876 return tx, b.initCtx(tx) 877 } 878 879 func (b *builder) getBalance( 880 chainID ids.ID, 881 options *common.Options, 882 ) ( 883 balance map[ids.ID]uint64, 884 err error, 885 ) { 886 utxos, err := b.backend.UTXOs(options.Context(), chainID) 887 if err != nil { 888 return nil, err 889 } 890 891 addrs := options.Addresses(b.addrs) 892 minIssuanceTime := options.MinIssuanceTime() 893 balance = make(map[ids.ID]uint64) 894 895 // Iterate over the UTXOs 896 for _, utxo := range utxos { 897 outIntf := utxo.Out 898 if lockedOut, ok := outIntf.(*stakeable.LockOut); ok { 899 if !options.AllowStakeableLocked() && lockedOut.Locktime > minIssuanceTime { 900 // This output is currently locked, so this output can't be 901 // burned. 902 continue 903 } 904 outIntf = lockedOut.TransferableOut 905 } 906 907 out, ok := outIntf.(*secp256k1fx.TransferOutput) 908 if !ok { 909 return nil, ErrUnknownOutputType 910 } 911 912 _, ok = common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 913 if !ok { 914 // We couldn't spend this UTXO, so we skip to the next one 915 continue 916 } 917 918 assetID := utxo.AssetID() 919 balance[assetID], err = math.Add64(balance[assetID], out.Amt) 920 if err != nil { 921 return nil, err 922 } 923 } 924 return balance, nil 925 } 926 927 // spend takes in the requested burn amounts and the requested stake amounts. 928 // 929 // - [amountsToBurn] maps assetID to the amount of the asset to spend without 930 // producing an output. This is typically used for fees. However, it can 931 // also be used to consume some of an asset that will be produced in 932 // separate outputs, such as ExportedOutputs. Only unlocked UTXOs are able 933 // to be burned here. 934 // - [amountsToStake] maps assetID to the amount of the asset to spend and 935 // place into the staked outputs. First locked UTXOs are attempted to be 936 // used for these funds, and then unlocked UTXOs will be attempted to be 937 // used. There is no preferential ordering on the unlock times. 938 func (b *builder) spend( 939 amountsToBurn map[ids.ID]uint64, 940 amountsToStake map[ids.ID]uint64, 941 options *common.Options, 942 ) ( 943 inputs []*avax.TransferableInput, 944 changeOutputs []*avax.TransferableOutput, 945 stakeOutputs []*avax.TransferableOutput, 946 err error, 947 ) { 948 utxos, err := b.backend.UTXOs(options.Context(), constants.PlatformChainID) 949 if err != nil { 950 return nil, nil, nil, err 951 } 952 953 addrs := options.Addresses(b.addrs) 954 minIssuanceTime := options.MinIssuanceTime() 955 956 addr, ok := addrs.Peek() 957 if !ok { 958 return nil, nil, nil, ErrNoChangeAddress 959 } 960 changeOwner := options.ChangeOwner(&secp256k1fx.OutputOwners{ 961 Threshold: 1, 962 Addrs: []ids.ShortID{addr}, 963 }) 964 965 // Initialize the return values with empty slices to preserve backward 966 // compatibility of the json representation of transactions with no 967 // inputs or outputs. 968 inputs = make([]*avax.TransferableInput, 0) 969 changeOutputs = make([]*avax.TransferableOutput, 0) 970 stakeOutputs = make([]*avax.TransferableOutput, 0) 971 972 // Iterate over the locked UTXOs 973 for _, utxo := range utxos { 974 assetID := utxo.AssetID() 975 remainingAmountToStake := amountsToStake[assetID] 976 977 // If we have staked enough of the asset, then we have no need burn 978 // more. 979 if remainingAmountToStake == 0 { 980 continue 981 } 982 983 outIntf := utxo.Out 984 lockedOut, ok := outIntf.(*stakeable.LockOut) 985 if !ok { 986 // This output isn't locked, so it will be handled during the next 987 // iteration of the UTXO set 988 continue 989 } 990 if minIssuanceTime >= lockedOut.Locktime { 991 // This output isn't locked, so it will be handled during the next 992 // iteration of the UTXO set 993 continue 994 } 995 996 out, ok := lockedOut.TransferableOut.(*secp256k1fx.TransferOutput) 997 if !ok { 998 return nil, nil, nil, ErrUnknownOutputType 999 } 1000 1001 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 1002 if !ok { 1003 // We couldn't spend this UTXO, so we skip to the next one 1004 continue 1005 } 1006 1007 inputs = append(inputs, &avax.TransferableInput{ 1008 UTXOID: utxo.UTXOID, 1009 Asset: utxo.Asset, 1010 In: &stakeable.LockIn{ 1011 Locktime: lockedOut.Locktime, 1012 TransferableIn: &secp256k1fx.TransferInput{ 1013 Amt: out.Amt, 1014 Input: secp256k1fx.Input{ 1015 SigIndices: inputSigIndices, 1016 }, 1017 }, 1018 }, 1019 }) 1020 1021 // Stake any value that should be staked 1022 amountToStake := min( 1023 remainingAmountToStake, // Amount we still need to stake 1024 out.Amt, // Amount available to stake 1025 ) 1026 1027 // Add the output to the staked outputs 1028 stakeOutputs = append(stakeOutputs, &avax.TransferableOutput{ 1029 Asset: utxo.Asset, 1030 Out: &stakeable.LockOut{ 1031 Locktime: lockedOut.Locktime, 1032 TransferableOut: &secp256k1fx.TransferOutput{ 1033 Amt: amountToStake, 1034 OutputOwners: out.OutputOwners, 1035 }, 1036 }, 1037 }) 1038 1039 amountsToStake[assetID] -= amountToStake 1040 if remainingAmount := out.Amt - amountToStake; remainingAmount > 0 { 1041 // This input had extra value, so some of it must be returned 1042 changeOutputs = append(changeOutputs, &avax.TransferableOutput{ 1043 Asset: utxo.Asset, 1044 Out: &stakeable.LockOut{ 1045 Locktime: lockedOut.Locktime, 1046 TransferableOut: &secp256k1fx.TransferOutput{ 1047 Amt: remainingAmount, 1048 OutputOwners: out.OutputOwners, 1049 }, 1050 }, 1051 }) 1052 } 1053 } 1054 1055 // Iterate over the unlocked UTXOs 1056 for _, utxo := range utxos { 1057 assetID := utxo.AssetID() 1058 remainingAmountToStake := amountsToStake[assetID] 1059 remainingAmountToBurn := amountsToBurn[assetID] 1060 1061 // If we have consumed enough of the asset, then we have no need burn 1062 // more. 1063 if remainingAmountToStake == 0 && remainingAmountToBurn == 0 { 1064 continue 1065 } 1066 1067 outIntf := utxo.Out 1068 if lockedOut, ok := outIntf.(*stakeable.LockOut); ok { 1069 if lockedOut.Locktime > minIssuanceTime { 1070 // This output is currently locked, so this output can't be 1071 // burned. 1072 continue 1073 } 1074 outIntf = lockedOut.TransferableOut 1075 } 1076 1077 out, ok := outIntf.(*secp256k1fx.TransferOutput) 1078 if !ok { 1079 return nil, nil, nil, ErrUnknownOutputType 1080 } 1081 1082 inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime) 1083 if !ok { 1084 // We couldn't spend this UTXO, so we skip to the next one 1085 continue 1086 } 1087 1088 inputs = append(inputs, &avax.TransferableInput{ 1089 UTXOID: utxo.UTXOID, 1090 Asset: utxo.Asset, 1091 In: &secp256k1fx.TransferInput{ 1092 Amt: out.Amt, 1093 Input: secp256k1fx.Input{ 1094 SigIndices: inputSigIndices, 1095 }, 1096 }, 1097 }) 1098 1099 // Burn any value that should be burned 1100 amountToBurn := min( 1101 remainingAmountToBurn, // Amount we still need to burn 1102 out.Amt, // Amount available to burn 1103 ) 1104 amountsToBurn[assetID] -= amountToBurn 1105 1106 amountAvalibleToStake := out.Amt - amountToBurn 1107 // Burn any value that should be burned 1108 amountToStake := min( 1109 remainingAmountToStake, // Amount we still need to stake 1110 amountAvalibleToStake, // Amount available to stake 1111 ) 1112 amountsToStake[assetID] -= amountToStake 1113 if amountToStake > 0 { 1114 // Some of this input was put for staking 1115 stakeOutputs = append(stakeOutputs, &avax.TransferableOutput{ 1116 Asset: utxo.Asset, 1117 Out: &secp256k1fx.TransferOutput{ 1118 Amt: amountToStake, 1119 OutputOwners: *changeOwner, 1120 }, 1121 }) 1122 } 1123 if remainingAmount := amountAvalibleToStake - amountToStake; remainingAmount > 0 { 1124 // This input had extra value, so some of it must be returned 1125 changeOutputs = append(changeOutputs, &avax.TransferableOutput{ 1126 Asset: utxo.Asset, 1127 Out: &secp256k1fx.TransferOutput{ 1128 Amt: remainingAmount, 1129 OutputOwners: *changeOwner, 1130 }, 1131 }) 1132 } 1133 } 1134 1135 for assetID, amount := range amountsToStake { 1136 if amount != 0 { 1137 return nil, nil, nil, fmt.Errorf( 1138 "%w: provided UTXOs need %d more units of asset %q to stake", 1139 ErrInsufficientFunds, 1140 amount, 1141 assetID, 1142 ) 1143 } 1144 } 1145 for assetID, amount := range amountsToBurn { 1146 if amount != 0 { 1147 return nil, nil, nil, fmt.Errorf( 1148 "%w: provided UTXOs need %d more units of asset %q", 1149 ErrInsufficientFunds, 1150 amount, 1151 assetID, 1152 ) 1153 } 1154 } 1155 1156 utils.Sort(inputs) // sort inputs 1157 avax.SortTransferableOutputs(changeOutputs, txs.Codec) // sort the change outputs 1158 avax.SortTransferableOutputs(stakeOutputs, txs.Codec) // sort stake outputs 1159 return inputs, changeOutputs, stakeOutputs, nil 1160 } 1161 1162 func (b *builder) authorizeSubnet(subnetID ids.ID, options *common.Options) (*secp256k1fx.Input, error) { 1163 ownerIntf, err := b.backend.GetSubnetOwner(options.Context(), subnetID) 1164 if err != nil { 1165 return nil, fmt.Errorf( 1166 "failed to fetch subnet owner for %q: %w", 1167 subnetID, 1168 err, 1169 ) 1170 } 1171 owner, ok := ownerIntf.(*secp256k1fx.OutputOwners) 1172 if !ok { 1173 return nil, ErrUnknownOwnerType 1174 } 1175 1176 addrs := options.Addresses(b.addrs) 1177 minIssuanceTime := options.MinIssuanceTime() 1178 inputSigIndices, ok := common.MatchOwners(owner, addrs, minIssuanceTime) 1179 if !ok { 1180 // We can't authorize the subnet 1181 return nil, ErrInsufficientAuthorization 1182 } 1183 return &secp256k1fx.Input{ 1184 SigIndices: inputSigIndices, 1185 }, nil 1186 } 1187 1188 func (b *builder) initCtx(tx txs.UnsignedTx) error { 1189 ctx, err := NewSnowContext(b.context.NetworkID, b.context.AVAXAssetID) 1190 if err != nil { 1191 return err 1192 } 1193 1194 tx.InitCtx(ctx) 1195 return nil 1196 }