Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chain/transaction] Introduce Multi-Action Support #698

Closed
wants to merge 16 commits into from
36 changes: 19 additions & 17 deletions chain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func BuildBlock(
log.Warn("invalid tx: invalid state keys")
return nil
}
result, err := tx.Execute(
txResults, err := tx.Execute(
ctx,
feeManager,
reads,
Expand All @@ -315,29 +315,31 @@ func BuildBlock(
defer blockLock.Unlock()

// Ensure block isn't too big
if ok, dimension := feeManager.Consume(result.Consumed, maxUnits); !ok {
log.Debug(
"skipping tx: too many units",
zap.Int("dimension", int(dimension)),
zap.Uint64("tx", result.Consumed[dimension]),
zap.Uint64("block units", feeManager.LastConsumed(dimension)),
zap.Uint64("max block units", maxUnits[dimension]),
)
restore = true
for _, result := range txResults {
if ok, dimension := feeManager.Consume(result.Consumed, maxUnits); !ok {
log.Debug(
"skipping tx: too many units",
zap.Int("dimension", int(dimension)),
zap.Uint64("tx", result.Consumed[dimension]),
zap.Uint64("block units", feeManager.LastConsumed(dimension)),
zap.Uint64("max block units", maxUnits[dimension]),
)
restore = true

// If we are above the target for the dimension we can't consume, we will
// stop building. This prevents a full mempool iteration looking for the
// "perfect fit".
if feeManager.LastConsumed(dimension) >= targetUnits[dimension] {
stop = true
return errBlockFull
// If we are above the target for the dimension we can't consume, we will
// stop building. This prevents a full mempool iteration looking for the
// "perfect fit".
if feeManager.LastConsumed(dimension) >= targetUnits[dimension] {
stop = true
return errBlockFull
}
}
}

// Update block with new transaction
tsv.Commit()
b.Txs = append(b.Txs, tx)
results = append(results, result)
results = append(results, txResults...)
return nil
})
}
Expand Down
9 changes: 8 additions & 1 deletion chain/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ type Rules interface {
GetStorageValueWriteUnits() uint64 // per chunk

FetchCustom(string) (any, bool)

GetMaxActionsPerTx() uint8
}

type MetadataManager interface {
Expand Down Expand Up @@ -217,6 +219,10 @@ type Object interface {
type Action interface {
Object

// GetActionID returns the ActionID for an [Action] in a [Transaction]. There may be
// multiple [Action]s, so we pass its index in the [Action] array along with the txID.
GetActionID(idx uint8, txID ids.ID) codec.Address

// MaxComputeUnits is the maximum amount of compute a given [Action] could use. This is
// used to determine whether the [Action] can be included in a given block and to compute
// the required fee to execute.
Expand All @@ -230,6 +236,7 @@ type Action interface {
// not be known until execution).
StateKeysMaxChunks() []uint16

// TODO: update comment
// StateKeys is a full enumeration of all database keys that could be touched during execution
// of an [Action]. This is used to prefetch state and will be used to parallelize execution (making
// an execution tree is trivial).
Expand All @@ -238,7 +245,7 @@ type Action interface {
// key (formatted as a big-endian uint16). This is used to automatically calculate storage usage.
//
// If any key is removed and then re-created, this will count as a creation instead of a modification.
StateKeys(actor codec.Address, txID ids.ID) state.Keys
StateKeys(actor codec.Address, actionID codec.Address) state.Keys

// Execute actually runs the [Action]. Any state changes that the [Action] performs should
// be done here.
Expand Down
21 changes: 11 additions & 10 deletions chain/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,11 @@ func (b *StatelessBlock) Execute(
f = fetcher.New(im, numTxs, b.vm.GetStateFetchConcurrency())
e = executor.New(numTxs, b.vm.GetTransactionExecutionCores(), MaxKeyDependencies, b.vm.GetExecutorVerifyRecorder())
ts = tstate.New(numTxs * 2) // TODO: tune this heuristic
results = make([]*Result, numTxs)
results = make([]*Result, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a 2d array with a length of numTxs instead? or flatten all the results into one array? not sure of the consequences of doing the flatten approach right now

)

// Fetch required keys and execute transactions
for li, ltx := range b.Txs {
i := li
for _, ltx := range b.Txs {
tx := ltx

stateKeys, err := tx.StateKeys(sm)
Expand Down Expand Up @@ -79,16 +78,18 @@ func (b *StatelessBlock) Execute(
return err
}

result, err := tx.Execute(ctx, feeManager, reads, sm, r, tsv, t)
txResults, err := tx.Execute(ctx, feeManager, reads, sm, r, tsv, t)
if err != nil {
return err
}
results[i] = result

// Update block metadata with units actually consumed (if more is consumed than block allows, we will non-deterministically
// exit with an error based on which tx over the limit is processed first)
if ok, d := feeManager.Consume(result.Consumed, r.GetMaxBlockUnits()); !ok {
return fmt.Errorf("%w: %d too large", ErrInvalidUnitsConsumed, d)
results = append(results, txResults...)

for _, result := range txResults {
// Update block metadata with units actually consumed (if more is consumed than block allows, we will non-deterministically
// exit with an error based on which tx over the limit is processed first)
if ok, d := feeManager.Consume(result.Consumed, r.GetMaxBlockUnits()); !ok {
return fmt.Errorf("%w: %d too large", ErrInvalidUnitsConsumed, d)
}
}

// Commit results to parent [TState]
Expand Down
Loading
Loading