diff --git a/continuous/collector.go b/continuous/collector.go index 8867c4c..fcd97a5 100644 --- a/continuous/collector.go +++ b/continuous/collector.go @@ -457,23 +457,40 @@ func queryDecryptionKeysBySlot(blame *ValidatorBlame, cfg *Configuration) error return nil } +// buildInnerTxHashToTargetSlotMap creates a map from inner transaction hash to targeted slot +// using the in-memory transactions stored in cfg.status +func buildInnerTxHashToTargetSlotMap(cfg *Configuration) map[string]int64 { + cfg.status.statusModMutex.Lock() + defer cfg.status.statusModMutex.Unlock() + + innerTxHashToTargetSlot := make(map[string]int64) + + allTxs := append(cfg.status.txInFlight, cfg.status.txDone...) + for _, tx := range allTxs { + if tx.innerTx != nil && tx.targetSlot != 0 { + innerTxHash := tx.innerTx.Hash().Hex() + innerTxHashToTargetSlot[innerTxHash] = tx.targetSlot + } + } + return innerTxHashToTargetSlot +} + func queryStatusRatios(w *bufio.Writer, startBlock, endBlock uint64, cfg *Configuration) error { queryStatusRatios := ` SELECT - COUNT(*) AS known_tx, - SUM(CASE WHEN dt.tx_status='shielded inclusion' THEN 1.0 END)/COUNT(*) * 100 AS shielded_ratio, - SUM(CASE WHEN dt.tx_status='shielded inclusion' THEN 1 END) AS shielded_amount, - SUM(CASE WHEN dt.tx_status='unshielded inclusion' THEN 1.0 END)/COUNT(*) * 100 AS unshielded_ratio, - SUM(CASE WHEN dt.tx_status='unshielded inclusion' THEN 1 END) AS unshielded_amount, - SUM(CASE WHEN dt.tx_status='not included' THEN 1.0 END)/COUNT(*) * 100 AS not_included_ratio, - SUM(CASE WHEN dt.tx_status='not included' THEN 1 END) not_included_amount, - SUM(CASE WHEN dt.tx_status='pending' THEN 1.0 END)/COUNT(*) * 100 AS pending_ratio, - SUM(CASE WHEN dt.tx_status='pending' THEN 1 END) AS pending_amount + dt.tx_hash, + dt.tx_status, + dt.slot AS inclusion_slot, + b_sequenced.slot AS sequenced_slot FROM decryption_key AS dk LEFT JOIN decrypted_tx AS dt ON dt.decryption_key_id=dk.id LEFT JOIN block AS b ON b.slot=dt.slot + LEFT JOIN transaction_submitted_event AS tse + ON tse.id=dt.transaction_submitted_event_id + LEFT JOIN block AS b_sequenced + ON b_sequenced.block_number=tse.event_block_number WHERE SUBSTRING( ENCODE(dk.identity_preimage, 'hex'), --- encode preimage as hex string @@ -487,26 +504,101 @@ func queryStatusRatios(w *bufio.Writer, startBlock, endBlock uint64, cfg *Config return err } var count uint64 - var shielded, unshielded, notIncluded, pending float64 - var shieldedAmount, unshieldedAmount, notIncludedAmount, pendingAmount int64 + var shieldedAmount, unshieldedAmount, notIncludedAmount, pendingAmount, inTargetedSlotAmount, invalidForTargetAmount int64 + + var txHash []byte + var txStatus string + var inclusionSlot *int64 + var sequencedSlot *int64 + + isGraffitiMode := len(cfg.GraffitiSet) > 0 + var innerTxHashToTargetSlot map[string]int64 + if isGraffitiMode { + innerTxHashToTargetSlot = buildInnerTxHashToTargetSlotMap(cfg) + } + defer rows.Close() for rows.Next() { - rows.Scan(&count, &shielded, &shieldedAmount, &unshielded, &unshieldedAmount, ¬Included, ¬IncludedAmount, &pending, &pendingAmount) + err = rows.Scan(&txHash, &txStatus, &inclusionSlot, &sequencedSlot) + if err != nil { + log.Printf("Error scanning row: %v", err) + continue + } + + count++ + + // Check if this is a late sequencer transaction (invalid for target) + if isGraffitiMode && sequencedSlot != nil { + txHashHex := common.BytesToHash(txHash).Hex() + targetedSlot, exists := innerTxHashToTargetSlot[txHashHex] + if exists && targetedSlot != 0 && *sequencedSlot >= targetedSlot { + invalidForTargetAmount++ + } + } + + switch txStatus { + case "shielded inclusion": + shieldedAmount++ - _, err = fmt.Fprintf(w, - `%v tx found by observer + // Check if included in targeted slot (only for shielded inclusions and in graffiti mode) + if isGraffitiMode && inclusionSlot != nil { + txHashHex := common.BytesToHash(txHash).Hex() + targetedSlot, exists := innerTxHashToTargetSlot[txHashHex] + if exists && targetedSlot != 0 && *inclusionSlot == targetedSlot { + inTargetedSlotAmount++ + } + } + case "unshielded inclusion": + unshieldedAmount++ + case "not included": + notIncludedAmount++ + case "pending": + pendingAmount++ + } + } + + if rows.Err() != nil { + return rows.Err() + } + + shielded := float64(shieldedAmount) / float64(count) * 100 + unshielded := float64(unshieldedAmount) / float64(count) * 100 + notIncluded := float64(notIncludedAmount) / float64(count) * 100 + pending := float64(pendingAmount) / float64(count) * 100 + + var inTargetedSlot float64 + var invalidForTarget float64 + if isGraffitiMode { + inTargetedSlot = float64(inTargetedSlotAmount) / float64(count) * 100 + invalidForTarget = float64(invalidForTargetAmount) / float64(count) * 100 + } + + outputFormat := `%v tx found by observer %3.2f%% shielded (%v/%v) %3.2f%% unshielded (%v/%v) %3.2f%% not included (%v/%v) %3.2f%% still pending (%v/%v) -`, - count, - shielded, shieldedAmount, count, - unshielded, unshieldedAmount, count, - notIncluded, notIncludedAmount, count, - pending, pendingAmount, count) +` + + outputArgs := []interface{}{ + count, + shielded, shieldedAmount, count, + unshielded, unshieldedAmount, count, + notIncluded, notIncludedAmount, count, + pending, pendingAmount, count, } - return nil + + // Add targeted slot stat and invalid for target stat only in graffiti mode + if isGraffitiMode { + outputFormat += ` +%3.2f%% included in targeted slot (shielded) (%v/%v) +%3.2f%% invalid for target (late sequencer transaction) (%v/%v)` + outputArgs = append(outputArgs, inTargetedSlot, inTargetedSlotAmount, count, invalidForTarget, invalidForTargetAmount, count) + } + + _, err = fmt.Fprintf(w, outputFormat+"\n", outputArgs...) + + return err } func CollectContinuousTestStats(startBlock uint64, endBlock uint64, cache *BlockCache, cfg *Configuration) error { diff --git a/continuous/config.go b/continuous/config.go index 35f7c4a..6cce8a7 100644 --- a/continuous/config.go +++ b/continuous/config.go @@ -126,7 +126,7 @@ func createConfiguration(mode string) (Configuration, error) { if err != nil { return cfg, err } - contracts, err := utils.SetupContracts(client, keyBroadcastAddress, sequencerAddress, keyperSetAddress) + contracts, err := utils.SetupContracts(client, keyBroadcastAddress, sequencerAddress, keyperSetAddress, chainID) if err != nil { return cfg, err } diff --git a/continuous/continuous.go b/continuous/continuous.go index cdad326..915ecde 100644 --- a/continuous/continuous.go +++ b/continuous/continuous.go @@ -33,8 +33,9 @@ func (s *Status) AddTxInFlight(t *ShutterTx) { } type ShutterBlock struct { - Number int64 - Ts pgtype.Date + Number int64 + Ts pgtype.Date + TargetedSlot int64 } func QueryAllShutterBlocks(out chan<- ShutterBlock, cfg *Configuration, mode string) { @@ -83,9 +84,9 @@ func QueryAllShutterBlocks(out chan<- ShutterBlock, cfg *Configuration, mode str out <- newShutterBlock } case "graffiti": - newShutterBlock, nextShutterSlot := queryGraffitiNextShutterBlock(status.nextShutterSlot, cfg) + newShutterBlock := queryGraffitiNextShutterBlock(status.nextShutterSlot, cfg) if !newShutterBlock.Ts.Time.IsZero() { - status.nextShutterSlot = nextShutterSlot + status.nextShutterSlot = newShutterBlock.TargetedSlot // send event (block number, timestamp) to out channel out <- newShutterBlock } @@ -133,7 +134,7 @@ func queryNewestShutterBlock(lastBlockTS pgtype.Date, cfg *Configuration) Shutte return res } -func queryGraffitiNextShutterBlock(nextShutterSlot int64, cfg *Configuration) (ShutterBlock, int64) { +func queryGraffitiNextShutterBlock(nextShutterSlot int64, cfg *Configuration) (ShutterBlock) { connection := GetConnection(cfg) // This query processes the current Shutter block while simultaneously computing @@ -192,12 +193,12 @@ func queryGraffitiNextShutterBlock(nextShutterSlot int64, cfg *Configuration) (S &ts, ) if err != nil { - return ShutterBlock{}, 0 + return ShutterBlock{} } - // Skip if a block was already returned for the same shutter slot - if nextSlot == nextShutterSlot { - return ShutterBlock{}, 0 + // Skip if a block was already returned for the same shutter slot or if there is a consecutive slot + if nextSlot <= nextShutterSlot + 1 { + return ShutterBlock{} } if graffiti != "" && cfg.GraffitiSet[graffiti] { @@ -205,9 +206,9 @@ func queryGraffitiNextShutterBlock(nextShutterSlot int64, cfg *Configuration) (S "Graffiti slot and target block found: nextSlot=%d next_shutter_validator=%d graffiti=%s block=%d ts=%v", nextSlot, validatorIndex, graffiti, blockNumber, ts.Time, ) - return ShutterBlock{Number: blockNumber, Ts: ts}, nextSlot + return ShutterBlock{Number: blockNumber, Ts: ts, TargetedSlot: nextSlot} } - return ShutterBlock{}, 0 + return ShutterBlock{} } func CheckTxInFlight(blockNumber int64, cfg *Configuration) { diff --git a/continuous/tx.go b/continuous/tx.go index 8dbcf20..0a007ac 100644 --- a/continuous/tx.go +++ b/continuous/tx.go @@ -24,6 +24,7 @@ type ShutterTx struct { submissionBlock int64 inclusionBlock int64 cancelBlock int64 + targetSlot int64 txStatus TxStatus ctx context.Context cancel context.CancelFunc @@ -85,7 +86,7 @@ func (ts TxStatus) String() string { func (ts TxStatus) EnumIndex() int { return int(ts) } -func SendShutterizedTX(blockNumber int64, lastTimestamp pgtype.Date, cfg *Configuration) { +func SendShutterizedTX(blockNumber int64, lastTimestamp pgtype.Date, targetSlot int64, cfg *Configuration) { account := cfg.NextAccount() log.Printf("SENDING NEW TX FOR %v from %v", blockNumber, account.Address.Hex()) gasLimit := uint64(21000) @@ -155,6 +156,7 @@ func SendShutterizedTX(blockNumber int64, lastTimestamp pgtype.Date, cfg *Config sender: account, prefix: identityPrefix, triggerBlock: blockNumber, + targetSlot: targetSlot, txStatus: TxStatus(Signed), ctx: ctx, cancel: cancel, diff --git a/main.go b/main.go index aa30463..ec0c8e0 100644 --- a/main.go +++ b/main.go @@ -97,7 +97,7 @@ func runContinuous(mode string) { startBlock = uint64(block.Number) } continuous.CheckTxInFlight(block.Number, &cfg) - continuous.SendShutterizedTX(block.Number, block.Ts, &cfg) + continuous.SendShutterizedTX(block.Number, block.Ts, block.TargetedSlot, &cfg) now := time.Now().Unix() if now-lastStats > 12 { log.Println("running stats") diff --git a/stress/stress_test.go b/stress/stress_test.go index 7be9ea3..0586a57 100644 --- a/stress/stress_test.go +++ b/stress/stress_test.go @@ -107,7 +107,7 @@ func createSetup(fundNewAccount bool) (utils.StressSetup, error) { return *setup, err } - contracts, err := utils.SetupContracts(client, KeyBroadcastContractAddress, SequencerContractAddress, KeyperSetManagerContractAddress) + contracts, err := utils.SetupContracts(client, KeyBroadcastContractAddress, SequencerContractAddress, KeyperSetManagerContractAddress, chainID) if err != nil { return *setup, err } diff --git a/utils/utils.go b/utils/utils.go index 2dbb0d8..c148964 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -431,7 +431,22 @@ type Contracts struct { depositContractAddress common.Address } -func SetupContracts(client *ethclient.Client, KeyBroadcastContractAddress, SequencerContractAddress, KeyperSetManagerContractAddress string) (Contracts, error) { +// GetDepositContractAddressByChainID returns the deposit contract address for the given chain ID +// Chain IDs: Gnosis = 100, Chiado = 10200 +func GetDepositContractAddressByChainID(chainID *big.Int) (common.Address, error) { + var address common.Address + switch chainID.Uint64() { + case 100: // Gnosis Chain + address = common.HexToAddress("0x0B98057eA310F4d31F2a452B414647007d1645d9") + case 10200: // Chiado + address = common.HexToAddress("0xb97036A26259B7147018913bD58a774cf91acf25") + default: + return address, fmt.Errorf("unsupported chain ID: %v", chainID) + } + return address, nil +} + +func SetupContracts(client *ethclient.Client, KeyBroadcastContractAddress, SequencerContractAddress, KeyperSetManagerContractAddress string, chainID *big.Int) (Contracts, error) { var setup Contracts keyperSetManagerContract, err := keypersetmanager.NewKeypersetmanager(common.HexToAddress(KeyperSetManagerContractAddress), client) if err != nil { @@ -453,8 +468,10 @@ func SetupContracts(client *ethclient.Client, KeyBroadcastContractAddress, Seque } setup.Sequencer = sequencerContract - // depositContractAddress := common.HexToAddress("0x4feF25519256e24A1FC536F7677152dA742Fe3Ef") - depositContractAddress := common.HexToAddress("0x0B98057eA310F4d31F2a452B414647007d1645d9") + depositContractAddress, err := GetDepositContractAddressByChainID(chainID) + if err != nil { + return setup, fmt.Errorf("can not get deposit contract address: %v", err) + } depositContract, err := NewDepositcontract(depositContractAddress, client) if err != nil { return setup, fmt.Errorf("can not get DepositContract %v", err)