Skip to content

Commit ada7311

Browse files
authored
Fix system transaction result indexing & event block/transaction indices (#907)
1 parent a82d326 commit ada7311

File tree

6 files changed

+547
-56
lines changed

6 files changed

+547
-56
lines changed

emulator/blockchain.go

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"encoding/hex"
3535
"errors"
3636
"fmt"
37-
"maps"
3837
"math"
3938
"strings"
4039
"sync"
@@ -756,9 +755,12 @@ func configureNewLedger(
756755
nil,
757756
nil,
758757
nil,
758+
nil, // systemTransactions (ordered slice)
759+
nil, // systemTransactionBodies
760+
nil, // systemTransactionResults
759761
genesisExecutionSnapshot,
760762
output.Events,
761-
nil,
763+
nil, // scheduledTransactionIDs
762764
)
763765
if err != nil {
764766
return nil, nil, err
@@ -1102,6 +1104,35 @@ func (b *Blockchain) getTransactionResult(txID flowgo.Identifier) (*accessmodel.
11021104
return &result, nil
11031105
}
11041106

1107+
func (b *Blockchain) getSystemTransactionResult(blockID flowgo.Identifier, txID flowgo.Identifier) (*accessmodel.TransactionResult, error) {
1108+
storedResult, err := b.storage.SystemTransactionResultByID(context.Background(), blockID, txID)
1109+
if err != nil {
1110+
if errors.Is(err, storage.ErrNotFound) {
1111+
return &accessmodel.TransactionResult{
1112+
Status: flowgo.TransactionStatusUnknown,
1113+
}, nil
1114+
}
1115+
return nil, err
1116+
}
1117+
1118+
statusCode := 0
1119+
if storedResult.ErrorCode > 0 {
1120+
statusCode = 1
1121+
}
1122+
result := accessmodel.TransactionResult{
1123+
Status: flowgo.TransactionStatusSealed,
1124+
StatusCode: uint(statusCode),
1125+
ErrorMessage: storedResult.ErrorMessage,
1126+
Events: storedResult.Events,
1127+
TransactionID: txID,
1128+
BlockHeight: storedResult.BlockHeight,
1129+
BlockID: storedResult.BlockID,
1130+
CollectionID: storedResult.CollectionID,
1131+
}
1132+
1133+
return &result, nil
1134+
}
1135+
11051136
// GetAccountByIndex returns the account for the given address.
11061137
func (b *Blockchain) GetAccountByIndex(index uint) (*flowgo.Account, error) {
11071138
generator := flowsdk.NewAddressGenerator(flowsdk.ChainID(b.vmCtx.Chain.ChainID()))
@@ -1425,24 +1456,28 @@ func (b *Blockchain) commitBlock() (*flowgo.Block, error) {
14251456
if len(collections) > 0 {
14261457
collectionID = collections[0].ID()
14271458
}
1459+
1460+
// Regular user transactions and results
14281461
transactions := b.pendingBlock.Transactions()
14291462
transactionResults, err := convertToSealedResults(b.pendingBlock.TransactionResults(), b.pendingBlock.ID(), b.pendingBlock.height, collectionID)
14301463
if err != nil {
14311464
return nil, err
14321465
}
14331466

1467+
// System transactions and results (stored separately, order preserved)
1468+
systemTransactionIDs := []flowgo.Identifier{} // Ordered list of system tx IDs
1469+
systemTransactionBodies := make(map[flowgo.Identifier]*flowgo.TransactionBody)
1470+
systemTransactionResults := make(map[flowgo.Identifier]*types.StorableTransactionResult)
1471+
scheduledTransactionIDs := make(map[uint64]flowgo.Identifier)
1472+
14341473
// execute scheduled transactions out-of-band before the system chunk
14351474
// (does not change collections or payload)
14361475
blockContext := fvm.NewContextFromParent(
14371476
b.vmCtx,
14381477
fvm.WithBlockHeader(block.ToHeader()),
14391478
)
1440-
systemTransactions := storage.SystemTransactions{
1441-
BlockID: b.pendingBlock.ID(),
1442-
Transactions: []flowgo.Identifier{},
1443-
}
14441479
if b.conf.ScheduledTransactionsEnabled {
1445-
tx, results, scheduledTxIDs, err := b.executeScheduledTransactions(blockContext)
1480+
tx, results, scheduledTxIDs, orderedIDs, err := b.executeScheduledTransactions(blockContext)
14461481
if err != nil {
14471482
return nil, err
14481483
}
@@ -1451,18 +1486,23 @@ func (b *Blockchain) commitBlock() (*flowgo.Block, error) {
14511486
if err != nil {
14521487
return nil, err
14531488
}
1454-
maps.Copy(transactionResults, convertedResults)
1489+
for id, result := range convertedResults {
1490+
systemTransactionResults[id] = result
1491+
}
14551492
for id, t := range tx {
1456-
transactions[id] = t
1457-
systemTransactions.Transactions = append(systemTransactions.Transactions, id)
1493+
systemTransactionBodies[id] = t
14581494
}
14591495

1496+
// Append scheduled tx IDs in execution order
1497+
systemTransactionIDs = append(systemTransactionIDs, orderedIDs...)
1498+
14601499
// Store scheduled transaction ID mappings
1461-
systemTransactions.ScheduledTransactionIDs = scheduledTxIDs
1500+
scheduledTransactionIDs = scheduledTxIDs
14621501
}
14631502

1464-
// lastly we execute the system chunk transaction
1465-
chunkBody, itr, err := b.executeSystemChunkTransaction()
1503+
// Calculate index for system chunk transaction which executes after all user and scheduled transactions
1504+
systemChunkIndex := uint32(len(transactions)) + uint32(len(systemTransactionIDs))
1505+
chunkBody, itr, err := b.executeSystemChunkTransaction(systemChunkIndex)
14661506
if err != nil {
14671507
return nil, err
14681508
}
@@ -1472,9 +1512,9 @@ func (b *Blockchain) commitBlock() (*flowgo.Block, error) {
14721512
if err != nil {
14731513
return nil, err
14741514
}
1475-
transactions[systemTxID] = chunkBody
1476-
transactionResults[systemTxID] = &systemTxStorableResult
1477-
systemTransactions.Transactions = append(systemTransactions.Transactions, systemTxID)
1515+
systemTransactionBodies[systemTxID] = chunkBody
1516+
systemTransactionResults[systemTxID] = &systemTxStorableResult
1517+
systemTransactionIDs = append(systemTransactionIDs, systemTxID)
14781518

14791519
executionSnapshot := b.pendingBlock.Finalize()
14801520
events := b.pendingBlock.Events()
@@ -1486,15 +1526,18 @@ func (b *Blockchain) commitBlock() (*flowgo.Block, error) {
14861526
collections,
14871527
transactions,
14881528
transactionResults,
1529+
systemTransactionIDs,
1530+
systemTransactionBodies,
1531+
systemTransactionResults,
14891532
executionSnapshot,
14901533
events,
1491-
&systemTransactions)
1534+
scheduledTransactionIDs)
14921535
if err != nil {
14931536
return nil, err
14941537
}
14951538

14961539
// Index scheduled transactions globally (scheduledTxID → blockID)
1497-
for scheduledTxID := range systemTransactions.ScheduledTransactionIDs {
1540+
for scheduledTxID := range scheduledTransactionIDs {
14981541
err = b.storage.IndexScheduledTransactionID(context.Background(), scheduledTxID, block.ID())
14991542
if err != nil {
15001543
return nil, fmt.Errorf("failed to index scheduled transaction %d: %w", scheduledTxID, err)
@@ -1866,9 +1909,9 @@ func (b *Blockchain) GetTransactionResultsByBlockID(blockID flowgo.Identifier) (
18661909
return nil, fmt.Errorf("failed to get system transactions %w", err)
18671910
}
18681911
for j, txID := range st.Transactions {
1869-
result, err := b.getTransactionResult(txID)
1912+
result, err := b.getSystemTransactionResult(blockID, txID)
18701913
if err != nil {
1871-
return nil, fmt.Errorf("failed to get transaction result [%d] %s: %w", j, txID, err)
1914+
return nil, fmt.Errorf("failed to get system transaction result [%d] %s: %w", j, txID, err)
18721915
}
18731916
results = append(results, result)
18741917
}
@@ -1952,8 +1995,8 @@ func (b *Blockchain) GetSystemTransactionResult(txID flowgo.Identifier, blockID
19521995
return nil, &types.TransactionNotFoundError{ID: txID}
19531996
}
19541997

1955-
// Retrieve the transaction result
1956-
return b.getTransactionResult(txID)
1998+
// Retrieve the system transaction result using composite key (blockID, txID)
1999+
return b.getSystemTransactionResult(blockID, txID)
19572000
}
19582001

19592002
// GetScheduledTransactionByBlockID returns a scheduled transaction by its scheduled transaction ID and block ID.
@@ -2036,8 +2079,8 @@ func (b *Blockchain) GetScheduledTransactionResultByBlockID(scheduledTxID uint64
20362079
return nil, &types.TransactionNotFoundError{ID: flowgo.ZeroID}
20372080
}
20382081

2039-
// Retrieve the transaction result
2040-
return b.getTransactionResult(txID)
2082+
// Retrieve the system transaction result using composite key
2083+
return b.getSystemTransactionResult(blockID, txID)
20412084
}
20422085

20432086
// GetScheduledTransactionResult returns the result of a scheduled transaction by its scheduled transaction ID.
@@ -2070,8 +2113,8 @@ func (b *Blockchain) GetScheduledTransactionResult(scheduledTxID uint64) (*acces
20702113
return nil, &types.TransactionNotFoundError{ID: flowgo.ZeroID}
20712114
}
20722115

2073-
// Retrieve the transaction result
2074-
return b.getTransactionResult(txID)
2116+
// Retrieve the system transaction result using composite key
2117+
return b.getSystemTransactionResult(blockID, txID)
20752118
}
20762119

20772120
func (b *Blockchain) GetLogs(identifier flowgo.Identifier) ([]string, error) {
@@ -2162,7 +2205,7 @@ func (b *Blockchain) systemChunkTransaction() (*flowgo.TransactionBody, error) {
21622205
return tx, nil
21632206
}
21642207

2165-
func (b *Blockchain) executeSystemChunkTransaction() (*flowgo.TransactionBody, *IndexedTransactionResult, error) {
2208+
func (b *Blockchain) executeSystemChunkTransaction(txIndex uint32) (*flowgo.TransactionBody, *IndexedTransactionResult, error) {
21662209
txn, err := b.systemChunkTransaction()
21672210
if err != nil {
21682211
return nil, nil, err
@@ -2178,7 +2221,7 @@ func (b *Blockchain) executeSystemChunkTransaction() (*flowgo.TransactionBody, *
21782221

21792222
executionSnapshot, output, err := b.vm.Run(
21802223
ctx,
2181-
fvm.Transaction(txn, uint32(len(b.pendingBlock.Transactions()))),
2224+
fvm.Transaction(txn, txIndex),
21822225
b.pendingBlock.ledgerState,
21832226
)
21842227
if err != nil {
@@ -2198,15 +2241,16 @@ func (b *Blockchain) executeSystemChunkTransaction() (*flowgo.TransactionBody, *
21982241

21992242
itr := &IndexedTransactionResult{
22002243
ProcedureOutput: output,
2201-
Index: 0,
2244+
Index: txIndex,
22022245
}
22032246
return txn, itr, nil
22042247
}
22052248

2206-
func (b *Blockchain) executeScheduledTransactions(blockContext fvm.Context) (map[flowgo.Identifier]*flowgo.TransactionBody, map[flowgo.Identifier]IndexedTransactionResult, map[uint64]flowgo.Identifier, error) {
2249+
func (b *Blockchain) executeScheduledTransactions(blockContext fvm.Context) (map[flowgo.Identifier]*flowgo.TransactionBody, map[flowgo.Identifier]IndexedTransactionResult, map[uint64]flowgo.Identifier, []flowgo.Identifier, error) {
22072250
systemTransactions := map[flowgo.Identifier]*flowgo.TransactionBody{}
22082251
systemTransactionResults := map[flowgo.Identifier]IndexedTransactionResult{}
22092252
scheduledTxIDMap := map[uint64]flowgo.Identifier{} // maps scheduled tx ID to transaction ID
2253+
orderedTxIDs := []flowgo.Identifier{} // execution order of system transactions
22102254

22112255
// disable checks for signatures and keys since we are executing a system transaction
22122256
ctx := fvm.NewContextFromParent(
@@ -2216,62 +2260,71 @@ func (b *Blockchain) executeScheduledTransactions(blockContext fvm.Context) (map
22162260
fvm.WithTransactionFeesEnabled(false),
22172261
)
22182262

2263+
// Base index for system transactions (equal to count of user transactions)
2264+
systemTxBaseIndex := uint32(len(b.pendingBlock.Transactions()))
2265+
22192266
// process scheduled transactions out-of-band (do not alter collections)
22202267
processTx, err := blueprints.ProcessCallbacksTransaction(b.GetChain())
22212268
if err != nil {
2222-
return nil, nil, nil, err
2269+
return nil, nil, nil, nil, err
22232270
}
22242271

22252272
// Use the real transaction ID from the process transaction
22262273
processID := processTx.ID()
22272274
systemTransactions[processID] = processTx
2275+
orderedTxIDs = append(orderedTxIDs, processID) // First in order
22282276

2277+
processTxIndex := systemTxBaseIndex
22292278
executionSnapshot, output, err := b.vm.Run(
22302279
ctx,
2231-
fvm.Transaction(processTx, uint32(len(b.pendingBlock.Transactions()))),
2280+
fvm.Transaction(processTx, processTxIndex),
22322281
b.pendingBlock.ledgerState,
22332282
)
22342283
if err != nil {
2235-
return nil, nil, nil, err
2284+
return nil, nil, nil, nil, err
22362285
}
22372286

22382287
systemTransactionResults[processID] = IndexedTransactionResult{
22392288
ProcedureOutput: output,
2240-
Index: 0,
2289+
Index: processTxIndex,
22412290
}
22422291

22432292
// record events and state changes
22442293
b.pendingBlock.events = append(b.pendingBlock.events, output.Events...)
22452294
if err := b.pendingBlock.ledgerState.Merge(executionSnapshot); err != nil {
2246-
return nil, nil, nil, err
2295+
return nil, nil, nil, nil, err
22472296
}
22482297

22492298
executeTxs, err := blueprints.ExecuteCallbacksTransactions(b.GetChain(), output.Events)
22502299
if err != nil {
2251-
return nil, nil, nil, err
2300+
return nil, nil, nil, nil, err
22522301
}
22532302

22542303
env := systemcontracts.SystemContractsForChain(b.GetChain().ChainID()).AsTemplateEnv()
22552304
scheduledIDs, err := parseScheduledIDs(env, output.Events)
22562305
if err != nil {
2257-
return nil, nil, nil, err
2306+
return nil, nil, nil, nil, err
22582307
}
22592308

22602309
// execute scheduled transactions out-of-band
22612310
for idx, tx := range executeTxs {
22622311
id := tx.ID()
2312+
orderedTxIDs = append(orderedTxIDs, id) // Add in execution order
2313+
2314+
// Each execute callback gets its own index after the process transaction
2315+
executeTxIndex := systemTxBaseIndex + 1 + uint32(idx)
22632316
execSnapshot, execOutput, err := b.vm.Run(
22642317
ctx,
2265-
fvm.Transaction(tx, uint32(len(b.pendingBlock.Transactions()))),
2318+
fvm.Transaction(tx, executeTxIndex),
22662319
b.pendingBlock.ledgerState,
22672320
)
22682321
if err != nil {
2269-
return nil, nil, nil, err
2322+
return nil, nil, nil, nil, err
22702323
}
22712324

22722325
systemTransactionResults[id] = IndexedTransactionResult{
22732326
ProcedureOutput: execOutput,
2274-
Index: 0,
2327+
Index: executeTxIndex,
22752328
}
22762329
systemTransactions[id] = tx
22772330

@@ -2288,7 +2341,7 @@ func (b *Blockchain) executeScheduledTransactions(blockContext fvm.Context) (map
22882341
// Print scheduled transaction result (labeled), including app-level scheduled tx id
22892342
schedResult, err := convert.VMTransactionResultToEmulator(tx.ID(), execOutput)
22902343
if err != nil {
2291-
return nil, nil, nil, err
2344+
return nil, nil, nil, nil, err
22922345
}
22932346
appScheduledID := ""
22942347
if idx < len(scheduledIDs) {
@@ -2298,11 +2351,11 @@ func (b *Blockchain) executeScheduledTransactions(blockContext fvm.Context) (map
22982351

22992352
b.pendingBlock.events = append(b.pendingBlock.events, execOutput.Events...)
23002353
if err := b.pendingBlock.ledgerState.Merge(execSnapshot); err != nil {
2301-
return nil, nil, nil, err
2354+
return nil, nil, nil, nil, err
23022355
}
23032356
}
23042357

2305-
return systemTransactions, systemTransactionResults, scheduledTxIDMap, nil
2358+
return systemTransactions, systemTransactionResults, scheduledTxIDMap, orderedTxIDs, nil
23062359
}
23072360

23082361
func (b *Blockchain) GetRegisterValues(registerIDs flowgo.RegisterIDs, height uint64) (values []flowgo.RegisterValue, err error) {

0 commit comments

Comments
 (0)