Skip to content

Commit 5ba0a9a

Browse files
committed
ResourcePopulator
1 parent 5ac3edc commit 5ba0a9a

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed

ledger/simulation/resources.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,3 +605,230 @@ func (p *resourcePolicy) AvailableBox(app basics.AppIndex, name string, operatio
605605
}
606606
return p.tracker.addBox(app, name, readSize, *p.initialBoxSurplusReadBudget, p.ep.Proto.BytesPerBoxReference)
607607
}
608+
609+
type TxnResources struct {
610+
// The static fields are resource arrays that were given in the transaciton group and thus cannot be removed
611+
// The assumption is that these are prefilled because of one of the following reaons:
612+
// - This transaction has already been signed
613+
// - One of the foreign arrays is accessed on-chain
614+
StaticAssets map[basics.AssetIndex]struct{}
615+
StaticApps map[basics.AppIndex]struct{}
616+
StaticAccounts map[basics.Address]struct{}
617+
StaticBoxes map[logic.BoxRef]struct{}
618+
619+
// The following fields are fields that are implicitly available to the transaction group from transaction fields
620+
AssetFromField basics.AssetIndex
621+
AppFromField basics.AppIndex
622+
AccountsFromFields map[basics.Address]struct{}
623+
624+
// These are the fields currently being populated, thus we can mutate them however we'd like
625+
Assets map[basics.AssetIndex]struct{}
626+
Apps map[basics.AppIndex]struct{}
627+
Boxes map[logic.BoxRef]struct{}
628+
Accounts map[basics.Address]struct{}
629+
630+
MaxTotalRefs int
631+
MaxAccounts int
632+
}
633+
634+
func (r *TxnResources) getTotalRefs() int {
635+
return len(r.Accounts) + len(r.Assets) + len(r.Apps) + len(r.Boxes) + len(r.StaticAccounts) + len(r.StaticAssets) + len(r.StaticApps) + len(r.StaticBoxes)
636+
}
637+
638+
// Methods for determining room for specific references
639+
640+
func (r *TxnResources) hasRoom() bool {
641+
return r.getTotalRefs() < r.MaxTotalRefs
642+
}
643+
644+
func (r *TxnResources) hasRoomForAccount() bool {
645+
return r.hasRoom() && (len(r.Accounts)+len(r.StaticAccounts)) < r.MaxAccounts
646+
}
647+
648+
func (r *TxnResources) hasRoomForCrossRef() bool {
649+
return r.hasRoomForAccount() && r.getTotalRefs() < r.MaxTotalRefs-1
650+
}
651+
652+
func (r *TxnResources) hasRoomForBoxWithApp() bool {
653+
return r.getTotalRefs() < r.MaxTotalRefs-1
654+
}
655+
656+
// Methods for determining if a resource is available
657+
658+
func (r *TxnResources) hasApp(app basics.AppIndex) bool {
659+
_, hasStatic := r.StaticApps[app]
660+
_, hasRef := r.Apps[app]
661+
return r.AppFromField == app || hasStatic || hasRef
662+
}
663+
664+
func (r *TxnResources) hasAsset(aid basics.AssetIndex) bool {
665+
_, hasStatic := r.StaticAssets[aid]
666+
_, hasRef := r.Assets[aid]
667+
return r.AssetFromField == aid || hasStatic || hasRef
668+
}
669+
670+
func (r *TxnResources) hasAccount(addr basics.Address) bool {
671+
_, hasStatic := r.StaticAccounts[addr]
672+
_, hasRef := r.Accounts[addr]
673+
_, hasField := r.AccountsFromFields[addr]
674+
675+
return hasField || hasStatic || hasRef
676+
}
677+
678+
func (r *TxnResources) addAccount(addr basics.Address) {
679+
r.Accounts[addr] = struct{}{}
680+
}
681+
682+
func (r *TxnResources) addAsset(aid basics.AssetIndex) {
683+
r.Assets[aid] = struct{}{}
684+
}
685+
686+
func (r *TxnResources) addApp(aid basics.AppIndex) {
687+
r.Apps[aid] = struct{}{}
688+
}
689+
690+
func (r *TxnResources) addBox(app basics.AppIndex, name string) {
691+
r.Boxes[logic.BoxRef{App: app, Name: name}] = struct{}{}
692+
}
693+
694+
type PopulatedArrays struct {
695+
Accounts []basics.Address
696+
Assets []basics.AssetIndex
697+
Apps []basics.AppIndex
698+
Boxes []logic.BoxRef
699+
}
700+
701+
func (r *TxnResources) getPopulatedArrays() PopulatedArrays {
702+
accounts := make([]basics.Address, 0, len(r.Accounts)+len(r.StaticAccounts))
703+
for account := range r.Accounts {
704+
accounts = append(accounts, account)
705+
}
706+
707+
assets := make([]basics.AssetIndex, 0, len(r.Assets)+len(r.StaticAssets))
708+
for asset := range r.Assets {
709+
assets = append(assets, asset)
710+
}
711+
712+
apps := make([]basics.AppIndex, 0, len(r.Apps)+len(r.StaticApps))
713+
for app := range r.Apps {
714+
apps = append(apps, app)
715+
}
716+
717+
boxes := make([]logic.BoxRef, 0, len(r.Boxes)+len(r.StaticBoxes))
718+
for box := range r.Boxes {
719+
boxes = append(boxes, box)
720+
}
721+
722+
return PopulatedArrays{
723+
Accounts: accounts,
724+
Assets: assets,
725+
Apps: apps,
726+
Boxes: boxes,
727+
}
728+
}
729+
730+
type ResourcePopulator struct {
731+
TxnResources []TxnResources
732+
}
733+
734+
func (p *ResourcePopulator) addTransaction(txn transactions.Transaction, groupIndex int) {
735+
p.TxnResources[groupIndex] = TxnResources{
736+
StaticAssets: make(map[basics.AssetIndex]struct{}),
737+
StaticApps: make(map[basics.AppIndex]struct{}),
738+
StaticAccounts: make(map[basics.Address]struct{}),
739+
StaticBoxes: make(map[logic.BoxRef]struct{}),
740+
AccountsFromFields: make(map[basics.Address]struct{}),
741+
Assets: make(map[basics.AssetIndex]struct{}),
742+
Apps: make(map[basics.AppIndex]struct{}),
743+
Accounts: make(map[basics.Address]struct{}),
744+
Boxes: make(map[logic.BoxRef]struct{}),
745+
// TODO: Get these values from the consensus params
746+
MaxTotalRefs: 8,
747+
MaxAccounts: 4,
748+
}
749+
750+
// The Sender and RekeyTo will always be implicitly available for every transaction type
751+
p.TxnResources[groupIndex].AccountsFromFields[txn.Sender] = struct{}{}
752+
p.TxnResources[groupIndex].StaticAccounts[txn.RekeyTo] = struct{}{}
753+
754+
if txn.Type == protocol.ApplicationCallTx {
755+
for _, asset := range txn.ForeignAssets {
756+
p.TxnResources[groupIndex].StaticAssets[asset] = struct{}{}
757+
}
758+
759+
for _, app := range txn.ForeignApps {
760+
p.TxnResources[groupIndex].StaticApps[app] = struct{}{}
761+
}
762+
763+
for _, account := range txn.Accounts {
764+
p.TxnResources[groupIndex].StaticAccounts[account] = struct{}{}
765+
}
766+
767+
for _, box := range txn.Boxes {
768+
ref := logic.BoxRef{App: txn.ForeignApps[box.Index], Name: string(box.Name)}
769+
p.TxnResources[groupIndex].StaticBoxes[ref] = struct{}{}
770+
}
771+
772+
p.TxnResources[groupIndex].AppFromField = txn.ApplicationID
773+
774+
return
775+
}
776+
777+
if txn.Type == protocol.AssetTransferTx {
778+
p.TxnResources[groupIndex].AssetFromField = txn.XferAsset
779+
780+
p.TxnResources[groupIndex].AccountsFromFields[txn.AssetReceiver] = struct{}{}
781+
p.TxnResources[groupIndex].AccountsFromFields[txn.AssetCloseTo] = struct{}{}
782+
p.TxnResources[groupIndex].AccountsFromFields[txn.AssetSender] = struct{}{}
783+
784+
return
785+
}
786+
787+
if txn.Type == protocol.PaymentTx {
788+
p.TxnResources[groupIndex].AccountsFromFields[txn.Receiver] = struct{}{}
789+
p.TxnResources[groupIndex].AccountsFromFields[txn.CloseRemainderTo] = struct{}{}
790+
791+
return
792+
}
793+
794+
if txn.Type == protocol.AssetConfigTx {
795+
p.TxnResources[groupIndex].AssetFromField = txn.ConfigAsset
796+
797+
return
798+
}
799+
800+
if txn.Type == protocol.AssetFreezeTx {
801+
p.TxnResources[groupIndex].AssetFromField = txn.FreezeAsset
802+
p.TxnResources[groupIndex].AccountsFromFields[txn.FreezeAccount] = struct{}{}
803+
804+
return
805+
}
806+
}
807+
808+
func (p *ResourcePopulator) populateResources(groupResourceTracker groupResourceTracker) {
809+
for i, tracker := range groupResourceTracker.localTxnResources {
810+
for asset := range tracker.Assets {
811+
p.TxnResources[i].addAsset(asset)
812+
}
813+
814+
for app := range tracker.Apps {
815+
p.TxnResources[i].addApp(app)
816+
}
817+
818+
for account := range tracker.Accounts {
819+
p.TxnResources[i].addAccount(account)
820+
}
821+
}
822+
}
823+
824+
func MakeResourcePopulator(txnGroup []transactions.SignedTxnWithAD) ResourcePopulator {
825+
populator := ResourcePopulator{
826+
TxnResources: make([]TxnResources, len(txnGroup)),
827+
}
828+
829+
for i, txn := range txnGroup {
830+
populator.addTransaction(txn.Txn, i)
831+
}
832+
833+
return populator
834+
}

ledger/simulation/resources_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,42 @@ func TestGlobalVsLocalResources(t *testing.T) {
296296
}, groupAssignment.localTxnResources[2].Apps)
297297
})
298298
}
299+
300+
func TestPopulatorWithLocalResources(t *testing.T) {
301+
partitiontest.PartitionTest(t)
302+
t.Parallel()
303+
304+
txns := make([]transactions.SignedTxnWithAD, 1)
305+
txns[0].Txn.Type = protocol.ApplicationCallTx
306+
307+
populator := MakeResourcePopulator(txns)
308+
309+
proto := config.Consensus[protocol.ConsensusFuture]
310+
groupTracker := makeGroupResourceTracker(txns, &proto)
311+
312+
// Note we don't need to test a box here since it will never be a local txn resource
313+
addr := basics.Address{1, 1, 1}
314+
app := basics.AppIndex(12345)
315+
asset := basics.AssetIndex(12345)
316+
317+
groupTracker.localTxnResources[0].Accounts = make(map[basics.Address]struct{})
318+
groupTracker.localTxnResources[0].Assets = make(map[basics.AssetIndex]struct{})
319+
groupTracker.localTxnResources[0].Apps = make(map[basics.AppIndex]struct{})
320+
321+
groupTracker.localTxnResources[0].Accounts[addr] = struct{}{}
322+
groupTracker.localTxnResources[0].Assets[asset] = struct{}{}
323+
groupTracker.localTxnResources[0].Apps[app] = struct{}{}
324+
325+
populator.populateResources(groupTracker)
326+
327+
require.Equal(
328+
t,
329+
PopulatedArrays{
330+
Assets: []basics.AssetIndex{asset},
331+
Apps: []basics.AppIndex{app},
332+
Accounts: []basics.Address{addr},
333+
Boxes: []logic.BoxRef{},
334+
},
335+
populator.TxnResources[0].getPopulatedArrays(),
336+
)
337+
}

0 commit comments

Comments
 (0)