diff --git a/accountwallet/config.go b/accountwallet/config.go index 685f4aa..4455c53 100644 --- a/accountwallet/config.go +++ b/accountwallet/config.go @@ -75,7 +75,7 @@ var accountConfigFile = "config.json" var ( dockerAccountConfigJSON = `{ - "bindAddress": "http://localhost:8080", + "bindAddress": "http://localhost:8050", "faucetBindAddress": "http://localhost:8088", "accountStatesFile": "wallet.dat", "genesisSeed": "7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih", diff --git a/accountwallet/faucet.go b/accountwallet/faucet.go index 743bd66..f75057f 100644 --- a/accountwallet/faucet.go +++ b/accountwallet/faucet.go @@ -8,6 +8,7 @@ import ( "github.com/mr-tron/base58" "github.com/iotaledger/evil-tools/models" + "github.com/iotaledger/evil-tools/utils" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/iota-core/pkg/blockhandler" @@ -39,38 +40,14 @@ func (a *AccountWallet) RequestBlockBuiltData(clt *nodeclient.Client, issuerID i } func (a *AccountWallet) RequestFaucetFunds(clt models.Client, receiveAddr iotago.Address) (*models.Output, error) { - //nolint:all,forcetypassert err := clt.RequestFaucetFunds(receiveAddr) if err != nil { return nil, ierrors.Wrap(err, "failed to request funds from faucet") } - indexer, err := clt.Indexer() + outputID, outputStruct, err := utils.AwaitAddressUnspentOutputToBeAccepted(clt, receiveAddr) if err != nil { - return nil, ierrors.Wrap(err, "failed to get indexer client") - } - - addrBech := receiveAddr.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) - - time.Sleep(10 * time.Second) - - res, err := indexer.Outputs(context.Background(), &apimodels.BasicOutputsQuery{ - AddressBech32: addrBech, - }) - if err != nil { - return nil, ierrors.Wrap(err, "indexer request failed in request faucet funds") - } - - var outputStruct iotago.Output - var outputID iotago.OutputID - for res.Next() { - unspents, err := res.Outputs(context.TODO()) - if err != nil { - return nil, ierrors.Wrap(err, "failed to get faucet unspent outputs") - } - - outputStruct = unspents[0] - outputID = lo.Return1(res.Response.Items.OutputIDs())[0] + return nil, ierrors.Wrap(err, "failed to await faucet funds") } return &models.Output{ diff --git a/config.go b/config.go index 0dd5e35..7a7e65e 100644 --- a/config.go +++ b/config.go @@ -12,7 +12,7 @@ import ( // Nodes used during the test, use at least two nodes to be able to double spend. var ( // urls = []string{"http://bootstrap-01.feature.shimmer.iota.cafe:8080", "http://vanilla-01.feature.shimmer.iota.cafe:8080", "http://drng-01.feature.shimmer.iota.cafe:8080"} - urls = []string{"http://localhost:8080", "http://localhost:8090"} //, "http://localhost:8070", "http://localhost:8040"} + urls = []string{"http://localhost:8050", "http://localhost:8060"} //, "http://localhost:8070", "http://localhost:8040"} ) var ( diff --git a/evilwallet/evilwallet.go b/evilwallet/evilwallet.go index 89b09b5..00a1b5e 100644 --- a/evilwallet/evilwallet.go +++ b/evilwallet/evilwallet.go @@ -8,6 +8,7 @@ import ( "github.com/iotaledger/evil-tools/accountwallet" evillogger "github.com/iotaledger/evil-tools/logger" "github.com/iotaledger/evil-tools/models" + "github.com/iotaledger/evil-tools/utils" "github.com/iotaledger/hive.go/ds/types" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/lo" @@ -24,13 +25,10 @@ const ( // FaucetRequestSplitNumber defines the number of outputs to split from a faucet request. FaucetRequestSplitNumber = 120 faucetTokensPerRequest iotago.BaseToken = 432_000_000 - - waitForAcceptance = 20 * time.Second - awaitAcceptationSleep = 1 * time.Second ) var ( - defaultClientsURLs = []string{"http://localhost:8080", "http://localhost:8090"} + defaultClientsURLs = []string{"http://localhost:8050", "http://localhost:8060"} defaultFaucetURL = "http://localhost:8088" ) @@ -278,14 +276,14 @@ func (e *EvilWallet) requestAndSplitFaucetFunds(initWallet, receiveWallet *Walle func (e *EvilWallet) requestFaucetFunds(wallet *Wallet) (output *models.Output, err error) { receiveAddr := wallet.AddressOnIndex(0) - clt := e.connector.GetClient() + clt := e.connector.GetIndexerClient() err = clt.RequestFaucetFunds(receiveAddr) if err != nil { return nil, ierrors.Wrap(err, "failed to request funds from faucet") } - outputID, iotaOutput, err := e.outputManager.AwaitAddressUnspentOutputToBeAccepted(receiveAddr, 10*time.Second) + outputID, iotaOutput, err := utils.AwaitAddressUnspentOutputToBeAccepted(clt, receiveAddr) if err != nil { return nil, ierrors.Wrap(err, "failed to await faucet output acceptance") } @@ -371,7 +369,7 @@ func (e *EvilWallet) splitOutputs(inputWallet, outputWallet *Wallet) ([]iotago.T } func (e *EvilWallet) createSplitOutputs(input *models.Output, splitNumber int, receiveWallet *Wallet) []*OutputOption { - balances := SplitBalanceEqually(splitNumber, input.Balance) + balances := utils.SplitBalanceEqually(splitNumber, input.Balance) outputs := make([]*OutputOption, splitNumber) for i, bal := range balances { outputs[i] = &OutputOption{amount: bal, address: receiveWallet.Address(), outputType: iotago.OutputBasic} @@ -744,7 +742,7 @@ func (e *EvilWallet) updateOutputBalances(buildOptions *Options) (err error) { totalBalance += in.Balance } } - balances := SplitBalanceEqually(len(buildOptions.outputs)+len(buildOptions.aliasOutputs), totalBalance) + balances := utils.SplitBalanceEqually(len(buildOptions.outputs)+len(buildOptions.aliasOutputs), totalBalance) i := 0 for out, output := range buildOptions.aliasOutputs { switch output.Type() { diff --git a/evilwallet/output_manager.go b/evilwallet/output_manager.go index d94064e..90b3251 100644 --- a/evilwallet/output_manager.go +++ b/evilwallet/output_manager.go @@ -1,24 +1,16 @@ package evilwallet import ( - "context" "sync" - "time" "go.uber.org/atomic" "github.com/iotaledger/evil-tools/models" + "github.com/iotaledger/evil-tools/utils" "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/logger" "github.com/iotaledger/hive.go/runtime/syncutils" iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" -) - -const ( - awaitOutputToBeConfirmed = 10 * time.Second ) // OutputManager keeps track of the output statuses. @@ -117,13 +109,13 @@ func (o *OutputManager) Track(outputIDs ...iotago.OutputID) (allConfirmed bool) for _, ID := range outputIDs { wg.Add(1) - go func(id iotago.OutputID) { + go func(id iotago.OutputID, clt models.Client) { defer wg.Done() - if !o.AwaitOutputToBeAccepted(id, awaitOutputToBeConfirmed) { + if !utils.AwaitOutputToBeAccepted(clt, id) { unconfirmedOutputFound.Store(true) } - }(ID) + }(ID, o.connector.GetClient()) } wg.Wait() @@ -206,22 +198,6 @@ func (o *OutputManager) getOutputFromWallet(outputID iotago.OutputID) (output *m return } -// RequestOutputsByTxID adds the outputs of a given transaction to the output status map. -func (o *OutputManager) RequestOutputsByTxID(txID iotago.TransactionID) (outputIDs iotago.OutputIDs) { - clt := o.connector.GetClient() - - tx, err := clt.GetTransaction(txID) - if err != nil { - return - } - - for index := range tx.Transaction.Outputs { - outputIDs = append(outputIDs, iotago.OutputIDFromTransactionIDAndIndex(txID, uint16(index))) - } - - return outputIDs -} - // AwaitWalletOutputsToBeConfirmed awaits for all outputs in the wallet are confirmed. func (o *OutputManager) AwaitWalletOutputsToBeConfirmed(wallet *Wallet) { wg := sync.WaitGroup{} @@ -243,59 +219,6 @@ func (o *OutputManager) AwaitWalletOutputsToBeConfirmed(wallet *Wallet) { wg.Wait() } -// AwaitOutputToBeAccepted awaits for output from a provided outputID is accepted. Timeout is waitFor. -// Useful when we have only an address and no transactionID, e.g. faucet funds request. -func (o *OutputManager) AwaitOutputToBeAccepted(outputID iotago.OutputID, waitFor time.Duration) (accepted bool) { - s := time.Now() - clt := o.connector.GetClient() - accepted = false - for ; time.Since(s) < waitFor; time.Sleep(awaitAcceptationSleep) { - confirmationState := clt.GetOutputConfirmationState(outputID) - if confirmationState == "confirmed" { - accepted = true - break - } - } - - return accepted -} - -func (o *OutputManager) AwaitAddressUnspentOutputToBeAccepted(addr *iotago.Ed25519Address, waitFor time.Duration) (outputID iotago.OutputID, output iotago.Output, err error) { - clt := o.connector.GetIndexerClient() - indexer, err := clt.Indexer() - if err != nil { - return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get indexer client") - } - - s := time.Now() - addrBech := addr.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) - - for ; time.Since(s) < waitFor; time.Sleep(awaitAcceptationSleep) { - res, err := indexer.Outputs(context.Background(), &apimodels.BasicOutputsQuery{ - AddressBech32: addrBech, - }) - if err != nil { - return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "indexer request failed in request faucet funds") - } - - for res.Next() { - unspents, err := res.Outputs(context.TODO()) - if err != nil { - return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get faucet unspent outputs") - } - - if len(unspents) == 0 { - o.log.Debugf("no unspent outputs found in indexer for address: %s", addrBech) - break - } - - return lo.Return1(res.Response.Items.OutputIDs())[0], unspents[0], nil - } - } - - return iotago.EmptyOutputID, nil, ierrors.Errorf("no unspent outputs found for address %s due to timeout", addrBech) -} - // AwaitTransactionsAcceptance awaits for transaction confirmation and updates wallet with outputIDs. func (o *OutputManager) AwaitTransactionsAcceptance(txIDs ...iotago.TransactionID) { wg := sync.WaitGroup{} @@ -305,61 +228,20 @@ func (o *OutputManager) AwaitTransactionsAcceptance(txIDs ...iotago.TransactionI for _, txID := range txIDs { wg.Add(1) - go func(txID iotago.TransactionID) { + go func(txID iotago.TransactionID, clt models.Client) { defer wg.Done() semaphore <- true defer func() { <-semaphore }() - err := o.AwaitTransactionToBeAccepted(txID, waitForAcceptance, txLeft) + err := utils.AwaitTransactionToBeAccepted(clt, txID, txLeft) txLeft.Dec() if err != nil { o.log.Errorf("Error awaiting transaction %s to be accepted: %s", txID.String(), err) return } - }(txID) + }(txID, o.connector.GetClient()) } wg.Wait() } - -// AwaitTransactionToBeAccepted awaits for acceptance of a single transaction. -func (o *OutputManager) AwaitTransactionToBeAccepted(txID iotago.TransactionID, waitFor time.Duration, txLeft *atomic.Int64) error { - s := time.Now() - clt := o.connector.GetClient() - var accepted bool - for ; time.Since(s) < waitFor; time.Sleep(awaitAcceptationSleep) { - resp, err := clt.GetBlockState(txID) - if resp == nil { - o.log.Debugf("Block state API error: %v", err) - - continue - } - if resp.BlockState == apimodels.BlockStateFailed.String() || resp.BlockState == apimodels.BlockStateRejected.String() { - failureReason, _, _ := apimodels.BlockFailureReasonFromBytes(lo.PanicOnErr(resp.BlockFailureReason.Bytes())) - - return ierrors.Errorf("tx %s failed because block failure: %d", txID, failureReason) - } - - if resp.TransactionState == apimodels.TransactionStateFailed.String() { - failureReason, _, _ := apimodels.TransactionFailureReasonFromBytes(lo.PanicOnErr(resp.TransactionFailureReason.Bytes())) - - return ierrors.Errorf("transaction %s failed: %d", txID, failureReason) - } - - confirmationState := resp.TransactionState - - o.log.Debugf("Tx %s confirmationState: %s, tx left: %d", txID.ToHex(), confirmationState, txLeft.Load()) - if confirmationState == "accepted" || confirmationState == "confirmed" || confirmationState == "finalized" { - accepted = true - break - } - } - if !accepted { - return ierrors.Errorf("transaction %s not accepted in time", txID) - } - - o.log.Debugf("Transaction %s accepted", txID) - - return nil -} diff --git a/evilwallet/utils.go b/evilwallet/utils.go deleted file mode 100644 index fe601eb..0000000 --- a/evilwallet/utils.go +++ /dev/null @@ -1,23 +0,0 @@ -package evilwallet - -import iotago "github.com/iotaledger/iota.go/v4" - -// SplitBalanceEqually splits the balance equally between `splitNumber` outputs. -func SplitBalanceEqually(splitNumber int, balance iotago.BaseToken) []iotago.BaseToken { - outputBalances := make([]iotago.BaseToken, 0) - - // make sure the output balances are equal input - var totalBalance iotago.BaseToken - - // input is divided equally among outputs - for i := 0; i < splitNumber-1; i++ { - outputBalances = append(outputBalances, balance/iotago.BaseToken(splitNumber)) - totalBalance += outputBalances[i] - } - lastBalance := balance - totalBalance - outputBalances = append(outputBalances, lastBalance) - - return outputBalances -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/go.mod b/go.mod index 34bf2fc..4367fdc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/google/martian v2.1.0+incompatible github.com/iotaledger/hive.go/app v0.0.0-20231020115340-13da292c580b - github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231020115340-13da292c580b github.com/iotaledger/hive.go/ds v0.0.0-20231020115340-13da292c580b github.com/iotaledger/hive.go/ierrors v0.0.0-20231024193930-47c6046e38a8 github.com/iotaledger/hive.go/lo v0.0.0-20231020115340-13da292c580b @@ -40,6 +39,7 @@ require ( github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 // indirect github.com/iotaledger/hive.go/ads v0.0.0-20231020115340-13da292c580b // indirect github.com/iotaledger/hive.go/constraints v0.0.0-20231020115340-13da292c580b // indirect + github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231020115340-13da292c580b // indirect github.com/iotaledger/hive.go/crypto v0.0.0-20231020115340-13da292c580b // indirect github.com/iotaledger/hive.go/kvstore v0.0.0-20231020115340-13da292c580b // indirect github.com/iotaledger/hive.go/log v0.0.0-20231020115340-13da292c580b // indirect diff --git a/models/connector.go b/models/connector.go index a83883e..6eb24bb 100644 --- a/models/connector.go +++ b/models/connector.go @@ -206,8 +206,8 @@ type Client interface { PostData(data []byte) (blkID string, err error) // GetBlockConfirmationState returns the AcceptanceState of a given block ID. GetBlockConfirmationState(blkID iotago.BlockID) string - // GetBlockState returns the AcceptanceState of a given transaction ID. - GetBlockState(txID iotago.TransactionID) (resp *apimodels.BlockMetadataResponse, err error) + // GetBlockStateFromTransaction returns the AcceptanceState of a given transaction ID. + GetBlockStateFromTransaction(txID iotago.TransactionID) (resp *apimodels.BlockMetadataResponse, err error) // GetOutput gets the output of a given outputID. GetOutput(outputID iotago.OutputID) iotago.Output // GetOutputConfirmationState gets the first unspent outputs of a given address. @@ -335,7 +335,7 @@ func (c *WebClient) PostData(data []byte) (blkID string, err error) { // GetOutputConfirmationState gets the first unspent outputs of a given address. func (c *WebClient) GetOutputConfirmationState(outputID iotago.OutputID) string { txID := outputID.TransactionID() - resp, err := c.GetBlockState(txID) + resp, err := c.GetBlockStateFromTransaction(txID) if err != nil { return "" } @@ -363,8 +363,8 @@ func (c *WebClient) GetBlockConfirmationState(blkID iotago.BlockID) string { return resp.BlockState } -// GetBlockState returns the AcceptanceState of a given transaction ID. -func (c *WebClient) GetBlockState(txID iotago.TransactionID) (*apimodels.BlockMetadataResponse, error) { +// GetBlockStateFromTransaction returns the AcceptanceState of a given transaction ID. +func (c *WebClient) GetBlockStateFromTransaction(txID iotago.TransactionID) (*apimodels.BlockMetadataResponse, error) { return c.client.TransactionIncludedBlockMetadata(context.Background(), txID) } diff --git a/spammer/spamming_functions.go b/spammer/spamming_functions.go index 8e03f44..09caff2 100644 --- a/spammer/spamming_functions.go +++ b/spammer/spamming_functions.go @@ -6,6 +6,7 @@ import ( "time" "github.com/iotaledger/evil-tools/models" + "github.com/iotaledger/evil-tools/utils" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/lo" iotago "github.com/iotaledger/iota.go/v4" @@ -137,21 +138,9 @@ func createBlowBallCenter(s *Spammer) (iotago.BlockID, error) { }, }, s.IssuerAlias, clt) - timer := time.NewTimer(10 * time.Second) - defer timer.Stop() - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - for { - select { - case <-ticker.C: - state := clt.GetBlockConfirmationState(centerID) - if state == "confirmed" { - return centerID, nil - } - case <-timer.C: - return iotago.EmptyBlockID, ierrors.Errorf("failed to confirm center block") - } - } + err := utils.AwaitBlockToBeConfirmed(clt, centerID) + + return centerID, err } func createBlowBall(center iotago.BlockID, s *Spammer) []*iotago.ProtocolBlock { diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..e50c9ea --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,144 @@ +package utils + +import ( + "context" + "time" + + "go.uber.org/atomic" + + evillogger "github.com/iotaledger/evil-tools/logger" + "github.com/iotaledger/evil-tools/models" + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" +) + +var UtilsLogger = evillogger.New("Utils") + +const ( + MaxRetries = 20 + AwaitInterval = 1 * time.Second +) + +// SplitBalanceEqually splits the balance equally between `splitNumber` outputs. +func SplitBalanceEqually(splitNumber int, balance iotago.BaseToken) []iotago.BaseToken { + outputBalances := make([]iotago.BaseToken, 0) + + // make sure the output balances are equal input + var totalBalance iotago.BaseToken + + // input is divided equally among outputs + for i := 0; i < splitNumber-1; i++ { + outputBalances = append(outputBalances, balance/iotago.BaseToken(splitNumber)) + totalBalance += outputBalances[i] + } + lastBalance := balance - totalBalance + outputBalances = append(outputBalances, lastBalance) + + return outputBalances +} + +// AwaitTransactionToBeAccepted awaits for acceptance of a single transaction. +func AwaitBlockToBeConfirmed(clt models.Client, blkID iotago.BlockID) error { + for i := 0; i < MaxRetries; i++ { + state := clt.GetBlockConfirmationState(blkID) + if state == apimodels.BlockStateConfirmed.String() || state == apimodels.BlockStateFinalized.String() { + UtilsLogger.Debugf("Block confirmed: %s", blkID.ToHex()) + return nil + } + + time.Sleep(AwaitInterval) + } + + UtilsLogger.Debugf("Block not confirmed: %s", blkID.ToHex()) + + return ierrors.Errorf("Block not confirmed: %s", blkID.ToHex()) +} + +// AwaitTransactionToBeAccepted awaits for acceptance of a single transaction. +func AwaitTransactionToBeAccepted(clt models.Client, txID iotago.TransactionID, txLeft *atomic.Int64) error { + for i := 0; i < MaxRetries; i++ { + resp, err := clt.GetBlockStateFromTransaction(txID) + if resp == nil { + UtilsLogger.Debugf("Block state API error: %v", err) + + continue + } + if resp.BlockState == apimodels.BlockStateFailed.String() || resp.BlockState == apimodels.BlockStateRejected.String() { + failureReason, _, _ := apimodels.BlockFailureReasonFromBytes(lo.PanicOnErr(resp.BlockFailureReason.Bytes())) + + return ierrors.Errorf("tx %s failed because block failure: %d", txID, failureReason) + } + + if resp.TransactionState == apimodels.TransactionStateFailed.String() { + failureReason, _, _ := apimodels.TransactionFailureReasonFromBytes(lo.PanicOnErr(resp.TransactionFailureReason.Bytes())) + + return ierrors.Errorf("transaction %s failed: %d", txID, failureReason) + } + + confirmationState := resp.TransactionState + + UtilsLogger.Debugf("Tx %s confirmationState: %s, tx left: %d", txID.ToHex(), confirmationState, txLeft.Load()) + if confirmationState == apimodels.TransactionStateAccepted.String() || + confirmationState == apimodels.TransactionStateConfirmed.String() || + confirmationState == apimodels.TransactionStateFinalized.String() { + return nil + } + + time.Sleep(AwaitInterval) + } + + return ierrors.Errorf("Transaction %s not accepted in time", txID) +} + +func AwaitAddressUnspentOutputToBeAccepted(clt models.Client, addr iotago.Address) (outputID iotago.OutputID, output iotago.Output, err error) { + indexer, err := clt.Indexer() + if err != nil { + return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get indexer client") + } + + addrBech := addr.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP()) + + for i := 0; i < MaxRetries; i++ { + res, err := indexer.Outputs(context.Background(), &apimodels.BasicOutputsQuery{ + AddressBech32: addrBech, + }) + if err != nil { + return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "indexer request failed in request faucet funds") + } + + for res.Next() { + unspents, err := res.Outputs(context.TODO()) + if err != nil { + return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get faucet unspent outputs") + } + + if len(unspents) == 0 { + UtilsLogger.Debugf("no unspent outputs found in indexer for address: %s", addrBech) + break + } + + return lo.Return1(res.Response.Items.OutputIDs())[0], unspents[0], nil + } + + time.Sleep(AwaitInterval) + } + + return iotago.EmptyOutputID, nil, ierrors.Errorf("no unspent outputs found for address %s due to timeout", addrBech) +} + +// AwaitOutputToBeAccepted awaits for output from a provided outputID is accepted. Timeout is waitFor. +// Useful when we have only an address and no transactionID, e.g. faucet funds request. +func AwaitOutputToBeAccepted(clt models.Client, outputID iotago.OutputID) bool { + for i := 0; i < MaxRetries; i++ { + confirmationState := clt.GetOutputConfirmationState(outputID) + if confirmationState == apimodels.TransactionStateConfirmed.String() { + return true + } + + time.Sleep(AwaitInterval) + } + + return false +}