diff --git a/.github/workflows/build_tools.yml b/.github/workflows/build_tools.yml index 89c14f9dc..262c56563 100644 --- a/.github/workflows/build_tools.yml +++ b/.github/workflows/build_tools.yml @@ -3,7 +3,6 @@ name: Build internal tools on: pull_request: paths: - - 'tools/evil-spammer/**' - 'tools/genesis-snapshot/**' jobs: @@ -23,10 +22,6 @@ jobs: - name: Print Go version run: go version - - name: Build evil-spammer tool - working-directory: tools/evil-spammer - run: go mod tidy && go build . - - name: Build genesis-snapshot tool working-directory: tools/genesis-snapshot run: go mod tidy && go build . diff --git a/.gitignore b/.gitignore index b404776d6..fea8c46c9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,3 @@ dist/ # snapshot and settings file *.bin tools/docker-network/docker-network.snapshot -tools/evil-spammer/evil-spammer - - diff --git a/.golangci.yml b/.golangci.yml index f9885c6fa..6000e9feb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,8 +2,6 @@ run: tests: true skip-dirs: - components/dashboard - - tools/evilwallet - - tools/evil-spammer skip-files: - ".*_test.go$" - "testframework.go" diff --git a/Dockerfile b/Dockerfile index 44a17fda1..ec10c0f75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,8 @@ RUN cp ./peering.json /app/peering.json # using distroless cc "nonroot" image, which includes everything in the base image (glibc, libssl and openssl) FROM gcr.io/distroless/cc-debian12:nonroot +HEALTHCHECK --interval=10s --timeout=5s --retries=30 CMD ["/app/iota-core", "tools", "node-info"] + # Copy the app dir into distroless image COPY --chown=nonroot:nonroot --from=build /app /app diff --git a/Dockerfile.dev b/Dockerfile.dev index e9073b384..f91006d26 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -61,6 +61,8 @@ RUN mkdir -p /app/data/peerdb # using distroless cc "nonroot" image, which includes everything in the base image (glibc, libssl and openssl) FROM gcr.io/distroless/cc-debian12:nonroot +HEALTHCHECK --interval=10s --timeout=5s --retries=30 CMD ["/app/iota-core", "tools", "node-info"] + # Copy the app dir into distroless image COPY --chown=nonroot:nonroot --from=build /app /app diff --git a/components/app/app.go b/components/app/app.go index 4cb029500..5c4b42957 100644 --- a/components/app/app.go +++ b/components/app/app.go @@ -1,6 +1,9 @@ package app import ( + "fmt" + "os" + "github.com/iotaledger/hive.go/app" "github.com/iotaledger/hive.go/app/components/profiling" "github.com/iotaledger/hive.go/app/components/shutdown" @@ -15,6 +18,7 @@ import ( "github.com/iotaledger/iota-core/components/restapi" coreapi "github.com/iotaledger/iota-core/components/restapi/core" "github.com/iotaledger/iota-core/components/validator" + "github.com/iotaledger/iota-core/pkg/toolset" ) var ( @@ -28,6 +32,12 @@ var ( func App() *app.App { return app.New(Name, Version, // app.WithVersionCheck("iotaledger", "iota-core"), + app.WithUsageText(fmt.Sprintf(`Usage of %s (%s %s): + +Run '%s tools' to list all available tools. + +Command line flags: +`, os.Args[0], Name, Version, os.Args[0])), app.WithInitComponent(InitComponent), app.WithComponents( shutdown.Component, @@ -63,5 +73,15 @@ func init() { AdditionalConfigs: []*app.ConfigurationSet{ app.NewConfigurationSet("peering", "peering", "peeringConfigFilePath", "peeringConfig", false, true, false, "peering.json", "n"), }, + Init: initialize, + } +} + +func initialize(_ *app.App) error { + if toolset.ShouldHandleTools() { + toolset.HandleTools() + // HandleTools will call os.Exit } + + return nil } diff --git a/components/debugapi/node.go b/components/debugapi/node.go index 893fb7c4e..280311b76 100644 --- a/components/debugapi/node.go +++ b/components/debugapi/node.go @@ -1,6 +1,7 @@ package debugapi import ( + "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/iota-core/pkg/core/account" iotago "github.com/iotaledger/iota.go/v4" @@ -10,8 +11,12 @@ import ( func validatorsSummary() (*ValidatorsSummaryResponse, error) { seatManager := deps.Protocol.MainEngineInstance().SybilProtection.SeatManager() latestSlotIndex := deps.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Slot() - latestCommittee := seatManager.Committee(latestSlotIndex) - validatorSeats := []*Validator{} + latestCommittee, exists := seatManager.CommitteeInSlot(latestSlotIndex) + if !exists { + return nil, ierrors.Errorf("committee for slot %d was not selected", latestSlotIndex) + } + + var validatorSeats []*Validator latestCommittee.Accounts().ForEach(func(id iotago.AccountID, pool *account.Pool) bool { validatorSeats = append(validatorSeats, &Validator{ AccountID: id, diff --git a/components/metricstracker/params.go b/components/metricstracker/params.go index a2886bd3a..9bd764443 100644 --- a/components/metricstracker/params.go +++ b/components/metricstracker/params.go @@ -14,6 +14,6 @@ var ParamsMetricsTracker = &ParametersMetricsTracker{} var params = &app.ComponentParams{ Params: map[string]any{ - "metricstracker": ParamsMetricsTracker, + "metricsTracker": ParamsMetricsTracker, }, } diff --git a/components/p2p/component.go b/components/p2p/component.go index 9635df585..dff432bb7 100644 --- a/components/p2p/component.go +++ b/components/p2p/component.go @@ -230,7 +230,7 @@ func provide(c *dig.Container) error { if err := c.Provide(func(deps p2pDeps) p2pResult { res := p2pResult{} - privKeyFilePath := filepath.Join(deps.P2PDatabasePath, "identity.key") + privKeyFilePath := filepath.Join(deps.P2PDatabasePath, IdentityPrivateKeyFileName) // make sure nobody copies around the peer store since it contains the private key of the node Component.LogInfof(`WARNING: never share your "%s" folder as it contains your node's private key!`, deps.P2PDatabasePath) diff --git a/components/p2p/params.go b/components/p2p/params.go index 37f099d85..7b2cba4e7 100644 --- a/components/p2p/params.go +++ b/components/p2p/params.go @@ -6,7 +6,8 @@ import ( const ( // CfgPeers defines the static peers this node should retain a connection to (CLI). - CfgPeers = "peers" + CfgPeers = "peers" + IdentityPrivateKeyFileName = "identity.key" ) // ParametersP2P contains the definition of configuration parameters used by the p2p plugin. diff --git a/components/protocol/component.go b/components/protocol/component.go index 4bbd8b6e3..dd70e21cb 100644 --- a/components/protocol/component.go +++ b/components/protocol/component.go @@ -177,139 +177,139 @@ func provide(c *dig.Container) error { func configure() error { deps.Protocol.Events.Error.Hook(func(err error) { - Component.LogErrorf("Error in Protocol: %s", err) + Component.LogErrorf("ProtocolError, error: %s", err) }) deps.Protocol.Events.Network.Error.Hook(func(err error, id peer.ID) { - Component.LogErrorf("NetworkError: %s Source: %s", err.Error(), id) + Component.LogErrorf("NetworkError, error: %s, peerID: %s", err.Error(), id) }) deps.Protocol.Events.Network.BlockReceived.Hook(func(block *model.Block, source peer.ID) { - Component.LogDebugf("BlockReceived: %s", block.ID()) + Component.LogDebugf("BlockReceived, blockID: %s, peerID: %s", block.ID(), source) }) deps.Protocol.Events.Engine.BlockProcessed.Hook(func(blockID iotago.BlockID) { - Component.LogDebugf("BlockProcessed: %s", blockID) + Component.LogDebugf("BlockProcessed, blockID: %s", blockID) }) deps.Protocol.Events.Engine.AcceptedBlockProcessed.Hook(func(block *blocks.Block) { - Component.LogDebugf("AcceptedBlockProcessed: %s", block.ID()) + Component.LogDebugf("AcceptedBlockProcessed, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.Filter.BlockPreFiltered.Hook(func(event *filter.BlockPreFilteredEvent) { - Component.LogDebugf("BlockPreFiltered: %s - %s", event.Block.ID(), event.Reason.Error()) + Component.LogDebugf("BlockPreFiltered, blockID: %s, reason: %s", event.Block.ID(), event.Reason.Error()) }) - deps.Protocol.Events.Engine.Filter.BlockPreAllowed.Hook(func(blk *model.Block) { - Component.LogDebugf("BlockPreAllowed: %s - %s", blk.ID()) + deps.Protocol.Events.Engine.Filter.BlockPreAllowed.Hook(func(block *model.Block) { + Component.LogDebugf("BlockPreAllowed, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.CommitmentFilter.BlockAllowed.Hook(func(block *blocks.Block) { - Component.LogDebugf("CommitmentFilter.BlockAllowed: %s\n", block.ID()) + Component.LogDebugf("CommitmentFilter.BlockAllowed, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.CommitmentFilter.BlockFiltered.Hook(func(event *commitmentfilter.BlockFilteredEvent) { - Component.LogWarnf("CommitmentFilter.BlockFiltered: %s - %s\n", event.Block.ID(), event.Reason.Error()) + Component.LogWarnf("CommitmentFilter.BlockFiltered, blockID: %s, reason: %s", event.Block.ID(), event.Reason.Error()) }) deps.Protocol.Events.Engine.TipManager.BlockAdded.Hook(func(tip tipmanager.TipMetadata) { - Component.LogDebugf("BlockAdded to tip pool: %s; is strong: %v; is weak: %v", tip.ID(), tip.IsStrongTip(), tip.IsWeakTip()) + Component.LogDebugf("TipManager.BlockAdded, blockID: %s, isStrong: %v, isWeak: %v", tip.ID(), tip.IsStrongTip(), tip.IsWeakTip()) }) deps.Protocol.Events.Engine.BlockDAG.BlockSolid.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockSolid: %s", block.ID()) + Component.LogDebugf("BlockDAG.BlockSolid, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.BlockDAG.BlockInvalid.Hook(func(block *blocks.Block, err error) { - Component.LogDebugf("BlockInvalid in blockDAG: %s, error: %v", block.ID(), err.Error()) + Component.LogDebugf("BlockDAG.BlockInvalid, blockID: %s, error: %v", block.ID(), err.Error()) }) deps.Protocol.Events.Engine.Booker.BlockBooked.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockBooked: %s", block.ID()) + Component.LogDebugf("BlockBooked, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.Booker.BlockInvalid.Hook(func(block *blocks.Block, err error) { - Component.LogDebugf("BlockInvalid in booker: %s, error: %v", block.ID(), err.Error()) + Component.LogDebugf("BlockInvalid in booker, blockID: %s, error: %v", block.ID(), err.Error()) }) deps.Protocol.Events.Engine.BlockGadget.BlockPreAccepted.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockPreAccepted: %s", block.ID()) + Component.LogDebugf("BlockPreAccepted, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.BlockGadget.BlockAccepted.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockAccepted: %s", block.ID()) + Component.LogDebugf("BlockAccepted, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.BlockGadget.BlockPreConfirmed.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockPreConfirmed: %s", block.ID()) + Component.LogDebugf("BlockPreConfirmed, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.BlockGadget.BlockConfirmed.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockConfirmed: %s", block.ID()) + Component.LogDebugf("BlockConfirmed, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.Clock.AcceptedTimeUpdated.Hook(func(time time.Time) { - Component.LogDebugf("AcceptedTimeUpdated: Slot %d @ %s", deps.Protocol.CommittedAPI().TimeProvider().SlotFromTime(time), time) + Component.LogDebugf("AcceptedTimeUpdated, slot: %d @ %s", deps.Protocol.CommittedAPI().TimeProvider().SlotFromTime(time), time) }) deps.Protocol.Events.Engine.Clock.ConfirmedTimeUpdated.Hook(func(time time.Time) { - Component.LogDebugf("ConfirmedTimeUpdated: Slot %d @ %s", deps.Protocol.CommittedAPI().TimeProvider().SlotFromTime(time), time) + Component.LogDebugf("ConfirmedTimeUpdated, slot: %d @ %s", deps.Protocol.CommittedAPI().TimeProvider().SlotFromTime(time), time) }) deps.Protocol.Events.Engine.Notarization.SlotCommitted.Hook(func(details *notarization.SlotCommittedDetails) { - Component.LogInfof("SlotCommitted: %s - %d", details.Commitment.ID(), details.Commitment.Slot()) + Component.LogInfof("SlotCommitted, commitmentID: %s, slot: %d", details.Commitment.ID(), details.Commitment.Slot()) }) - deps.Protocol.Events.Engine.SlotGadget.SlotFinalized.Hook(func(index iotago.SlotIndex) { - Component.LogInfof("SlotFinalized: %d", index) + deps.Protocol.Events.Engine.SlotGadget.SlotFinalized.Hook(func(slot iotago.SlotIndex) { + Component.LogInfof("SlotFinalized, slot: %d", slot) }) deps.Protocol.Events.Engine.Scheduler.BlockScheduled.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockScheduled: %s", block.ID()) + Component.LogDebugf("BlockScheduled, blockID: %s", block.ID()) }) deps.Protocol.Events.Engine.Scheduler.BlockDropped.Hook(func(block *blocks.Block, err error) { - Component.LogDebugf("BlockDropped: %s; reason: %s", block.ID(), err) + Component.LogDebugf("BlockDropped, blockID: %s, reason: %s", block.ID(), err) }) deps.Protocol.Events.Engine.Scheduler.BlockSkipped.Hook(func(block *blocks.Block) { - Component.LogDebugf("BlockSkipped: %s", block.ID()) + Component.LogDebugf("BlockSkipped, blockID: %s", block.ID()) }) deps.Protocol.Events.ChainManager.RequestCommitment.Hook(func(id iotago.CommitmentID) { - Component.LogDebugf("RequestCommitment: %s", id) + Component.LogDebugf("RequestCommitment, commitmentID: %s", id) }) deps.Protocol.Events.Network.SlotCommitmentRequestReceived.Hook(func(commitmentID iotago.CommitmentID, id peer.ID) { - Component.LogDebugf("SlotCommitmentRequestReceived: %s", commitmentID) + Component.LogDebugf("SlotCommitmentRequestReceived, commitmentID: %s", commitmentID) }) deps.Protocol.Events.Network.SlotCommitmentReceived.Hook(func(commitment *model.Commitment, id peer.ID) { - Component.LogDebugf("SlotCommitmentReceived: %s", commitment.ID()) + Component.LogDebugf("SlotCommitmentReceived, commitmentID: %s", commitment.ID()) }) deps.Protocol.Events.Engine.SybilProtection.CommitteeSelected.Hook(func(committee *account.Accounts, epoch iotago.EpochIndex) { - Component.LogInfof("CommitteeSelected: Epoch %d - %s (reused: %t)", epoch, committee.IDs(), committee.IsReused()) + Component.LogInfof("CommitteeSelected, epoch: %d, committeeIDs: %s, reused: %t", epoch, committee.IDs(), committee.IsReused()) }) deps.Protocol.Events.Engine.SybilProtection.RewardsCommitted.Hook(func(epoch iotago.EpochIndex) { - Component.LogInfof("RewardsCommitted: Epoch %d", epoch) + Component.LogInfof("RewardsCommitted, epoch: %d", epoch) }) deps.Protocol.Events.Engine.Booker.BlockInvalid.Hook(func(block *blocks.Block, err error) { - Component.LogWarnf("Booker BlockInvalid: Block %s - %s", block.ID(), err.Error()) + Component.LogWarnf("Booker BlockInvalid, blockID: %s, error: %s", block.ID(), err.Error()) }) deps.Protocol.Events.Engine.SeatManager.OnlineCommitteeSeatAdded.Hook(func(seatIndex account.SeatIndex, account iotago.AccountID) { - Component.LogWarnf("OnlineCommitteeSeatAdded: %s - %d", account.ToHex(), seatIndex) + Component.LogWarnf("OnlineCommitteeSeatAdded, accountID: %s, seatIndex: %d", account.ToHex(), seatIndex) }) deps.Protocol.Events.Engine.SeatManager.OnlineCommitteeSeatRemoved.Hook(func(seatIndex account.SeatIndex) { - Component.LogWarnf("OnlineCommitteeSeatRemoved: seatIndex: %d", seatIndex) + Component.LogWarnf("OnlineCommitteeSeatRemoved, seatIndex: %d", seatIndex) }) deps.Protocol.Events.Engine.Booker.TransactionInvalid.Hook(func(transaction mempool.TransactionMetadata, reason error) { - Component.LogWarnf("TransactionInvalid: transaction %s - %s", transaction.ID(), reason.Error()) + Component.LogWarnf("TransactionInvalid, transactionID: %s, error: %s", transaction.ID(), reason.Error()) }) return nil diff --git a/components/restapi/core/accounts.go b/components/restapi/core/accounts.go index ed6f62648..91c6211c3 100644 --- a/components/restapi/core/accounts.go +++ b/components/restapi/core/accounts.go @@ -26,10 +26,10 @@ func congestionForAccountID(c echo.Context) (*apimodels.CongestionResponse, erro acc, exists, err := deps.Protocol.MainEngineInstance().Ledger.Account(accountID, commitment.Slot()) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get account: %s form the Ledger", accountID.ToHex()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get account %s from the Ledger: %s", accountID.ToHex(), err) } if !exists { - return nil, ierrors.Errorf("account not found: %s", accountID.ToHex()) + return nil, ierrors.Wrapf(echo.ErrNotFound, "account not found: %s", accountID.ToHex()) } return &apimodels.CongestionResponse{ @@ -46,7 +46,7 @@ func validators(c echo.Context) (*apimodels.ValidatorsResponse, error) { if len(c.QueryParam(restapipkg.QueryParameterPageSize)) > 0 { pageSize, err = httpserver.ParseUint32QueryParam(c, restapipkg.QueryParameterPageSize) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse the %s parameter", restapipkg.QueryParameterPageSize) + return nil, ierrors.Wrapf(err, "failed to parse page size %s", c.Param(restapipkg.QueryParameterPageSize)) } if pageSize > restapi.ParamsRestAPI.MaxPageSize { pageSize = restapi.ParamsRestAPI.MaxPageSize @@ -59,13 +59,13 @@ func validators(c echo.Context) (*apimodels.ValidatorsResponse, error) { if len(c.QueryParam(restapipkg.QueryParameterCursor)) != 0 { requestedSlot, cursorIndex, err = httpserver.ParseCursorQueryParam(c, restapipkg.QueryParameterCursor) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse the %s parameter", restapipkg.QueryParameterCursor) + return nil, ierrors.Wrapf(err, "failed to parse cursor %s", c.Param(restapipkg.QueryParameterCursor)) } } // do not respond to really old requests if requestedSlot+iotago.SlotIndex(restapi.ParamsRestAPI.MaxRequestedSlotAge) < latestCommittedSlot { - return nil, ierrors.Errorf("request is too old, request started at %d, latest committed slot index is %d", requestedSlot, latestCommittedSlot) + return nil, ierrors.Wrapf(echo.ErrBadRequest, "request is too old, request started at %d, latest committed slot index is %d", requestedSlot, latestCommittedSlot) } nextEpoch := deps.Protocol.APIForSlot(latestCommittedSlot).TimeProvider().EpochFromSlot(latestCommittedSlot) + 1 @@ -75,7 +75,7 @@ func validators(c echo.Context) (*apimodels.ValidatorsResponse, error) { if !exists { registeredValidators, err = deps.Protocol.MainEngineInstance().SybilProtection.OrderedRegisteredCandidateValidatorsList(nextEpoch) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get ordered registered validators list for epoch %d", nextEpoch) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get ordered registered validators list for epoch %d : %s", nextEpoch, err) } deps.Protocol.MainEngineInstance().Retainer.RetainRegisteredValidatorsCache(slotRange, registeredValidators) } @@ -98,19 +98,23 @@ func validators(c echo.Context) (*apimodels.ValidatorsResponse, error) { func validatorByAccountID(c echo.Context) (*apimodels.ValidatorResponse, error) { accountID, err := httpserver.ParseAccountIDParam(c, restapipkg.ParameterAccountID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse the %s parameter", restapipkg.ParameterAccountID) + return nil, ierrors.Wrapf(err, "failed to parse account ID %s", c.Param(restapipkg.ParameterAccountID)) } latestCommittedSlot := deps.Protocol.MainEngineInstance().SyncManager.LatestCommitment().Slot() accountData, exists, err := deps.Protocol.MainEngineInstance().Ledger.Account(accountID, latestCommittedSlot) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get account: %s form the Ledger", accountID.ToHex()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get account %s from the Ledger: %s", accountID.ToHex(), err) } if !exists { - return nil, ierrors.Errorf("account not found: %s for latest committedSlot %d", accountID.ToHex(), latestCommittedSlot) + return nil, ierrors.Wrapf(echo.ErrNotFound, "account %s not found for latest committedSlot %d", accountID.ToHex(), latestCommittedSlot) } nextEpoch := deps.Protocol.APIForSlot(latestCommittedSlot).TimeProvider().EpochFromSlot(latestCommittedSlot) + 1 - active := deps.Protocol.MainEngineInstance().SybilProtection.IsCandidateActive(accountID, nextEpoch) + + active, err := deps.Protocol.MainEngineInstance().SybilProtection.IsCandidateActive(accountID, nextEpoch) + if err != nil { + return nil, ierrors.Wrapf(err, "failed to check if account %s is an active candidate", accountID.ToHex()) + } return &apimodels.ValidatorResponse{ AccountID: accountID, @@ -127,12 +131,12 @@ func validatorByAccountID(c echo.Context) (*apimodels.ValidatorResponse, error) func rewardsByOutputID(c echo.Context) (*apimodels.ManaRewardsResponse, error) { outputID, err := httpserver.ParseOutputIDParam(c, restapipkg.ParameterOutputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse the %s parameter", restapipkg.ParameterOutputID) + return nil, ierrors.Wrapf(err, "failed to parse output ID %s", c.Param(restapipkg.ParameterOutputID)) } utxoOutput, err := deps.Protocol.MainEngineInstance().Ledger.Output(outputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get output %s from ledger", outputID.ToHex()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get output %s from ledger: %s", outputID.ToHex(), err) } var reward iotago.Mana @@ -143,7 +147,7 @@ func rewardsByOutputID(c echo.Context) (*apimodels.ManaRewardsResponse, error) { accountOutput := utxoOutput.Output().(*iotago.AccountOutput) feature, exists := accountOutput.FeatureSet()[iotago.FeatureStaking] if !exists { - return nil, ierrors.Errorf("account %s is not a validator", outputID) + return nil, ierrors.Wrapf(echo.ErrBadRequest, "account %s is not a validator", outputID.ToHex()) } //nolint:forcetypeassert @@ -174,7 +178,7 @@ func rewardsByOutputID(c echo.Context) (*apimodels.ManaRewardsResponse, error) { ) } if err != nil { - return nil, ierrors.Wrapf(err, "failed to calculate reward for output %s", outputID) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to calculate reward for output %s: %s", outputID.ToHex(), err) } return &apimodels.ManaRewardsResponse{ @@ -198,7 +202,13 @@ func selectedCommittee(c echo.Context) *apimodels.CommitteeResponse { slot = timeProvider.EpochEnd(epoch) } - seatedAccounts := deps.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(slot) + seatedAccounts, exists := deps.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(slot) + if !exists { + return &apimodels.CommitteeResponse{ + Epoch: epoch, + } + } + committee := make([]*apimodels.CommitteeMemberResponse, 0, seatedAccounts.Accounts().Size()) seatedAccounts.Accounts().ForEach(func(accountID iotago.AccountID, seat *account.Pool) bool { committee = append(committee, &apimodels.CommitteeMemberResponse{ diff --git a/components/restapi/core/blocks.go b/components/restapi/core/blocks.go index 03c3598d6..5db11730f 100644 --- a/components/restapi/core/blocks.go +++ b/components/restapi/core/blocks.go @@ -15,12 +15,12 @@ import ( func blockByID(c echo.Context) (*model.Block, error) { blockID, err := httpserver.ParseBlockIDParam(c, restapi.ParameterBlockID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse block ID: %s", c.Param(restapi.ParameterBlockID)) + return nil, ierrors.Wrapf(err, "failed to parse block ID %s", c.Param(restapi.ParameterBlockID)) } block, exists := deps.Protocol.MainEngineInstance().Block(blockID) if !exists { - return nil, ierrors.Errorf("block not found: %s", blockID.ToHex()) + return nil, ierrors.Wrapf(echo.ErrNotFound, "block not found: %s", blockID.ToHex()) } return block, nil @@ -29,7 +29,7 @@ func blockByID(c echo.Context) (*model.Block, error) { func blockMetadataByBlockID(blockID iotago.BlockID) (*apimodels.BlockMetadataResponse, error) { blockMetadata, err := deps.Protocol.MainEngineInstance().Retainer.BlockMetadata(blockID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get block metadata: %s", blockID.ToHex()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get block metadata %s: %s", blockID.ToHex(), err) } return blockMetadata.BlockMetadataResponse(), nil @@ -38,7 +38,7 @@ func blockMetadataByBlockID(blockID iotago.BlockID) (*apimodels.BlockMetadataRes func blockMetadataByID(c echo.Context) (*apimodels.BlockMetadataResponse, error) { blockID, err := httpserver.ParseBlockIDParam(c, restapi.ParameterBlockID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse block ID: %s", c.Param(restapi.ParameterBlockID)) + return nil, ierrors.Wrapf(err, "failed to parse block ID %s", c.Param(restapi.ParameterBlockID)) } return blockMetadataByBlockID(blockID) @@ -55,7 +55,7 @@ func blockIssuanceBySlot(slotIndex iotago.SlotIndex) (*apimodels.IssuanceBlockHe } else { slotCommitment, err = deps.Protocol.MainEngineInstance().Storage.Commitments().Load(slotIndex) if err != nil { - return nil, ierrors.Wrapf(err, "failed to load commitment for requested slot %d", slotIndex) + return nil, ierrors.Wrapf(echo.ErrNotFound, "failed to load commitment for requested slot %d: %s", slotIndex, err) } } @@ -77,7 +77,7 @@ func blockIssuanceBySlot(slotIndex iotago.SlotIndex) (*apimodels.IssuanceBlockHe func sendBlock(c echo.Context) (*apimodels.BlockCreatedResponse, error) { iotaBlock, err := httpserver.ParseRequestByHeader(c, deps.Protocol.CommittedAPI(), iotago.ProtocolBlockFromBytes(deps.Protocol)) if err != nil { - return nil, err + return nil, ierrors.Wrapf(err, "failed to parse iotablock") } blockID, err := deps.BlockHandler.AttachBlock(c.Request().Context(), iotaBlock) diff --git a/components/restapi/core/commitment.go b/components/restapi/core/commitment.go index 99059825c..4ff0f581d 100644 --- a/components/restapi/core/commitment.go +++ b/components/restapi/core/commitment.go @@ -13,7 +13,7 @@ import ( func indexByCommitmentID(c echo.Context) (iotago.SlotIndex, error) { commitmentID, err := httpserver.ParseCommitmentIDParam(c, restapipkg.ParameterCommitmentID) if err != nil { - return iotago.SlotIndex(0), ierrors.Wrapf(err, "failed to parse commitment ID: %s", c.Param(restapipkg.ParameterCommitmentID)) + return iotago.SlotIndex(0), ierrors.Wrapf(err, "failed to parse commitment ID %s", c.Param(restapipkg.ParameterCommitmentID)) } return commitmentID.Slot(), nil @@ -22,7 +22,7 @@ func indexByCommitmentID(c echo.Context) (iotago.SlotIndex, error) { func getCommitmentDetails(index iotago.SlotIndex) (*iotago.Commitment, error) { commitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(index) if err != nil { - return nil, ierrors.Wrapf(err, "failed to load commitment: %d", index) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment %d: %s", index, err) } return commitment.Commitment(), nil @@ -31,7 +31,7 @@ func getCommitmentDetails(index iotago.SlotIndex) (*iotago.Commitment, error) { func getUTXOChanges(slot iotago.SlotIndex) (*apimodels.UTXOChangesResponse, error) { diffs, err := deps.Protocol.MainEngineInstance().Ledger.SlotDiffs(slot) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get slot diffs: %d", slot) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get slot diffs %d: %s", slot, err) } createdOutputs := make(iotago.OutputIDs, len(diffs.Outputs)) diff --git a/components/restapi/core/transaction.go b/components/restapi/core/transaction.go index 44575426a..93f660134 100644 --- a/components/restapi/core/transaction.go +++ b/components/restapi/core/transaction.go @@ -14,7 +14,7 @@ import ( func blockIDByTransactionID(c echo.Context) (iotago.BlockID, error) { txID, err := httpserver.ParseTransactionIDParam(c, restapipkg.ParameterTransactionID) if err != nil { - return iotago.EmptyBlockID, ierrors.Wrapf(err, "failed to parse transaction ID: %s", c.Param(restapipkg.ParameterTransactionID)) + return iotago.EmptyBlockID, ierrors.Wrapf(err, "failed to parse transaction ID %s", c.Param(restapipkg.ParameterTransactionID)) } return blockIDFromTransactionID(txID) @@ -26,7 +26,7 @@ func blockIDFromTransactionID(transactionID iotago.TransactionID) (iotago.BlockI output, spent, err := deps.Protocol.MainEngineInstance().Ledger.OutputOrSpent(outputID) if err != nil { - return iotago.EmptyBlockID, ierrors.Wrapf(err, "failed to get output: %s", outputID.ToHex()) + return iotago.EmptyBlockID, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get output %s: %s", outputID.ToHex(), err) } if output != nil { @@ -39,12 +39,12 @@ func blockIDFromTransactionID(transactionID iotago.TransactionID) (iotago.BlockI func blockByTransactionID(c echo.Context) (*model.Block, error) { blockID, err := blockIDByTransactionID(c) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get block ID by transaction ID") + return nil, ierrors.Wrapf(echo.ErrBadRequest, "failed to get block ID by transaction ID: %s", err) } block, exists := deps.Protocol.MainEngineInstance().Block(blockID) if !exists { - return nil, ierrors.Errorf("block not found: %s", blockID.String()) + return nil, ierrors.Wrapf(echo.ErrNotFound, "block not found: %s", blockID.ToHex()) } return block, nil @@ -53,7 +53,7 @@ func blockByTransactionID(c echo.Context) (*model.Block, error) { func blockMetadataFromTransactionID(c echo.Context) (*apimodels.BlockMetadataResponse, error) { blockID, err := blockIDByTransactionID(c) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get block ID by transaction ID") + return nil, ierrors.Wrapf(echo.ErrBadRequest, "failed to get block ID by transaction ID: %s", err) } return blockMetadataByBlockID(blockID) diff --git a/components/restapi/core/utxo.go b/components/restapi/core/utxo.go index 196b6c514..bdf62c89c 100644 --- a/components/restapi/core/utxo.go +++ b/components/restapi/core/utxo.go @@ -13,12 +13,12 @@ import ( func getOutput(c echo.Context) (*apimodels.OutputResponse, error) { outputID, err := httpserver.ParseOutputIDParam(c, restapipkg.ParameterOutputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse output ID param: %s", c.Param(restapipkg.ParameterOutputID)) + return nil, ierrors.Wrapf(err, "failed to parse output ID %s", c.Param(restapipkg.ParameterOutputID)) } output, err := deps.Protocol.MainEngineInstance().Ledger.Output(outputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get output: %s from the Ledger", outputID.String()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get output %s from the Ledger: %s", outputID.ToHex(), err) } return &apimodels.OutputResponse{ @@ -30,12 +30,12 @@ func getOutput(c echo.Context) (*apimodels.OutputResponse, error) { func getOutputMetadata(c echo.Context) (*apimodels.OutputMetadata, error) { outputID, err := httpserver.ParseOutputIDParam(c, restapipkg.ParameterOutputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse output ID param: %s", c.Param(restapipkg.ParameterOutputID)) + return nil, ierrors.Wrapf(err, "failed to parse output ID %s", c.Param(restapipkg.ParameterOutputID)) } output, spent, err := deps.Protocol.MainEngineInstance().Ledger.OutputOrSpent(outputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get output: %s from the Ledger", outputID.String()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get output %s from the Ledger: %s", outputID.ToHex(), err) } if spent != nil { @@ -48,18 +48,18 @@ func getOutputMetadata(c echo.Context) (*apimodels.OutputMetadata, error) { func getOutputWithMetadata(c echo.Context) (*apimodels.OutputWithMetadataResponse, error) { outputID, err := httpserver.ParseOutputIDParam(c, restapipkg.ParameterOutputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to parse output ID param: %s", c.Param(restapipkg.ParameterOutputID)) + return nil, ierrors.Wrapf(err, "failed to parse output ID %s", c.Param(restapipkg.ParameterOutputID)) } output, spent, err := deps.Protocol.MainEngineInstance().Ledger.OutputOrSpent(outputID) if err != nil { - return nil, ierrors.Wrapf(err, "failed to get output: %s from the Ledger", outputID.String()) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to get output %s from the Ledger: %s", outputID.ToHex(), err) } if spent != nil { metadata, err := newSpentMetadataResponse(spent) if err != nil { - return nil, err + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load spent output metadata: %s", err) } return &apimodels.OutputWithMetadataResponse{ @@ -96,7 +96,7 @@ func newOutputMetadataResponse(output *utxoledger.Output) (*apimodels.OutputMeta if includedSlotIndex <= latestCommitment.Slot() { includedCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(includedSlotIndex) if err != nil { - return nil, ierrors.Wrapf(err, "failed to load commitment with index: %d", includedSlotIndex) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment with index %d: %s", includedSlotIndex, err) } resp.IncludedCommitmentID = includedCommitment.ID() } @@ -120,7 +120,7 @@ func newSpentMetadataResponse(spent *utxoledger.Spent) (*apimodels.OutputMetadat if includedSlotIndex <= latestCommitment.Slot() { includedCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(includedSlotIndex) if err != nil { - return nil, ierrors.Wrapf(err, "failed to load commitment with index: %d", includedSlotIndex) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment with index %d: %s", includedSlotIndex, err) } resp.IncludedCommitmentID = includedCommitment.ID() } @@ -129,7 +129,7 @@ func newSpentMetadataResponse(spent *utxoledger.Spent) (*apimodels.OutputMetadat if spentSlotIndex <= latestCommitment.Slot() { spentCommitment, err := deps.Protocol.MainEngineInstance().Storage.Commitments().Load(spentSlotIndex) if err != nil { - return nil, ierrors.Wrapf(err, "failed to load commitment with index: %d", spentSlotIndex) + return nil, ierrors.Wrapf(echo.ErrInternalServerError, "failed to load commitment with index %d: %s", spentSlotIndex, err) } resp.CommitmentIDSpent = spentCommitment.ID() } diff --git a/components/restapi/params.go b/components/restapi/params.go index 440fdc102..121d07ae8 100644 --- a/components/restapi/params.go +++ b/components/restapi/params.go @@ -9,7 +9,7 @@ type ParametersRestAPI struct { // Enabled defines whether the REST API plugin is enabled. Enabled bool `default:"true" usage:"whether the REST API plugin is enabled"` // the bind address on which the REST API listens on - BindAddress string `default:"0.0.0.0:8080" usage:"the bind address on which the REST API listens on"` + BindAddress string `default:"0.0.0.0:14265" usage:"the bind address on which the REST API listens on"` // the HTTP REST routes which can be called without authorization. Wildcards using * are allowed PublicRoutes []string `usage:"the HTTP REST routes which can be called without authorization. Wildcards using * are allowed"` // the HTTP REST routes which need to be called with authorization. Wildcards using * are allowed diff --git a/components/validator/issuer.go b/components/validator/issuer.go index 7f325777c..8a748b58f 100644 --- a/components/validator/issuer.go +++ b/components/validator/issuer.go @@ -81,7 +81,15 @@ func issueValidatorBlock(ctx context.Context) { return } - if !engineInstance.SybilProtection.SeatManager().Committee(deps.Protocol.CommittedAPI().TimeProvider().SlotFromTime(blockIssuingTime)).HasAccount(validatorAccount.ID()) { + blockSlot := deps.Protocol.CommittedAPI().TimeProvider().SlotFromTime(blockIssuingTime) + committee, exists := engineInstance.SybilProtection.SeatManager().CommitteeInSlot(blockSlot) + if !exists { + Component.LogWarnf("committee for slot %d not selected: %s", blockSlot, err.Error()) + + return + } + + if !committee.HasAccount(validatorAccount.ID()) { // update nextBroadcast value here, so that this updated value is used in the `defer` // callback to schedule issuing of the next block at a different interval than for committee members nextBroadcast = blockIssuingTime.Add(ParamsValidator.CandidateBroadcastInterval) diff --git a/config_defaults.json b/config_defaults.json index 2dc55de16..f24471e14 100644 --- a/config_defaults.json +++ b/config_defaults.json @@ -44,7 +44,7 @@ }, "restAPI": { "enabled": true, - "bindAddress": "0.0.0.0:8080", + "bindAddress": "0.0.0.0:14265", "publicRoutes": [ "/health", "/api/routes", @@ -83,7 +83,7 @@ "pruningThreshold": 1, "dbGranularity": 100 }, - "metricstracker": { + "metricsTracker": { "enabled": true }, "database": { diff --git a/deploy/ansible/hosts/feature.yml b/deploy/ansible/hosts/feature.yml index 52405d60a..ac8e44cfc 100644 --- a/deploy/ansible/hosts/feature.yml +++ b/deploy/ansible/hosts/feature.yml @@ -8,22 +8,20 @@ cores: hosts: node-01.feature.shimmer.iota.cafe: validatorAccount: "{{ NODE_01_ACCOUNTID }}" - validatorPrivKey: "{{ NODE_01_VALIDATOR_PRIVKEY }}" - p2pIdentityPrivateKey: "{{ NODE_01_P2PIDENTITYPRIVATEKEY }}" + validatorPrvKey: "{{ NODE_01_VALIDATOR_PRIVKEY }}" + p2pIdentityPrvKey: "{{ NODE_01_P2PIDENTITYPRIVATEKEY }}" node-02.feature.shimmer.iota.cafe: validatorAccount: "{{ NODE_02_ACCOUNTID }}" - validatorPrivKey: "{{ NODE_02_VALIDATOR_PRIVKEY }}" - p2pIdentityPrivateKey: "{{ NODE_02_P2PIDENTITYPRIVATEKEY }}" + validatorPrvKey: "{{ NODE_02_VALIDATOR_PRIVKEY }}" + p2pIdentityPrvKey: "{{ NODE_02_P2PIDENTITYPRIVATEKEY }}" node-03.feature.shimmer.iota.cafe: validatorAccount: "{{ NODE_03_ACCOUNTID }}" - validatorPrivKey: "{{ NODE_03_VALIDATOR_PRIVKEY }}" - p2pIdentityPrivateKey: "{{ NODE_03_P2PIDENTITYPRIVATEKEY }}" + validatorPrvKey: "{{ NODE_03_VALIDATOR_PRIVKEY }}" + p2pIdentityPrvKey: "{{ NODE_03_P2PIDENTITYPRIVATEKEY }}" node-04.feature.shimmer.iota.cafe: - validatorAccount: "{{ NODE_04_ACCOUNTID }}" - validatorPrivKey: "{{ NODE_04_VALIDATOR_PRIVKEY }}" - p2pIdentityPrivateKey: "{{ NODE_04_P2PIDENTITYPRIVATEKEY }}" + p2pIdentityPrvKey: "{{ NODE_04_P2PIDENTITYPRIVATEKEY }}" + blockissuerPrvKey: "{{ NODE_04_BLOCKISSUER_PRV_KEY }}" + faucetPrvKey: "{{ NODE_04_FAUCET_PRV_KEY }}" node-05.feature.shimmer.iota.cafe: - validatorAccount: "{{ NODE_05_ACCOUNTID }}" - validatorPrivKey: "{{ NODE_05_VALIDATOR_PRIVKEY }}" - p2pIdentityPrivateKey: "{{ NODE_05_P2PIDENTITYPRIVATEKEY }}" + p2pIdentityPrvKey: "{{ NODE_05_P2PIDENTITYPRIVATEKEY }}" vars: diff --git a/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2 b/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2 index e86b3be51..d3bc9d82b 100644 --- a/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2 +++ b/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2 @@ -9,58 +9,120 @@ version: '3.3' services: + +################### +# IOTA-CORE Nodes # +################### + iota_core: image: {{iota_core_docker_image_repo}}:{{iota_core_docker_image_tag}} container_name: iota-core + stop_grace_period: 1m + restart: unless-stopped + ulimits: + nofile: + soft: 16384 + hard: 16384 + ports: + - "14666:14666/tcp" # P2P + - "6061:6061/tcp" # pprof + - "8080:14265/tcp" # REST-API + - "8081:8081/tcp" # Dashboard + - "9311:9311/tcp" # Prometheus + - "9029:9029/tcp" # INX volumes: - - ./snapshot.bin:/app/data/snapshot.bin:ro - ./config.json:/app/config.json:ro - ./data:/app/data/ + - ./snapshot.bin:/app/data/snapshot.bin:ro - /etc/localtime:/etc/localtime:ro - ports: - - "0.0.0.0:14666:14666/tcp" - - "0.0.0.0:8080:8080/tcp" - - "0.0.0.0:8081:8081/tcp" - - "0.0.0.0:6061:6061/tcp" - # prometheus - - "0.0.0.0:9311:9311/tcp" - environment: - - WEBAPI_BINDADDRESS=0.0.0.0:8080 - - DASHBOARD_BINDADDRESS=0.0.0.0:8081 - - PROFILING_BINDADDRESS=0.0.0.0:6061 command: > - -c config.json + -c + config.json --logger.level=debug --logger.disableCaller=false - --logger.disableStacktrace=false - --logger.encoding=console - --logger.outputPaths=stdout - --database.path=/app/data/database + --p2p.peers=/dns/node-01.feature/tcp/14666/p2p/12D3KooWCrjmh4dUCWfGVQT6ivzArieJB9Z3eKdy2mdEEN95NDPS + --p2p.externalMultiAddresses={{ ips | join(',') }} + --p2p.identityPrivateKey={{p2pIdentityPrvKey}} --p2p.db.path=/app/data/peerdb - --profiling.bindAddress=0.0.0.0:6061 --profiling.enabled=true + --profiling.bindAddress=0.0.0.0:6061 + --restAPI.bindAddress=0.0.0.0:14265 + --database.path=/app/data/database --protocol.snapshot.path=/app/data/snapshot.bin {% if 'node-01' in inventory_hostname or 'node-02' in inventory_hostname or 'node-03' in inventory_hostname %} --validator.enabled=true - --validator.account={{validatorAccount}} - --validator.privateKey={{validatorPrivKey}} - {% endif %} - {% if 'node-01' in inventory_hostname %} + {% if 'node-01' in inventory_hostname %} --validator.ignoreBootstrapped=true + {% endif %} + --validator.account={{validatorAccount}} + --validator.privateKey={{validatorPrvKey}} {% endif %} - --p2p.peers=/dns/node-01.feature/tcp/14666/p2p/12D3KooWCrjmh4dUCWfGVQT6ivzArieJB9Z3eKdy2mdEEN95NDPS - --p2p.externalMultiAddresses={{ ips | join(',') }} - --p2p.identityPrivateKey={{p2pIdentityPrivateKey}} + --dashboard.bindAddress=0.0.0.0:8081 + --metrics.bindAddress=iota-core:9311 --inx.enabled=true --inx.bindAddress=iota-core:9029 +################## +# INX Extensions # +################## + inx-indexer: container_name: inx-indexer image: iotaledger/inx-indexer:2.0-alpha - stop_grace_period: 5m + stop_grace_period: 1m + restart: unless-stopped + depends_on: + iota-core: + condition: service_healthy + ulimits: + nofile: + soft: 16384 + hard: 16384 volumes: - ./data:/app/database - command: - - "--inx.address=iota-core:9029" - - "--indexer.db.sqlite.path=database/indexer" - - "--restAPI.bindAddress=inx-indexer:9091" \ No newline at end of file + command: > + --inx.address=iota-core:9029 + --indexer.db.sqlite.path=database/indexer + --restAPI.bindAddress=inx-indexer:9091 + +{% if 'node-04' in inventory_hostname %} + inx-blockissuer: + container_name: inx-blockissuer + image: iotaledger/inx-blockissuer:1.0-alpha + stop_grace_period: 1m + restart: unless-stopped + depends_on: + iota-core: + condition: service_healthy + inx-indexer: + condition: service_started + environment: + - "BLOCKISSUER_PRV_KEY={{blockissuerPrvKey}}" + command: > + --inx.address=iota-core:9029 + --restAPI.bindAddress=inx-blockissuer:9086 + --blockIssuer.accountAddress=rms1pqas0clgfsf8du9e6dw0yx9nwclqe0dd4f728pvgmcshpscm8r5mkddrrfc + --blockIssuer.proofOfWork.targetTrailingZeros=5 + + inx-faucet: + container_name: inx-faucet + image: iotaledger/inx-faucet:2.0-alpha + stop_grace_period: 1m + restart: unless-stopped + depends_on: + iota-core: + condition: service_healthy + inx-indexer: + condition: service_started + inx-blockissuer: + condition: service_started + networks: + - iota-core + ports: + - "8091:8091/tcp" # Faucet Frontend + environment: + - "FAUCET_PRV_KEY={{faucetPrvKey}}" + command: > + --inx.address=iota-core:9029 + --faucet.bindAddress=0.0.0.0:8091 +{% endif %} \ No newline at end of file diff --git a/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard.json b/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard.json index f464af4c6..571712c82 100644 --- a/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard.json +++ b/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard.json @@ -1340,8 +1340,8 @@ { "current": { "selected": false, - "text": "validator-3:9311", - "value": "validator-3:9311" + "text": "node-3-validator:9311", + "value": "node-3-validator:9311" }, "datasource": { "type": "prometheus", diff --git a/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard_old.json b/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard_old.json index 71fb84826..320e8c1a6 100644 --- a/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard_old.json +++ b/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/local_dashboard_old.json @@ -4847,8 +4847,8 @@ { "current": { "selected": false, - "text": "validator-3:9311", - "value": "validator-3:9311" + "text": "node-3-validator:9311", + "value": "node-3-validator:9311" }, "datasource": { "type": "prometheus", diff --git a/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/scheduler-metrics.json b/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/scheduler-metrics.json index c3d8798c3..e76dd6b1c 100644 --- a/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/scheduler-metrics.json +++ b/deploy/ansible/roles/metrics/files/grafana/provisioning/dashboards/scheduler-metrics.json @@ -772,8 +772,8 @@ { "current": { "selected": true, - "text": "validator-2:9311", - "value": "validator-2:9311" + "text": "node-2-validator:9311", + "value": "node-2-validator:9311" }, "datasource": { "type": "prometheus", diff --git a/deploy/ansible/run.sh b/deploy/ansible/run.sh index 766e1e3df..3d800611d 100755 --- a/deploy/ansible/run.sh +++ b/deploy/ansible/run.sh @@ -16,20 +16,23 @@ wireguard_server_private_key=$WIREGUARD_SERVER_PRIVKEY elkElasticUser=$ELASTIC_USER elkElasticPassword=$ELASTIC_PASSWORD grafanaAdminPassword=$GRAFANA_ADMIN_PASSWORD + NODE_01_ACCOUNTID=$NODE_01_ACCOUNTID NODE_01_VALIDATOR_PRIVKEY=$NODE_01_VALIDATOR_PRIVKEY NODE_01_P2PIDENTITYPRIVATEKEY=$NODE_01_P2PIDENTITYPRIVATEKEY + NODE_02_ACCOUNTID=$NODE_02_ACCOUNTID NODE_02_VALIDATOR_PRIVKEY=$NODE_02_VALIDATOR_PRIVKEY NODE_02_P2PIDENTITYPRIVATEKEY=$NODE_02_P2PIDENTITYPRIVATEKEY + NODE_03_ACCOUNTID=$NODE_03_ACCOUNTID NODE_03_VALIDATOR_PRIVKEY=$NODE_03_VALIDATOR_PRIVKEY NODE_03_P2PIDENTITYPRIVATEKEY=$NODE_03_P2PIDENTITYPRIVATEKEY -NODE_04_ACCOUNTID=$NODE_04_ACCOUNTID -NODE_04_VALIDATOR_PRIVKEY=$NODE_04_VALIDATOR_PRIVKEY + NODE_04_P2PIDENTITYPRIVATEKEY=$NODE_04_P2PIDENTITYPRIVATEKEY -NODE_05_ACCOUNTID=$NODE_05_ACCOUNTID -NODE_05_VALIDATOR_PRIVKEY=$NODE_05_VALIDATOR_PRIVKEY +NODE_04_BLOCKISSUER_PRV_KEY=$NODE_04_BLOCKISSUER_PRIVKEY +NODE_04_FAUCET_PRV_KEY=$NODE_04_FAUCET_PRIVKEY + NODE_05_P2PIDENTITYPRIVATEKEY=$NODE_05_P2PIDENTITYPRIVATEKEY -NODE_05_P2PPUBKEY=$NODE_05_P2PPUBKEY" \ + ${ARGS[@]:2} deploy/ansible/"${2:-deploy.yml}" diff --git a/documentation/docs/references/configuration.md b/documentation/docs/references/configuration.md index acf7bfa3f..ba034fde1 100644 --- a/documentation/docs/references/configuration.md +++ b/documentation/docs/references/configuration.md @@ -175,7 +175,7 @@ Example: | Name | Description | Type | Default value | | ------------------------------ | ---------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | enabled | Whether the REST API plugin is enabled | boolean | true | -| bindAddress | The bind address on which the REST API listens on | string | "0.0.0.0:8080" | +| bindAddress | The bind address on which the REST API listens on | string | "0.0.0.0:14265" | | publicRoutes | The HTTP REST routes which can be called without authorization. Wildcards using \* are allowed | array | /health
/api/routes
/api/core/v3/info
/api/core/v3/blocks\*
/api/core/v3/transactions\*
/api/core/v3/commitments\*
/api/core/v3/outputs\*
/api/core/v3/accounts\*
/api/core/v3/validators\*
/api/core/v3/rewards\*
/api/core/v3/committee
/api/debug/v2/\*
/api/indexer/v2/\*
/api/mqtt/v2 | | protectedRoutes | The HTTP REST routes which need to be called with authorization. Wildcards using \* are allowed | array | /api/\* | | debugRequestLoggerEnabled | Whether the debug logging for requests should be enabled | boolean | false | @@ -204,7 +204,7 @@ Example: { "restAPI": { "enabled": true, - "bindAddress": "0.0.0.0:8080", + "bindAddress": "0.0.0.0:14265", "publicRoutes": [ "/health", "/api/routes", @@ -263,7 +263,7 @@ Example: } ``` -## 7. Metricstracker +## 7. MetricsTracker | Name | Description | Type | Default value | | ------- | --------------------------------------------- | ------- | ------------- | @@ -273,7 +273,7 @@ Example: ```json { - "metricstracker": { + "metricsTracker": { "enabled": true } } diff --git a/go.mod b/go.mod index 5ed1609ae..2fa655a8c 100644 --- a/go.mod +++ b/go.mod @@ -23,18 +23,20 @@ require ( github.com/iotaledger/hive.go/runtime v0.0.0-20231020115340-13da292c580b github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b - github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231023191159-38919c4705e0 - github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231023190837-6e7b2cdfd4fd - github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 + github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231026154618-820b8eaed2cc + github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231026154523-9ee0c47a283a + github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d github.com/labstack/echo/v4 v4.11.2 github.com/labstack/gommon v0.4.0 github.com/libp2p/go-libp2p v0.31.0 github.com/libp2p/go-libp2p-kad-dht v0.25.1 + github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multiaddr v0.12.0 github.com/multiformats/go-varint v0.0.7 github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e github.com/otiai10/copy v1.14.0 github.com/prometheus/client_golang v1.17.0 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 github.com/zyedidia/generic v1.2.1 @@ -126,7 +128,6 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect @@ -157,7 +158,6 @@ require ( github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect diff --git a/go.sum b/go.sum index c3565bd8b..d8951f2a0 100644 --- a/go.sum +++ b/go.sum @@ -305,12 +305,12 @@ github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292 github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b/go.mod h1:SdK26z8/VhWtxaqCuQrufm80SELgowQPmu9T/8eUQ8g= github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b h1:MDZhTZTVDiydXcW5j4TA7HixVCyAdToIMPhHfJee7cE= github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= -github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231023191159-38919c4705e0 h1:/8pbFXhTSroJvjJMfJqfHjzoT9N8B4LUY3SbKruD5MM= -github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231023191159-38919c4705e0/go.mod h1:My1SB4vZj42EgTDNJ/dgW8lUpLNmvtzu8f89J5y2kP0= -github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231023190837-6e7b2cdfd4fd h1:hh5mAnnaZHOYAi4CIqR9K/mv786ex9AQgpisbJ4ZMow= -github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231023190837-6e7b2cdfd4fd/go.mod h1:MK0SHfNicBmcaZb3qS3tA8NEJIWKNbcNtNNKuSDKqXY= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 h1:81aWESXFC04iKI9I140eDrBb9zBWXfVoAUMp9berk0c= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= +github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231026154618-820b8eaed2cc h1:Foz7Q1vNh0Ts+YTEODHO3LSKVGM/uNV3RqbBJS6u7yA= +github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231026154618-820b8eaed2cc/go.mod h1:gaQbe/L+wjjUeQj5N8+o/XdZnSosFmDQfxmfyrK05hc= +github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231026154523-9ee0c47a283a h1:8JbC44pNud1rT091fJA4bDC+35MozksapuCXf8M1kmg= +github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231026154523-9ee0c47a283a/go.mod h1:iXG/tO+GQZQzgIUyITnQDigb6Ny1wSueHFIYne4HBkc= +github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d h1:gcJz0J3xFELIPT7y4xqW+q25oOcK6QMlxNnrfFu8srA= +github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= github.com/ipfs/boxo v0.13.1 h1:nQ5oQzcMZR3oL41REJDcTbrvDvuZh3J9ckc9+ILeRQI= github.com/ipfs/boxo v0.13.1/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= diff --git a/pkg/protocol/commitment_verifier.go b/pkg/protocol/commitment_verifier.go index 1df7c3a97..85e715d89 100644 --- a/pkg/protocol/commitment_verifier.go +++ b/pkg/protocol/commitment_verifier.go @@ -19,15 +19,18 @@ type CommitmentVerifier struct { validatorAccountsAtFork map[iotago.AccountID]*accounts.AccountData } -func NewCommitmentVerifier(mainEngine *engine.Engine, lastCommonCommitmentBeforeFork *model.Commitment) *CommitmentVerifier { - committeeAtForkingPoint := mainEngine.SybilProtection.SeatManager().Committee(lastCommonCommitmentBeforeFork.Slot()).Accounts().IDs() +func NewCommitmentVerifier(mainEngine *engine.Engine, lastCommonCommitmentBeforeFork *model.Commitment) (*CommitmentVerifier, error) { + committeeAtForkingPoint, exists := mainEngine.SybilProtection.SeatManager().CommitteeInSlot(lastCommonCommitmentBeforeFork.Slot()) + if !exists { + return nil, ierrors.Errorf("committee in slot %d does not exist", lastCommonCommitmentBeforeFork.Slot()) + } return &CommitmentVerifier{ engine: mainEngine, cumulativeWeight: lastCommonCommitmentBeforeFork.CumulativeWeight(), - validatorAccountsAtFork: lo.PanicOnErr(mainEngine.Ledger.PastAccounts(committeeAtForkingPoint, lastCommonCommitmentBeforeFork.Slot())), + validatorAccountsAtFork: lo.PanicOnErr(mainEngine.Ledger.PastAccounts(committeeAtForkingPoint.Accounts().IDs(), lastCommonCommitmentBeforeFork.Slot())), // TODO: what happens if the committee rotated after the fork? - } + }, nil } func (c *CommitmentVerifier) verifyCommitment(commitment *model.Commitment, attestations []*iotago.Attestation, merkleProof *merklehasher.Proof[iotago.Identifier]) (blockIDsFromAttestations iotago.BlockIDs, cumulativeWeight uint64, err error) { @@ -153,7 +156,13 @@ func (c *CommitmentVerifier) verifyAttestations(attestations []*iotago.Attestati if err != nil { return nil, 0, ierrors.Wrap(err, "error calculating blockID from attestation") } - if _, seatExists := c.engine.SybilProtection.SeatManager().Committee(attestationBlockID.Slot()).GetSeat(att.IssuerID); seatExists { + + committee, exists := c.engine.SybilProtection.SeatManager().CommitteeInSlot(attestationBlockID.Slot()) + if !exists { + return nil, 0, ierrors.Errorf("committee for slot %d does not exist", attestationBlockID.Slot()) + } + + if _, seatExists := committee.GetSeat(att.IssuerID); seatExists { seatCount++ } diff --git a/pkg/protocol/engine/attestation/attestations.go b/pkg/protocol/engine/attestation/attestations.go index 06572e53b..1803a4b0f 100644 --- a/pkg/protocol/engine/attestation/attestations.go +++ b/pkg/protocol/engine/attestation/attestations.go @@ -17,7 +17,7 @@ type Attestations interface { // GetMap returns the attestations that are included in the commitment of the given slot as ads.Map. // If attestationCommitmentOffset=3 and commitment is 10, then the returned attestations are blocks from 7 to 10 that commit to at least 7. GetMap(index iotago.SlotIndex) (attestations ads.Map[iotago.Identifier, iotago.AccountID, *iotago.Attestation], err error) - AddAttestationFromValidationBlock(block *blocks.Block) + AddAttestationFromValidationBlock(block *blocks.Block) error Commit(index iotago.SlotIndex) (newCW uint64, attestationsRoot iotago.Identifier, err error) Import(reader io.ReadSeeker) (err error) diff --git a/pkg/protocol/engine/attestation/slotattestation/manager.go b/pkg/protocol/engine/attestation/slotattestation/manager.go index 890ffe56b..6b27b5b2d 100644 --- a/pkg/protocol/engine/attestation/slotattestation/manager.go +++ b/pkg/protocol/engine/attestation/slotattestation/manager.go @@ -49,7 +49,7 @@ const ( // - obtain and evict from it attestations that *commit to* lastCommittedSlot-attestationCommitmentOffset // - committed attestations: retrieved at slot that we are committing, stored at slot lastCommittedSlot-attestationCommitmentOffset type Manager struct { - committeeFunc func(slot iotago.SlotIndex) *account.SeatedAccounts + committeeFunc func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) futureAttestations *memstorage.IndexedStorage[iotago.SlotIndex, iotago.AccountID, *iotago.Attestation] pendingAttestations *memstorage.IndexedStorage[iotago.SlotIndex, iotago.AccountID, *iotago.Attestation] @@ -73,7 +73,7 @@ func NewProvider() module.Provider[*engine.Engine, attestation.Attestations] { latestCommitment.Slot(), latestCommitment.CumulativeWeight(), e.Storage.Attestations, - e.SybilProtection.SeatManager().Committee, + e.SybilProtection.SeatManager().CommitteeInSlot, e, ) }) @@ -83,7 +83,7 @@ func NewManager( lastCommittedSlot iotago.SlotIndex, lastCumulativeWeight uint64, bucketedStorage func(slot iotago.SlotIndex) (kvstore.KVStore, error), - committeeFunc func(slot iotago.SlotIndex) *account.SeatedAccounts, + committeeFunc func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool), apiProvider iotago.APIProvider, ) *Manager { m := &Manager{ @@ -145,15 +145,19 @@ func (m *Manager) GetMap(slot iotago.SlotIndex) (ads.Map[iotago.Identifier, iota } // AddAttestationFromValidationBlock adds an attestation from a block to the future attestations (beyond the attestation window). -func (m *Manager) AddAttestationFromValidationBlock(block *blocks.Block) { +func (m *Manager) AddAttestationFromValidationBlock(block *blocks.Block) error { // Only track validator blocks. if _, isValidationBlock := block.ValidationBlock(); !isValidationBlock { - return + return nil } + committee, exists := m.committeeFunc(block.ID().Slot()) + if !exists { + return ierrors.Errorf("committee for slot %d does not exist", block.ID().Slot()) + } // Only track attestations of active committee members. - if _, exists := m.committeeFunc(block.ID().Slot()).GetSeat(block.ProtocolBlock().IssuerID); !exists { - return + if _, exists := committee.GetSeat(block.ProtocolBlock().IssuerID); !exists { + return nil } m.commitmentMutex.RLock() @@ -161,7 +165,7 @@ func (m *Manager) AddAttestationFromValidationBlock(block *blocks.Block) { // We only care about attestations that are newer than the last committed slot. if block.ID().Slot() <= m.lastCommittedSlot { - return + return nil } newAttestation := iotago.NewAttestation(m.apiProvider.APIForSlot(block.ID().Slot()), block.ProtocolBlock()) @@ -179,6 +183,8 @@ func (m *Manager) AddAttestationFromValidationBlock(block *blocks.Block) { return currentValue }) + + return nil } func (m *Manager) applyToPendingAttestations(attestation *iotago.Attestation, cutoffSlot iotago.SlotIndex) { @@ -254,9 +260,14 @@ func (m *Manager) Commit(slot iotago.SlotIndex) (newCW uint64, attestationsRoot } // Add all attestations to the tree and calculate the new cumulative weight. + committee, exists := m.committeeFunc(slot) + if !exists { + return 0, iotago.Identifier{}, ierrors.Wrapf(err, "failed to get committee when committing slot %d", slot) + } + for _, a := range attestations { // TODO: which weight are we using here? The current one? Or the one of the slot of the attestation/commitmentID? - if _, exists := m.committeeFunc(slot).GetSeat(a.IssuerID); exists { + if _, exists := committee.GetSeat(a.IssuerID); exists { if err := tree.Set(a.IssuerID, a); err != nil { return 0, iotago.Identifier{}, ierrors.Wrapf(err, "failed to set attestation %s in tree", a.IssuerID) } diff --git a/pkg/protocol/engine/attestation/slotattestation/testframework_test.go b/pkg/protocol/engine/attestation/slotattestation/testframework_test.go index b931d2a2a..ad77b48fe 100644 --- a/pkg/protocol/engine/attestation/slotattestation/testframework_test.go +++ b/pkg/protocol/engine/attestation/slotattestation/testframework_test.go @@ -57,7 +57,7 @@ func NewTestFramework(test *testing.T) *TestFramework { })), nil } - committeeFunc := func(index iotago.SlotIndex) *account.SeatedAccounts { + committeeFunc := func(index iotago.SlotIndex) (*account.SeatedAccounts, bool) { accounts := account.NewAccounts() var members []iotago.AccountID t.issuerByAlias.ForEach(func(alias string, issuer *issuer) bool { @@ -65,7 +65,7 @@ func NewTestFramework(test *testing.T) *TestFramework { members = append(members, issuer.accountID) return true }) - return accounts.SelectCommittee(members...) + return accounts.SelectCommittee(members...), true } t.testAPI = iotago.V3API( diff --git a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go index dacc96faa..034ead081 100644 --- a/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go +++ b/pkg/protocol/engine/congestioncontrol/scheduler/drr/scheduler.go @@ -66,12 +66,18 @@ func NewProvider(opts ...options.Option[Scheduler]) module.Provider[*engine.Engi }) e.Events.Notarization.LatestCommitmentUpdated.Hook(func(commitment *model.Commitment) { // when the last slot of an epoch is committed, remove the queues of validators that are no longer in the committee. - if s.apiProvider.CommittedAPI().TimeProvider().SlotsBeforeNextEpoch(commitment.Slot()) == 0 { + if s.apiProvider.APIForSlot(commitment.Slot()).TimeProvider().SlotsBeforeNextEpoch(commitment.Slot()) == 0 { s.bufferMutex.Lock() defer s.bufferMutex.Unlock() + committee, exists := s.seatManager.CommitteeInSlot(commitment.Slot() + 1) + if !exists { + s.errorHandler(ierrors.Errorf("committee does not exist in committed slot %d", commitment.Slot()+1)) + + return + } s.validatorBuffer.buffer.ForEach(func(accountID iotago.AccountID, validatorQueue *ValidatorQueue) bool { - if !s.seatManager.Committee(commitment.Slot() + 1).HasAccount(accountID) { + if !committee.HasAccount(accountID) { s.shutdownValidatorQueue(validatorQueue) s.validatorBuffer.Delete(accountID) } @@ -615,7 +621,7 @@ func (s *Scheduler) updateDeficit(accountID iotago.AccountID, delta Deficit) err func (s *Scheduler) incrementDeficit(issuerID iotago.AccountID, rounds Deficit, slot iotago.SlotIndex) error { quantum, err := s.quantumFunc(issuerID, slot) if err != nil { - return err + return ierrors.Wrap(err, "failed to retrieve quantum") } delta, err := safemath.SafeMul(quantum, rounds) diff --git a/pkg/protocol/engine/consensus/blockgadget/testframework_test.go b/pkg/protocol/engine/consensus/blockgadget/testframework_test.go index 144307109..a2d7371bc 100644 --- a/pkg/protocol/engine/consensus/blockgadget/testframework_test.go +++ b/pkg/protocol/engine/consensus/blockgadget/testframework_test.go @@ -9,14 +9,17 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/ds/shrinkingmap" + "github.com/iotaledger/hive.go/kvstore" "github.com/iotaledger/hive.go/kvstore/mapdb" "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/model" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/protocol/engine/consensus/blockgadget" "github.com/iotaledger/iota-core/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget" "github.com/iotaledger/iota-core/pkg/protocol/engine/eviction" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/mock" + "github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore" "github.com/iotaledger/iota-core/pkg/storage/prunable/slotstore" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/api" @@ -40,7 +43,7 @@ func NewTestFramework(test *testing.T) *TestFramework { T: test, blocks: shrinkingmap.New[string, *blocks.Block](), - SeatManager: mock.NewManualPOA(), + SeatManager: mock.NewManualPOA(api.SingleVersionProvider(tpkg.TestAPI), epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes)), } evictionState := eviction.NewState(mapdb.NewMapDB(), func(slot iotago.SlotIndex) (*slotstore.Store[iotago.BlockID, iotago.CommitmentID], error) { @@ -53,7 +56,10 @@ func NewTestFramework(test *testing.T) *TestFramework { }) t.blockCache = blocks.New(evictionState, api.SingleVersionProvider(tpkg.TestAPI)) - instance := thresholdblockgadget.New(t.blockCache, t.SeatManager) + instance := thresholdblockgadget.New(t.blockCache, t.SeatManager, func(err error) { + fmt.Printf(">> Gadget.Error: %s\n", err) + }) + t.Events = instance.Events() t.Instance = instance diff --git a/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go b/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go index 55f53835a..9ee2becde 100644 --- a/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go +++ b/pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/gadget.go @@ -5,6 +5,7 @@ import ( "github.com/iotaledger/hive.go/ds" "github.com/iotaledger/hive.go/ds/walker" + "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/runtime/event" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/hive.go/runtime/options" @@ -22,8 +23,9 @@ import ( type Gadget struct { events *blockgadget.Events - seatManager seatmanager.SeatManager - blockCache *blocks.Blocks + seatManager seatmanager.SeatManager + blockCache *blocks.Blocks + errorHandler func(error) optsAcceptanceThreshold float64 optsConfirmationThreshold float64 @@ -34,7 +36,7 @@ type Gadget struct { func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine, blockgadget.Gadget] { return module.Provide(func(e *engine.Engine) blockgadget.Gadget { - g := New(e.BlockCache, e.SybilProtection.SeatManager(), opts...) + g := New(e.BlockCache, e.SybilProtection.SeatManager(), e.ErrorHandler("gadget"), opts...) wp := e.Workers.CreatePool("ThresholdBlockGadget", workerpool.WithWorkerCount(1)) e.Events.Booker.BlockBooked.Hook(g.TrackWitnessWeight, event.WithWorkerPool(wp)) @@ -45,11 +47,12 @@ func NewProvider(opts ...options.Option[Gadget]) module.Provider[*engine.Engine, }) } -func New(blockCache *blocks.Blocks, seatManager seatmanager.SeatManager, opts ...options.Option[Gadget]) *Gadget { +func New(blockCache *blocks.Blocks, seatManager seatmanager.SeatManager, errorHandler func(error), opts ...options.Option[Gadget]) *Gadget { return options.Apply(&Gadget{ - events: blockgadget.NewEvents(), - seatManager: seatManager, - blockCache: blockCache, + events: blockgadget.NewEvents(), + seatManager: seatManager, + blockCache: blockCache, + errorHandler: errorHandler, optsAcceptanceThreshold: 0.67, optsConfirmationThreshold: 0.67, @@ -98,8 +101,15 @@ func (g *Gadget) isCommitteeValidationBlock(block *blocks.Block) (seat account.S return 0, false } + committee, exists := g.seatManager.CommitteeInSlot(block.ID().Slot()) + if !exists { + g.errorHandler(ierrors.Errorf("committee for slot %d does not exist", block.ID().Slot())) + + return 0, false + } + // Only accept blocks for issuers that are part of the committee. - return g.seatManager.Committee(block.ID().Slot()).GetSeat(block.ProtocolBlock().IssuerID) + return committee.GetSeat(block.ProtocolBlock().IssuerID) } func anyChildInSet(block *blocks.Block, set ds.Set[iotago.BlockID]) bool { diff --git a/pkg/protocol/engine/engine.go b/pkg/protocol/engine/engine.go index 6406d5b28..1bb299aa8 100644 --- a/pkg/protocol/engine/engine.go +++ b/pkg/protocol/engine/engine.go @@ -407,7 +407,7 @@ func (e *Engine) acceptanceHandler() { e.Events.BlockGadget.BlockAccepted.Hook(func(block *blocks.Block) { e.Ledger.TrackBlock(block) - e.SybilProtection.TrackValidationBlock(block) + e.SybilProtection.TrackBlock(block) e.UpgradeOrchestrator.TrackValidationBlock(block) e.Events.AcceptedBlockProcessed.Trigger(block) diff --git a/pkg/protocol/engine/filter/blockfilter/filter.go b/pkg/protocol/engine/filter/blockfilter/filter.go index 6b20f66f6..0ed6900ec 100644 --- a/pkg/protocol/engine/filter/blockfilter/filter.go +++ b/pkg/protocol/engine/filter/blockfilter/filter.go @@ -29,7 +29,7 @@ type Filter struct { optsMaxAllowedWallClockDrift time.Duration - committeeFunc func(iotago.SlotIndex) *account.SeatedAccounts + committeeFunc func(iotago.SlotIndex) (*account.SeatedAccounts, bool) module.Module } @@ -42,7 +42,7 @@ func NewProvider(opts ...options.Option[Filter]) module.Provider[*engine.Engine, e.HookConstructed(func() { e.Events.Filter.LinkTo(f.events) e.SybilProtection.HookInitialized(func() { - f.committeeFunc = e.SybilProtection.SeatManager().Committee + f.committeeFunc = e.SybilProtection.SeatManager().CommitteeInSlot }) f.TriggerInitialized() }) @@ -92,7 +92,18 @@ func (f *Filter) ProcessReceivedBlock(block *model.Block, source peer.ID) { if _, isValidation := block.ValidationBlock(); isValidation { blockSlot := block.ProtocolBlock().API.TimeProvider().SlotFromTime(block.ProtocolBlock().IssuingTime) - if !f.committeeFunc(blockSlot).HasAccount(block.ProtocolBlock().IssuerID) { + committee, exists := f.committeeFunc(blockSlot) + if !exists { + f.events.BlockPreFiltered.Trigger(&filter.BlockPreFilteredEvent{ + Block: block, + Reason: ierrors.Wrapf(ErrValidatorNotInCommittee, "no committee for slot %d", blockSlot), + Source: source, + }) + + return + } + + if !committee.HasAccount(block.ProtocolBlock().IssuerID) { f.events.BlockPreFiltered.Trigger(&filter.BlockPreFilteredEvent{ Block: block, Reason: ierrors.Wrapf(ErrValidatorNotInCommittee, "validation block issuer %s is not part of the committee for slot %d", block.ProtocolBlock().IssuerID, blockSlot), diff --git a/pkg/protocol/engine/filter/blockfilter/filter_test.go b/pkg/protocol/engine/filter/blockfilter/filter_test.go index 1e7347fc7..02646bb56 100644 --- a/pkg/protocol/engine/filter/blockfilter/filter_test.go +++ b/pkg/protocol/engine/filter/blockfilter/filter_test.go @@ -96,14 +96,14 @@ func (t *TestFramework) IssueBlockAtSlotWithVersion(alias string, slot iotago.Sl return t.processBlock(alias, block) } -func mockedCommitteeFunc(validatorAccountID iotago.AccountID) func(iotago.SlotIndex) *account.SeatedAccounts { +func mockedCommitteeFunc(validatorAccountID iotago.AccountID) func(iotago.SlotIndex) (*account.SeatedAccounts, bool) { mockedAccounts := account.NewAccounts() mockedAccounts.Set(validatorAccountID, new(account.Pool)) seatedAccounts := account.NewSeatedAccounts(mockedAccounts) seatedAccounts.Set(account.SeatIndex(0), validatorAccountID) - return func(slot iotago.SlotIndex) *account.SeatedAccounts { - return seatedAccounts + return func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) { + return seatedAccounts, true } } diff --git a/pkg/protocol/engine/ledger/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger/ledger.go index 316055615..a6c177204 100644 --- a/pkg/protocol/engine/ledger/ledger/ledger.go +++ b/pkg/protocol/engine/ledger/ledger/ledger.go @@ -655,7 +655,7 @@ func (l *Ledger) processStateDiffTransactions(stateDiff mempool.StateDiff) (spen continue } - accountDiff.BICChange += iotago.BlockIssuanceCredits(allotment.Value) + accountDiff.BICChange += iotago.BlockIssuanceCredits(allotment.Mana) accountDiff.PreviousUpdatedTime = accountData.Credits.UpdateTime // we are not transitioning the allotted account, so the new and previous expiry slots are the same @@ -748,7 +748,12 @@ func (l *Ledger) resolveState(stateRef mempool.StateReference) *promise.Promise[ func (l *Ledger) blockPreAccepted(block *blocks.Block) { voteRank := ledger.NewBlockVoteRank(block.ID(), block.ProtocolBlock().IssuingTime) - seat, exists := l.sybilProtection.SeatManager().Committee(block.ID().Slot()).GetSeat(block.ProtocolBlock().IssuerID) + committee, exists := l.sybilProtection.SeatManager().CommitteeInSlot(block.ID().Slot()) + if !exists { + panic("committee should exist because we pre-accepted the block") + } + + seat, exists := committee.GetSeat(block.ProtocolBlock().IssuerID) if !exists { return } diff --git a/pkg/protocol/engine/notarization/slotnotarization/manager.go b/pkg/protocol/engine/notarization/slotnotarization/manager.go index 1555dc0d1..217b3288b 100644 --- a/pkg/protocol/engine/notarization/slotnotarization/manager.go +++ b/pkg/protocol/engine/notarization/slotnotarization/manager.go @@ -143,9 +143,7 @@ func (m *Manager) notarizeAcceptedBlock(block *blocks.Block) (err error) { return ierrors.Wrap(err, "failed to add accepted block to slot mutations") } - m.attestation.AddAttestationFromValidationBlock(block) - - return + return m.attestation.AddAttestationFromValidationBlock(block) } func (m *Manager) tryCommitSlotUntil(acceptedBlockIndex iotago.SlotIndex) { diff --git a/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go b/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go index 48b7f0e4d..4e5ee6026 100644 --- a/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go +++ b/pkg/protocol/engine/upgrade/signalingupgradeorchestrator/orchestrator.go @@ -146,7 +146,11 @@ func (o *Orchestrator) TrackValidationBlock(block *blocks.Block) { } newSignaledBlock := model.NewSignaledBlock(block.ID(), block.ProtocolBlock(), validationBlock) - committee := o.seatManager.Committee(block.ID().Slot()) + committee, exists := o.seatManager.CommitteeInSlot(block.ID().Slot()) + if !exists { + return + } + seat, exists := committee.GetSeat(block.ProtocolBlock().IssuerID) if !exists { return diff --git a/pkg/protocol/protocol_fork.go b/pkg/protocol/protocol_fork.go index 88e508028..1425276cd 100644 --- a/pkg/protocol/protocol_fork.go +++ b/pkg/protocol/protocol_fork.go @@ -194,7 +194,11 @@ func (p *Protocol) processFork(fork *chainmanager.Fork) (anchorBlockIDs iotago.B ch := make(chan *commitmentVerificationResult) defer close(ch) - commitmentVerifier := NewCommitmentVerifier(p.MainEngineInstance(), fork.MainChain.Commitment(fork.ForkingPoint.Slot()-1).Commitment()) + commitmentVerifier, err := NewCommitmentVerifier(p.MainEngineInstance(), fork.MainChain.Commitment(fork.ForkingPoint.Slot()-1).Commitment()) + if err != nil { + return nil, false, true, ierrors.Wrapf(err, "failed to create commitment verifier for %s", fork.MainChain.Commitment(fork.ForkingPoint.Slot()-1).ID()) + } + verifyCommitmentFunc := func(commitment *model.Commitment, attestations []*iotago.Attestation, merkleProof *merklehasher.Proof[iotago.Identifier], _ peer.ID) { blockIDs, actualCumulativeWeight, err := commitmentVerifier.verifyCommitment(commitment, attestations, merkleProof) diff --git a/pkg/protocol/sybilprotection/activitytracker/activitytracker.go b/pkg/protocol/sybilprotection/activitytracker/activitytracker.go new file mode 100644 index 000000000..07b95bb39 --- /dev/null +++ b/pkg/protocol/sybilprotection/activitytracker/activitytracker.go @@ -0,0 +1,14 @@ +package activitytracker + +import ( + "time" + + "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/iota-core/pkg/core/account" + iotago "github.com/iotaledger/iota.go/v4" +) + +type ActivityTracker interface { + OnlineCommittee() ds.Set[account.SeatIndex] + MarkSeatActive(seat account.SeatIndex, id iotago.AccountID, seatActivityTime time.Time) +} diff --git a/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go b/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go new file mode 100644 index 000000000..e167e2ff5 --- /dev/null +++ b/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1/activitytracker.go @@ -0,0 +1,83 @@ +package activitytrackerv1 + +import ( + "time" + + "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/hive.go/ds/shrinkingmap" + "github.com/iotaledger/hive.go/runtime/syncutils" + "github.com/iotaledger/hive.go/runtime/timed" + "github.com/iotaledger/iota-core/pkg/core/account" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/activitytracker" + iotago "github.com/iotaledger/iota.go/v4" +) + +// ActivityTracker is a sybil protection module for tracking activity of committee members. +type ActivityTracker struct { + Events *activitytracker.Events + + onlineCommittee ds.Set[account.SeatIndex] + inactivityQueue timed.PriorityQueue[account.SeatIndex] + lastActivities *shrinkingmap.ShrinkingMap[account.SeatIndex, time.Time] + lastActivityTime time.Time + activityMutex syncutils.RWMutex + + activityWindow time.Duration +} + +func NewActivityTracker(activityWindow time.Duration) *ActivityTracker { + return &ActivityTracker{ + Events: activitytracker.NewEvents(), + onlineCommittee: ds.NewSet[account.SeatIndex](), + inactivityQueue: timed.NewPriorityQueue[account.SeatIndex](true), + lastActivities: shrinkingmap.New[account.SeatIndex, time.Time](), + + activityWindow: activityWindow, + } +} + +// OnlineCommittee returns the set of validators selected to be part of the committee that has been seen recently. +func (a *ActivityTracker) OnlineCommittee() ds.Set[account.SeatIndex] { + a.activityMutex.RLock() + defer a.activityMutex.RUnlock() + + return a.onlineCommittee +} + +func (a *ActivityTracker) MarkSeatActive(seat account.SeatIndex, id iotago.AccountID, seatActivityTime time.Time) { + a.activityMutex.Lock() + defer a.activityMutex.Unlock() + + if lastActivity, exists := a.lastActivities.Get(seat); (exists && lastActivity.After(seatActivityTime)) || seatActivityTime.Before(a.lastActivityTime.Add(-a.activityWindow)) { + return + } else if !exists { + a.onlineCommittee.Add(seat) + a.Events.OnlineCommitteeSeatAdded.Trigger(seat, id) + } + + a.lastActivities.Set(seat, seatActivityTime) + + a.inactivityQueue.Push(seat, seatActivityTime) + + if seatActivityTime.Before(a.lastActivityTime) { + return + } + + a.lastActivityTime = seatActivityTime + + activityThreshold := seatActivityTime.Add(-a.activityWindow) + for _, inactiveSeat := range a.inactivityQueue.PopUntil(activityThreshold) { + if lastActivityForInactiveSeat, exists := a.lastActivities.Get(inactiveSeat); exists && lastActivityForInactiveSeat.After(activityThreshold) { + continue + } + + a.markSeatInactive(inactiveSeat) + } +} + +func (a *ActivityTracker) markSeatInactive(seat account.SeatIndex) { + a.lastActivities.Delete(seat) + a.onlineCommittee.Delete(seat) + + a.Events.OnlineCommitteeSeatRemoved.Trigger(seat) +} diff --git a/pkg/protocol/sybilprotection/activitytracker/events.go b/pkg/protocol/sybilprotection/activitytracker/events.go new file mode 100644 index 000000000..1c210f50b --- /dev/null +++ b/pkg/protocol/sybilprotection/activitytracker/events.go @@ -0,0 +1,22 @@ +package activitytracker + +import ( + "github.com/iotaledger/hive.go/runtime/event" + "github.com/iotaledger/iota-core/pkg/core/account" + iotago "github.com/iotaledger/iota.go/v4" +) + +type Events struct { + OnlineCommitteeSeatAdded *event.Event2[account.SeatIndex, iotago.AccountID] + OnlineCommitteeSeatRemoved *event.Event1[account.SeatIndex] + + event.Group[Events, *Events] +} + +// NewEvents contains the constructor of the Events object (it is generated by a generic factory). +var NewEvents = event.CreateGroupConstructor(func() (newEvents *Events) { + return &Events{ + OnlineCommitteeSeatAdded: event.New2[account.SeatIndex, iotago.AccountID](), + OnlineCommitteeSeatRemoved: event.New1[account.SeatIndex](), + } +}) diff --git a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go index 223f212df..d8b53b4a1 100644 --- a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go +++ b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go @@ -2,20 +2,27 @@ package mock import ( "fmt" + "time" "github.com/iotaledger/hive.go/ds" "github.com/iotaledger/hive.go/ds/shrinkingmap" + "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/protocol/engine" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager" + "github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/tpkg" ) type ManualPOA struct { - events *seatmanager.Events + events *seatmanager.Events + apiProvider iotago.APIProvider + committeeStore *epochstore.Store[*account.Accounts] + accounts *account.Accounts committee *account.SeatedAccounts online ds.Set[account.SeatIndex] @@ -24,12 +31,14 @@ type ManualPOA struct { module.Module } -func NewManualPOA() *ManualPOA { +func NewManualPOA(e iotago.APIProvider, committeeStore *epochstore.Store[*account.Accounts]) *ManualPOA { m := &ManualPOA{ - events: seatmanager.NewEvents(), - accounts: account.NewAccounts(), - online: ds.NewSet[account.SeatIndex](), - aliases: shrinkingmap.New[string, iotago.AccountID](), + events: seatmanager.NewEvents(), + apiProvider: e, + committeeStore: committeeStore, + accounts: account.NewAccounts(), + online: ds.NewSet[account.SeatIndex](), + aliases: shrinkingmap.New[string, iotago.AccountID](), } m.committee = m.accounts.SelectCommittee() @@ -38,7 +47,7 @@ func NewManualPOA() *ManualPOA { func NewManualPOAProvider() module.Provider[*engine.Engine, seatmanager.SeatManager] { return module.Provide(func(e *engine.Engine) seatmanager.SeatManager { - poa := NewManualPOA() + poa := NewManualPOA(e, e.Storage.Committee()) e.Events.CommitmentFilter.BlockAllowed.Hook(func(block *blocks.Block) { poa.events.BlockProcessed.Trigger(block) }) @@ -52,17 +61,36 @@ func NewManualPOAProvider() module.Provider[*engine.Engine, seatmanager.SeatMana func (m *ManualPOA) AddRandomAccount(alias string) iotago.AccountID { id := iotago.AccountID(tpkg.Rand32ByteArray()) id.RegisterAlias(alias) - m.accounts.Set(id, &account.Pool{}) // We don't care about pools with PoA + m.accounts.Set(id, &account.Pool{ + PoolStake: 1, + ValidatorStake: 1, + FixedCost: 1, + }) // We don't care about pools with PoA, but need to set something to avoid division by zero errors. + m.aliases.Set(alias, id) - m.committee.Set(account.SeatIndex(m.committee.SeatCount()), id) + + m.committee = m.accounts.SelectCommittee(m.accounts.IDs()...) + + if err := m.committeeStore.Store(0, m.accounts); err != nil { + panic(err) + } return id } func (m *ManualPOA) AddAccount(id iotago.AccountID, alias string) iotago.AccountID { - m.accounts.Set(id, &account.Pool{}) // We don't care about pools with PoA + m.accounts.Set(id, &account.Pool{ + PoolStake: 1, + ValidatorStake: 1, + FixedCost: 1, + }) // We don't care about pools with PoA, but need to set something to avoid division by zero errors. m.aliases.Set(alias, id) - m.committee.Set(account.SeatIndex(m.committee.SeatCount()), id) + + m.committee = m.accounts.SelectCommittee(m.accounts.IDs()...) + + if err := m.committeeStore.Store(0, m.accounts); err != nil { + panic(err) + } return id } @@ -100,8 +128,27 @@ func (m *ManualPOA) Accounts() *account.Accounts { return m.accounts } -func (m *ManualPOA) Committee(_ iotago.SlotIndex) *account.SeatedAccounts { - return m.committee +// CommitteeInSlot returns the set of validators selected to be part of the committee in the given slot. +func (m *ManualPOA) CommitteeInSlot(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) { + return m.committeeInEpoch(m.apiProvider.APIForSlot(slot).TimeProvider().EpochFromSlot(slot)) +} + +// CommitteeInEpoch returns the set of validators selected to be part of the committee in the given epoch. +func (m *ManualPOA) CommitteeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) { + return m.committeeInEpoch(epoch) +} + +func (m *ManualPOA) committeeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) { + c, err := m.committeeStore.Load(epoch) + if err != nil { + panic(ierrors.Wrapf(err, "failed to load committee for epoch %d", epoch)) + } + + if c == nil { + return nil, false + } + + return c.SelectCommittee(c.IDs()...), true } func (m *ManualPOA) OnlineCommittee() ds.Set[account.SeatIndex] { @@ -112,16 +159,42 @@ func (m *ManualPOA) SeatCount() int { return m.committee.SeatCount() } -func (m *ManualPOA) RotateCommittee(_ iotago.EpochIndex, _ *account.Accounts) *account.SeatedAccounts { - return m.committee +func (m *ManualPOA) RotateCommittee(epoch iotago.EpochIndex, validators accounts.AccountsData) (*account.SeatedAccounts, error) { + if m.committee == nil || m.accounts.Size() == 0 { + m.accounts = account.NewAccounts() + + for _, validatorData := range validators { + m.accounts.Set(validatorData.ID, &account.Pool{ + PoolStake: validatorData.ValidatorStake + validatorData.DelegationStake, + ValidatorStake: validatorData.ValidatorStake, + FixedCost: validatorData.FixedCost, + }) + } + m.committee = m.accounts.SelectCommittee(m.accounts.IDs()...) + } + + if err := m.committeeStore.Store(epoch, m.accounts); err != nil { + panic(err) + } + + return m.committee, nil } -func (m *ManualPOA) SetCommittee(_ iotago.EpochIndex, _ *account.Accounts) { +func (m *ManualPOA) SetCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error { + if m.committee == nil || m.accounts.Size() == 0 { + m.accounts = validators + m.committee = m.accounts.SelectCommittee(validators.IDs()...) + } + + if err := m.committeeStore.Store(epoch, validators); err != nil { + panic(err) + } + + return nil } -func (m *ManualPOA) ImportCommittee(_ iotago.EpochIndex, validators *account.Accounts) { - m.accounts = validators - m.committee = m.accounts.SelectCommittee(validators.IDs()...) +func (m *ManualPOA) InitializeCommittee(_ iotago.EpochIndex, _ time.Time) error { + return nil } func (m *ManualPOA) Shutdown() {} diff --git a/pkg/protocol/sybilprotection/seatmanager/poa/poa.go b/pkg/protocol/sybilprotection/seatmanager/poa/poa.go index 8d5de21dd..8a0256e61 100644 --- a/pkg/protocol/sybilprotection/seatmanager/poa/poa.go +++ b/pkg/protocol/sybilprotection/seatmanager/poa/poa.go @@ -4,34 +4,31 @@ import ( "time" "github.com/iotaledger/hive.go/ds" - "github.com/iotaledger/hive.go/ds/shrinkingmap" + "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/hive.go/runtime/timed" - "github.com/iotaledger/hive.go/runtime/workerpool" "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/protocol/engine" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" - "github.com/iotaledger/iota-core/pkg/protocol/engine/clock" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/activitytracker" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1" "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager" + "github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore" iotago "github.com/iotaledger/iota.go/v4" ) // SeatManager is a sybil protection module for the engine that manages the weights of actors according to their stake. type SeatManager struct { - events *seatmanager.Events - - clock clock.Clock - workers *workerpool.Group - accounts *account.Accounts - committee *account.SeatedAccounts - onlineCommittee ds.Set[account.SeatIndex] - inactivityQueue timed.PriorityQueue[account.SeatIndex] - lastActivities *shrinkingmap.ShrinkingMap[account.SeatIndex, time.Time] - lastActivityTime time.Time - activityMutex syncutils.RWMutex - committeeMutex syncutils.RWMutex + events *seatmanager.Events + apiProvider iotago.APIProvider + + committee *account.SeatedAccounts + committeeStore *epochstore.Store[*account.Accounts] + activityTracker activitytracker.ActivityTracker + + committeeMutex syncutils.RWMutex optsActivityWindow time.Duration optsOnlineCommitteeStartup []iotago.AccountID @@ -39,34 +36,39 @@ type SeatManager struct { module.Module } -// NewProvider returns a new sybil protection provider that uses the ProofOfStake module. +// NewProvider returns a new sybil protection provider that uses the ProofOfAuthority module. func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.Engine, seatmanager.SeatManager] { return module.Provide(func(e *engine.Engine) seatmanager.SeatManager { return options.Apply( &SeatManager{ - events: seatmanager.NewEvents(), - workers: e.Workers.CreateGroup("SeatManager"), - accounts: account.NewAccounts(), - onlineCommittee: ds.NewSet[account.SeatIndex](), - inactivityQueue: timed.NewPriorityQueue[account.SeatIndex](true), - lastActivities: shrinkingmap.New[account.SeatIndex, time.Time](), + events: seatmanager.NewEvents(), + apiProvider: e, + committeeStore: e.Storage.Committee(), optsActivityWindow: time.Second * 30, }, opts, func(s *SeatManager) { + activityTracker := activitytrackerv1.NewActivityTracker(s.optsActivityWindow) + s.activityTracker = activityTracker + s.events.OnlineCommitteeSeatAdded.LinkTo(activityTracker.Events.OnlineCommitteeSeatAdded) + s.events.OnlineCommitteeSeatRemoved.LinkTo(activityTracker.Events.OnlineCommitteeSeatRemoved) + e.Events.SeatManager.LinkTo(s.events) e.HookConstructed(func() { - s.clock = e.Clock - s.TriggerConstructed() // We need to mark validators as active upon solidity of blocks as otherwise we would not be able to // recover if no node was part of the online committee anymore. e.Events.CommitmentFilter.BlockAllowed.Hook(func(block *blocks.Block) { // Only track identities that are part of the committee. - seat, exists := s.Committee(block.ID().Slot()).GetSeat(block.ProtocolBlock().IssuerID) + committee, exists := s.CommitteeInSlot(block.ID().Slot()) + if !exists { + panic(ierrors.Errorf("committee not selected for slot %d, but received block in that slot", block.ID().Slot())) + } + + seat, exists := committee.GetSeat(block.ProtocolBlock().IssuerID) if exists { - s.markSeatActive(seat, block.ProtocolBlock().IssuerID, block.IssuingTime()) + s.activityTracker.MarkSeatActive(seat, block.ProtocolBlock().IssuerID, block.IssuingTime()) } s.events.BlockProcessed.Trigger(block) @@ -78,29 +80,64 @@ func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.En var _ seatmanager.SeatManager = &SeatManager{} -func (s *SeatManager) RotateCommittee(_ iotago.EpochIndex, _ *account.Accounts) *account.SeatedAccounts { +func (s *SeatManager) RotateCommittee(epoch iotago.EpochIndex, validators accounts.AccountsData) (*account.SeatedAccounts, error) { + s.committeeMutex.RLock() + defer s.committeeMutex.RUnlock() + + // if committee is not set, then set it according to passed validators (used for creating a snapshot) + if s.committee == nil { + committeeAccounts := account.NewAccounts() + + for _, validatorData := range validators { + committeeAccounts.Set(validatorData.ID, &account.Pool{ + PoolStake: validatorData.ValidatorStake + validatorData.DelegationStake, + ValidatorStake: validatorData.ValidatorStake, + FixedCost: validatorData.FixedCost, + }) + } + s.committee = committeeAccounts.SelectCommittee(committeeAccounts.IDs()...) + } + + err := s.committeeStore.Store(epoch, s.committee.Accounts()) + if err != nil { + return nil, ierrors.Wrapf(err, "error while storing committee for epoch %d", epoch) + } + + return s.committee, nil +} + +// CommitteeInSlot returns the set of validators selected to be part of the committee in the given slot. +func (s *SeatManager) CommitteeInSlot(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) { s.committeeMutex.RLock() defer s.committeeMutex.RUnlock() - // we do nothing on PoA, we keep the same accounts and committee - return s.committee + return s.committeeInEpoch(s.apiProvider.APIForSlot(slot).TimeProvider().EpochFromSlot(slot)) } -// Committee returns the set of validators selected to be part of the committee. -func (s *SeatManager) Committee(_ iotago.SlotIndex) *account.SeatedAccounts { +// CommitteeInEpoch returns the set of validators selected to be part of the committee in the given epoch. +func (s *SeatManager) CommitteeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) { s.committeeMutex.RLock() defer s.committeeMutex.RUnlock() - // Note: we have PoA so our committee do not rotate right now - return s.committee + return s.committeeInEpoch(epoch) +} + +func (s *SeatManager) committeeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) { + c, err := s.committeeStore.Load(epoch) + if err != nil { + panic(ierrors.Wrapf(err, "failed to load committee for epoch %d", epoch)) + } + + if c == nil { + return nil, false + } + + return c.SelectCommittee(c.IDs()...), true } // OnlineCommittee returns the set of validators selected to be part of the committee that has been seen recently. func (s *SeatManager) OnlineCommittee() ds.Set[account.SeatIndex] { - s.activityMutex.RLock() - defer s.activityMutex.RUnlock() - - return s.onlineCommittee + return s.activityTracker.OnlineCommittee() } func (s *SeatManager) SeatCount() int { @@ -112,76 +149,47 @@ func (s *SeatManager) SeatCount() int { func (s *SeatManager) Shutdown() { s.TriggerStopped() - s.workers.Shutdown() } -func (s *SeatManager) ImportCommittee(_ iotago.EpochIndex, validators *account.Accounts) { +func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime time.Time) error { s.committeeMutex.Lock() defer s.committeeMutex.Unlock() - s.accounts = validators - s.committee = s.accounts.SelectCommittee(validators.IDs()...) + committeeAccounts, err := s.committeeStore.Load(epoch) + if err != nil { + return ierrors.Wrapf(err, "failed to load PoA committee for epoch %d", epoch) + } + + s.committee = committeeAccounts.SelectCommittee(committeeAccounts.IDs()...) - onlineValidators := s.accounts.IDs() + onlineValidators := committeeAccounts.IDs() if len(s.optsOnlineCommitteeStartup) > 0 { onlineValidators = s.optsOnlineCommitteeStartup } for _, v := range onlineValidators { - activityTime := s.clock.Accepted().RelativeTime() - seat, exists := s.committee.GetSeat(v) if !exists { // Only track identities that are part of the committee. - return + continue } - s.markSeatActive(seat, v, activityTime) + s.activityTracker.MarkSeatActive(seat, v, activityTime) } + + return nil } -func (s *SeatManager) SetCommittee(_ iotago.EpochIndex, validators *account.Accounts) { +func (s *SeatManager) SetCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error { s.committeeMutex.Lock() defer s.committeeMutex.Unlock() - s.accounts = validators - s.committee = s.accounts.SelectCommittee(validators.IDs()...) -} - -func (s *SeatManager) markSeatActive(seat account.SeatIndex, id iotago.AccountID, seatActivityTime time.Time) { - s.activityMutex.Lock() - defer s.activityMutex.Unlock() - - if lastActivity, exists := s.lastActivities.Get(seat); (exists && lastActivity.After(seatActivityTime)) || seatActivityTime.Before(s.lastActivityTime.Add(-s.optsActivityWindow)) { - return - } else if !exists { - s.onlineCommittee.Add(seat) - s.events.OnlineCommitteeSeatAdded.Trigger(seat, id) - } - - s.lastActivities.Set(seat, seatActivityTime) - - s.inactivityQueue.Push(seat, seatActivityTime) + s.committee = validators.SelectCommittee(validators.IDs()...) - if seatActivityTime.Before(s.lastActivityTime) { - return + err := s.committeeStore.Store(epoch, s.committee.Accounts()) + if err != nil { + return ierrors.Wrapf(err, "failed to set committee for epoch %d", epoch) } - s.lastActivityTime = seatActivityTime - - activityThreshold := seatActivityTime.Add(-s.optsActivityWindow) - for _, inactiveSeat := range s.inactivityQueue.PopUntil(activityThreshold) { - if lastActivityForInactiveSeat, exists := s.lastActivities.Get(inactiveSeat); exists && lastActivityForInactiveSeat.After(activityThreshold) { - continue - } - - s.markSeatInactive(inactiveSeat) - } -} - -func (s *SeatManager) markSeatInactive(seat account.SeatIndex) { - s.lastActivities.Delete(seat) - s.onlineCommittee.Delete(seat) - - s.events.OnlineCommitteeSeatRemoved.Trigger(seat) + return nil } diff --git a/pkg/protocol/sybilprotection/seatmanager/seatmanager.go b/pkg/protocol/sybilprotection/seatmanager/seatmanager.go index 32f282569..fe87322c4 100644 --- a/pkg/protocol/sybilprotection/seatmanager/seatmanager.go +++ b/pkg/protocol/sybilprotection/seatmanager/seatmanager.go @@ -1,27 +1,33 @@ package seatmanager import ( + "time" + "github.com/iotaledger/hive.go/ds" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/iota-core/pkg/core/account" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" iotago "github.com/iotaledger/iota.go/v4" ) // SeatManager is the minimal interface for the SeatManager component of the IOTA protocol. type SeatManager interface { // RotateCommittee rotates the committee evaluating the given set of candidates to produce the new committee. - RotateCommittee(epoch iotago.EpochIndex, candidates *account.Accounts) *account.SeatedAccounts + RotateCommittee(epoch iotago.EpochIndex, candidates accounts.AccountsData) (*account.SeatedAccounts, error) // SetCommittee sets the committee for a given slot. // This is used when re-using the same committee for consecutive epochs. - SetCommittee(epoch iotago.EpochIndex, committee *account.Accounts) + SetCommittee(epoch iotago.EpochIndex, committee *account.Accounts) error - // ImportCommittee sets the committee for a given slot and marks the whole committee as active. + // InitializeCommittee initializes the committee for the current slot by marking whole or a subset of the committee as active. // This is used when initializing committee after node startup (loaded from snapshot or database). - ImportCommittee(epoch iotago.EpochIndex, committee *account.Accounts) + InitializeCommittee(epoch iotago.EpochIndex, activityTime time.Time) error + + // CommitteeInSlot returns the set of validators that is used to track confirmation at a given slot. + CommitteeInSlot(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) - // Committee returns the set of validators that is used to track confirmation. - Committee(slot iotago.SlotIndex) *account.SeatedAccounts + // CommitteeInEpoch returns the set of validators that is used to track confirmation in a given epoch. + CommitteeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) // OnlineCommittee returns the set of online validators that is used to track acceptance. OnlineCommittee() ds.Set[account.SeatIndex] diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go new file mode 100644 index 000000000..4f190f182 --- /dev/null +++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/options.go @@ -0,0 +1,27 @@ +package topstakers + +import ( + "time" + + "github.com/iotaledger/hive.go/runtime/options" + iotago "github.com/iotaledger/iota.go/v4" +) + +// WithActivityWindow sets the duration for which a validator is recognized as active after issuing a block. +func WithActivityWindow(activityWindow time.Duration) options.Option[SeatManager] { + return func(p *SeatManager) { + p.optsActivityWindow = activityWindow + } +} + +func WithOnlineCommitteeStartup(optsOnlineCommittee ...iotago.AccountID) options.Option[SeatManager] { + return func(p *SeatManager) { + p.optsOnlineCommitteeStartup = optsOnlineCommittee + } +} + +func WithSeatCount(optsSeatCount uint32) options.Option[SeatManager] { + return func(p *SeatManager) { + p.optsSeatCount = optsSeatCount + } +} diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go new file mode 100644 index 000000000..5d85f85bf --- /dev/null +++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers.go @@ -0,0 +1,245 @@ +package topstakers + +import ( + "bytes" + "sort" + "time" + + "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/runtime/module" + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/hive.go/runtime/syncutils" + "github.com/iotaledger/iota-core/pkg/core/account" + "github.com/iotaledger/iota-core/pkg/protocol/engine" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" + "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/activitytracker" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager" + "github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore" + iotago "github.com/iotaledger/iota.go/v4" +) + +// SeatManager is a sybil protection module for the engine that manages the weights of actors according to their stake. +type SeatManager struct { + apiProvider iotago.APIProvider + events *seatmanager.Events + + committeeStore *epochstore.Store[*account.Accounts] + committeeMutex syncutils.RWMutex + activityTracker activitytracker.ActivityTracker + + optsSeatCount uint32 + optsActivityWindow time.Duration + optsOnlineCommitteeStartup []iotago.AccountID + + module.Module +} + +// NewProvider returns a new sybil protection provider that uses the ProofOfStake module. +func NewProvider(opts ...options.Option[SeatManager]) module.Provider[*engine.Engine, seatmanager.SeatManager] { + return module.Provide(func(e *engine.Engine) seatmanager.SeatManager { + return options.Apply( + &SeatManager{ + apiProvider: e, + events: seatmanager.NewEvents(), + committeeStore: e.Storage.Committee(), + + optsActivityWindow: time.Second * 30, + }, opts, func(s *SeatManager) { + activityTracker := activitytrackerv1.NewActivityTracker(s.optsActivityWindow) + s.activityTracker = activityTracker + s.events.OnlineCommitteeSeatAdded.LinkTo(activityTracker.Events.OnlineCommitteeSeatAdded) + s.events.OnlineCommitteeSeatRemoved.LinkTo(activityTracker.Events.OnlineCommitteeSeatRemoved) + + e.Events.SeatManager.LinkTo(s.events) + + e.HookConstructed(func() { + s.TriggerConstructed() + + // We need to mark validators as active upon solidity of blocks as otherwise we would not be able to + // recover if no node was part of the online committee anymore. + e.Events.CommitmentFilter.BlockAllowed.Hook(func(block *blocks.Block) { + // Only track identities that are part of the committee. + committee, exists := s.CommitteeInSlot(block.ID().Slot()) + if !exists { + panic(ierrors.Errorf("committee not selected for slot %d, but received block in that slot", block.ID().Slot())) + } + + seat, exists := committee.GetSeat(block.ProtocolBlock().IssuerID) + if exists { + s.activityTracker.MarkSeatActive(seat, block.ProtocolBlock().IssuerID, block.IssuingTime()) + } + + s.events.BlockProcessed.Trigger(block) + }) + }) + }) + }) +} + +var _ seatmanager.SeatManager = &SeatManager{} + +func (s *SeatManager) RotateCommittee(epoch iotago.EpochIndex, candidates accounts.AccountsData) (*account.SeatedAccounts, error) { + s.committeeMutex.Lock() + defer s.committeeMutex.Unlock() + + // If there are fewer candidates than required for epoch 0, then the previous committee cannot be copied. + if len(candidates) < s.SeatCount() && epoch == 0 { + return nil, ierrors.Errorf("at least %d candidates are required for committee in epoch 0, got %d", s.SeatCount(), len(candidates)) + } + + // If there are fewer candidates than required, then re-use the previous committee. + if len(candidates) < s.SeatCount() { + // TODO: what if staking period of a committee member ends in the next epoch? + committee, exists := s.committeeInEpoch(epoch - 1) + if !exists { + return nil, ierrors.Errorf("cannot re-use previous committee from epoch %d as it does not exist", epoch-1) + } + + err := s.committeeStore.Store(epoch, committee.Accounts()) + if err != nil { + return nil, ierrors.Wrapf(err, "error while storing committee for epoch %d", epoch) + } + + return committee, nil + } + + committee := s.selectNewCommittee(candidates) + + err := s.committeeStore.Store(epoch, committee.Accounts()) + if err != nil { + return nil, ierrors.Wrapf(err, "error while storing committee for epoch %d", epoch) + } + + return committee, nil +} + +// CommitteeInSlot returns the set of validators selected to be part of the committee in the given slot. +func (s *SeatManager) CommitteeInSlot(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) { + s.committeeMutex.RLock() + defer s.committeeMutex.RUnlock() + + return s.committeeInEpoch(s.apiProvider.APIForSlot(slot).TimeProvider().EpochFromSlot(slot)) +} + +// CommitteeInEpoch returns the set of validators selected to be part of the committee in the given epoch. +func (s *SeatManager) CommitteeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) { + s.committeeMutex.RLock() + defer s.committeeMutex.RUnlock() + + return s.committeeInEpoch(epoch) +} + +func (s *SeatManager) committeeInEpoch(epoch iotago.EpochIndex) (*account.SeatedAccounts, bool) { + c, err := s.committeeStore.Load(epoch) + if err != nil { + panic(ierrors.Wrapf(err, "failed to load committee for epoch %d", epoch)) + } + + if c == nil { + return nil, false + } + + return c.SelectCommittee(c.IDs()...), true +} + +// OnlineCommittee returns the set of validators selected to be part of the committee that has been seen recently. +func (s *SeatManager) OnlineCommittee() ds.Set[account.SeatIndex] { + return s.activityTracker.OnlineCommittee() +} + +func (s *SeatManager) SeatCount() int { + return int(s.optsSeatCount) +} + +func (s *SeatManager) Shutdown() { + s.TriggerStopped() +} + +func (s *SeatManager) InitializeCommittee(epoch iotago.EpochIndex, activityTime time.Time) error { + s.committeeMutex.Lock() + defer s.committeeMutex.Unlock() + + committeeAccounts, err := s.committeeStore.Load(epoch) + if err != nil { + return ierrors.Wrapf(err, "failed to load PoA committee for epoch %d", epoch) + } + + committee := committeeAccounts.SelectCommittee(committeeAccounts.IDs()...) + + onlineValidators := committeeAccounts.IDs() + if len(s.optsOnlineCommitteeStartup) > 0 { + onlineValidators = s.optsOnlineCommitteeStartup + } + + for _, v := range onlineValidators { + seat, exists := committee.GetSeat(v) + if !exists { + // Only track identities that are part of the committee. + continue + } + + s.activityTracker.MarkSeatActive(seat, v, activityTime) + } + + return nil +} + +func (s *SeatManager) SetCommittee(epoch iotago.EpochIndex, validators *account.Accounts) error { + s.committeeMutex.Lock() + defer s.committeeMutex.Unlock() + + if validators.Size() != int(s.optsSeatCount) { + return ierrors.Errorf("invalid number of validators: %d, expected: %d", validators.Size(), s.optsSeatCount) + } + + err := s.committeeStore.Store(epoch, validators) + if err != nil { + return ierrors.Wrapf(err, "failed to set committee for epoch %d", epoch) + } + + return nil +} + +func (s *SeatManager) selectNewCommittee(candidates accounts.AccountsData) *account.SeatedAccounts { + sort.Slice(candidates, func(i, j int) bool { + // Prioritize the candidate that has a larger pool stake. + if candidates[i].ValidatorStake+candidates[i].DelegationStake != candidates[j].ValidatorStake+candidates[j].DelegationStake { + return candidates[i].ValidatorStake+candidates[i].DelegationStake > candidates[j].ValidatorStake+candidates[j].DelegationStake + } + + // Prioritize the candidate that has a larger validator stake. + if candidates[i].ValidatorStake != candidates[j].ValidatorStake { + return candidates[i].ValidatorStake > candidates[j].ValidatorStake + } + + // Prioritize the candidate that declares a longer staking period. + if candidates[i].StakeEndEpoch != candidates[j].StakeEndEpoch { + return candidates[i].StakeEndEpoch > candidates[j].StakeEndEpoch + } + + // Prioritize the candidate that has smaller FixedCost. + if candidates[i].FixedCost != candidates[j].FixedCost { + return candidates[i].FixedCost < candidates[j].FixedCost + } + + // two candidates never have the same account ID because they come in a map + return bytes.Compare(candidates[i].ID[:], candidates[j].ID[:]) > 0 + }) + + // Create new Accounts instance that only included validators selected to be part of the committee. + newCommitteeAccounts := account.NewAccounts() + + for _, candidateData := range candidates[:s.optsSeatCount] { + newCommitteeAccounts.Set(candidateData.ID, &account.Pool{ + PoolStake: candidateData.ValidatorStake + candidateData.DelegationStake, + ValidatorStake: candidateData.ValidatorStake, + FixedCost: candidateData.FixedCost, + }) + } + committee := newCommitteeAccounts.SelectCommittee(newCommitteeAccounts.IDs()...) + + return committee +} diff --git a/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go new file mode 100644 index 000000000..4b539e72f --- /dev/null +++ b/pkg/protocol/sybilprotection/seatmanager/topstakers/topstakers_test.go @@ -0,0 +1,227 @@ +package topstakers + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/hive.go/kvstore" + "github.com/iotaledger/hive.go/kvstore/mapdb" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/core/account" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/activitytracker/activitytrackerv1" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager" + "github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore" + iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/api" + "github.com/iotaledger/iota.go/v4/tpkg" +) + +func TestTopStakers_InitializeCommittee(t *testing.T) { + committeeStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes) + + topStakersSeatManager := &SeatManager{ + apiProvider: api.SingleVersionProvider(tpkg.TestAPI), + committeeStore: committeeStore, + events: seatmanager.NewEvents(), + activityTracker: activitytrackerv1.NewActivityTracker(time.Second * 30), + + optsSeatCount: 3, + } + + // Create committee for epoch 0 + initialCommittee := account.NewAccounts() + for i := 0; i < 3; i++ { + initialCommittee.Set(tpkg.RandAccountID(), &account.Pool{ + PoolStake: 1900, + ValidatorStake: 900, + FixedCost: 11, + }) + } + // Try setting committee that is too small - should return an error. + err := topStakersSeatManager.SetCommittee(0, initialCommittee) + require.NoError(t, err) + weightedSeats, exists := topStakersSeatManager.CommitteeInEpoch(0) + require.True(t, exists) + initialCommitteeAccountIDs := initialCommittee.IDs() + + // Make sure that the online committee is handled correctly. + require.True(t, topStakersSeatManager.OnlineCommittee().IsEmpty()) + + require.NoError(t, topStakersSeatManager.InitializeCommittee(0, time.Time{})) + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[0])), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2])), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2]))) + +} + +func TestTopStakers_RotateCommittee(t *testing.T) { + committeeStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes) + + topStakersSeatManager := &SeatManager{ + apiProvider: api.SingleVersionProvider(tpkg.TestAPI), + committeeStore: committeeStore, + events: seatmanager.NewEvents(), + activityTracker: activitytrackerv1.NewActivityTracker(time.Second * 30), + + optsSeatCount: 3, + } + + // Committee should not exist because it was never set. + _, exists := topStakersSeatManager.CommitteeInSlot(10) + require.False(t, exists) + + _, exists = topStakersSeatManager.CommitteeInEpoch(0) + require.False(t, exists) + + // Create committee for epoch 0 + initialCommittee := account.NewAccounts() + initialCommittee.Set(tpkg.RandAccountID(), &account.Pool{ + PoolStake: 1900, + ValidatorStake: 900, + FixedCost: 11, + }) + + initialCommittee.Set(tpkg.RandAccountID(), &account.Pool{ + PoolStake: 1900, + ValidatorStake: 900, + FixedCost: 11, + }) + + // Try setting committee that is too small - should return an error. + err := topStakersSeatManager.SetCommittee(0, initialCommittee) + require.Error(t, err) + + initialCommittee.Set(tpkg.RandAccountID(), &account.Pool{ + PoolStake: 1900, + ValidatorStake: 900, + FixedCost: 11, + }) + + // Set committee with the correct size + err = topStakersSeatManager.SetCommittee(0, initialCommittee) + require.NoError(t, err) + weightedSeats, exists := topStakersSeatManager.CommitteeInEpoch(0) + require.True(t, exists) + initialCommitteeAccountIDs := initialCommittee.IDs() + + // Make sure that the online committee is handled correctly. + require.True(t, topStakersSeatManager.OnlineCommittee().IsEmpty()) + + topStakersSeatManager.activityTracker.MarkSeatActive(lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[0])), initialCommitteeAccountIDs[0], tpkg.TestAPI.TimeProvider().SlotStartTime(1)) + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[0]))) + + topStakersSeatManager.activityTracker.MarkSeatActive(lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[1])), initialCommitteeAccountIDs[1], tpkg.TestAPI.TimeProvider().SlotStartTime(2)) + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[0])), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[1]))) + + topStakersSeatManager.activityTracker.MarkSeatActive(lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2])), initialCommitteeAccountIDs[2], tpkg.TestAPI.TimeProvider().SlotStartTime(3)) + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[0])), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[1])), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2]))) + + // Make sure that after a period of inactivity, the inactive seats are marked as offline. + topStakersSeatManager.activityTracker.MarkSeatActive(lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2])), initialCommitteeAccountIDs[2], tpkg.TestAPI.TimeProvider().SlotEndTime(7)) + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2]))) + + // Make sure that the committee was assigned to the correct epoch. + _, exists = topStakersSeatManager.CommitteeInEpoch(1) + require.False(t, exists) + + // Make sure that the committee members match the expected ones. + committee, exists := topStakersSeatManager.CommitteeInEpoch(0) + require.True(t, exists) + assertCommittee(t, initialCommittee, committee) + + committee, exists = topStakersSeatManager.CommitteeInSlot(3) + require.True(t, exists) + assertCommittee(t, initialCommittee, committee) + + // Design candidate list and expected committee members. + accountsContext := make(accounts.AccountsData, 0) + expectedCommittee := account.NewAccounts() + numCandidates := 10 + + // Add some candidates that have the same fields to test sorting by secondary fields. + candidate1ID := tpkg.RandAccountID() + accountsContext = append(accountsContext, &accounts.AccountData{ + ID: candidate1ID, + ValidatorStake: 399, + DelegationStake: 800 - 399, + FixedCost: 3, + StakeEndEpoch: iotago.MaxEpochIndex, + }) + + candidate2ID := tpkg.RandAccountID() + accountsContext = append(accountsContext, &accounts.AccountData{ + ID: candidate2ID, + ValidatorStake: 399, + DelegationStake: 800 - 399, + FixedCost: 3, + StakeEndEpoch: iotago.MaxEpochIndex, + }) + + for i := 1; i <= numCandidates; i++ { + candidateAccountID := tpkg.RandAccountID() + candidatePool := &account.Pool{ + PoolStake: iotago.BaseToken(i * 100), + ValidatorStake: iotago.BaseToken(i * 50), + FixedCost: iotago.Mana(i), + } + accountsContext = append(accountsContext, &accounts.AccountData{ + ID: candidateAccountID, + ValidatorStake: iotago.BaseToken(i * 50), + DelegationStake: iotago.BaseToken(i*100) - iotago.BaseToken(i*50), + FixedCost: tpkg.RandMana(iotago.MaxMana), + StakeEndEpoch: tpkg.RandEpoch(), + }) + + if i+topStakersSeatManager.SeatCount() > numCandidates { + expectedCommittee.Set(candidateAccountID, candidatePool) + } + } + + // Rotate the committee and make sure that the returned committee matches the expected. + newCommittee, err := topStakersSeatManager.RotateCommittee(1, accountsContext) + require.NoError(t, err) + assertCommittee(t, expectedCommittee, newCommittee) + + // Make sure that after committee rotation, the online committee is not changed. + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(initialCommitteeAccountIDs[2]))) + + newCommitteeMemberIDs := newCommittee.Accounts().IDs() + + // A new committee member appears online and makes the previously active committee seat inactive. + topStakersSeatManager.activityTracker.MarkSeatActive(lo.Return1(weightedSeats.GetSeat(newCommitteeMemberIDs[0])), newCommitteeMemberIDs[0], tpkg.TestAPI.TimeProvider().SlotEndTime(14)) + assertOnlineCommittee(t, topStakersSeatManager.OnlineCommittee(), lo.Return1(weightedSeats.GetSeat(newCommitteeMemberIDs[0]))) + + // Make sure that the committee retrieved from the committee store matches the expected. + committee, exists = topStakersSeatManager.CommitteeInEpoch(1) + require.True(t, exists) + assertCommittee(t, expectedCommittee, committee) + + committee, exists = topStakersSeatManager.CommitteeInSlot(tpkg.TestAPI.TimeProvider().EpochStart(1)) + require.True(t, exists) + assertCommittee(t, expectedCommittee, committee) + + // Make sure that the previous committee was not modified and is still accessible. + committee, exists = topStakersSeatManager.CommitteeInEpoch(0) + require.True(t, exists) + assertCommittee(t, initialCommittee, committee) + + committee, exists = topStakersSeatManager.CommitteeInSlot(tpkg.TestAPI.TimeProvider().EpochEnd(0)) + require.True(t, exists) + assertCommittee(t, initialCommittee, committee) +} + +func assertCommittee(t *testing.T, expectedCommittee *account.Accounts, actualCommittee *account.SeatedAccounts) { + require.Equal(t, actualCommittee.Accounts().Size(), expectedCommittee.Size()) + for _, memberID := range expectedCommittee.IDs() { + require.Truef(t, actualCommittee.Accounts().Has(memberID), "expected account %s to be part of committee, but it is not, actual committee members: %s", memberID, actualCommittee.Accounts().IDs()) + } +} + +func assertOnlineCommittee(t *testing.T, onlineCommittee ds.Set[account.SeatIndex], expectedOnlineSeats ...account.SeatIndex) { + require.Equal(t, onlineCommittee.Size(), len(expectedOnlineSeats)) + for _, seatIndex := range expectedOnlineSeats { + require.Truef(t, onlineCommittee.Has(seatIndex), "expected account %s to be part of committee, but it is not, actual committee members: %s", seatIndex, onlineCommittee) + } +} diff --git a/pkg/protocol/sybilprotection/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotection.go index 4935e4572..74db9aa0b 100644 --- a/pkg/protocol/sybilprotection/sybilprotection.go +++ b/pkg/protocol/sybilprotection/sybilprotection.go @@ -12,10 +12,10 @@ import ( ) type SybilProtection interface { - TrackValidationBlock(block *blocks.Block) + TrackBlock(block *blocks.Block) EligibleValidators(epoch iotago.EpochIndex) (accounts.AccountsData, error) OrderedRegisteredCandidateValidatorsList(epoch iotago.EpochIndex) ([]*apimodels.ValidatorResponse, error) - IsCandidateActive(validatorID iotago.AccountID, epoch iotago.EpochIndex) bool + IsCandidateActive(validatorID iotago.AccountID, epoch iotago.EpochIndex) (bool, error) // ValidatorReward returns the amount of mana that a validator has earned in a given epoch range. // The actual used epoch range is returned, only until usedEnd the decay was applied. ValidatorReward(validatorID iotago.AccountID, stakeAmount iotago.BaseToken, epochStart, epochEnd iotago.EpochIndex) (validatorReward iotago.Mana, decayedStart, decayedEnd iotago.EpochIndex, err error) diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/performance.go b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/performance.go index 0a2468466..37f061d5d 100644 --- a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/performance.go +++ b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/performance.go @@ -5,9 +5,9 @@ import ( "time" "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/hive.go/ds/shrinkingmap" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/kvstore" - "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/syncutils" "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/model" @@ -18,12 +18,13 @@ import ( ) type Tracker struct { - rewardsStorePerEpochFunc func(epoch iotago.EpochIndex) (kvstore.KVStore, error) - poolStatsStore *epochstore.Store[*model.PoolsStats] - committeeStore *epochstore.Store[*account.Accounts] - - validatorPerformancesFunc func(slot iotago.SlotIndex) (*slotstore.Store[iotago.AccountID, *model.ValidatorPerformance], error) - latestAppliedEpoch iotago.EpochIndex + rewardsStorePerEpochFunc func(epoch iotago.EpochIndex) (kvstore.KVStore, error) + poolStatsStore *epochstore.Store[*model.PoolsStats] + committeeStore *epochstore.Store[*account.Accounts] + committeeCandidatesInEpochFunc func(epoch iotago.EpochIndex) (kvstore.KVStore, error) + nextEpochCommitteeCandidates *shrinkingmap.ShrinkingMap[iotago.AccountID, iotago.SlotIndex] + validatorPerformancesFunc func(slot iotago.SlotIndex) (*slotstore.Store[iotago.AccountID, *model.ValidatorPerformance], error) + latestAppliedEpoch iotago.EpochIndex apiProvider iotago.APIProvider @@ -33,44 +34,39 @@ type Tracker struct { mutex syncutils.RWMutex } -func NewTracker( - rewardsStorePerEpochFunc func(epoch iotago.EpochIndex) (kvstore.KVStore, error), - poolStatsStore *epochstore.Store[*model.PoolsStats], - committeeStore *epochstore.Store[*account.Accounts], - validatorPerformancesFunc func(slot iotago.SlotIndex) (*slotstore.Store[iotago.AccountID, *model.ValidatorPerformance], error), - latestAppliedEpoch iotago.EpochIndex, - apiProvider iotago.APIProvider, - errHandler func(error), -) *Tracker { +func NewTracker(rewardsStorePerEpochFunc func(epoch iotago.EpochIndex) (kvstore.KVStore, error), poolStatsStore *epochstore.Store[*model.PoolsStats], committeeStore *epochstore.Store[*account.Accounts], committeeCandidatesInEpochFunc func(epoch iotago.EpochIndex) (kvstore.KVStore, error), validatorPerformancesFunc func(slot iotago.SlotIndex) (*slotstore.Store[iotago.AccountID, *model.ValidatorPerformance], error), latestAppliedEpoch iotago.EpochIndex, apiProvider iotago.APIProvider, errHandler func(error)) *Tracker { return &Tracker{ - rewardsStorePerEpochFunc: rewardsStorePerEpochFunc, - poolStatsStore: poolStatsStore, - committeeStore: committeeStore, - validatorPerformancesFunc: validatorPerformancesFunc, - latestAppliedEpoch: latestAppliedEpoch, - apiProvider: apiProvider, - errHandler: errHandler, + nextEpochCommitteeCandidates: shrinkingmap.New[iotago.AccountID, iotago.SlotIndex](), + rewardsStorePerEpochFunc: rewardsStorePerEpochFunc, + poolStatsStore: poolStatsStore, + committeeStore: committeeStore, + committeeCandidatesInEpochFunc: committeeCandidatesInEpochFunc, + validatorPerformancesFunc: validatorPerformancesFunc, + latestAppliedEpoch: latestAppliedEpoch, + apiProvider: apiProvider, + errHandler: errHandler, } } -func (t *Tracker) RegisterCommittee(epoch iotago.EpochIndex, committee *account.Accounts) error { - return t.committeeStore.Store(epoch, committee) +func (t *Tracker) ClearCandidates() { + // clean the candidate cache stored in memory to make room for candidates in the next epoch + t.nextEpochCommitteeCandidates.Clear() } func (t *Tracker) TrackValidationBlock(block *blocks.Block) { + t.mutex.Lock() + defer t.mutex.Unlock() + validatorBlock, isValidationBlock := block.ValidationBlock() if !isValidationBlock { return } - t.mutex.Lock() - defer t.mutex.Unlock() - t.performanceFactorsMutex.Lock() defer t.performanceFactorsMutex.Unlock() isCommitteeMember, err := t.isCommitteeMember(block.ID().Slot(), block.ProtocolBlock().IssuerID) if err != nil { - t.errHandler(ierrors.Errorf("failed to check if account %s is committee member", block.ProtocolBlock().IssuerID)) + t.errHandler(ierrors.Wrapf(err, "error while checking if account %s is a committee member in slot %d", block.ProtocolBlock().IssuerID, block.ID().Slot())) return } @@ -80,35 +76,103 @@ func (t *Tracker) TrackValidationBlock(block *blocks.Block) { } } -func (t *Tracker) EligibleValidatorCandidates(epoch iotago.EpochIndex) ds.Set[iotago.AccountID] { - // TODO: to be implemented for 1.1, for now we just pick previous committee +func (t *Tracker) TrackCandidateBlock(block *blocks.Block) { + t.mutex.Lock() + defer t.mutex.Unlock() - eligible := ds.NewSet[iotago.AccountID]() + blockEpoch := t.apiProvider.APIForSlot(block.ID().Slot()).TimeProvider().EpochFromSlot(block.ID().Slot()) - lo.PanicOnErr(t.committeeStore.Load(epoch - 1)).ForEach(func(accountID iotago.AccountID, _ *account.Pool) bool { - eligible.Add(accountID) + if block.Payload().PayloadType() != iotago.PayloadCandidacyAnnouncement { + return + } - return true + var rollback bool + t.nextEpochCommitteeCandidates.Compute(block.ProtocolBlock().IssuerID, func(currentValue iotago.SlotIndex, exists bool) iotago.SlotIndex { + if !exists || currentValue > block.ID().Slot() { + committeeCandidatesStore, err := t.committeeCandidatesInEpochFunc(blockEpoch) + if err != nil { + // if there is an error, and we don't register a candidate, then we might eventually create a different commitment + t.errHandler(ierrors.Wrapf(err, "error while retrieving candidate storage for epoch %d", blockEpoch)) + + // rollback on error if entry did not exist before + rollback = !exists + + return currentValue + } + + err = committeeCandidatesStore.Set(block.ProtocolBlock().IssuerID[:], block.ID().Slot().MustBytes()) + if err != nil { + // if there is an error, and we don't register a candidate, then we might eventually create a different commitment + t.errHandler(ierrors.Wrapf(err, "error while updating candidate activity for epoch %d", blockEpoch)) + + // rollback on error if entry did not exist before + rollback = !exists + + return currentValue + } + + return block.ID().Slot() + } + + return currentValue }) - return eligible - - // epochStart := t.apiProvider.APIForEpoch(epoch).TimeProvider().EpochStart(epoch) - // registeredStore := t.registeredValidatorsFunc(epochStart) - // eligible := ds.NewSet[iotago.AccountID]() - // registeredStore.ForEach(func(accountID iotago.AccountID, a *prunable.RegisteredValidatorActivity) bool { - // if a.Active { - // eligible.Add(accountID) - // } - // return true - // } + // if there was an error when computing the value, + // and it was the first entry for the given issuer, then remove the entry + if rollback { + t.nextEpochCommitteeCandidates.Delete(block.ProtocolBlock().IssuerID) + } + +} + +func (t *Tracker) EligibleValidatorCandidates(epoch iotago.EpochIndex) (ds.Set[iotago.AccountID], error) { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return t.getValidatorCandidates(epoch) } // ValidatorCandidates returns the registered validator candidates for the given epoch. -func (t *Tracker) ValidatorCandidates(_ iotago.EpochIndex) ds.Set[iotago.AccountID] { - // TODO: we should choose candidates we tracked performance for no matter if they were active +func (t *Tracker) ValidatorCandidates(epoch iotago.EpochIndex) (ds.Set[iotago.AccountID], error) { + t.mutex.RLock() + defer t.mutex.RUnlock() - return ds.NewSet[iotago.AccountID]() + return t.getValidatorCandidates(epoch) +} + +func (t *Tracker) getValidatorCandidates(epoch iotago.EpochIndex) (ds.Set[iotago.AccountID], error) { + // we store candidates in the store for the epoch of their activity, but the passed argument points to the target epoch, + // so it's necessary to subtract one epoch from the passed value + candidateStore, err := t.committeeCandidatesInEpochFunc(epoch - 1) + if err != nil { + return nil, ierrors.Wrapf(err, "error while retrieving candidates for epoch %d", epoch) + } + + candidates := ds.NewSet[iotago.AccountID]() + + var innerErr error + err = candidateStore.IterateKeys(kvstore.EmptyPrefix, func(key kvstore.Key) bool { + accountID, _, err := iotago.AccountIDFromBytes(key) + if err != nil { + innerErr = err + + return false + } + + candidates.Add(accountID) + + return true + }) + + if innerErr != nil { + return nil, ierrors.Wrapf(innerErr, "error while iterating through candidates for epoch %d", epoch) + } + + if err != nil { + return nil, ierrors.Wrapf(err, "error while retrieving candidates for epoch %d", epoch) + } + + return candidates, nil } func (t *Tracker) LoadCommitteeForEpoch(epoch iotago.EpochIndex) (committee *account.Accounts, exists bool) { @@ -116,6 +180,7 @@ func (t *Tracker) LoadCommitteeForEpoch(epoch iotago.EpochIndex) (committee *acc if err != nil { panic(ierrors.Wrapf(err, "failed to load committee for epoch %d", epoch)) } + if c == nil { return nil, false } @@ -259,25 +324,31 @@ func (t *Tracker) trackCommitteeMemberPerformance(validationBlock *iotago.Valida validatorPerformance, err := validatorPerformances.Load(block.ProtocolBlock().IssuerID) if err != nil { t.errHandler(ierrors.Errorf("failed to load performance factor for account %s", block.ProtocolBlock().IssuerID)) + + return } + // key not found if validatorPerformance == nil { validatorPerformance = model.NewValidatorPerformance() } - // set bit at subslotIndex to 1 to indicate activity in that subslot + // set a bit at subslotIndex to 1 to indicate activity in that subslot validatorPerformance.SlotActivityVector = validatorPerformance.SlotActivityVector | (1 << t.subslotIndex(block.ID().Slot(), block.ProtocolBlock().IssuingTime)) apiForSlot := t.apiProvider.APIForSlot(block.ID().Slot()) + // we restrict the number up to ValidatorBlocksPerSlot + 1 to know later if the validator issued more blocks than allowed and be able to punish for it // also it can fint into uint8 if validatorPerformance.BlockIssuedCount < apiForSlot.ProtocolParameters().ValidationBlocksPerSlot()+1 { validatorPerformance.BlockIssuedCount++ } + validatorPerformance.HighestSupportedVersionAndHash = model.VersionAndHash{ Version: validationBlock.HighestSupportedVersion, Hash: validationBlock.ProtocolParametersHash, } + if err = validatorPerformances.Store(block.ProtocolBlock().IssuerID, validatorPerformance); err != nil { t.errHandler(ierrors.Errorf("failed to store performance factor for account %s", block.ProtocolBlock().IssuerID)) } diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go index 5d9c9ce5d..6615023d4 100644 --- a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go +++ b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/testsuite_test.go @@ -73,8 +73,18 @@ func (t *TestSuite) InitPerformanceTracker() { rewardsStore := epochstore.NewEpochKVStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0) poolStatsStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*model.PoolsStats).Bytes, model.PoolsStatsFromBytes) committeeStore := epochstore.NewStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0, (*account.Accounts).Bytes, account.AccountsFromBytes) - - t.Instance = NewTracker(rewardsStore.GetEpoch, poolStatsStore, committeeStore, performanceFactorFunc, t.latestCommittedEpoch, api.SingleVersionProvider(t.api), func(err error) {}) + committeeCandidatesStore := epochstore.NewEpochKVStore(kvstore.Realm{}, kvstore.Realm{}, mapdb.NewMapDB(), 0) + + t.Instance = NewTracker( + rewardsStore.GetEpoch, + poolStatsStore, + committeeStore, + committeeCandidatesStore.GetEpoch, + performanceFactorFunc, + t.latestCommittedEpoch, + api.SingleVersionProvider(t.api), + func(err error) {}, + ) } func (t *TestSuite) Account(alias string, createIfNotExists bool) iotago.AccountID { @@ -103,7 +113,8 @@ func (t *TestSuite) ApplyEpochActions(epoch iotago.EpochIndex, actions map[strin }) } - err := t.Instance.RegisterCommittee(epoch, committee) + // Store directly on the committee store, because in actual code the SeatManager is responsible for adding the storage entry. + err := t.Instance.committeeStore.Store(epoch, committee) require.NoError(t.T, err) for accIDAlias, action := range actions { accID := t.Account(accIDAlias, false) diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/tracker_test.go b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/tracker_test.go index 46033f793..49a1a95bf 100644 --- a/pkg/protocol/sybilprotection/sybilprotectionv1/performance/tracker_test.go +++ b/pkg/protocol/sybilprotection/sybilprotectionv1/performance/tracker_test.go @@ -3,7 +3,14 @@ package performance import ( "testing" + "github.com/stretchr/testify/require" + + "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/model" + "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/tpkg" ) func TestManager_Rewards(t *testing.T) { @@ -80,5 +87,77 @@ func TestManager_Rewards(t *testing.T) { ts.AssertRewardForDelegatorsOnly("D", epoch, epochActions) // test the epoch after initial phase +} + +func TestManager_Candidates(t *testing.T) { + ts := NewTestSuite(t) + + issuer1 := tpkg.RandAccountID() + issuer2 := tpkg.RandAccountID() + issuer3 := tpkg.RandAccountID() + { + block1 := tpkg.RandProtocolBlock(tpkg.RandBasicBlock(ts.api, iotago.PayloadCandidacyAnnouncement), ts.api, 0) + + block1.IssuingTime = ts.api.TimeProvider().SlotStartTime(1) + block1.IssuerID = issuer1 + + block2 := tpkg.RandProtocolBlock(tpkg.RandBasicBlock(ts.api, iotago.PayloadCandidacyAnnouncement), ts.api, 0) + + block2.IssuingTime = ts.api.TimeProvider().SlotStartTime(2) + block2.IssuerID = issuer2 + + block3 := tpkg.RandProtocolBlock(tpkg.RandBasicBlock(ts.api, iotago.PayloadCandidacyAnnouncement), ts.api, 0) + + block3.IssuingTime = ts.api.TimeProvider().SlotStartTime(3) + block3.IssuerID = issuer3 + + ts.Instance.TrackCandidateBlock(blocks.NewBlock(lo.PanicOnErr(model.BlockFromBlock(block1)))) + ts.Instance.TrackCandidateBlock(blocks.NewBlock(lo.PanicOnErr(model.BlockFromBlock(block2)))) + ts.Instance.TrackCandidateBlock(blocks.NewBlock(lo.PanicOnErr(model.BlockFromBlock(block3)))) + } + + { + block4 := tpkg.RandProtocolBlock(tpkg.RandBasicBlock(ts.api, iotago.PayloadCandidacyAnnouncement), ts.api, 0) + block4.IssuingTime = ts.api.TimeProvider().SlotStartTime(4) + block4.IssuerID = issuer1 + + block5 := tpkg.RandProtocolBlock(tpkg.RandBasicBlock(ts.api, iotago.PayloadCandidacyAnnouncement), ts.api, 0) + + block5.IssuingTime = ts.api.TimeProvider().SlotStartTime(5) + block5.IssuerID = issuer2 + + block6 := tpkg.RandProtocolBlock(tpkg.RandBasicBlock(ts.api, iotago.PayloadCandidacyAnnouncement), ts.api, 0) + + block6.IssuingTime = ts.api.TimeProvider().SlotStartTime(6) + block6.IssuerID = issuer3 + + ts.Instance.TrackCandidateBlock(blocks.NewBlock(lo.PanicOnErr(model.BlockFromBlock(block4)))) + ts.Instance.TrackCandidateBlock(blocks.NewBlock(lo.PanicOnErr(model.BlockFromBlock(block5)))) + ts.Instance.TrackCandidateBlock(blocks.NewBlock(lo.PanicOnErr(model.BlockFromBlock(block6)))) + } + + require.True(t, lo.PanicOnErr(ts.Instance.EligibleValidatorCandidates(1)).HasAll(ds.NewReadableSet(issuer1, issuer2, issuer3))) + require.True(t, lo.PanicOnErr(ts.Instance.ValidatorCandidates(1)).HasAll(ds.NewReadableSet(issuer1, issuer2, issuer3))) + require.True(t, lo.PanicOnErr(ts.Instance.EligibleValidatorCandidates(2)).IsEmpty()) + require.True(t, lo.PanicOnErr(ts.Instance.ValidatorCandidates(2)).IsEmpty()) + + // retrieve epoch candidates for epoch 0, because we candidates prefixed with epoch in which they candidated + candidatesStore, err := ts.Instance.committeeCandidatesInEpochFunc(0) + require.NoError(t, err) + + candidacySlotIssuer1, err := candidatesStore.Get(issuer1[:]) + require.NoError(t, err) + require.Equal(t, iotago.SlotIndex(1).MustBytes(), candidacySlotIssuer1) + + candidacySlotIssuer2, err := candidatesStore.Get(issuer2[:]) + require.NoError(t, err) + require.Equal(t, iotago.SlotIndex(2).MustBytes(), candidacySlotIssuer2) + + candidacySlotIssuer3, err := candidatesStore.Get(issuer3[:]) + require.NoError(t, err) + require.Equal(t, iotago.SlotIndex(3).MustBytes(), candidacySlotIssuer3) + + ts.Instance.ClearCandidates() + require.True(t, ts.Instance.nextEpochCommitteeCandidates.IsEmpty()) } diff --git a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go index 01ac56ae7..1e15ec4e2 100644 --- a/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go +++ b/pkg/protocol/sybilprotection/sybilprotectionv1/sybilprotection.go @@ -37,7 +37,7 @@ type SybilProtection struct { errHandler func(error) - optsInitialCommittee *account.Accounts + optsInitialCommittee accounts.AccountsData optsSeatManagerProvider module.Provider[*engine.Engine, seatmanager.SeatManager] mutex syncutils.Mutex @@ -62,34 +62,25 @@ func NewProvider(opts ...options.Option[SybilProtection]) module.Provider[*engin latestCommittedSlot := e.Storage.Settings().LatestCommitment().Slot() latestCommittedEpoch := o.apiProvider.APIForSlot(latestCommittedSlot).TimeProvider().EpochFromSlot(latestCommittedSlot) - o.performanceTracker = performance.NewTracker(e.Storage.RewardsForEpoch, e.Storage.PoolStats(), e.Storage.Committee(), e.Storage.ValidatorPerformances, latestCommittedEpoch, e, o.errHandler) + o.performanceTracker = performance.NewTracker(e.Storage.RewardsForEpoch, e.Storage.PoolStats(), e.Storage.Committee(), e.Storage.CommitteeCandidates, e.Storage.ValidatorPerformances, latestCommittedEpoch, e, o.errHandler) o.lastCommittedSlot = latestCommittedSlot if o.optsInitialCommittee != nil { - if err := o.performanceTracker.RegisterCommittee(0, o.optsInitialCommittee); err != nil { + if _, err := o.seatManager.RotateCommittee(0, o.optsInitialCommittee); err != nil { panic(ierrors.Wrap(err, "error while registering initial committee for epoch 0")) } } + o.TriggerConstructed() // When the engine is triggered initialized, snapshot has been read or database has been initialized properly, // so the committee should be available in the performance manager. e.HookInitialized(func() { - // Make sure that the sybil protection knows about the committee of the current epoch - // (according to the latest committed slot), and potentially the next selected - // committee if we have one. - - currentEpoch := e.LatestAPI().TimeProvider().EpochFromSlot(e.Storage.Settings().LatestCommitment().Slot()) - - committee, exists := o.performanceTracker.LoadCommitteeForEpoch(currentEpoch) - if !exists { - panic("failed to load committee for last finalized slot to initialize sybil protection") - } - o.seatManager.ImportCommittee(currentEpoch, committee) - fmt.Println("committee import", committee.TotalStake(), currentEpoch) - if nextCommittee, nextCommitteeExists := o.performanceTracker.LoadCommitteeForEpoch(currentEpoch + 1); nextCommitteeExists { - o.seatManager.ImportCommittee(currentEpoch+1, nextCommittee) - fmt.Println("next committee", nextCommittee.TotalStake(), currentEpoch+1) + // Mark the committee for the last committed slot as active. + currentEpoch := e.CommittedAPI().TimeProvider().EpochFromSlot(e.Storage.Settings().LatestCommitment().Slot()) + err := o.seatManager.InitializeCommittee(currentEpoch, e.Clock.Accepted().RelativeTime()) + if err != nil { + panic(ierrors.Wrap(err, "error while initializing committee")) } o.TriggerInitialized() @@ -108,8 +99,43 @@ func (o *SybilProtection) Shutdown() { o.TriggerStopped() } -func (o *SybilProtection) TrackValidationBlock(block *blocks.Block) { - o.performanceTracker.TrackValidationBlock(block) +func (o *SybilProtection) TrackBlock(block *blocks.Block) { + if _, isValidationBlock := block.ValidationBlock(); isValidationBlock { + o.performanceTracker.TrackValidationBlock(block) + + return + } + + accountData, exists, err := o.ledger.Account(block.ProtocolBlock().IssuerID, block.SlotCommitmentID().Slot()) + if err != nil { + o.errHandler(ierrors.Wrapf(err, "error while retrieving account from account %s in slot %d from accounts ledger", block.ProtocolBlock().IssuerID, block.SlotCommitmentID().Slot())) + + return + } + + if !exists { + return + } + + blockEpoch := o.apiProvider.APIForSlot(block.ID().Slot()).TimeProvider().EpochFromSlot(block.ID().Slot()) + + // if the block is issued before the stake end epoch, then it's not a valid validator or candidate block + if accountData.StakeEndEpoch < blockEpoch { + return + } + + // if a candidate block is issued in the stake end epoch, + // or if block is issued after EpochEndSlot - EpochNearingThreshold, because candidates can register only until that point. + // then don't consider it because the validator can't be part of the committee in the next epoch + if accountData.StakeEndEpoch == blockEpoch || + block.ID().Slot()+o.apiProvider.APIForSlot(block.ID().Slot()).ProtocolParameters().EpochNearingThreshold() > o.apiProvider.APIForSlot(block.ID().Slot()).TimeProvider().EpochEnd(blockEpoch) { + + return + } + + if block.Payload().PayloadType() == iotago.PayloadCandidacyAnnouncement { + o.performanceTracker.TrackCandidateBlock(block) + } } func (o *SybilProtection) CommitSlot(slot iotago.SlotIndex) (committeeRoot, rewardsRoot iotago.Identifier, err error) { @@ -126,23 +152,23 @@ func (o *SybilProtection) CommitSlot(slot iotago.SlotIndex) (committeeRoot, rewa // If the committed slot is `maxCommittableSlot` // away from the end of the epoch, then register a committee for the next epoch. if timeProvider.EpochEnd(currentEpoch) == slot+maxCommittableAge { - if _, committeeExists := o.performanceTracker.LoadCommitteeForEpoch(nextEpoch); !committeeExists { + if _, committeeExists := o.seatManager.CommitteeInEpoch(nextEpoch); !committeeExists { // If the committee for the epoch wasn't set before due to finalization of a slot, // we promote the current committee to also serve in the next epoch. - committee, exists := o.performanceTracker.LoadCommitteeForEpoch(currentEpoch) + committee, exists := o.seatManager.CommitteeInEpoch(currentEpoch) if !exists { // that should never happen as it is already the fallback strategy panic(fmt.Sprintf("committee for current epoch %d not found", currentEpoch)) } - committee.SetReused() - o.seatManager.SetCommittee(nextEpoch, committee) - - o.events.CommitteeSelected.Trigger(committee, nextEpoch) - - if err = o.performanceTracker.RegisterCommittee(nextEpoch, committee); err != nil { - return iotago.Identifier{}, iotago.Identifier{}, ierrors.Wrapf(err, "failed to register committee for epoch %d", nextEpoch) + committeeAccounts := committee.Accounts() + committeeAccounts.SetReused() + if err = o.seatManager.SetCommittee(nextEpoch, committeeAccounts); err != nil { + return iotago.Identifier{}, iotago.Identifier{}, ierrors.Wrapf(err, "failed to set committee for epoch %d", nextEpoch) } + o.performanceTracker.ClearCandidates() + + o.events.CommitteeSelected.Trigger(committeeAccounts, nextEpoch) } } @@ -191,7 +217,7 @@ func (o *SybilProtection) CommitSlot(slot iotago.SlotIndex) (committeeRoot, rewa func (o *SybilProtection) committeeRoot(targetCommitteeEpoch iotago.EpochIndex) (committeeRoot iotago.Identifier, err error) { committee, exists := o.performanceTracker.LoadCommitteeForEpoch(targetCommitteeEpoch) if !exists { - return iotago.Identifier{}, ierrors.Wrapf(err, "committee for a finished epoch %d not found", targetCommitteeEpoch) + return iotago.Identifier{}, ierrors.Wrapf(err, "committee for an epoch %d not found", targetCommitteeEpoch) } committeeTree := ads.NewSet[iotago.Identifier]( @@ -251,23 +277,30 @@ func (o *SybilProtection) slotFinalized(slot iotago.SlotIndex) { if slot+apiForSlot.ProtocolParameters().EpochNearingThreshold() == epochEndSlot && epochEndSlot > o.lastCommittedSlot+apiForSlot.ProtocolParameters().MaxCommittableAge() { newCommittee := o.selectNewCommittee(slot) - fmt.Println("new committee selection finalization", epoch, newCommittee.TotalStake(), newCommittee.TotalValidatorStake()) o.events.CommitteeSelected.Trigger(newCommittee, epoch+1) } } // IsCandidateActive returns true if the given validator is currently active. -func (o *SybilProtection) IsCandidateActive(validatorID iotago.AccountID, epoch iotago.EpochIndex) bool { - activeCandidates := o.performanceTracker.EligibleValidatorCandidates(epoch) - return activeCandidates.Has(validatorID) +func (o *SybilProtection) IsCandidateActive(validatorID iotago.AccountID, epoch iotago.EpochIndex) (bool, error) { + activeCandidates, err := o.performanceTracker.EligibleValidatorCandidates(epoch) + if err != nil { + return false, ierrors.Wrapf(err, "failed to retrieve eligible candidates") + } + + return activeCandidates.Has(validatorID), nil } // EligibleValidators returns the currently known list of recently active validator candidates for the given epoch. func (o *SybilProtection) EligibleValidators(epoch iotago.EpochIndex) (accounts.AccountsData, error) { - candidates := o.performanceTracker.EligibleValidatorCandidates(epoch) + candidates, err := o.performanceTracker.EligibleValidatorCandidates(epoch) + if err != nil { + return nil, ierrors.Wrapf(err, "failed to retrieve eligible validator candidates for epoch %d", epoch) + } + validators := make(accounts.AccountsData, 0) - if err := candidates.ForEach(func(candidate iotago.AccountID) error { + if err = candidates.ForEach(func(candidate iotago.AccountID) error { accountData, exists, err := o.ledger.Account(candidate, o.lastCommittedSlot) if err != nil { return ierrors.Wrapf(err, "failed to load account data for candidate %s", candidate) @@ -291,8 +324,15 @@ func (o *SybilProtection) EligibleValidators(epoch iotago.EpochIndex) (accounts. // OrderedRegisteredCandidateValidatorsList returns the currently known list of registered validator candidates for the given epoch. func (o *SybilProtection) OrderedRegisteredCandidateValidatorsList(epoch iotago.EpochIndex) ([]*apimodels.ValidatorResponse, error) { - candidates := o.performanceTracker.ValidatorCandidates(epoch) - activeCandidates := o.performanceTracker.EligibleValidatorCandidates(epoch) + candidates, err := o.performanceTracker.ValidatorCandidates(epoch) + if err != nil { + return nil, ierrors.Wrapf(err, "failed to retrieve candidates") + } + + activeCandidates, err := o.performanceTracker.EligibleValidatorCandidates(epoch) + if err != nil { + return nil, ierrors.Wrapf(err, "failed to retrieve eligible candidates") + } validatorResp := make([]*apimodels.ValidatorResponse, 0, candidates.Size()) if err := candidates.ForEach(func(candidate iotago.AccountID) error { @@ -335,43 +375,41 @@ func (o *SybilProtection) selectNewCommittee(slot iotago.SlotIndex) *account.Acc timeProvider := o.apiProvider.APIForSlot(slot).TimeProvider() currentEpoch := timeProvider.EpochFromSlot(slot) nextEpoch := currentEpoch + 1 - candidates := o.performanceTracker.EligibleValidatorCandidates(nextEpoch) + candidates, err := o.performanceTracker.EligibleValidatorCandidates(nextEpoch) + if err != nil { + panic(ierrors.Wrapf(err, "failed to retrieve candidates for epoch %d", nextEpoch)) + } - weightedCandidates := account.NewAccounts() + candidateAccounts := make(accounts.AccountsData, 0) if err := candidates.ForEach(func(candidate iotago.AccountID) error { - a, exists, err := o.ledger.Account(candidate, slot) + accountData, exists, err := o.ledger.Account(candidate, slot) if err != nil { return err } if !exists { - o.errHandler(ierrors.Errorf("account of committee candidate does not exist: %s", candidate)) + return ierrors.Errorf("account of committee candidate %s does not exist in slot %d", candidate, slot) } - weightedCandidates.Set(candidate, &account.Pool{ - PoolStake: a.ValidatorStake + a.DelegationStake, - ValidatorStake: a.ValidatorStake, - FixedCost: a.FixedCost, - }) + candidateAccounts = append(candidateAccounts, accountData) return nil }); err != nil { - o.errHandler(err) + panic(ierrors.Wrap(err, "failed to iterate through candidates")) } - newCommittee := o.seatManager.RotateCommittee(nextEpoch, weightedCandidates) - weightedCommittee := newCommittee.Accounts() - - err := o.performanceTracker.RegisterCommittee(nextEpoch, weightedCommittee) + newCommittee, err := o.seatManager.RotateCommittee(nextEpoch, candidateAccounts) if err != nil { - o.errHandler(ierrors.Wrap(err, "failed to register committee for epoch")) + panic(ierrors.Wrap(err, "failed to rotate committee")) } - return weightedCommittee + o.performanceTracker.ClearCandidates() + + return newCommittee.Accounts() } // WithInitialCommittee registers the passed committee on a given slot. // This is needed to generate Genesis snapshot with some initial committee. -func WithInitialCommittee(committee *account.Accounts) options.Option[SybilProtection] { +func WithInitialCommittee(committee accounts.AccountsData) options.Option[SybilProtection] { return func(o *SybilProtection) { o.optsInitialCommittee = committee } diff --git a/pkg/retainer/retainer/errors.go b/pkg/retainer/retainer/errors.go index 9ef4908a0..88d9594f6 100644 --- a/pkg/retainer/retainer/errors.go +++ b/pkg/retainer/retainer/errors.go @@ -62,6 +62,14 @@ var txErrorsFailureReasonMap = map[error]apimodels.TransactionFailureReason{ iotago.ErrInputOutputManaMismatch: apimodels.TxFailureManaAmountInvalid, iotago.ErrManaAmountInvalid: apimodels.TxFailureManaAmountInvalid, iotago.ErrInputCreationAfterTxCreation: apimodels.TxFailureInputCreationAfterTxCreation, + + // tx capabilities errors + iotago.ErrTxCapabilitiesNativeTokenBurningNotAllowed: apimodels.TxFailureCapabilitiesNativeTokenBurningNotAllowed, + iotago.ErrTxCapabilitiesManaBurningNotAllowed: apimodels.TxFailureCapabilitiesManaBurningNotAllowed, + iotago.ErrTxCapabilitiesAccountDestructionNotAllowed: apimodels.TxFailureCapabilitiesAccountDestructionNotAllowed, + iotago.ErrTxCapabilitiesAnchorDestructionNotAllowed: apimodels.TxFailureCapabilitiesAnchorDestructionNotAllowed, + iotago.ErrTxCapabilitiesFoundryDestructionNotAllowed: apimodels.TxFailureCapabilitiesFoundryDestructionNotAllowed, + iotago.ErrTxCapabilitiesNFTDestructionNotAllowed: apimodels.TxFailureCapabilitiesNFTDestructionNotAllowed, } func determineTxFailureReason(err error) apimodels.TransactionFailureReason { diff --git a/pkg/storage/prunable/prunable.go b/pkg/storage/prunable/prunable.go index 1fc392877..9e4396539 100644 --- a/pkg/storage/prunable/prunable.go +++ b/pkg/storage/prunable/prunable.go @@ -155,6 +155,10 @@ func (p *Prunable) Rollback(targetSlot iotago.SlotIndex) error { return ierrors.Wrapf(err, "error while rolling back slots in a bucket for epoch %d", targetSlotEpoch) } + if err := p.rollbackCommitteesCandidates(targetSlotEpoch, targetSlot); err != nil { + return ierrors.Wrapf(err, "error while rolling back committee for epoch %d", targetSlotEpoch) + } + // Shut down the prunableSlotStore in order to flush and get consistent state on disk after reopening. p.prunableSlotStore.Shutdown() @@ -218,3 +222,49 @@ func (p *Prunable) shouldRollbackCommittee(epoch iotago.EpochIndex, targetSlot i return true, nil } + +func (p *Prunable) rollbackCommitteesCandidates(targetSlotEpoch iotago.EpochIndex, targetSlot iotago.SlotIndex) error { + candidatesToRollback := make([]iotago.AccountID, 0) + + candidates, err := p.CommitteeCandidates(targetSlotEpoch) + if err != nil { + return ierrors.Wrap(err, "failed to get candidates store") + } + + var innerErr error + if err = candidates.Iterate(kvstore.EmptyPrefix, func(key kvstore.Key, value kvstore.Value) bool { + accountID, _, err := iotago.AccountIDFromBytes(key) + if err != nil { + innerErr = err + + return false + } + + candidacySlot, _, err := iotago.SlotIndexFromBytes(value) + if err != nil { + innerErr = err + + return false + } + + if candidacySlot < targetSlot { + candidatesToRollback = append(candidatesToRollback, accountID) + } + + return true + }); err != nil { + return ierrors.Wrap(err, "failed to collect candidates to rollback") + } + + if innerErr != nil { + return ierrors.Wrap(innerErr, "failed to iterate through candidates") + } + + for _, candidateToRollback := range candidatesToRollback { + if err = candidates.Delete(candidateToRollback[:]); err != nil { + return ierrors.Wrapf(innerErr, "failed to rollback candidate %s", candidateToRollback) + } + } + + return nil +} diff --git a/pkg/storage/prunable/prunable_slot.go b/pkg/storage/prunable/prunable_slot.go index a7632ca3c..0549278b8 100644 --- a/pkg/storage/prunable/prunable_slot.go +++ b/pkg/storage/prunable/prunable_slot.go @@ -21,6 +21,7 @@ const ( slotPrefixUpgradeSignals slotPrefixRoots slotPrefixRetainer + epochPrefixCommitteeCandidates ) func (p *Prunable) getKVStoreFromSlot(slot iotago.SlotIndex, prefix kvstore.Realm) (kvstore.KVStore, error) { @@ -52,6 +53,12 @@ func (p *Prunable) RootBlocks(slot iotago.SlotIndex) (*slotstore.Store[iotago.Bl ), nil } +func (p *Prunable) CommitteeCandidates(epoch iotago.EpochIndex) (kvstore.KVStore, error) { + // Use the first slot of an epoch to avoid random clashes with other keys. + // Candidates belong to an epoch, but we store them here so that they're pruned more quickly and easily without unnecessary key iteration. + return p.prunableSlotStore.Get(epoch, byteutils.ConcatBytes(p.apiProvider.APIForEpoch(epoch).TimeProvider().EpochStart(epoch).MustBytes(), kvstore.Realm{epochPrefixCommitteeCandidates})) +} + func (p *Prunable) Mutations(slot iotago.SlotIndex) (kvstore.KVStore, error) { return p.getKVStoreFromSlot(slot, kvstore.Realm{slotPrefixMutations}) } diff --git a/pkg/storage/storage_prunable.go b/pkg/storage/storage_prunable.go index a65cd9406..5403484d9 100644 --- a/pkg/storage/storage_prunable.go +++ b/pkg/storage/storage_prunable.go @@ -29,6 +29,10 @@ func (s *Storage) Committee() *epochstore.Store[*account.Accounts] { return s.prunable.Committee() } +func (s *Storage) CommitteeCandidates(epoch iotago.EpochIndex) (kvstore.KVStore, error) { + return s.prunable.CommitteeCandidates(epoch) +} + func (s *Storage) Blocks(slot iotago.SlotIndex) (*slotstore.Blocks, error) { return s.prunable.Blocks(slot) } diff --git a/pkg/tests/committee_rotation_test.go b/pkg/tests/committee_rotation_test.go new file mode 100644 index 000000000..56b4f397d --- /dev/null +++ b/pkg/tests/committee_rotation_test.go @@ -0,0 +1,115 @@ +package tests + +import ( + "testing" + + "github.com/iotaledger/hive.go/runtime/options" + "github.com/iotaledger/iota-core/pkg/protocol" + "github.com/iotaledger/iota-core/pkg/protocol/snapshotcreator" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/topstakers" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/sybilprotectionv1" + "github.com/iotaledger/iota-core/pkg/testsuite" + iotago "github.com/iotaledger/iota.go/v4" +) + +func Test_TopStakersRotation(t *testing.T) { + ts := testsuite.NewTestSuite(t, + testsuite.WithProtocolParametersOptions( + iotago.WithTimeProviderOptions( + testsuite.GenesisTimeWithOffsetBySlots(1000, testsuite.DefaultSlotDurationInSeconds), + testsuite.DefaultSlotDurationInSeconds, + 4, + ), + iotago.WithLivenessOptions( + 10, + 10, + 3, + 4, + 5, + ), + ), + testsuite.WithSnapshotOptions( + snapshotcreator.WithSeatManagerProvider( + topstakers.NewProvider( + topstakers.WithSeatCount(3), + ), + ), + ), + ) + defer ts.Shutdown() + + ts.AddValidatorNode("node1", 1_000_006) + ts.AddValidatorNode("node2", 1_000_005) + ts.AddValidatorNode("node3", 1_000_004) + ts.AddValidatorNode("node4", 1_000_003) + ts.AddValidatorNode("node5", 1_000_002) + ts.AddValidatorNode("node6", 1_000_001) + + nodeOptions := make(map[string][]options.Option[protocol.Protocol]) + + for _, node := range ts.Nodes() { + nodeOptions[node.Name] = []options.Option[protocol.Protocol]{protocol.WithSybilProtectionProvider( + sybilprotectionv1.NewProvider( + sybilprotectionv1.WithSeatManagerProvider( + topstakers.NewProvider( + topstakers.WithSeatCount(3), + ), + ), + ), + )} + } + ts.Run(true, nodeOptions) + + for _, node := range ts.Nodes() { + nodeOptions[node.Name] = []options.Option[protocol.Protocol]{protocol.WithSybilProtectionProvider( + sybilprotectionv1.NewProvider( + sybilprotectionv1.WithSeatManagerProvider( + topstakers.NewProvider(topstakers.WithSeatCount(3)), + ), + ), + )} + } + ts.AssertSybilProtectionCommittee(0, []iotago.AccountID{ + ts.Node("node1").Validator.AccountID, + ts.Node("node2").Validator.AccountID, + ts.Node("node3").Validator.AccountID, + }, ts.Nodes()...) + + ts.IssueBlocksAtSlots("wave-1:", []iotago.SlotIndex{1, 2, 3, 4}, 4, "Genesis", ts.Nodes(), true, nil) + + ts.IssueCandidacyAnnouncementInSlot("node1-candidacy:1", 4, "wave-1:4.3", ts.Node("node1")) + ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:1", 5, "node1-candidacy:1", ts.Node("node4")) + + ts.IssueBlocksAtSlots("wave-2:", []iotago.SlotIndex{5, 6, 7, 8, 9}, 4, "node4-candidacy:1", ts.Nodes(), true, nil) + + ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:2", 9, "wave-2:9.3", ts.Node("node4")) + ts.IssueCandidacyAnnouncementInSlot("node5-candidacy:1", 9, "node4-candidacy:2", ts.Node("node5")) + + // This candidacy should be considered as it's announced at the last possible slot. + ts.IssueCandidacyAnnouncementInSlot("node6-candidacy:1", 10, "node5-candidacy:1", ts.Node("node6")) + + ts.IssueBlocksAtSlots("wave-3:", []iotago.SlotIndex{10}, 4, "node6-candidacy:1", ts.Nodes(), true, nil) + + // Those candidacies should not be considered as they're issued after EpochNearingThreshold (slot 10). + ts.IssueCandidacyAnnouncementInSlot("node2-candidacy:1", 11, "wave-3:10.3", ts.Node("node2")) + ts.IssueCandidacyAnnouncementInSlot("node3-candidacy:1", 11, "node2-candidacy:1", ts.Node("node3")) + ts.IssueCandidacyAnnouncementInSlot("node4-candidacy:3", 11, "node3-candidacy:1", ts.Node("node3")) + ts.IssueCandidacyAnnouncementInSlot("node5-candidacy:2", 11, "node4-candidacy:3", ts.Node("node3")) + + // Assert that only candidates that issued before slot 11 are considered. + ts.AssertSybilProtectionCandidates(1, []iotago.AccountID{ + ts.Node("node1").Validator.AccountID, + ts.Node("node4").Validator.AccountID, + ts.Node("node5").Validator.AccountID, + ts.Node("node6").Validator.AccountID, + }, ts.Nodes()...) + + ts.IssueBlocksAtSlots("wave-4:", []iotago.SlotIndex{11, 12, 13, 14, 15, 16, 17}, 4, "node5-candidacy:2", ts.Nodes(), true, nil) + + ts.AssertLatestFinalizedSlot(13, ts.Nodes()...) + ts.AssertSybilProtectionCommittee(1, []iotago.AccountID{ + ts.Node("node1").Validator.AccountID, + ts.Node("node4").Validator.AccountID, + ts.Node("node5").Validator.AccountID, + }, ts.Nodes()...) +} diff --git a/pkg/tests/confirmation_state_test.go b/pkg/tests/confirmation_state_test.go index 4a0285d08..c874dd400 100644 --- a/pkg/tests/confirmation_state_test.go +++ b/pkg/tests/confirmation_state_test.go @@ -108,7 +108,7 @@ func TestConfirmationFlags(t *testing.T) { testsuite.WithChainID(genesisCommitment.MustID()), testsuite.WithStorageCommitments([]*iotago.Commitment{genesisCommitment}), testsuite.WithSybilProtectionCommittee(0, expectedCommittee), - testsuite.WithSybilProtectionOnlineCommittee(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeA.Validator.AccountID))), + testsuite.WithSybilProtectionOnlineCommittee(lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeA.Validator.AccountID))), testsuite.WithEvictedSlot(0), testsuite.WithActiveRootBlocks(ts.Blocks("Genesis")), testsuite.WithStorageRootBlocks(ts.Blocks("Genesis")), @@ -163,10 +163,10 @@ func TestConfirmationFlags(t *testing.T) { testsuite.WithLatestFinalizedSlot(0), testsuite.WithLatestCommitmentSlotIndex(2), testsuite.WithEqualStoredCommitmentAtIndex(2), - testsuite.WithSybilProtectionCommittee(slot2CommittableSlot, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(slot2CommittableSlot), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee( - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeA.Validator.AccountID)), - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeB.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeA.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeB.Validator.AccountID)), ), testsuite.WithEvictedSlot(2), ) @@ -203,11 +203,11 @@ func TestConfirmationFlags(t *testing.T) { testsuite.WithLatestFinalizedSlot(0), testsuite.WithLatestCommitmentSlotIndex(2), testsuite.WithEqualStoredCommitmentAtIndex(2), - testsuite.WithSybilProtectionCommittee(slot3CommittableSlot, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(slot3CommittableSlot), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee( - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeA.Validator.AccountID)), - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeB.Validator.AccountID)), - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeC.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeA.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeB.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeC.Validator.AccountID)), ), testsuite.WithEvictedSlot(2), ) @@ -258,11 +258,11 @@ func TestConfirmationFlags(t *testing.T) { testsuite.WithLatestFinalizedSlot(1), testsuite.WithLatestCommitmentSlotIndex(3), testsuite.WithEqualStoredCommitmentAtIndex(3), - testsuite.WithSybilProtectionCommittee(slot4CommittableSlot, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(slot4CommittableSlot), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee( - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeA.Validator.AccountID)), - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeB.Validator.AccountID)), - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeC.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeA.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeB.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeC.Validator.AccountID)), ), testsuite.WithEvictedSlot(3), ) diff --git a/pkg/tests/protocol_engine_rollback_test.go b/pkg/tests/protocol_engine_rollback_test.go index 1ab75a4ed..e99339b19 100644 --- a/pkg/tests/protocol_engine_rollback_test.go +++ b/pkg/tests/protocol_engine_rollback_test.go @@ -65,7 +65,6 @@ func TestProtocol_EngineRollbackFinalization(t *testing.T) { return poa }) } - nodeOptions := make(map[string][]options.Option[protocol.Protocol]) for _, node := range ts.Nodes() { nodeOptions[node.Name] = []options.Option[protocol.Protocol]{ @@ -105,10 +104,10 @@ func TestProtocol_EngineRollbackFinalization(t *testing.T) { node3.Validator.AccountID, } expectedOnlineCommitteeFull := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node2.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node3.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node2.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node3.Validator.AccountID)), } for _, node := range ts.Nodes() { @@ -144,7 +143,7 @@ func TestProtocol_EngineRollbackFinalization(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(9), testsuite.WithEqualStoredCommitmentAtIndex(9), testsuite.WithLatestCommitmentCumulativeWeight(28), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(9, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(9), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeFull...), testsuite.WithEvictedSlot(9), ) @@ -170,7 +169,7 @@ func TestProtocol_EngineRollbackFinalization(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(14), testsuite.WithEqualStoredCommitmentAtIndex(14), testsuite.WithLatestCommitmentCumulativeWeight(48), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(14, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(14), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeFull...), testsuite.WithEvictedSlot(14), ) @@ -292,15 +291,15 @@ func TestProtocol_EngineRollbackNoFinalization(t *testing.T) { node3.Validator.AccountID, } expectedOnlineCommitteeFull := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node2.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node3.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node2.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node3.Validator.AccountID)), } expectedOnlineCommitteeHalf := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), } for _, node := range ts.Nodes() { @@ -327,7 +326,7 @@ func TestProtocol_EngineRollbackNoFinalization(t *testing.T) { } // Issue up to slot 11 - just before committee selection for the next epoch. - // Committee will be reused at slot 10 is finalized or slot 12 is committed, whichever happens first. + // Committee will be reused when slot 10 is finalized or slot 12 is committed, whichever happens first. { ts.IssueBlocksAtSlots("P0:", []iotago.SlotIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, 4, "Genesis", ts.Nodes(), true, nil) @@ -336,7 +335,7 @@ func TestProtocol_EngineRollbackNoFinalization(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(9), testsuite.WithEqualStoredCommitmentAtIndex(9), testsuite.WithLatestCommitmentCumulativeWeight(28), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(9, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(9), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeFull...), testsuite.WithEvictedSlot(9), ) @@ -369,7 +368,7 @@ func TestProtocol_EngineRollbackNoFinalization(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(14), testsuite.WithEqualStoredCommitmentAtIndex(14), testsuite.WithLatestCommitmentCumulativeWeight(44), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(14, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(14), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeHalf...), testsuite.WithEvictedSlot(14), ) @@ -491,15 +490,15 @@ func TestProtocol_EngineRollbackNoFinalizationLastSlot(t *testing.T) { node3.Validator.AccountID, } expectedOnlineCommitteeFull := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node2.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node3.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node2.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node3.Validator.AccountID)), } expectedOnlineCommitteeHalf := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), } for _, node := range ts.Nodes() { @@ -535,7 +534,7 @@ func TestProtocol_EngineRollbackNoFinalizationLastSlot(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(9), testsuite.WithEqualStoredCommitmentAtIndex(9), testsuite.WithLatestCommitmentCumulativeWeight(28), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(9, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(9), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeFull...), testsuite.WithEvictedSlot(9), ) @@ -568,7 +567,7 @@ func TestProtocol_EngineRollbackNoFinalizationLastSlot(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(17), testsuite.WithEqualStoredCommitmentAtIndex(17), testsuite.WithLatestCommitmentCumulativeWeight(50), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(17, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(17), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeHalf...), testsuite.WithEvictedSlot(17), ) @@ -690,15 +689,15 @@ func TestProtocol_EngineRollbackNoFinalizationBeforePointOfNoReturn(t *testing.T node3.Validator.AccountID, } expectedOnlineCommitteeFull := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node2.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node3.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node2.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node3.Validator.AccountID)), } expectedOnlineCommitteeHalf := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), } for _, node := range ts.Nodes() { @@ -734,7 +733,7 @@ func TestProtocol_EngineRollbackNoFinalizationBeforePointOfNoReturn(t *testing.T testsuite.WithLatestCommitmentSlotIndex(9), testsuite.WithEqualStoredCommitmentAtIndex(9), testsuite.WithLatestCommitmentCumulativeWeight(28), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(9, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(9), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeFull...), testsuite.WithEvictedSlot(9), ) @@ -767,7 +766,7 @@ func TestProtocol_EngineRollbackNoFinalizationBeforePointOfNoReturn(t *testing.T testsuite.WithLatestCommitmentSlotIndex(13), testsuite.WithEqualStoredCommitmentAtIndex(13), testsuite.WithLatestCommitmentCumulativeWeight(42), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(13, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(13), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommitteeHalf...), testsuite.WithEvictedSlot(13), ) diff --git a/pkg/tests/protocol_engine_switching_test.go b/pkg/tests/protocol_engine_switching_test.go index 43290eac5..e5a61698f 100644 --- a/pkg/tests/protocol_engine_switching_test.go +++ b/pkg/tests/protocol_engine_switching_test.go @@ -124,15 +124,15 @@ func TestProtocol_EngineSwitching(t *testing.T) { node7.Validator.AccountID, } expectedP1OnlineCommittee := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node0.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node1.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node2.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node3.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node4.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node0.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node1.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node2.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node3.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node4.Validator.AccountID)), } expectedP2OnlineCommittee := []account.SeatIndex{ - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node6.Validator.AccountID)), - lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(node7.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node6.Validator.AccountID)), + lo.Return1(lo.Return1(node0.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(node7.Validator.AccountID)), } expectedOnlineCommittee := append(expectedP1OnlineCommittee, expectedP2OnlineCommittee...) @@ -170,7 +170,7 @@ func TestProtocol_EngineSwitching(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(11), testsuite.WithEqualStoredCommitmentAtIndex(11), testsuite.WithLatestCommitmentCumulativeWeight(56), // 7 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(11, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(11), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommittee...), testsuite.WithEvictedSlot(11), ) @@ -225,7 +225,7 @@ func TestProtocol_EngineSwitching(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(18), testsuite.WithEqualStoredCommitmentAtIndex(18), testsuite.WithLatestCommitmentCumulativeWeight(99), // 56 + slot 12-15=7 + 5 for each slot starting from 16 - testsuite.WithSybilProtectionCommittee(18, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(18), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedP1OnlineCommittee...), testsuite.WithEvictedSlot(18), ) @@ -279,7 +279,7 @@ func TestProtocol_EngineSwitching(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(18), testsuite.WithEqualStoredCommitmentAtIndex(18), testsuite.WithLatestCommitmentCumulativeWeight(90), // 56 + slot 12-15=7 + 2 for each slot starting from 16 - testsuite.WithSybilProtectionCommittee(18, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(18), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedP2OnlineCommittee...), testsuite.WithEvictedSlot(18), ) diff --git a/pkg/tests/protocol_startup_test.go b/pkg/tests/protocol_startup_test.go index c76fd6bc1..5ba4f4960 100644 --- a/pkg/tests/protocol_startup_test.go +++ b/pkg/tests/protocol_startup_test.go @@ -58,7 +58,7 @@ func Test_BookInCommittedSlot(t *testing.T) { } expectedOnlineCommittee := []account.SeatIndex{ - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeA.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeA.Validator.AccountID)), } // Verify that nodes have the expected states. @@ -168,8 +168,8 @@ func Test_StartNodeFromSnapshotAndDisk(t *testing.T) { } expectedOnlineCommittee := []account.SeatIndex{ - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeA.Validator.AccountID)), - lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(1).GetSeat(nodeB.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeA.Validator.AccountID)), + lo.Return1(lo.Return1(nodeA.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(1)).GetSeat(nodeB.Validator.AccountID)), } // Verify that nodes have the expected states. @@ -222,7 +222,7 @@ func Test_StartNodeFromSnapshotAndDisk(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(5), testsuite.WithEqualStoredCommitmentAtIndex(5), testsuite.WithLatestCommitmentCumulativeWeight(4), // 2 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(5, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(5), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommittee...), testsuite.WithEvictedSlot(5), testsuite.WithActiveRootBlocks(expectedActiveRootBlocks), @@ -270,7 +270,7 @@ func Test_StartNodeFromSnapshotAndDisk(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(11), testsuite.WithEqualStoredCommitmentAtIndex(11), testsuite.WithLatestCommitmentCumulativeWeight(16), // 2 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(11, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(11), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommittee...), testsuite.WithEvictedSlot(11), testsuite.WithActiveRootBlocks(expectedActiveRootBlocks), @@ -354,7 +354,7 @@ func Test_StartNodeFromSnapshotAndDisk(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(11), testsuite.WithEqualStoredCommitmentAtIndex(11), testsuite.WithLatestCommitmentCumulativeWeight(16), // 2 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(11, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(11), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommittee...), testsuite.WithEvictedSlot(11), testsuite.WithActiveRootBlocks(expectedActiveRootBlocks), @@ -410,7 +410,7 @@ func Test_StartNodeFromSnapshotAndDisk(t *testing.T) { testsuite.WithLatestCommitmentSlotIndex(37), testsuite.WithEqualStoredCommitmentAtIndex(37), testsuite.WithLatestCommitmentCumulativeWeight(68), // 2 for each slot starting from 4 - testsuite.WithSybilProtectionCommittee(37, expectedCommittee), + testsuite.WithSybilProtectionCommittee(ts.API.TimeProvider().EpochFromSlot(37), expectedCommittee), testsuite.WithSybilProtectionOnlineCommittee(expectedOnlineCommittee...), testsuite.WithEvictedSlot(37), testsuite.WithActiveRootBlocks(expectedActiveRootBlocks), diff --git a/pkg/testsuite/mock/blockissuer.go b/pkg/testsuite/mock/blockissuer.go index db6e532ba..b29b015ba 100644 --- a/pkg/testsuite/mock/blockissuer.go +++ b/pkg/testsuite/mock/blockissuer.go @@ -155,7 +155,7 @@ func (i *BlockIssuer) CreateValidationBlock(ctx context.Context, alias string, i blockBuilder.IssuingTime(*blockParams.BlockHeader.IssuingTime) strongParents, exists := blockParams.BlockHeader.References[iotago.StrongParentType] - require.True(i.Testing, exists && len(strongParents) > 0) + require.True(i.Testing, exists && len(strongParents) > 0, "block should have strong parents (exists: %t, parents: %s)", exists, strongParents) blockBuilder.StrongParents(strongParents) if weakParents, exists := blockParams.BlockHeader.References[iotago.WeakParentType]; exists { diff --git a/pkg/testsuite/mock/node.go b/pkg/testsuite/mock/node.go index ae3d69847..36112ad13 100644 --- a/pkg/testsuite/mock/node.go +++ b/pkg/testsuite/mock/node.go @@ -380,6 +380,10 @@ func (n *Node) attachEngineLogsWithName(failOnBlockFiltered bool, instance *engi fmt.Printf("%s > [%s] SybilProtection.OnlineCommitteeSeatRemoved: %d\n", n.Name, engineName, seat) }) + events.SybilProtection.CommitteeSelected.Hook(func(committee *account.Accounts, epoch iotago.EpochIndex) { + fmt.Printf("%s > [%s] SybilProtection.CommitteeSelected: epoch %d - %s\n", n.Name, engineName, epoch, committee.IDs()) + }) + events.ConflictDAG.ConflictCreated.Hook(func(conflictID iotago.TransactionID) { fmt.Printf("%s > [%s] ConflictDAG.ConflictCreated: %s\n", n.Name, engineName, conflictID) }) diff --git a/pkg/testsuite/node_state.go b/pkg/testsuite/node_state.go index 88b7ee2b4..65b60c728 100644 --- a/pkg/testsuite/node_state.go +++ b/pkg/testsuite/node_state.go @@ -32,8 +32,8 @@ func (t *TestSuite) AssertNodeState(nodes []*mock.Node, opts ...options.Option[N if state.chainID != nil { t.AssertChainID(*state.chainID, nodes...) } - if state.sybilProtectionCommitteeSlot != nil && state.sybilProtectionCommittee != nil { - t.AssertSybilProtectionCommittee(*state.sybilProtectionCommitteeSlot, *state.sybilProtectionCommittee, nodes...) + if state.sybilProtectionCommitteeEpoch != nil && state.sybilProtectionCommittee != nil { + t.AssertSybilProtectionCommittee(*state.sybilProtectionCommitteeEpoch, *state.sybilProtectionCommittee, nodes...) } if state.sybilProtectionOnlineCommittee != nil { t.AssertSybilProtectionOnlineCommittee(*state.sybilProtectionOnlineCommittee, nodes...) @@ -67,7 +67,7 @@ type NodeState struct { latestFinalizedSlot *iotago.SlotIndex chainID *iotago.CommitmentID - sybilProtectionCommitteeSlot *iotago.SlotIndex + sybilProtectionCommitteeEpoch *iotago.EpochIndex sybilProtectionCommittee *[]iotago.AccountID sybilProtectionOnlineCommittee *[]account.SeatIndex @@ -130,9 +130,9 @@ func WithChainID(chainID iotago.CommitmentID) options.Option[NodeState] { } } -func WithSybilProtectionCommittee(slot iotago.SlotIndex, committee []iotago.AccountID) options.Option[NodeState] { +func WithSybilProtectionCommittee(epoch iotago.EpochIndex, committee []iotago.AccountID) options.Option[NodeState] { return func(state *NodeState) { - state.sybilProtectionCommitteeSlot = &slot + state.sybilProtectionCommitteeEpoch = &epoch state.sybilProtectionCommittee = &committee } } diff --git a/pkg/testsuite/snapshotcreator/options.go b/pkg/testsuite/snapshotcreator/options.go index f1da75f43..74d986fe4 100644 --- a/pkg/testsuite/snapshotcreator/options.go +++ b/pkg/testsuite/snapshotcreator/options.go @@ -6,6 +6,8 @@ import ( "github.com/iotaledger/iota-core/pkg/protocol/engine" "github.com/iotaledger/iota-core/pkg/protocol/engine/ledger" ledger1 "github.com/iotaledger/iota-core/pkg/protocol/engine/ledger/ledger" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/poa" iotago "github.com/iotaledger/iota.go/v4" ) @@ -29,19 +31,27 @@ type Options struct { // BasicOutput defines the basic outputs that are created in the ledger as part of the Genesis. BasicOutputs []BasicOutputDetails - DataBaseVersion byte - LedgerProvider func() module.Provider[*engine.Engine, ledger.Ledger] + DataBaseVersion byte + LedgerProvider module.Provider[*engine.Engine, ledger.Ledger] + SeatManagerProvider module.Provider[*engine.Engine, seatmanager.SeatManager] } func NewOptions(opts ...options.Option[Options]) *Options { return options.Apply(&Options{ - FilePath: "snapshot.bin", - DataBaseVersion: 1, - LedgerProvider: ledger1.NewProvider, + FilePath: "snapshot.bin", + DataBaseVersion: 1, + LedgerProvider: ledger1.NewProvider(), + SeatManagerProvider: poa.NewProvider(), }, opts) } -func WithLedgerProvider(ledgerProvider func() module.Provider[*engine.Engine, ledger.Ledger]) options.Option[Options] { +func WithSeatManagerProvider(seatManagerProvider module.Provider[*engine.Engine, seatmanager.SeatManager]) options.Option[Options] { + return func(m *Options) { + m.SeatManagerProvider = seatManagerProvider + } +} + +func WithLedgerProvider(ledgerProvider module.Provider[*engine.Engine, ledger.Ledger]) options.Option[Options] { return func(m *Options) { m.LedgerProvider = ledgerProvider } diff --git a/pkg/testsuite/snapshotcreator/snapshotcreator.go b/pkg/testsuite/snapshotcreator/snapshotcreator.go index 8acc56027..f2040d79d 100644 --- a/pkg/testsuite/snapshotcreator/snapshotcreator.go +++ b/pkg/testsuite/snapshotcreator/snapshotcreator.go @@ -9,9 +9,9 @@ import ( "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/options" "github.com/iotaledger/hive.go/runtime/workerpool" - "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/model" "github.com/iotaledger/iota-core/pkg/protocol/engine" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" "github.com/iotaledger/iota-core/pkg/protocol/engine/attestation/slotattestation" "github.com/iotaledger/iota-core/pkg/protocol/engine/blockdag/inmemoryblockdag" "github.com/iotaledger/iota-core/pkg/protocol/engine/booker/inmemorybooker" @@ -70,20 +70,27 @@ func CreateSnapshot(opts ...options.Option[Options]) error { return ierrors.Wrap(err, "failed to store empty commitment") } - accounts := account.NewAccounts() - for _, accountData := range opt.Accounts { + committeeAccountsData := make(accounts.AccountsData, 0) + for _, snapshotAccountDetails := range opt.Accounts { // Only add genesis validators if an account has both - StakedAmount and StakingEndEpoch - specified. - if accountData.StakedAmount > 0 && accountData.StakingEpochEnd > 0 { - blockIssuerKeyEd25519, ok := accountData.IssuerKey.(*iotago.Ed25519PublicKeyBlockIssuerKey) + if snapshotAccountDetails.StakedAmount > 0 && snapshotAccountDetails.StakingEpochEnd > 0 { + blockIssuerKeyEd25519, ok := snapshotAccountDetails.IssuerKey.(*iotago.Ed25519PublicKeyBlockIssuerKey) if !ok { panic("block issuer key must be of type ed25519") } ed25519PubKey := blockIssuerKeyEd25519.ToEd25519PublicKey() accountID := blake2b.Sum256(ed25519PubKey[:]) - accounts.Set(accountID, &account.Pool{ - PoolStake: accountData.StakedAmount, - ValidatorStake: accountData.StakedAmount, - FixedCost: accountData.FixedCost, + committeeAccountsData = append(committeeAccountsData, &accounts.AccountData{ + ID: accountID, + Credits: &accounts.BlockIssuanceCredits{Value: snapshotAccountDetails.BlockIssuanceCredits, UpdateTime: 0}, + ExpirySlot: snapshotAccountDetails.ExpirySlot, + OutputID: iotago.OutputID{}, + BlockIssuerKeys: iotago.BlockIssuerKeys{snapshotAccountDetails.IssuerKey}, + ValidatorStake: snapshotAccountDetails.StakedAmount, + DelegationStake: 0, + FixedCost: snapshotAccountDetails.FixedCost, + StakeEndEpoch: snapshotAccountDetails.StakingEpochEnd, + LatestSupportedProtocolVersionAndHash: model.VersionAndHash{}, }) } } @@ -98,10 +105,11 @@ func CreateSnapshot(opts ...options.Option[Options]) error { blocktime.NewProvider(), thresholdblockgadget.NewProvider(), totalweightslotgadget.NewProvider(), - sybilprotectionv1.NewProvider(sybilprotectionv1.WithInitialCommittee(accounts)), + sybilprotectionv1.NewProvider(sybilprotectionv1.WithInitialCommittee(committeeAccountsData), + sybilprotectionv1.WithSeatManagerProvider(opt.SeatManagerProvider)), slotnotarization.NewProvider(), slotattestation.NewProvider(), - opt.LedgerProvider(), + opt.LedgerProvider, passthrough.NewProvider(), tipmanagerv1.NewProvider(), tipselectionv1.NewProvider(), diff --git a/pkg/testsuite/sybilprotection.go b/pkg/testsuite/sybilprotection.go index edbfe0feb..3eac2c524 100644 --- a/pkg/testsuite/sybilprotection.go +++ b/pkg/testsuite/sybilprotection.go @@ -2,19 +2,22 @@ package testsuite import ( "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/ierrors" + "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/iota-core/pkg/core/account" + "github.com/iotaledger/iota-core/pkg/protocol/engine/accounts" "github.com/iotaledger/iota-core/pkg/testsuite/mock" iotago "github.com/iotaledger/iota.go/v4" ) -func (t *TestSuite) AssertSybilProtectionCommittee(slot iotago.SlotIndex, expectedAccounts []iotago.AccountID, nodes ...*mock.Node) { +func (t *TestSuite) AssertSybilProtectionCommittee(epoch iotago.EpochIndex, expectedAccounts []iotago.AccountID, nodes ...*mock.Node) { mustNodes(nodes) for _, node := range nodes { t.Eventually(func() error { - accounts := node.Protocol.MainEngineInstance().SybilProtection.SeatManager().Committee(slot).Accounts().IDs() + accounts := lo.Return1(node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInEpoch(epoch)).Accounts().IDs() if !assert.ElementsMatch(t.fakeTesting, expectedAccounts, accounts) { return ierrors.Errorf("AssertSybilProtectionCommittee: %s: expected %s, got %s", node.Name, expectedAccounts, accounts) } @@ -28,6 +31,30 @@ func (t *TestSuite) AssertSybilProtectionCommittee(slot iotago.SlotIndex, expect } } +func (t *TestSuite) AssertSybilProtectionCandidates(epoch iotago.EpochIndex, expectedAccounts []iotago.AccountID, nodes ...*mock.Node) { + mustNodes(nodes) + + for _, node := range nodes { + t.Eventually(func() error { + candidates, err := node.Protocol.MainEngineInstance().SybilProtection.EligibleValidators(epoch) + candidateIDs := lo.Map(candidates, func(candidate *accounts.AccountData) iotago.AccountID { + return candidate.ID + }) + require.NoError(t.Testing, err) + + if !assert.ElementsMatch(t.fakeTesting, expectedAccounts, candidateIDs) { + return ierrors.Errorf("AssertSybilProtectionCandidates: %s: expected %s, got %s", node.Name, expectedAccounts, candidateIDs) + } + + if len(expectedAccounts) != len(candidates) { + return ierrors.Errorf("AssertSybilProtectionCandidates: %s: expected %v, got %v", node.Name, len(expectedAccounts), len(candidateIDs)) + } + + return nil + }) + } +} + func (t *TestSuite) AssertSybilProtectionOnlineCommittee(expectedSeats []account.SeatIndex, nodes ...*mock.Node) { mustNodes(nodes) diff --git a/pkg/testsuite/testsuite_issue_blocks.go b/pkg/testsuite/testsuite_issue_blocks.go index 051de5159..38ce7ed98 100644 --- a/pkg/testsuite/testsuite_issue_blocks.go +++ b/pkg/testsuite/testsuite_issue_blocks.go @@ -158,6 +158,22 @@ func (t *TestSuite) IssueValidationBlock(blockName string, node *mock.Node, bloc return block } +func (t *TestSuite) IssueCandidacyAnnouncementInSlot(alias string, slot iotago.SlotIndex, parentsPrefixAlias string, node *mock.Node, issuingOptions ...options.Option[mock.BlockHeaderParams]) *blocks.Block { + timeProvider := t.API.TimeProvider() + issuingTime := timeProvider.SlotStartTime(slot).Add(time.Duration(t.uniqueBlockTimeCounter.Add(1))) + require.Truef(t.Testing, issuingTime.Before(time.Now()), "node: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", node.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) + + return t.IssuePayloadWithOptions( + alias, + node.Validator, + node, + &iotago.CandidacyAnnouncement{}, + append(issuingOptions, + mock.WithStrongParents(t.BlockIDsWithPrefix(parentsPrefixAlias)...), + mock.WithIssuingTime(issuingTime), + )..., + ) +} func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, row int, parentsPrefix string, nodes []*mock.Node, issuingOptions map[string][]options.Option[mock.BlockHeaderParams]) []*blocks.Block { blocksIssued := make([]*blocks.Block, 0, len(nodes)) @@ -174,7 +190,8 @@ func (t *TestSuite) IssueBlockRowInSlot(prefix string, slot iotago.SlotIndex, ro require.Truef(t.Testing, issuingTime.Before(time.Now()), "node: %s: issued block (%s, slot: %d) is in the current (%s, slot: %d) or future slot", node.Name, issuingTime, slot, time.Now(), timeProvider.SlotFromTime(time.Now())) var b *blocks.Block - if node.Validator != nil { + // Only issue validator blocks if account has staking feature and is part of committee. + if node.Validator != nil && lo.Return1(node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(slot)).HasAccount(node.Validator.AccountID) { blockHeaderOptions := append(issuingOptionsCopy[node.Name], mock.WithIssuingTime(issuingTime)) t.assertParentsCommitmentExistFromBlockOptions(blockHeaderOptions, node) t.assertParentsExistFromBlockOptions(blockHeaderOptions, node) @@ -284,17 +301,31 @@ func (t *TestSuite) CommitUntilSlot(slot iotago.SlotIndex, parent *blocks.Block) // preacceptance of nextBlockSlot for _, node := range activeValidators { require.True(t.Testing, node.IsValidator(), "node: %s: is not a validator node", node.Name) - blockName := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex, node.Name) - tip = t.IssueValidationBlockAtSlot(blockName, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) + committeeAtBlockSlot, exists := node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(nextBlockSlot) + require.True(t.Testing, exists, "node: %s: does not have committee selected for slot %d", node.Name, nextBlockSlot) + if committeeAtBlockSlot.HasAccount(node.Validator.AccountID) { + blockName := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex, node.Name) + tip = t.IssueValidationBlockAtSlot(blockName, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) + } } // acceptance of nextBlockSlot for _, node := range activeValidators { - blockName := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex+1, node.Name) - tip = t.IssueValidationBlockAtSlot(blockName, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) + committeeAtBlockSlot, exists := node.Protocol.MainEngineInstance().SybilProtection.SeatManager().CommitteeInSlot(nextBlockSlot) + require.True(t.Testing, exists, "node: %s: does not have committee selected for slot %d", node.Name, nextBlockSlot) + if committeeAtBlockSlot.HasAccount(node.Validator.AccountID) { + blockName := fmt.Sprintf("chain-%s-%d-%s", parent.ID().Alias(), chainIndex+1, node.Name) + tip = t.IssueValidationBlockAtSlot(blockName, nextBlockSlot, node.Protocol.MainEngineInstance().Storage.Settings().LatestCommitment().Commitment(), node, tip.ID()) + } + } + + for _, node := range activeValidators { + t.AssertLatestCommitmentSlotIndex(nextBlockSlot-t.API.ProtocolParameters().MinCommittableAge(), node) } + if nextBlockSlot == slot+t.API.ProtocolParameters().MinCommittableAge() { break } + nextBlockSlot = lo.Min(slot+t.API.ProtocolParameters().MinCommittableAge(), nextBlockSlot+t.API.ProtocolParameters().MinCommittableAge()) chainIndex += 2 } diff --git a/pkg/toolset/ed25519.go b/pkg/toolset/ed25519.go new file mode 100644 index 000000000..bae379fbf --- /dev/null +++ b/pkg/toolset/ed25519.go @@ -0,0 +1,172 @@ +package toolset + +import ( + "crypto/ed25519" + "encoding/hex" + "fmt" + "os" + + flag "github.com/spf13/pflag" + "github.com/wollac/iota-crypto-demo/pkg/bip32path" + "github.com/wollac/iota-crypto-demo/pkg/bip39" + "github.com/wollac/iota-crypto-demo/pkg/slip10" + "github.com/wollac/iota-crypto-demo/pkg/slip10/eddsa" + + "github.com/iotaledger/hive.go/app/configuration" + "github.com/iotaledger/hive.go/crypto" + iotago "github.com/iotaledger/iota.go/v4" +) + +func printEd25519Info(mnemonic bip39.Mnemonic, path bip32path.Path, prvKey ed25519.PrivateKey, pubKey ed25519.PublicKey, hrp iotago.NetworkPrefix, outputJSON bool) error { + addr := iotago.Ed25519AddressFromPubKey(pubKey) + + type keys struct { + BIP39 string `json:"mnemonic,omitempty"` + BIP32 string `json:"path,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` + PublicKey string `json:"publicKey"` + Ed25519Address string `json:"ed25519"` + Bech32Address string `json:"bech32"` + } + + k := keys{ + PublicKey: hex.EncodeToString(pubKey), + Ed25519Address: hex.EncodeToString(addr[:]), + Bech32Address: addr.Bech32(hrp), + } + + if prvKey != nil { + k.PrivateKey = hex.EncodeToString(prvKey) + } + + if mnemonic != nil { + k.BIP39 = mnemonic.String() + k.BIP32 = path.String() + } + + if outputJSON { + return printJSON(k) + } + + if len(k.BIP39) > 0 { + fmt.Println("Your seed BIP39 mnemonic: ", k.BIP39) + fmt.Println() + fmt.Println("Your BIP32 path: ", k.BIP32) + } + + if k.PrivateKey != "" { + fmt.Println("Your ed25519 private key: ", k.PrivateKey) + } + + fmt.Println("Your ed25519 public key: ", k.PublicKey) + fmt.Println("Your ed25519 address: ", k.Ed25519Address) + fmt.Println("Your bech32 address: ", k.Bech32Address) + + return nil +} + +func generateEd25519Key(args []string) error { + fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + hrpFlag := fs.String(FlagToolHRP, string(iotago.PrefixTestnet), "the HRP which should be used for the Bech32 address") + bip32Path := fs.String(FlagToolBIP32Path, "m/44'/4218'/0'/0'/0'", "the BIP32 path that should be used to derive keys from seed") + mnemonicFlag := fs.String(FlagToolMnemonic, "", "the BIP-39 mnemonic sentence that should be used to derive the seed from (optional)") + outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON) + + fs.Usage = func() { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolEd25519Key) + fs.PrintDefaults() + println(fmt.Sprintf("\nexample: %s --%s %s", + ToolEd25519Key, + FlagToolHRP, + string(iotago.PrefixTestnet))) + } + + if err := parseFlagSet(fs, args); err != nil { + return err + } + + if len(*hrpFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolHRP) + } + + if len(*bip32Path) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolBIP32Path) + } + + var mnemonicSentence bip39.Mnemonic + if len(*mnemonicFlag) == 0 { + // Generate random entropy by using ed25519 key generation and using the private key seed (32 bytes) + _, random, err := ed25519.GenerateKey(nil) + if err != nil { + return err + } + entropy := random.Seed() + + mnemonicSentence, err = bip39.EntropyToMnemonic(entropy) + if err != nil { + return err + } + } else { + mnemonicSentence = bip39.ParseMnemonic(*mnemonicFlag) + if len(mnemonicSentence) != 24 { + return fmt.Errorf("'%s' contains an invalid sentence length. Mnemonic should be 24 words", FlagToolMnemonic) + } + } + + path, err := bip32path.ParsePath(*bip32Path) + if err != nil { + return err + } + + seed, err := bip39.MnemonicToSeed(mnemonicSentence, "") + if err != nil { + return err + } + + key, err := slip10.DeriveKeyFromPath(seed, eddsa.Ed25519(), path) + if err != nil { + return err + } + pubKey, prvKey := key.Key.(eddsa.Seed).Ed25519Key() + + return printEd25519Info(mnemonicSentence, path, ed25519.PrivateKey(prvKey), ed25519.PublicKey(pubKey), iotago.NetworkPrefix(*hrpFlag), *outputJSONFlag) +} + +func generateEd25519Address(args []string) error { + fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + hrpFlag := fs.String(FlagToolHRP, string(iotago.PrefixTestnet), "the HRP which should be used for the Bech32 address") + publicKeyFlag := fs.String(FlagToolPublicKey, "", "an ed25519 public key") + outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON) + + fs.Usage = func() { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolEd25519Addr) + fs.PrintDefaults() + println(fmt.Sprintf("\nexample: %s --%s %s --%s %s", + ToolEd25519Addr, + FlagToolHRP, + string(iotago.PrefixTestnet), + FlagToolPublicKey, + "[PUB_KEY]", + )) + } + + if err := parseFlagSet(fs, args); err != nil { + return err + } + + if len(*hrpFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolHRP) + } + + if len(*publicKeyFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolPublicKey) + } + + // parse pubkey + pubKey, err := crypto.ParseEd25519PublicKeyFromString(*publicKeyFlag) + if err != nil { + return fmt.Errorf("can't decode '%s': %w", FlagToolPublicKey, err) + } + + return printEd25519Info(nil, nil, nil, pubKey, iotago.NetworkPrefix(*hrpFlag), *outputJSONFlag) +} diff --git a/pkg/toolset/jwt.go b/pkg/toolset/jwt.go new file mode 100644 index 000000000..9585eee8c --- /dev/null +++ b/pkg/toolset/jwt.go @@ -0,0 +1,109 @@ +package toolset + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/libp2p/go-libp2p/core/peer" + flag "github.com/spf13/pflag" + + "github.com/iotaledger/hive.go/app/configuration" + hivep2p "github.com/iotaledger/hive.go/crypto/p2p" + "github.com/iotaledger/hive.go/crypto/pem" + "github.com/iotaledger/iota-core/components/p2p" + "github.com/iotaledger/iota-core/pkg/jwt" +) + +func generateJWTApiToken(args []string) error { + + fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + databasePathFlag := fs.String(FlagToolDatabasePath, DefaultValueP2PDatabasePath, "the path to the p2p database folder") + apiJWTSaltFlag := fs.String(FlagToolSalt, DefaultValueAPIJWTTokenSalt, "salt used inside the JWT tokens for the REST API") + outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON) + + fs.Usage = func() { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolJWTApi) + fs.PrintDefaults() + println(fmt.Sprintf("\nexample: %s --%s %s --%s %s", + ToolJWTApi, + FlagToolDatabasePath, + DefaultValueP2PDatabasePath, + FlagToolSalt, + DefaultValueAPIJWTTokenSalt)) + } + + if err := parseFlagSet(fs, args); err != nil { + return err + } + + if len(*databasePathFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolDatabasePath) + } + if len(*apiJWTSaltFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolSalt) + } + + databasePath := *databasePathFlag + privKeyFilePath := filepath.Join(databasePath, p2p.IdentityPrivateKeyFileName) + + salt := *apiJWTSaltFlag + + _, err := os.Stat(privKeyFilePath) + switch { + case os.IsNotExist(err): + // private key does not exist + return fmt.Errorf("private key file (%s) does not exist", privKeyFilePath) + + case err == nil || os.IsExist(err): + // private key file exists + + default: + return fmt.Errorf("unable to check private key file (%s): %w", privKeyFilePath, err) + } + + privKey, err := pem.ReadEd25519PrivateKeyFromPEMFile(privKeyFilePath) + if err != nil { + return fmt.Errorf("reading private key file for peer identity failed: %w", err) + } + + libp2pPrivKey, err := hivep2p.Ed25519PrivateKeyToLibp2pPrivateKey(privKey) + if err != nil { + return fmt.Errorf("reading private key file for peer identity failed: %w", err) + } + + peerID, err := peer.IDFromPublicKey(libp2pPrivKey.GetPublic()) + if err != nil { + return fmt.Errorf("unable to get peer identity from public key: %w", err) + } + + // API tokens do not expire. + jwtAuth, err := jwt.NewAuth(salt, + 0, + peerID.String(), + libp2pPrivKey, + ) + if err != nil { + return fmt.Errorf("JWT auth initialization failed: %w", err) + } + + jwtToken, err := jwtAuth.IssueJWT() + if err != nil { + return fmt.Errorf("issuing JWT token failed: %w", err) + } + + if *outputJSONFlag { + + result := struct { + JWT string `json:"jwt"` + }{ + JWT: jwtToken, + } + + return printJSON(result) + } + + fmt.Println("Your API JWT token: ", jwtToken) + + return nil +} diff --git a/pkg/toolset/node_info.go b/pkg/toolset/node_info.go new file mode 100644 index 000000000..058a3e6d6 --- /dev/null +++ b/pkg/toolset/node_info.go @@ -0,0 +1,50 @@ +package toolset + +import ( + "context" + "fmt" + "os" + + flag "github.com/spf13/pflag" + + "github.com/iotaledger/hive.go/app/configuration" + "github.com/iotaledger/iota.go/v4/nodeclient" +) + +func nodeInfo(args []string) error { + fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + nodeURLFlag := fs.String(FlagToolNodeURL, "http://localhost:14265", "URL of the node (optional)") + outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON) + + fs.Usage = func() { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolNodeInfo) + fs.PrintDefaults() + println(fmt.Sprintf("\nexample: %s --%s %s", + ToolNodeInfo, + FlagToolNodeURL, + "http://192.168.1.221:14265", + )) + } + + if err := parseFlagSet(fs, args); err != nil { + return err + } + + client, err := nodeclient.New(*nodeURLFlag) + if err != nil { + return err + } + + info, err := client.Info(context.Background()) + if err != nil { + return err + } + + if *outputJSONFlag { + return printJSON(info) + } + + fmt.Printf("Name: %s\nVersion: %s\nLatestAcceptedBlockSlot: %d\nLatestConfirmedBlockSlot: %d\nIsHealthy: %s\n", info.Name, info.Version, info.Status.LatestAcceptedBlockSlot, info.Status.LatestConfirmedBlockSlot, yesOrNo(info.Status.IsHealthy)) + + return nil +} diff --git a/pkg/toolset/p2p_identity_extract.go b/pkg/toolset/p2p_identity_extract.go new file mode 100644 index 000000000..45a364d10 --- /dev/null +++ b/pkg/toolset/p2p_identity_extract.go @@ -0,0 +1,66 @@ +package toolset + +import ( + "fmt" + "os" + "path/filepath" + + flag "github.com/spf13/pflag" + + "github.com/iotaledger/hive.go/app/configuration" + hivep2p "github.com/iotaledger/hive.go/crypto/p2p" + "github.com/iotaledger/hive.go/crypto/pem" + "github.com/iotaledger/iota-core/components/p2p" +) + +func extractP2PIdentity(args []string) error { + + fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + databasePathFlag := fs.String(FlagToolDatabasePath, DefaultValueP2PDatabasePath, "the path to the p2p database folder") + outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON) + + fs.Usage = func() { + _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolP2PExtractIdentity) + fs.PrintDefaults() + println(fmt.Sprintf("\nexample: %s --%s %s", + ToolP2PExtractIdentity, + FlagToolDatabasePath, + DefaultValueP2PDatabasePath)) + } + + if err := parseFlagSet(fs, args); err != nil { + return err + } + + if len(*databasePathFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolDatabasePath) + } + + databasePath := *databasePathFlag + privKeyFilePath := filepath.Join(databasePath, p2p.IdentityPrivateKeyFileName) + + _, err := os.Stat(privKeyFilePath) + switch { + case os.IsNotExist(err): + // private key does not exist + return fmt.Errorf("private key file (%s) does not exist", privKeyFilePath) + + case err == nil || os.IsExist(err): + // private key file exists + + default: + return fmt.Errorf("unable to check private key file (%s): %w", privKeyFilePath, err) + } + + privKey, err := pem.ReadEd25519PrivateKeyFromPEMFile(privKeyFilePath) + if err != nil { + return fmt.Errorf("reading private key file for peer identity failed: %w", err) + } + + libp2pPrivKey, err := hivep2p.Ed25519PrivateKeyToLibp2pPrivateKey(privKey) + if err != nil { + return err + } + + return printP2PIdentity(libp2pPrivKey, libp2pPrivKey.GetPublic(), *outputJSONFlag) +} diff --git a/pkg/toolset/p2p_identity_gen.go b/pkg/toolset/p2p_identity_gen.go new file mode 100644 index 000000000..b376fd5e8 --- /dev/null +++ b/pkg/toolset/p2p_identity_gen.go @@ -0,0 +1,136 @@ +package toolset + +import ( + "crypto/ed25519" + "encoding/hex" + "fmt" + "os" + "path/filepath" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/mr-tron/base58" + flag "github.com/spf13/pflag" + + "github.com/iotaledger/hive.go/app/configuration" + hivecrypto "github.com/iotaledger/hive.go/crypto" + "github.com/iotaledger/hive.go/crypto/pem" + "github.com/iotaledger/iota-core/components/p2p" + "github.com/iotaledger/iota.go/v4/hexutil" +) + +func generateP2PIdentity(args []string) error { + + fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + databasePathFlag := fs.String(FlagToolOutputPath, DefaultValueP2PDatabasePath, "the path to the output folder") + privateKeyFlag := fs.String(FlagToolPrivateKey, "", "the p2p private key") + outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON) + + fs.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolP2PIdentityGen) + fs.PrintDefaults() + println(fmt.Sprintf("\nexample: %s --%s %s --%s %s", + ToolP2PIdentityGen, + FlagToolDatabasePath, + DefaultValueP2PDatabasePath, + FlagToolPrivateKey, + "[PRIVATE_KEY]", + )) + } + + if err := parseFlagSet(fs, args); err != nil { + return err + } + + if len(*databasePathFlag) == 0 { + return fmt.Errorf("'%s' not specified", FlagToolDatabasePath) + } + + databasePath := *databasePathFlag + privKeyFilePath := filepath.Join(databasePath, p2p.IdentityPrivateKeyFileName) + + if err := os.MkdirAll(databasePath, 0700); err != nil { + return fmt.Errorf("could not create peer store database dir '%s': %w", databasePath, err) + } + + _, err := os.Stat(privKeyFilePath) + switch { + case err == nil || os.IsExist(err): + // private key file already exists + return fmt.Errorf("private key file (%s) already exists", privKeyFilePath) + + case os.IsNotExist(err): + // private key file does not exist, create a new one + + default: + return fmt.Errorf("unable to check private key file (%s): %w", privKeyFilePath, err) + } + + var privKey ed25519.PrivateKey + if privateKeyFlag != nil && len(*privateKeyFlag) > 0 { + privKey, err = hivecrypto.ParseEd25519PrivateKeyFromString(*privateKeyFlag) + if err != nil { + return fmt.Errorf("invalid private key given '%s': %w", *privateKeyFlag, err) + } + } else { + // create identity + _, privKey, err = ed25519.GenerateKey(nil) + if err != nil { + return fmt.Errorf("unable to generate Ed25519 private key for peer identity: %w", err) + } + } + + libp2pPrivKey, libp2pPubKey, err := crypto.KeyPairFromStdKey(&privKey) + if err != nil { + return fmt.Errorf("unable to convert given private key '%s': %w", hexutil.EncodeHex(privKey), err) + } + + if err := pem.WriteEd25519PrivateKeyToPEMFile(privKeyFilePath, privKey); err != nil { + return fmt.Errorf("writing private key file for peer identity failed: %w", err) + } + + return printP2PIdentity(libp2pPrivKey, libp2pPubKey, *outputJSONFlag) +} + +func printP2PIdentity(libp2pPrivKey crypto.PrivKey, libp2pPubKey crypto.PubKey, outputJSON bool) error { + + type P2PIdentity struct { + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` + PublicKeyBase58 string `json:"publicKeyBase58"` + PeerID string `json:"peerId"` + } + + privKeyBytes, err := libp2pPrivKey.Raw() + if err != nil { + return fmt.Errorf("unable to get raw private key bytes: %w", err) + } + + pubKeyBytes, err := libp2pPubKey.Raw() + if err != nil { + return fmt.Errorf("unable to get raw public key bytes: %w", err) + } + + peerID, err := peer.IDFromPublicKey(libp2pPubKey) + if err != nil { + return fmt.Errorf("unable to get peer identity from public key: %w", err) + } + + identity := P2PIdentity{ + PrivateKey: hex.EncodeToString(privKeyBytes), + PublicKey: hex.EncodeToString(pubKeyBytes), + PublicKeyBase58: base58.Encode(pubKeyBytes), + PeerID: peerID.String(), + } + + if outputJSON { + return printJSON(identity) + } + + fmt.Println("Your p2p private key (hex): ", identity.PrivateKey) + fmt.Println("Your p2p public key (hex): ", identity.PublicKey) + fmt.Println("Your p2p public key (base58): ", identity.PublicKeyBase58) + fmt.Println("Your p2p PeerID: ", identity.PeerID) + + return nil +} diff --git a/pkg/toolset/toolset.go b/pkg/toolset/toolset.go new file mode 100644 index 000000000..cad18b14e --- /dev/null +++ b/pkg/toolset/toolset.go @@ -0,0 +1,157 @@ +package toolset + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + flag "github.com/spf13/pflag" + + "github.com/iotaledger/hive.go/app/configuration" + "github.com/iotaledger/hive.go/ierrors" +) + +const ( + FlagToolDatabasePath = "databasePath" + + FlagToolOutputPath = "outputPath" + + FlagToolPrivateKey = "privateKey" + FlagToolPublicKey = "publicKey" + + FlagToolHRP = "hrp" + FlagToolBIP32Path = "bip32Path" + FlagToolMnemonic = "mnemonic" + FlagToolSalt = "salt" + + FlagToolNodeURL = "nodeURL" + + FlagToolOutputJSON = "json" + FlagToolDescriptionOutputJSON = "format output as JSON" +) + +const ( + ToolP2PIdentityGen = "p2pidentity-gen" + ToolP2PExtractIdentity = "p2pidentity-extract" + ToolEd25519Key = "ed25519-key" + ToolEd25519Addr = "ed25519-addr" + ToolJWTApi = "jwt-api" + ToolNodeInfo = "node-info" +) + +const ( + DefaultValueAPIJWTTokenSalt = "IOTA" + DefaultValueP2PDatabasePath = "testnet/p2pstore" +) + +// ShouldHandleTools checks if tools were requested. +func ShouldHandleTools() bool { + args := os.Args[1:] + + for _, arg := range args { + if strings.ToLower(arg) == "tool" || strings.ToLower(arg) == "tools" { + return true + } + } + + return false +} + +// HandleTools handles available tools. +func HandleTools() { + + args := os.Args[1:] + if len(args) == 1 { + listTools() + os.Exit(1) + } + + tools := map[string]func([]string) error{ + ToolP2PIdentityGen: generateP2PIdentity, + ToolP2PExtractIdentity: extractP2PIdentity, + ToolEd25519Key: generateEd25519Key, + ToolEd25519Addr: generateEd25519Address, + ToolJWTApi: generateJWTApiToken, + ToolNodeInfo: nodeInfo, + } + + tool, exists := tools[strings.ToLower(args[1])] + if !exists { + fmt.Print("tool not found.\n\n") + listTools() + os.Exit(1) + } + + if err := tool(args[2:]); err != nil { + if ierrors.Is(err, flag.ErrHelp) { + // help text was requested + os.Exit(0) + } + + fmt.Printf("\nerror: %s\n", err) + os.Exit(1) + } + + os.Exit(0) +} + +func listTools() { + fmt.Printf("%-20s generates a p2p identity private key file\n", fmt.Sprintf("%s:", ToolP2PIdentityGen)) + fmt.Printf("%-20s extracts the p2p identity from the private key file\n", fmt.Sprintf("%s:", ToolP2PExtractIdentity)) + fmt.Printf("%-20s generates an ed25519 key pair\n", fmt.Sprintf("%s:", ToolEd25519Key)) + fmt.Printf("%-20s generates an ed25519 address from a public key\n", fmt.Sprintf("%s:", ToolEd25519Addr)) + fmt.Printf("%-20s generates a JWT token for REST-API access\n", fmt.Sprintf("%s:", ToolJWTApi)) + fmt.Printf("%-20s queries the info endpoint of a node\n", fmt.Sprintf("%s:", ToolNodeInfo)) +} + +func yesOrNo(value bool) string { + if value { + return "YES" + } + + return "NO" +} + +func parseFlagSet(fs *flag.FlagSet, args []string) error { + + if err := fs.Parse(args); err != nil { + return err + } + + // Check if all parameters were parsed + if fs.NArg() != 0 { + return ierrors.New("too much arguments") + } + + return nil +} + +func printJSON(obj interface{}) error { + output, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + + fmt.Println(string(output)) + + return nil +} + +//nolint:unused // we will need it at a later point in time +func loadConfigFile(filePath string, parameters map[string]any) error { + config := configuration.New() + flagset := configuration.NewUnsortedFlagSet("", flag.ContinueOnError) + + for namespace, pointerToStruct := range parameters { + config.BindParameters(flagset, namespace, pointerToStruct) + } + + if err := config.LoadFile(filePath); err != nil { + return fmt.Errorf("loading config file failed: %w", err) + } + + config.UpdateBoundParameters() + + return nil +} diff --git a/scripts/go_mod_tidy.sh b/scripts/go_mod_tidy.sh index cd28ef1b6..1166ea010 100755 --- a/scripts/go_mod_tidy.sh +++ b/scripts/go_mod_tidy.sh @@ -11,8 +11,4 @@ pushd tools/genesis-snapshot go mod tidy popd -pushd tools/evil-spammer -go mod tidy -popd - popd \ No newline at end of file diff --git a/tools/docker-network/.env b/tools/docker-network/.env index ce7903fb0..0a81fe337 100644 --- a/tools/docker-network/.env +++ b/tools/docker-network/.env @@ -5,16 +5,13 @@ COMMON_CONFIG=" --logger.disableStacktrace=false --logger.encoding=console --logger.outputPaths=stdout - ---database.path=/app/data/database - --p2p.db.path=/app/data/peerdb - ---profiling.bindAddress=0.0.0.0:6061 --profiling.enabled=true - +--profiling.bindAddress=0.0.0.0:6061 +--database.path=/app/data/database --protocol.snapshot.path=/app/data/snapshot.bin " + MANUALPEERING_CONFIG=" ---p2p.peers=/dns/validator-1/tcp/14666/p2p/12D3KooWRVt4Engu27jHnF2RjfX48EqiAqJbgLfFdHNt3Vn6BtJK\ +--p2p.peers=/dns/node-1-validator/tcp/14666/p2p/12D3KooWRVt4Engu27jHnF2RjfX48EqiAqJbgLfFdHNt3Vn6BtJK\ " diff --git a/tools/docker-network/docker-compose.yml b/tools/docker-network/docker-compose.yml index 92ae8e023..22d21b2dc 100644 --- a/tools/docker-network/docker-compose.yml +++ b/tools/docker-network/docker-compose.yml @@ -4,9 +4,29 @@ x-build-iota-core: &iota-core_build context: ${DOCKER_BUILD_CONTEXT:-../../} dockerfile: ${DOCKERFILE_PATH:-./Dockerfile} services: - validator-1: + +################### +# IOTA-CORE Nodes # +################### + + node-1-validator: build: *iota-core_build stop_grace_period: 1m + restart: unless-stopped + ulimits: + nofile: + soft: 16384 + hard: 16384 + networks: + - iota-core + ports: + - "8080:14265/tcp" # REST-API + - "8081:8081/tcp" # Dashboard + - "6081:6061/tcp" # pprof + - "9089:9029/tcp" # INX + volumes: + - ./docker-network.snapshot:/app/data/snapshot.bin + - ./config.json:/app/config.json:ro command: > ${COMMON_CONFIG} ${MANUALPEERING_CONFIG} @@ -17,20 +37,25 @@ services: --validator.privateKey=443a988ea61797651217de1f4662d4d6da11fd78e67f94511453bf6576045a05293dc170d9a59474e6d81cfba7f7d924c09b25d7166bcfba606e53114d0a758b --inx.enabled=true --inx.bindAddress=0.0.0.0:9029 + + node-2-validator: + image: docker-network-node-1-validator:latest + stop_grace_period: 1m + restart: unless-stopped + ulimits: + nofile: + soft: 16384 + hard: 16384 + networks: + - iota-core + ports: + - "8070:14265/tcp" # REST-API + - "8071:8081/tcp" # Dashboard + - "6071:6061/tcp" # pprof + - "9029:9029/tcp" # INX volumes: - ./docker-network.snapshot:/app/data/snapshot.bin - ./config.json:/app/config.json:ro - ports: - - "8080:8080/tcp" # web API - - "8081:8081/tcp" # dashboard - - "6081:6061/tcp" # pprof - - "9089:9029/tcp" # inx - networks: - - iota-core - - validator-2: - image: docker-network-validator-1:latest - stop_grace_period: 1m command: > ${COMMON_CONFIG} ${MANUALPEERING_CONFIG} @@ -40,20 +65,25 @@ services: --validator.privateKey=3a5d39f8b60367a17fd54dac2a32c172c8e1fd6cf74ce65f1e13edba565f281705c1de274451db8de8182d64c6ee0dca3ae0c9077e0b4330c976976171d79064 --inx.enabled=true --inx.bindAddress=0.0.0.0:9029 + + node-3-validator: + image: docker-network-node-1-validator:latest + stop_grace_period: 1m + restart: unless-stopped + ulimits: + nofile: + soft: 16384 + hard: 16384 + networks: + - iota-core + ports: + - "8090:14265/tcp" # REST-API + - "8091:8081/tcp" # Dashboard + - "6091:6061/tcp" # pprof + - "9099:9029/tcp" # INX volumes: - ./docker-network.snapshot:/app/data/snapshot.bin - ./config.json:/app/config.json:ro - ports: - - "8070:8080/tcp" # web API - - "8071:8081/tcp" # dashboard - - "6071:6061/tcp" # pprof - - "9029:9029/tcp" # inx - networks: - - iota-core - - validator-3: - image: docker-network-validator-1:latest - stop_grace_period: 1m command: > ${COMMON_CONFIG} ${MANUALPEERING_CONFIG} @@ -63,146 +93,167 @@ services: --validator.privateKey=db39d2fde6301d313b108dc9db1ee724d0f405f6fde966bd776365bc5f4a5fb31e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648 --inx.enabled=true --inx.bindAddress=0.0.0.0:9029 + + node-4: + image: docker-network-node-1-validator:latest + stop_grace_period: 1m + restart: unless-stopped + ulimits: + nofile: + soft: 16384 + hard: 16384 + networks: + - iota-core + ports: + - "8040:14265/tcp" # REST-API + - "8041:8081/tcp" # Dashboard + - "6041:6061/tcp" # pprof + - "9049:9029/tcp" # INX volumes: - ./docker-network.snapshot:/app/data/snapshot.bin - ./config.json:/app/config.json:ro - ports: - - "8090:8080/tcp" # web API - - "8091:8081/tcp" # dashboard - - "6091:6061/tcp" # pprof - - "9099:9029/tcp" # inx - networks: - - iota-core - - node-1: - image: docker-network-validator-1:latest - stop_grace_period: 1m command: > ${COMMON_CONFIG} ${MANUALPEERING_CONFIG} --p2p.identityPrivateKey=03feb3bcd25e57f75697bb329e6e0100680431e4c45c85bc013da2aea9e9d0345e08a0c37407dc62369deebc64cb0fb3ea26127d19d141ee7fb8eaa6b92019d7 --inx.enabled=true --inx.bindAddress=0.0.0.0:9029 + + node-5: + image: docker-network-node-1-validator:latest + stop_grace_period: 1m + restart: unless-stopped + ulimits: + nofile: + soft: 16384 + hard: 16384 + networks: + - iota-core + ports: + - "8030:14265/tcp" # REST-API + - "8031:8081/tcp" # Dashboard + - "6031:6061/tcp" # pprof + - "9039:9029/tcp" # INX volumes: - ./docker-network.snapshot:/app/data/snapshot.bin - ./config.json:/app/config.json:ro - ports: - - "8040:8080/tcp" # web API - - "8041:8081/tcp" # dashboard - - "6041:6061/tcp" # pprof - - "9049:9029/tcp" # inx - networks: - - iota-core - - node-2: - image: docker-network-validator-1:latest - stop_grace_period: 1m command: > ${COMMON_CONFIG} ${MANUALPEERING_CONFIG} --p2p.identityPrivateKey=7d1491df3ef334dee988d6cdfc4b430b996d520bd63375a01d6754f8cee979b855b200fbea8c936ea1937a27e6ad72a7c9a21c1b17c2bd3c11f1f6994d813446 --inx.enabled=true --inx.bindAddress=0.0.0.0:9029 - volumes: - - ./docker-network.snapshot:/app/data/snapshot.bin - - ./config.json:/app/config.json:ro + +################################################################## +# Monitoring # +################################################################## + + prometheus: + image: prom/prometheus:latest + stop_grace_period: 1m + restart: unless-stopped + depends_on: + node-1-validator: + condition: service_started + networks: + - iota-core ports: - - "8030:8080/tcp" # web API - - "8031:8081/tcp" # dashboard - - "6031:6061/tcp" # pprof - - "9039:9029/tcp" # inx + - "9090:9090" # prometheus + secrets: + - prometheus.yml + command: + - "--config.file=/run/secrets/prometheus.yml" + profiles: + - monitoring + + grafana: + image: grafana/grafana:9.5.6 + restart: unless-stopped networks: - iota-core + ports: + - "3000:3000" # Grafana Dashboard + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/:/etc/grafana/:ro + environment: + - GF_ALERTING_ENABLED=true + - GF_UNIFIED_ALERTING_ENABLED=false + - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards/local_dashboard.json + profiles: + - monitoring + +################## +# INX Extensions # +################## inx-indexer: image: iotaledger/inx-indexer:2.0-alpha stop_grace_period: 1m + restart: unless-stopped depends_on: - validator-1: - condition: service_started - command: - - "--inx.address=validator-1:9029" - - "--restAPI.bindAddress=inx-indexer:9091" + node-1-validator: + condition: service_healthy + ulimits: + nofile: + soft: 16384 + hard: 16384 networks: - iota-core + command: > + --inx.address=node-1-validator:9029 + --restAPI.bindAddress=inx-indexer:9091 inx-blockissuer: image: iotaledger/inx-blockissuer:1.0-alpha stop_grace_period: 1m + restart: unless-stopped depends_on: - validator-1: - condition: service_started + node-1-validator: + condition: service_healthy inx-indexer: condition: service_started - restart: on-failure - environment: - - "BLOCKISSUER_PRV_KEY=432c624ca3260f910df35008d5c740593b222f1e196e6cdb8cd1ad080f0d4e33997be92a22b1933f36e26fba5f721756f95811d6b4ae21564197c2bfa4f28270" - command: - - "--inx.address=validator-1:9029" - - "--restAPI.bindAddress=inx-blockissuer:9086" - - "--blockIssuer.accountAddress=rms1prkursay9fs2qjmfctamd6yxg9x8r3ry47786x0mvwek4qr9xd9d5c6gkun" networks: - iota-core + environment: + - "BLOCKISSUER_PRV_KEY=432c624ca3260f910df35008d5c740593b222f1e196e6cdb8cd1ad080f0d4e33997be92a22b1933f36e26fba5f721756f95811d6b4ae21564197c2bfa4f28270" + command: > + --inx.address=node-1-validator:9029 + --restAPI.bindAddress=inx-blockissuer:9086 + --blockIssuer.accountAddress=rms1prkursay9fs2qjmfctamd6yxg9x8r3ry47786x0mvwek4qr9xd9d5c6gkun + --blockIssuer.proofOfWork.targetTrailingZeros=5 inx-faucet: image: iotaledger/inx-faucet:2.0-alpha stop_grace_period: 1m + restart: unless-stopped depends_on: - validator-1: - condition: service_started + node-1-validator: + condition: service_healthy inx-indexer: condition: service_started inx-blockissuer: condition: service_started - restart: on-failure - environment: - - "FAUCET_PRV_KEY=de52b9964dda96564e9fab362ab16c2669c715c6a2a853bece8a25fc58c599755b938327ea463e0c323c0fd44f6fc1843ed94daecc6909c6043d06b7152e4737" - command: - - "--inx.address=validator-1:9029" - - "--faucet.bindAddress=inx-faucet:8091" - ports: - - "8088:8091/tcp" # faucet frontend networks: - iota-core - - prometheus: - image: prom/prometheus:latest - profiles: - - grafana ports: - - "9090:9090" - command: - - --config.file=/run/secrets/prometheus.yml - secrets: - - prometheus.yml - networks: - - iota-core - depends_on: - - validator-1 - - grafana: - image: grafana/grafana:9.5.6 - profiles: - - grafana + - "8088:8091/tcp" # Faucet Frontend environment: - - GF_ALERTING_ENABLED=true - - GF_UNIFIED_ALERTING_ENABLED=false - - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards/local_dashboard.json - networks: - - iota-core - ports: - - "3000:3000" - volumes: - - grafana_data:/var/lib/grafana - - ./grafana/:/etc/grafana/:ro + - "FAUCET_PRV_KEY=de52b9964dda96564e9fab362ab16c2669c715c6a2a853bece8a25fc58c599755b938327ea463e0c323c0fd44f6fc1843ed94daecc6909c6043d06b7152e4737" + command: > + --inx.address=node-1-validator:9029 + --faucet.bindAddress=inx-faucet:8091 + --faucet.rateLimit.enabled=false # Create our own network networks: iota-core: driver: bridge + # Named Docker volumes for data persistence volumes: grafana_data: + # read only files to load in the containers that may be shared across containers secrets: prometheus.yml: diff --git a/tools/docker-network/grafana/provisioning/dashboards/local_dashboard.json b/tools/docker-network/grafana/provisioning/dashboards/local_dashboard.json index f464af4c6..571712c82 100644 --- a/tools/docker-network/grafana/provisioning/dashboards/local_dashboard.json +++ b/tools/docker-network/grafana/provisioning/dashboards/local_dashboard.json @@ -1340,8 +1340,8 @@ { "current": { "selected": false, - "text": "validator-3:9311", - "value": "validator-3:9311" + "text": "node-3-validator:9311", + "value": "node-3-validator:9311" }, "datasource": { "type": "prometheus", diff --git a/tools/docker-network/grafana/provisioning/dashboards/local_dashboard_old.json b/tools/docker-network/grafana/provisioning/dashboards/local_dashboard_old.json index 71fb84826..320e8c1a6 100644 --- a/tools/docker-network/grafana/provisioning/dashboards/local_dashboard_old.json +++ b/tools/docker-network/grafana/provisioning/dashboards/local_dashboard_old.json @@ -4847,8 +4847,8 @@ { "current": { "selected": false, - "text": "validator-3:9311", - "value": "validator-3:9311" + "text": "node-3-validator:9311", + "value": "node-3-validator:9311" }, "datasource": { "type": "prometheus", diff --git a/tools/docker-network/grafana/provisioning/dashboards/scheduler-metrics.json b/tools/docker-network/grafana/provisioning/dashboards/scheduler-metrics.json index c3d8798c3..e76dd6b1c 100644 --- a/tools/docker-network/grafana/provisioning/dashboards/scheduler-metrics.json +++ b/tools/docker-network/grafana/provisioning/dashboards/scheduler-metrics.json @@ -772,8 +772,8 @@ { "current": { "selected": true, - "text": "validator-2:9311", - "value": "validator-2:9311" + "text": "node-2-validator:9311", + "value": "node-2-validator:9311" }, "datasource": { "type": "prometheus", diff --git a/tools/docker-network/prometheus.yml b/tools/docker-network/prometheus.yml index 28817e795..07728e80f 100644 --- a/tools/docker-network/prometheus.yml +++ b/tools/docker-network/prometheus.yml @@ -3,11 +3,11 @@ scrape_configs: scrape_interval: 5s static_configs: - targets: - - validator-1:9311 - - validator-2:9311 - - validator-3:9311 - - node-1:9311 - - node-2:9311 + - node-1-validator:9311 + - node-2-validator:9311 + - node-3-validator:9311 + - node-4:9311 + - node-5:9311 dns_sd_configs: - names: - 'peer_replica' diff --git a/tools/docker-network/run.sh b/tools/docker-network/run.sh index e1021c8e1..b137a67a4 100755 --- a/tools/docker-network/run.sh +++ b/tools/docker-network/run.sh @@ -6,12 +6,12 @@ function join { local IFS="$1"; shift; echo "$*"; } # All parameters can be optional now, just make sure we don't have too many if [[ $# -gt 4 ]] ; then - echo 'Call with ./run [replicas=1|2|3|...] [grafana=0|1] [feature=0|1]' + echo 'Call with ./run [replicas=1|2|3|...] [monitoring=0|1] [feature=0|1]' exit 0 fi REPLICAS=${1:-1} -GRAFANA=${2:-0} +MONITORING=${2:-0} FEATURE=${3:-0} DOCKER_COMPOSE_FILE=docker-compose.yml @@ -67,9 +67,9 @@ export IOTA_CORE_PEER_REPLICAS=$REPLICAS # Profiles is created to set which docker profiles to run # https://docs.docker.com/compose/profiles/ PROFILES=() -if [ $GRAFANA -ne 0 ] +if [ $MONITORING -ne 0 ] then - PROFILES+=("grafana") + PROFILES+=("monitoring") fi export COMPOSE_PROFILES=$(join , ${PROFILES[@]}) diff --git a/tools/evil-spammer/.gitignore b/tools/evil-spammer/.gitignore deleted file mode 100644 index 301d5d62d..000000000 --- a/tools/evil-spammer/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.log -*.json -*.dat \ No newline at end of file diff --git a/tools/evil-spammer/accountwallet/commands.go b/tools/evil-spammer/accountwallet/commands.go deleted file mode 100644 index 37a7843cb..000000000 --- a/tools/evil-spammer/accountwallet/commands.go +++ /dev/null @@ -1,40 +0,0 @@ -package accountwallet - -import ( - "fmt" - - "github.com/iotaledger/hive.go/ierrors" - iotago "github.com/iotaledger/iota.go/v4" -) - -func (a *AccountWallet) CreateAccount(params *CreateAccountParams) (iotago.AccountID, error) { - implicitAccountOutput, privateKey, err := a.getFunds(params.Amount, iotago.AddressImplicitAccountCreation) - if err != nil { - return iotago.EmptyAccountID, ierrors.Wrap(err, "Failed to create account") - } - - accountID := a.registerAccount(params.Alias, implicitAccountOutput.OutputID, a.latestUsedIndex, privateKey) - - fmt.Printf("Created account %s with %d tokens\n", accountID.ToHex(), params.Amount) - - return accountID, nil -} - -func (a *AccountWallet) DestroyAccount(params *DestroyAccountParams) error { - return a.destroyAccount(params.AccountAlias) -} - -func (a *AccountWallet) ListAccount() error { - fmt.Printf("%-10s \t%-33s\n\n", "Alias", "AccountID") - for _, accData := range a.accountsAliases { - fmt.Printf("%-10s \t", accData.Alias) - fmt.Printf("%-33s ", accData.Account.ID().ToHex()) - fmt.Printf("\n") - } - - return nil -} - -func (a *AccountWallet) AllotToAccount(params *AllotAccountParams) error { - return nil -} diff --git a/tools/evil-spammer/accountwallet/config.go b/tools/evil-spammer/accountwallet/config.go deleted file mode 100644 index a363cb8ad..000000000 --- a/tools/evil-spammer/accountwallet/config.go +++ /dev/null @@ -1,219 +0,0 @@ -package accountwallet - -import ( - "encoding/json" - "os" - - "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" -) - -// commands - -type AccountOperation int - -const ( - OperationCreateAccount AccountOperation = iota - OperationConvertAccount - OperationDestroyAccound - OperationAllotAccount - OperationDelegateAccount - OperationStakeAccount - OperationListAccounts - OperationUpdateAccount - - CmdNameCreateAccount = "create" - CmdNameConvertAccount = "convert" - CmdNameDestroyAccount = "destroy" - CmdNameAllotAccount = "allot" - CmdNameDelegateAccount = "delegate" - CmdNameStakeAccount = "stake" - CmdNameListAccounts = "list" - CmdNameUpdateAccount = "update" -) - -func (a AccountOperation) String() string { - return []string{ - CmdNameCreateAccount, - CmdNameConvertAccount, - CmdNameDestroyAccount, - CmdNameAllotAccount, - CmdNameDelegateAccount, - CmdNameStakeAccount, - CmdNameListAccounts, - CmdNameUpdateAccount, - }[a] -} - -func AvailableCommands(cmd string) bool { - availableCommands := map[string]types.Empty{ - CmdNameCreateAccount: types.Void, - CmdNameConvertAccount: types.Void, - CmdNameDestroyAccount: types.Void, - CmdNameAllotAccount: types.Void, - CmdNameDelegateAccount: types.Void, - CmdNameStakeAccount: types.Void, - CmdNameListAccounts: types.Void, - CmdNameUpdateAccount: types.Void, - } - - _, ok := availableCommands[cmd] - return ok -} - -type Configuration struct { - BindAddress string `json:"bindAddress,omitempty"` - AccountStatesFile string `json:"accountStatesFile,omitempty"` - GenesisSeed string `json:"genesisSeed,omitempty"` - BlockIssuerPrivateKey string `json:"blockIssuerPrivateKey,omitempty"` - AccountID string `json:"accountID,omitempty"` -} - -var accountConfigFile = "config.json" - -var ( - dockerAccountConfigJSON = `{ - "bindAddress": "http://localhost:8080", - "accountStatesFile": "wallet.dat", - "genesisSeed": "7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih", - "blockIssuerPrivateKey": "db39d2fde6301d313b108dc9db1ee724d0f405f6fde966bd776365bc5f4a5fb31e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648", - "accountID": "0x6aee704f25558e8aa7630fed0121da53074188abc423b3c5810f80be4936eb6e"}` -) - -// LoadConfiguration loads the config file. -func LoadConfiguration() *Configuration { - // open config file - config := new(Configuration) - file, err := os.Open(accountConfigFile) - if err != nil { - if !os.IsNotExist(err) { - panic(err) - } - - //nolint:gosec // users should be able to read the file - if err = os.WriteFile(accountConfigFile, []byte(dockerAccountConfigJSON), 0o644); err != nil { - panic(err) - } - if file, err = os.Open(accountConfigFile); err != nil { - panic(err) - } - } - defer file.Close() - - // decode config file - if err = json.NewDecoder(file).Decode(config); err != nil { - panic(err) - } - - return config -} - -func SaveConfiguration(config *Configuration) { - // open config file - file, err := os.Open(accountConfigFile) - if err != nil { - panic(err) - } - defer file.Close() - - jsonConfigs, err := json.MarshalIndent(config, "", " ") - - if err != nil { - log.Errorf("failed to write configs to file %s", err) - } - - //nolint:gosec // users should be able to read the file - if err = os.WriteFile(accountConfigFile, jsonConfigs, 0o644); err != nil { - panic(err) - } -} - -type AccountSubcommands interface { - Type() AccountOperation -} - -type CreateAccountParams struct { - Alias string - Amount uint64 - NoBIF bool - Implicit bool -} - -func (c *CreateAccountParams) Type() AccountOperation { - return OperationCreateAccount -} - -type DestroyAccountParams struct { - AccountAlias string - ExpirySlot uint64 -} - -func (d *DestroyAccountParams) Type() AccountOperation { - return OperationDestroyAccound -} - -type AllotAccountParams struct { - Amount uint64 - To string - From string // if not set we use faucet -} - -func (a *AllotAccountParams) Type() AccountOperation { - return OperationAllotAccount -} - -type ConvertAccountParams struct { - AccountAlias string -} - -func (d *ConvertAccountParams) Type() AccountOperation { - return OperationConvertAccount -} - -type DelegateAccountParams struct { - Amount uint64 - To string - From string // if not set we use faucet -} - -func (a *DelegateAccountParams) Type() AccountOperation { - return OperationDelegateAccount -} - -type StakeAccountParams struct { - Alias string - Amount uint64 - FixedCost uint64 - StartEpoch uint64 - EndEpoch uint64 -} - -func (a *StakeAccountParams) Type() AccountOperation { - return OperationStakeAccount -} - -type UpdateAccountParams struct { - Alias string - BlockIssuerKey string - Mana uint64 - Amount uint64 - ExpirySlot uint64 -} - -func (a *UpdateAccountParams) Type() AccountOperation { - return OperationUpdateAccount -} - -type NoAccountParams struct { - Operation AccountOperation -} - -func (a *NoAccountParams) Type() AccountOperation { - return a.Operation -} - -type StateData struct { - Seed string `serix:"0,mapKey=seed,lengthPrefixType=uint8"` - LastUsedIndex uint64 `serix:"1,mapKey=lastUsedIndex"` - AccountsData []*models.AccountState `serix:"2,mapKey=accounts,lengthPrefixType=uint8"` -} diff --git a/tools/evil-spammer/accountwallet/faucet.go b/tools/evil-spammer/accountwallet/faucet.go deleted file mode 100644 index 05dbccf7f..000000000 --- a/tools/evil-spammer/accountwallet/faucet.go +++ /dev/null @@ -1,284 +0,0 @@ -package accountwallet - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/mr-tron/base58" - - "github.com/iotaledger/hive.go/core/safemath" - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/lo" - "github.com/iotaledger/iota-core/pkg/blockhandler" - "github.com/iotaledger/iota-core/pkg/testsuite/mock" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/builder" - "github.com/iotaledger/iota.go/v4/nodeclient" - "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" -) - -const ( - FaucetAccountAlias = "faucet" -) - -func (a *AccountWallet) RequestBlockBuiltData(clt *nodeclient.Client, issuerID iotago.AccountID) (*apimodels.CongestionResponse, *apimodels.IssuanceBlockHeaderResponse, iotago.Version, error) { - congestionResp, err := clt.Congestion(context.Background(), issuerID) - if err != nil { - return nil, nil, 0, ierrors.Wrapf(err, "failed to get congestion data for issuer %s", issuerID.ToHex()) - } - - issuerResp, err := clt.BlockIssuance(context.Background(), congestionResp.Slot) - if err != nil { - return nil, nil, 0, ierrors.Wrap(err, "failed to get block issuance data") - } - - version := clt.APIForSlot(congestionResp.Slot).Version() - - return congestionResp, issuerResp, version, nil -} - -func (a *AccountWallet) RequestFaucetFunds(clt models.Client, receiveAddr iotago.Address, amount iotago.BaseToken) (*models.Output, error) { - congestionResp, issuerResp, version, err := a.RequestBlockBuiltData(clt.Client(), a.faucet.account.ID()) - if err != nil { - return nil, ierrors.Wrapf(err, "failed to get block built data for issuer %s", a.faucet.account.ID().ToHex()) - } - - signedTx, err := a.faucet.prepareFaucetRequest(receiveAddr, amount, congestionResp.ReferenceManaCost) - if err != nil { - log.Errorf("failed to prepare faucet request: %s", err) - - return nil, err - } - - blkID, err := a.PostWithBlock(clt, signedTx, a.faucet.account, congestionResp, issuerResp, version) - if err != nil { - log.Errorf("failed to create block: %s", err) - - return nil, err - } - fmt.Println("block sent:", blkID.ToHex()) - - // set remainder output to be reused by the Faucet wallet - a.faucet.unspentOutput = &models.Output{ - OutputID: iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), 1), - Address: a.faucet.genesisKeyManager.Address(iotago.AddressEd25519).(*iotago.Ed25519Address), - Index: 0, - Balance: signedTx.Transaction.Outputs[1].BaseTokenAmount(), - OutputStruct: signedTx.Transaction.Outputs[1], - } - - return &models.Output{ - OutputID: iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), 0), - Address: receiveAddr, - Index: 0, - Balance: signedTx.Transaction.Outputs[0].BaseTokenAmount(), - OutputStruct: signedTx.Transaction.Outputs[0], - }, nil -} - -func (a *AccountWallet) PostWithBlock(clt models.Client, payload iotago.Payload, issuer blockhandler.Account, congestionResp *apimodels.CongestionResponse, issuerResp *apimodels.IssuanceBlockHeaderResponse, version iotago.Version) (iotago.BlockID, error) { - signedBlock, err := a.CreateBlock(payload, issuer, congestionResp, issuerResp, version) - if err != nil { - log.Errorf("failed to create block: %s", err) - - return iotago.EmptyBlockID, err - } - - blockID, err := clt.PostBlock(signedBlock) - if err != nil { - log.Errorf("failed to post block: %s", err) - - return iotago.EmptyBlockID, err - } - - return blockID, nil -} - -func (a *AccountWallet) CreateBlock(payload iotago.Payload, issuer blockhandler.Account, congestionResp *apimodels.CongestionResponse, issuerResp *apimodels.IssuanceBlockHeaderResponse, version iotago.Version) (*iotago.ProtocolBlock, error) { - issuingTime := time.Now() - issuingSlot := a.client.LatestAPI().TimeProvider().SlotFromTime(issuingTime) - apiForSlot := a.client.APIForSlot(issuingSlot) - - blockBuilder := builder.NewBasicBlockBuilder(apiForSlot) - - commitmentID, err := issuerResp.Commitment.ID() - if err != nil { - return nil, ierrors.Wrap(err, "failed to get commitment id") - } - - blockBuilder.ProtocolVersion(version) - blockBuilder.SlotCommitmentID(commitmentID) - blockBuilder.LatestFinalizedSlot(issuerResp.LatestFinalizedSlot) - blockBuilder.IssuingTime(time.Now()) - blockBuilder.StrongParents(issuerResp.StrongParents) - blockBuilder.WeakParents(issuerResp.WeakParents) - blockBuilder.ShallowLikeParents(issuerResp.ShallowLikeParents) - - blockBuilder.Payload(payload) - blockBuilder.CalculateAndSetMaxBurnedMana(congestionResp.ReferenceManaCost) - blockBuilder.Sign(issuer.ID(), issuer.PrivateKey()) - - blk, err := blockBuilder.Build() - if err != nil { - return nil, ierrors.Errorf("failed to build block: %w", err) - } - - return blk, nil -} - -type faucetParams struct { - faucetPrivateKey string - faucetAccountID string - genesisSeed string -} - -type faucet struct { - unspentOutput *models.Output - account blockhandler.Account - genesisKeyManager *mock.KeyManager - - clt models.Client - - sync.Mutex -} - -func newFaucet(clt models.Client, faucetParams *faucetParams) (*faucet, error) { - genesisSeed, err := base58.Decode(faucetParams.genesisSeed) - if err != nil { - log.Warnf("failed to decode base58 seed, using the default one: %v", err) - } - faucetAddr := mock.NewKeyManager(genesisSeed, 0).Address(iotago.AddressEd25519) - - f := &faucet{ - clt: clt, - account: blockhandler.AccountFromParams(faucetParams.faucetAccountID, faucetParams.faucetPrivateKey), - genesisKeyManager: mock.NewKeyManager(genesisSeed, 0), - } - - faucetUnspentOutput, faucetUnspentOutputID, faucetAmount, err := f.getGenesisOutputFromIndexer(clt, faucetAddr) - if err != nil { - return nil, ierrors.Wrap(err, "failed to get faucet output from indexer") - } - - f.unspentOutput = &models.Output{ - Address: faucetAddr.(*iotago.Ed25519Address), - Index: 0, - OutputID: faucetUnspentOutputID, - Balance: faucetAmount, - OutputStruct: faucetUnspentOutput, - } - - return f, nil -} - -func (f *faucet) getGenesisOutputFromIndexer(clt models.Client, faucetAddr iotago.DirectUnlockableAddress) (iotago.Output, iotago.OutputID, iotago.BaseToken, error) { - indexer, err := clt.Indexer() - if err != nil { - log.Errorf("account wallet failed due to failure in connecting to indexer") - - return nil, iotago.EmptyOutputID, 0, ierrors.Wrapf(err, "failed to get indexer from client") - } - - results, err := indexer.Outputs(context.Background(), &apimodels.BasicOutputsQuery{ - AddressBech32: faucetAddr.Bech32(iotago.PrefixTestnet), - }) - if err != nil { - return nil, iotago.EmptyOutputID, 0, ierrors.Wrap(err, "failed to prepare faucet unspent outputs indexer request") - } - - var ( - faucetUnspentOutput iotago.Output - faucetUnspentOutputID iotago.OutputID - faucetAmount iotago.BaseToken - ) - for results.Next() { - unspents, err := results.Outputs(context.TODO()) - if err != nil { - return nil, iotago.EmptyOutputID, 0, ierrors.Wrap(err, "failed to get faucet unspent outputs") - } - - faucetUnspentOutput = unspents[0] - faucetAmount = faucetUnspentOutput.BaseTokenAmount() - faucetUnspentOutputID = lo.Return1(results.Response.Items.OutputIDs())[0] - } - - return faucetUnspentOutput, faucetUnspentOutputID, faucetAmount, nil -} - -func (f *faucet) prepareFaucetRequest(receiveAddr iotago.Address, amount iotago.BaseToken, rmc iotago.Mana) (*iotago.SignedTransaction, error) { - remainderAmount, err := safemath.SafeSub(f.unspentOutput.Balance, amount) - if err != nil { - panic(err) - } - - txBuilder, remainderIndex, err := f.createFaucetTransactionNoManaHandling(receiveAddr, amount, remainderAmount) - if err != nil { - return nil, err - } - - rmcAllotedTxBuilder := txBuilder.Clone() - // faucet will allot exact mana to be burnt, rest of the mana is alloted to faucet output remainder - rmcAllotedTxBuilder.AllotRequiredManaAndStoreRemainingManaInOutput(txBuilder.CreationSlot(), rmc, f.account.ID(), remainderIndex) - - var signedTx *iotago.SignedTransaction - signedTx, err = rmcAllotedTxBuilder.Build(f.genesisKeyManager.AddressSigner()) - if err != nil { - log.Infof("WARN: failed to build tx with min required mana allotted, genesis potential mana was not enough, fallback to faucet account") - txBuilder.AllotAllMana(txBuilder.CreationSlot(), f.account.ID()) - if signedTx, err = txBuilder.Build(f.genesisKeyManager.AddressSigner()); err != nil { - return nil, ierrors.Wrapf(err, "failed to build transaction with all mana allotted, after not having enough mana required based on RMC") - } - } - - return signedTx, nil -} - -func (f *faucet) createFaucetTransactionNoManaHandling(receiveAddr iotago.Address, amount iotago.BaseToken, remainderAmount iotago.BaseToken) (*builder.TransactionBuilder, int, error) { - currentTime := time.Now() - currentSlot := f.clt.LatestAPI().TimeProvider().SlotFromTime(currentTime) - - apiForSlot := f.clt.APIForSlot(currentSlot) - txBuilder := builder.NewTransactionBuilder(apiForSlot) - - txBuilder.AddInput(&builder.TxInput{ - UnlockTarget: f.genesisKeyManager.Address(iotago.AddressEd25519).(*iotago.Ed25519Address), - InputID: f.unspentOutput.OutputID, - Input: f.unspentOutput.OutputStruct, - }) - - switch receiveAddr.(type) { - case *iotago.Ed25519Address: - txBuilder.AddOutput(&iotago.BasicOutput{ - Amount: amount, - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: receiveAddr}, - }, - }) - case *iotago.ImplicitAccountCreationAddress: - log.Infof("creating account %s", receiveAddr) - accOutputBuilder := builder.NewAccountOutputBuilder(receiveAddr, receiveAddr, amount) - output, err := accOutputBuilder.Build() - if err != nil { - log.Errorf("failed to build account output: %s", err) - - return nil, 0, err - } - txBuilder.AddOutput(output) - } - - // remainder output - remainderIndex := 1 - txBuilder.AddOutput(&iotago.BasicOutput{ - Amount: remainderAmount, - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: f.genesisKeyManager.Address(iotago.AddressEd25519).(*iotago.Ed25519Address)}, - }, - }) - txBuilder.AddTaggedDataPayload(&iotago.TaggedData{Tag: []byte("Faucet funds"), Data: []byte("to addr" + receiveAddr.String())}) - txBuilder.SetCreationSlot(currentSlot) - - return txBuilder, remainderIndex, nil -} diff --git a/tools/evil-spammer/accountwallet/options.go b/tools/evil-spammer/accountwallet/options.go deleted file mode 100644 index 51b28fcd7..000000000 --- a/tools/evil-spammer/accountwallet/options.go +++ /dev/null @@ -1,24 +0,0 @@ -package accountwallet - -import ( - "github.com/iotaledger/hive.go/runtime/options" -) - -// WithClientURL sets the client bind address. -func WithClientURL(url string) options.Option[AccountWallet] { - return func(w *AccountWallet) { - w.optsClientBindAddress = url - } -} - -func WithAccountStatesFile(fileName string) options.Option[AccountWallet] { - return func(w *AccountWallet) { - w.optsAccountStatesFile = fileName - } -} - -func WithFaucetAccountParams(params *faucetParams) options.Option[AccountWallet] { - return func(w *AccountWallet) { - w.optsFaucetParams = params - } -} diff --git a/tools/evil-spammer/accountwallet/wallet.go b/tools/evil-spammer/accountwallet/wallet.go deleted file mode 100644 index 327d6c150..000000000 --- a/tools/evil-spammer/accountwallet/wallet.go +++ /dev/null @@ -1,343 +0,0 @@ -package accountwallet - -import ( - "crypto/ed25519" - "os" - "time" - - "github.com/mr-tron/base58" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/runtime/options" - "github.com/iotaledger/hive.go/runtime/timeutil" - "github.com/iotaledger/iota-core/pkg/blockhandler" - "github.com/iotaledger/iota-core/pkg/testsuite/mock" - "github.com/iotaledger/iota-core/tools/evil-spammer/logger" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/builder" - "github.com/iotaledger/iota.go/v4/tpkg" -) - -var log = logger.New("AccountWallet") - -func Run(config *Configuration) (*AccountWallet, error) { - var opts []options.Option[AccountWallet] - if config.BindAddress != "" { - opts = append(opts, WithClientURL(config.BindAddress)) - } - if config.AccountStatesFile != "" { - opts = append(opts, WithAccountStatesFile(config.AccountStatesFile)) - } - - opts = append(opts, WithFaucetAccountParams(&faucetParams{ - genesisSeed: config.GenesisSeed, - faucetPrivateKey: config.BlockIssuerPrivateKey, - faucetAccountID: config.AccountID, - })) - - wallet, err := NewAccountWallet(opts...) - if err != nil { - return nil, ierrors.Wrap(err, "failed to create wallet") - } - - // load wallet - err = wallet.fromAccountStateFile() - if err != nil { - return nil, ierrors.Wrap(err, "failed to load wallet from file") - } - - return wallet, nil -} - -func SaveState(w *AccountWallet) error { - return w.toAccountStateFile() -} - -type AccountWallet struct { - faucet *faucet - seed [32]byte - - accountsAliases map[string]*models.AccountData - - //accountsStatus map[string]models.AccountMetadata - latestUsedIndex uint64 - - client *models.WebClient - - optsClientBindAddress string - optsAccountStatesFile string - optsFaucetParams *faucetParams - optsRequestTimeout time.Duration - optsRequestTicker time.Duration -} - -func NewAccountWallet(opts ...options.Option[AccountWallet]) (*AccountWallet, error) { - var initErr error - return options.Apply(&AccountWallet{ - accountsAliases: make(map[string]*models.AccountData), - seed: tpkg.RandEd25519Seed(), - optsRequestTimeout: time.Second * 120, - optsRequestTicker: time.Second * 5, - }, opts, func(w *AccountWallet) { - w.client, initErr = models.NewWebClient(w.optsClientBindAddress) - if initErr != nil { - return - } - - var f *faucet - f, initErr = newFaucet(w.client, w.optsFaucetParams) - if initErr != nil { - return - } - - w.faucet = f - w.accountsAliases[FaucetAccountAlias] = &models.AccountData{ - Alias: FaucetAccountAlias, - Status: models.AccountReady, - OutputID: iotago.EmptyOutputID, - Index: 0, - Account: w.faucet.account, - } - }), initErr -} - -// toAccountStateFile write account states to file. -func (a *AccountWallet) toAccountStateFile() error { - accounts := make([]*models.AccountState, 0) - - for _, acc := range a.accountsAliases { - accounts = append(accounts, models.AccountStateFromAccountData(acc)) - } - - stateBytes, err := a.client.LatestAPI().Encode(&StateData{ - Seed: base58.Encode(a.seed[:]), - LastUsedIndex: a.latestUsedIndex, - AccountsData: accounts, - }) - if err != nil { - return ierrors.Wrap(err, "failed to encode state") - } - - //nolint:gosec // users should be able to read the file - if err = os.WriteFile(a.optsAccountStatesFile, stateBytes, 0o644); err != nil { - return ierrors.Wrap(err, "failed to write account states to file") - } - - return nil -} - -func (a *AccountWallet) fromAccountStateFile() error { - walletStateBytes, err := os.ReadFile(a.optsAccountStatesFile) - if err != nil { - if !os.IsNotExist(err) { - return ierrors.Wrap(err, "failed to read file") - } - return nil - } - - var data StateData - _, err = a.client.LatestAPI().Decode(walletStateBytes, &data) - if err != nil { - return ierrors.Wrap(err, "failed to decode from file") - } - - // copy seeds - decodedSeeds, err := base58.Decode(data.Seed) - if err != nil { - return ierrors.Wrap(err, "failed to decode seed") - } - copy(a.seed[:], decodedSeeds) - - // set latest used index - a.latestUsedIndex = data.LastUsedIndex - - // account data - for _, acc := range data.AccountsData { - a.accountsAliases[acc.Alias] = acc.ToAccountData() - if acc.Alias == FaucetAccountAlias { - a.accountsAliases[acc.Alias].Status = models.AccountReady - } - } - - return nil -} - -func (a *AccountWallet) registerAccount(alias string, outputID iotago.OutputID, index uint64, privKey ed25519.PrivateKey) iotago.AccountID { - accountID := iotago.AccountIDFromOutputID(outputID) - account := blockhandler.NewEd25519Account(accountID, privKey) - - a.accountsAliases[alias] = &models.AccountData{ - Alias: alias, - Account: account, - Status: models.AccountPending, - OutputID: outputID, - Index: index, - } - - return accountID -} - -func (a *AccountWallet) updateAccountStatus(alias string, status models.AccountStatus) (*models.AccountData, bool) { - accData, exists := a.accountsAliases[alias] - if !exists { - return nil, false - } - - if accData.Status == status { - return accData, false - } - - accData.Status = status - a.accountsAliases[alias] = accData - - return accData, true -} - -func (a *AccountWallet) GetReadyAccount(alias string) (*models.AccountData, error) { - accData, exists := a.accountsAliases[alias] - if !exists { - return nil, ierrors.Errorf("account with alias %s does not exist", alias) - } - - // check if account is ready (to be included in a commitment) - ready := a.isAccountReady(accData) - if !ready { - return nil, ierrors.Errorf("account with alias %s is not ready", alias) - } - - accData, _ = a.updateAccountStatus(alias, models.AccountReady) - - return accData, nil -} - -func (a *AccountWallet) GetAccount(alias string) (*models.AccountData, error) { - accData, exists := a.accountsAliases[alias] - if !exists { - return nil, ierrors.Errorf("account with alias %s does not exist", alias) - } - - return accData, nil -} - -func (a *AccountWallet) isAccountReady(accData *models.AccountData) bool { - if accData.Status == models.AccountReady { - return true - } - - creationSlot := accData.OutputID.CreationSlot() - - // wait for the account to be committed - log.Infof("Waiting for account %s to be committed within slot %d...", accData.Alias, creationSlot) - err := a.retry(func() (bool, error) { - resp, err := a.client.GetBlockIssuance() - if err != nil { - return false, err - } - - if resp.Commitment.Slot >= creationSlot { - log.Infof("Slot %d commited, account %s is ready to use", creationSlot, accData.Alias) - return true, nil - } - - return false, nil - }) - - if err != nil { - log.Errorf("failed to get commitment details while waiting %s: %s", accData.Alias, err) - return false - } - - return true -} - -func (a *AccountWallet) getFunds(amount uint64, addressType iotago.AddressType) (*models.Output, ed25519.PrivateKey, error) { - keyManager := mock.NewKeyManager(a.seed[:], a.latestUsedIndex+1) - privKey, _ := keyManager.KeyPair() - receiverAddr := keyManager.Address(addressType) - createdOutput, err := a.RequestFaucetFunds(a.client, receiverAddr, iotago.BaseToken(amount)) - if err != nil { - return nil, nil, ierrors.Wrap(err, "failed to request funds from Faucet") - } - - a.latestUsedIndex++ - createdOutput.Index = a.latestUsedIndex - - return createdOutput, privKey, nil -} - -func (a *AccountWallet) destroyAccount(alias string) error { - accData, err := a.GetAccount(alias) - if err != nil { - return err - } - hdWallet := mock.NewKeyManager(a.seed[:], accData.Index) - - issuingTime := time.Now() - issuingSlot := a.client.LatestAPI().TimeProvider().SlotFromTime(issuingTime) - apiForSlot := a.client.APIForSlot(issuingSlot) - - // get output from node - // From TIP42: Indexers and node plugins shall map the account address of the output derived with Account ID to the regular address -> output mapping table, so that given an Account Address, its most recent unspent account output can be retrieved. - // TODO: use correct outputID - accountOutput := a.client.GetOutput(accData.OutputID) - - txBuilder := builder.NewTransactionBuilder(apiForSlot) - txBuilder.AddInput(&builder.TxInput{ - UnlockTarget: a.accountsAliases[alias].Account.ID().ToAddress(), - InputID: accData.OutputID, - Input: accountOutput, - }) - - // send all tokens to faucet - txBuilder.AddOutput(&iotago.BasicOutput{ - Amount: accountOutput.BaseTokenAmount(), - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: a.faucet.genesisKeyManager.Address(iotago.AddressEd25519).(*iotago.Ed25519Address)}, - }, - }) - - tx, err := txBuilder.Build(hdWallet.AddressSigner()) - if err != nil { - return ierrors.Wrapf(err, "failed to build transaction for account alias destruction %s", alias) - } - - congestionResp, issuerResp, version, err := a.RequestBlockBuiltData(a.client.Client(), a.faucet.account.ID()) - if err != nil { - return ierrors.Wrap(err, "failed to request block built data for the faucet account") - } - - blockID, err := a.PostWithBlock(a.client, tx, a.faucet.account, congestionResp, issuerResp, version) - if err != nil { - return ierrors.Wrapf(err, "failed to post block with ID %s", blockID) - } - - // remove account from wallet - delete(a.accountsAliases, alias) - - log.Infof("Account %s has been destroyed", alias) - return nil -} - -func (a *AccountWallet) retry(requestFunc func() (bool, error)) error { - timeout := time.NewTimer(a.optsRequestTimeout) - interval := time.NewTicker(a.optsRequestTicker) - defer timeutil.CleanupTimer(timeout) - defer timeutil.CleanupTicker(interval) - - for { - done, err := requestFunc() - if err != nil { - return err - } - if done { - return nil - } - select { - case <-interval.C: - continue - case <-timeout.C: - return ierrors.New("timeout while trying to request") - } - } -} diff --git a/tools/evil-spammer/config.go b/tools/evil-spammer/config.go deleted file mode 100644 index a62e16dca..000000000 --- a/tools/evil-spammer/config.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/accountwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/programs" - "github.com/iotaledger/iota-core/tools/evil-spammer/spammer" -) - -// Nodes used during the test, use at least two nodes to be able to doublespend. -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{} -) - -var ( - Script = "basic" - Subcommand = "" - - customSpamParams = programs.CustomSpamParams{ - ClientURLs: urls, - SpamTypes: []string{spammer.TypeBlock}, - Rates: []int{1}, - Durations: []time.Duration{time.Second * 20}, - BlkToBeSent: []int{0}, - TimeUnit: time.Second, - DelayBetweenConflicts: 0, - NSpend: 2, - Scenario: evilwallet.Scenario1(), - DeepSpam: false, - EnableRateSetter: false, - AccountAlias: accountwallet.FaucetAccountAlias, - } - - quickTestParams = programs.QuickTestParams{ - ClientURLs: urls, - Rate: 100, - Duration: time.Second * 30, - TimeUnit: time.Second, - DelayBetweenConflicts: 0, - EnableRateSetter: false, - } - - accountsSubcommandsFlags []accountwallet.AccountSubcommands - - //nolint:godot - // commitmentsSpamParams = CommitmentsSpamParams{ - // Rate: 1, - // Duration: time.Second * 20, - // TimeUnit: time.Second, - // NetworkAlias: "docker", - // SpammerAlias: "validator-1", - // ValidAlias: accountwallet.FaucetAccountAlias, - // CommitmentType: "latest", - // ForkAfter: 10, - // } -) diff --git a/tools/evil-spammer/evilwallet/aliasmanager.go b/tools/evil-spammer/evilwallet/aliasmanager.go deleted file mode 100644 index 284e89f6d..000000000 --- a/tools/evil-spammer/evilwallet/aliasmanager.go +++ /dev/null @@ -1,110 +0,0 @@ -package evilwallet - -import ( - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" -) - -// region AliasManager ///////////////////////////////////////////////////////////////////////////////////////////////// - -// AliasManager is the manager for output aliases. -type AliasManager struct { - outputMap map[string]*models.Output - inputMap map[string]*models.Output - - outputAliasCount *atomic.Uint64 - mu syncutils.RWMutex -} - -// NewAliasManager creates and returns a new AliasManager. -func NewAliasManager() *AliasManager { - return &AliasManager{ - outputMap: make(map[string]*models.Output), - inputMap: make(map[string]*models.Output), - outputAliasCount: atomic.NewUint64(0), - } -} - -// AddOutputAlias maps the given outputAliasName to output, if there's duplicate outputAliasName, it will be overwritten. -func (a *AliasManager) AddOutputAlias(output *models.Output, aliasName string) { - a.mu.Lock() - defer a.mu.Unlock() - - a.outputMap[aliasName] = output -} - -// AddInputAlias adds an input alias. -func (a *AliasManager) AddInputAlias(input *models.Output, aliasName string) { - a.mu.Lock() - defer a.mu.Unlock() - - a.inputMap[aliasName] = input -} - -// GetInput returns the input for the alias specified. -func (a *AliasManager) GetInput(aliasName string) (*models.Output, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - in, ok := a.inputMap[aliasName] - - return in, ok -} - -// GetOutput returns the output for the alias specified. -func (a *AliasManager) GetOutput(aliasName string) *models.Output { - a.mu.RLock() - defer a.mu.RUnlock() - - return a.outputMap[aliasName] -} - -// ClearAllAliases clears all aliases. -func (a *AliasManager) ClearAllAliases() { - a.mu.Lock() - defer a.mu.Unlock() - - a.inputMap = make(map[string]*models.Output) - a.outputMap = make(map[string]*models.Output) -} - -// ClearAliases clears provided aliases. -func (a *AliasManager) ClearAliases(aliases ScenarioAlias) { - a.mu.Lock() - defer a.mu.Unlock() - - for _, in := range aliases.Inputs { - delete(a.inputMap, in) - } - for _, out := range aliases.Outputs { - delete(a.outputMap, out) - } -} - -// AddOutputAliases batch adds the outputs their respective aliases. -func (a *AliasManager) AddOutputAliases(outputs []*models.Output, aliases []string) error { - if len(outputs) != len(aliases) { - return ierrors.New("mismatch outputs and aliases length") - } - for i, out := range outputs { - a.AddOutputAlias(out, aliases[i]) - } - - return nil -} - -// AddInputAliases batch adds the inputs their respective aliases. -func (a *AliasManager) AddInputAliases(inputs []*models.Output, aliases []string) error { - if len(inputs) != len(aliases) { - return ierrors.New("mismatch outputs and aliases length") - } - for i, out := range inputs { - a.AddInputAlias(out, aliases[i]) - } - - return nil -} - -// endregion ///////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/evilwallet/customscenarios.go b/tools/evil-spammer/evilwallet/customscenarios.go deleted file mode 100644 index e26142746..000000000 --- a/tools/evil-spammer/evilwallet/customscenarios.go +++ /dev/null @@ -1,238 +0,0 @@ -package evilwallet - -import ( - "strconv" -) - -var scenariosMap map[string]EvilBatch - -func init() { - scenariosMap = make(map[string]EvilBatch) - scenariosMap["tx"] = SingleTransactionBatch() - scenariosMap["ds"] = NSpendBatch(2) - scenariosMap["conflict-circle"] = ConflictSetCircle(4) - scenariosMap["guava"] = Scenario1() - scenariosMap["orange"] = Scenario2() - scenariosMap["mango"] = Scenario3() - scenariosMap["pear"] = Scenario4() - scenariosMap["lemon"] = Scenario5() - scenariosMap["banana"] = Scenario6() - scenariosMap["kiwi"] = Scenario7() - scenariosMap["peace"] = NoConflictsScenario1() -} - -// GetScenario returns an evil batch based i=on its name. -func GetScenario(scenarioName string) (batch EvilBatch, ok bool) { - batch, ok = scenariosMap[scenarioName] - return -} - -// SingleTransactionBatch returns an EvilBatch that is a single transaction. -func SingleTransactionBatch() EvilBatch { - return EvilBatch{{ScenarioAlias{Inputs: []string{"1"}, Outputs: []string{"2"}}}} -} - -// ConflictSetCircle creates a circular conflict set for a given size, e.g. for size=3, conflict set: A-B-C-A. -func ConflictSetCircle(size int) EvilBatch { - scenarioAlias := make([]ScenarioAlias, 0) - inputStartNum := size - - for i := 0; i < inputStartNum; i++ { - in := i - in2 := (in + 1) % inputStartNum - scenarioAlias = append(scenarioAlias, - ScenarioAlias{ - Inputs: []string{strconv.Itoa(in), strconv.Itoa(in2)}, - Outputs: []string{strconv.Itoa(inputStartNum + i)}, - }) - } - - return EvilBatch{scenarioAlias} -} - -func NSpendBatch(nSpent int) EvilBatch { - conflictSlice := make(EvilBatch, 0) - scenarioAlias := make([]ScenarioAlias, 0) - inputStartNum := nSpent + 1 - - for i := 1; i <= nSpent; i++ { - scenarioAlias = append(scenarioAlias, - ScenarioAlias{ - Inputs: []string{strconv.Itoa(inputStartNum)}, - Outputs: []string{strconv.Itoa(i)}, - }, - ) - } - conflictSlice = append(conflictSlice, scenarioAlias) - - return conflictSlice -} - -// Scenario1 describes two double spends and aggregates them. -func Scenario1() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"1"}, Outputs: []string{"2", "3"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"2"}, Outputs: []string{"4"}}, - {Inputs: []string{"2"}, Outputs: []string{"5"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"3"}, Outputs: []string{"6"}}, - {Inputs: []string{"3"}, Outputs: []string{"7"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"6", "5"}, Outputs: []string{"8"}}, - }, - } -} - -// Scenario2 is a reflection of UTXO unit test scenario example B - packages/ledgerstate/utxo_dag_test_exampleB.png. -func Scenario2() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"A"}, Outputs: []string{"C"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"B"}, Outputs: []string{"D"}}, - {Inputs: []string{"B"}, Outputs: []string{"G"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"C", "D"}, Outputs: []string{"E"}}, - {Inputs: []string{"D"}, Outputs: []string{"F"}}, - }, - } -} - -// Scenario3 is a reflection of UTXO unit test scenario example C - packages/ledgerstate/utxo_dag_test_exampleC.png. -func Scenario3() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"A"}, Outputs: []string{"C"}}, - {Inputs: []string{"A"}, Outputs: []string{"D"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"B"}, Outputs: []string{"E"}}, - {Inputs: []string{"B"}, Outputs: []string{"F"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"D", "E"}, Outputs: []string{"G"}}, - }, - } -} - -// Scenario4 is a reflection of ledgerstate unit test for conflict confirmation - packages/ledgerstate/ledgerstate_test_SetConflictConfirmed.png. -func Scenario4() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"1"}, Outputs: []string{"A"}}, - {Inputs: []string{"1"}, Outputs: []string{"B"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"2"}, Outputs: []string{"C"}}, - {Inputs: []string{"2"}, Outputs: []string{"D"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"3"}, Outputs: []string{"E"}}, - {Inputs: []string{"3"}, Outputs: []string{"F"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"D", "E"}, Outputs: []string{"G"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"G"}, Outputs: []string{"H"}}, - {Inputs: []string{"G"}, Outputs: []string{"I"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"A", "I"}, Outputs: []string{"J"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"J"}, Outputs: []string{"K"}}, - }, - } -} - -// Scenario5 uses ConflictSetCircle with size 4 and aggregate its outputs. -func Scenario5() EvilBatch { - circularConflict := ConflictSetCircle(4) - circularConflict = append(circularConflict, []ScenarioAlias{{ - Inputs: []string{"4", "6"}, - Outputs: []string{"8"}, - }}) - circularConflict = append(circularConflict, []ScenarioAlias{{ - Inputs: []string{"5", "7"}, - Outputs: []string{"9"}, - }}) - - return circularConflict -} - -// Scenario6 returns 5 levels deep scenario. -func Scenario6() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"1"}, Outputs: []string{"A", "B"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"A"}, Outputs: []string{"C", "D"}}, - {Inputs: []string{"B"}, Outputs: []string{"E"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"C"}, Outputs: []string{"F", "G"}}, - {Inputs: []string{"C", "D"}, Outputs: []string{"H"}}, - {Inputs: []string{"D"}, Outputs: []string{"I"}}, - {Inputs: []string{"F", "D"}, Outputs: []string{"J"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"G"}, Outputs: []string{"K"}}, - {Inputs: []string{"I"}, Outputs: []string{"L"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"K", "I"}, Outputs: []string{"M"}}, - {Inputs: []string{"L"}, Outputs: []string{"N"}}, - }, - } -} - -// Scenario7 three level deep scenario, with two separate conflict sets aggregated. -func Scenario7() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"A"}, Outputs: []string{"E"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"A", "B"}, Outputs: []string{"F"}}, - {Inputs: []string{"B"}, Outputs: []string{"G"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"C"}, Outputs: []string{"H"}}, - {Inputs: []string{"D"}, Outputs: []string{"I"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"E", "G"}, Outputs: []string{"J"}}, - {Inputs: []string{"H"}, Outputs: []string{"K"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"J", "K"}, Outputs: []string{"L"}}, - {Inputs: []string{"I", "K"}, Outputs: []string{"M"}}, - }, - } -} - -// NoConflictsScenario1 returns batch with no conflicts that is 3 levels deep. -func NoConflictsScenario1() EvilBatch { - return EvilBatch{ - []ScenarioAlias{ - {Inputs: []string{"1"}, Outputs: []string{"3", "4"}}, - {Inputs: []string{"2"}, Outputs: []string{"5", "6"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"6", "4"}, Outputs: []string{"7"}}, - {Inputs: []string{"3", "5"}, Outputs: []string{"8"}}, - }, - []ScenarioAlias{ - {Inputs: []string{"8", "7"}, Outputs: []string{"9"}}, - }, - } -} diff --git a/tools/evil-spammer/evilwallet/evilscenario.go b/tools/evil-spammer/evilwallet/evilscenario.go deleted file mode 100644 index 00f15d968..000000000 --- a/tools/evil-spammer/evilwallet/evilscenario.go +++ /dev/null @@ -1,143 +0,0 @@ -package evilwallet - -import ( - "fmt" - "strconv" - - "github.com/mr-tron/base58" - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/ds/types" - iotago "github.com/iotaledger/iota.go/v4" -) - -// The custom conflict in spammer can be provided like this: -// EvilBatch{ -// { -// ScenarioAlias{inputs: []{"1"}, outputs: []{"2","3"}} -// }, -// { -// ScenarioAlias{inputs: []{"2"}, outputs: []{"4"}}, -// ScenarioAlias{inputs: []{"2"}, outputs: []{"5"}} -// } -// } - -type ScenarioAlias struct { - Inputs []string - Outputs []string -} - -func NewScenarioAlias() ScenarioAlias { - return ScenarioAlias{ - Inputs: make([]string, 0), - Outputs: make([]string, 0), - } -} - -type EvilBatch [][]ScenarioAlias - -type EvilScenario struct { - ID string - // provides a user-friendly way of listing input and output aliases - ConflictBatch EvilBatch - // determines whether outputs of the batch should be reused during the spam to create deep UTXO tree structure. - Reuse bool - // specifies the output type of the spam, if not provided, defaults to BasicOutput - OutputType iotago.OutputType - // if provided, the outputs from the spam will be saved into this wallet, accepted types of wallet: Reuse, RestrictedReuse. - // if type == Reuse, then wallet is available for reuse spamming scenarios that did not provide RestrictedWallet. - OutputWallet *Wallet - // if provided and reuse set to true, outputs from this wallet will be used for deep spamming, allows for controllable building of UTXO deep structures. - // if not provided evil wallet will use Reuse wallet if any is available. Accepts only RestrictedReuse wallet type. - RestrictedInputWallet *Wallet - // used together with scenario ID to create a prefix for distinct batch alias creation - BatchesCreated *atomic.Uint64 - // used to determine how many clients are needed to run this scenario, some double spends need more than one client to pass the filter - NumOfClientsNeeded int -} - -func NewEvilScenario(options ...ScenarioOption) *EvilScenario { - scenario := &EvilScenario{ - ConflictBatch: SingleTransactionBatch(), - Reuse: false, - OutputType: iotago.OutputBasic, - OutputWallet: NewWallet(), - BatchesCreated: atomic.NewUint64(0), - } - - for _, option := range options { - option(scenario) - } - scenario.ID = base58.Encode([]byte(fmt.Sprintf("%v%v%v", scenario.ConflictBatch, scenario.Reuse, scenario.OutputWallet.ID)))[:11] - scenario.NumOfClientsNeeded = calculateNumofClientsNeeded(scenario) - - return scenario -} - -func calculateNumofClientsNeeded(scenario *EvilScenario) (counter int) { - for _, conflictMap := range scenario.ConflictBatch { - if len(conflictMap) > counter { - counter = len(conflictMap) - } - } - - return -} - -// readCustomConflictsPattern determines outputs of the batch, needed for saving batch outputs to the outputWallet. -func (e *EvilScenario) readCustomConflictsPattern(batch EvilBatch) (batchOutputs map[string]types.Empty) { - outputs := make(map[string]types.Empty) - inputs := make(map[string]types.Empty) - - for _, conflictMap := range batch { - for _, conflicts := range conflictMap { - // add output to outputsAliases - for _, input := range conflicts.Inputs { - inputs[input] = types.Void - } - for _, output := range conflicts.Outputs { - outputs[output] = types.Void - } - } - } - // remove outputs that were never used as input in this EvilBatch to determine batch outputs - for output := range outputs { - if _, ok := inputs[output]; ok { - delete(outputs, output) - } - } - batchOutputs = outputs - - return -} - -// NextBatchPrefix creates a new batch prefix by increasing the number of created batches for this scenario. -func (e *EvilScenario) nextBatchPrefix() string { - return e.ID + strconv.Itoa(int(e.BatchesCreated.Add(1))) -} - -// ConflictBatchWithPrefix generates a new conflict batch with scenario prefix created from scenario ID and batch count. -// BatchOutputs are outputs of the batch that can be reused in deep spamming by collecting them in Reuse wallet. -func (e *EvilScenario) ConflictBatchWithPrefix() (prefixedBatch EvilBatch, allAliases ScenarioAlias, batchOutputs map[string]types.Empty) { - allAliases = NewScenarioAlias() - prefix := e.nextBatchPrefix() - for _, conflictMap := range e.ConflictBatch { - scenarioAlias := make([]ScenarioAlias, 0) - for _, aliases := range conflictMap { - sa := NewScenarioAlias() - for _, in := range aliases.Inputs { - sa.Inputs = append(sa.Inputs, prefix+in) - allAliases.Inputs = append(allAliases.Inputs, prefix+in) - } - for _, out := range aliases.Outputs { - sa.Outputs = append(sa.Outputs, prefix+out) - allAliases.Outputs = append(allAliases.Outputs, prefix+out) - } - scenarioAlias = append(scenarioAlias, sa) - } - prefixedBatch = append(prefixedBatch, scenarioAlias) - } - batchOutputs = e.readCustomConflictsPattern(prefixedBatch) - - return -} diff --git a/tools/evil-spammer/evilwallet/evilwallet.go b/tools/evil-spammer/evilwallet/evilwallet.go deleted file mode 100644 index 007059cce..000000000 --- a/tools/evil-spammer/evilwallet/evilwallet.go +++ /dev/null @@ -1,846 +0,0 @@ -package evilwallet - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/log" - - "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/options" - "github.com/iotaledger/iota-core/pkg/blockhandler" - "github.com/iotaledger/iota-core/tools/evil-spammer/accountwallet" - evillogger "github.com/iotaledger/iota-core/tools/evil-spammer/logger" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/builder" - "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" - "github.com/iotaledger/iota.go/v4/tpkg" -) - -const ( - // FaucetRequestSplitNumber defines the number of outputs to split from a faucet request. - FaucetRequestSplitNumber = 100 - faucetTokensPerRequest iotago.BaseToken = 1_000_000 - - waitForConfirmation = 15 * time.Second - waitForSolidification = 10 * time.Second - - awaitConfirmationSleep = 2 * time.Second - awaitSolidificationSleep = time.Millisecond * 500 -) - -var ( - defaultClientsURLs = []string{"http://localhost:8080", "http://localhost:8090"} -) - -// region EvilWallet /////////////////////////////////////////////////////////////////////////////////////////////////////// - -// EvilWallet provides a user-friendly way to do complicated double spend scenarios. -type EvilWallet struct { - // faucet is the wallet of faucet - faucet *Wallet - wallets *Wallets - accWallet *accountwallet.AccountWallet - connector models.Connector - outputManager *OutputManager - aliasManager *AliasManager - - optsClientURLs []string - log *logger.Logger -} - -// NewEvilWallet creates an EvilWallet instance. -func NewEvilWallet(opts ...options.Option[EvilWallet]) *EvilWallet { - return options.Apply(&EvilWallet{ - wallets: NewWallets(), - aliasManager: NewAliasManager(), - optsClientURLs: defaultClientsURLs, - log: evillogger.New("EvilWallet"), - }, opts, func(w *EvilWallet) { - connector := models.NewWebClients(w.optsClientURLs) - w.connector = connector - w.outputManager = NewOutputManager(connector, w.wallets, w.log) - - }) -} - -func (e *EvilWallet) LastFaucetUnspentOutput() iotago.OutputID { - faucetAddr := e.faucet.AddressOnIndex(0) - unspentFaucet := e.faucet.UnspentOutput(faucetAddr.String()) - - return unspentFaucet.OutputID -} - -// NewWallet creates a new wallet of the given wallet type. -func (e *EvilWallet) NewWallet(wType ...WalletType) *Wallet { - walletType := Other - if len(wType) != 0 { - walletType = wType[0] - } - - return e.wallets.NewWallet(walletType) -} - -// GetClients returns the given number of clients. -func (e *EvilWallet) GetClients(num int) []models.Client { - return e.connector.GetClients(num) -} - -// Connector give access to the EvilWallet connector. -func (e *EvilWallet) Connector() models.Connector { - return e.connector -} - -func (e *EvilWallet) UnspentOutputsLeft(walletType WalletType) int { - return e.wallets.UnspentOutputsLeft(walletType) -} - -func (e *EvilWallet) NumOfClient() int { - clts := e.connector.Clients() - return len(clts) -} - -func (e *EvilWallet) AddClient(clientURL string) { - e.connector.AddClient(clientURL) -} - -func (e *EvilWallet) RemoveClient(clientURL string) { - e.connector.RemoveClient(clientURL) -} - -func (e *EvilWallet) GetAccount(alias string) (blockhandler.Account, error) { - account, err := e.accWallet.GetAccount(alias) - if err != nil { - return nil, err - } - - return account.Account, nil -} - -func (e *EvilWallet) PrepareAndPostBlock(clt models.Client, payload iotago.Payload, congestionResp *apimodels.CongestionResponse, issuer blockhandler.Account) (iotago.BlockID, error) { - congestionResp, issuerResp, version, err := e.accWallet.RequestBlockBuiltData(clt.Client(), issuer.ID()) - if err != nil { - return iotago.EmptyBlockID, ierrors.Wrapf(err, "failed to get block built data for issuer %s", issuer.ID().ToHex()) - } - blockID, err := e.accWallet.PostWithBlock(clt, payload, issuer, congestionResp, issuerResp, version) - if err != nil { - return iotago.EmptyBlockID, err - } - - return blockID, nil -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region EvilWallet Faucet Requests /////////////////////////////////////////////////////////////////////////////////// - -// RequestFundsFromFaucet requests funds from the faucet, then track the confirmed status of unspent output, -// also register the alias name for the unspent output if provided. -func (e *EvilWallet) RequestFundsFromFaucet(options ...FaucetRequestOption) (initWallet *Wallet, err error) { - initWallet = e.NewWallet(Fresh) - buildOptions := NewFaucetRequestOptions(options...) - - output, err := e.requestFaucetFunds(initWallet) - if err != nil { - return - } - - if buildOptions.outputAliasName != "" { - e.aliasManager.AddInputAlias(output, buildOptions.outputAliasName) - } - - log.Debug("Funds requested succesfully") - - return -} - -// RequestFreshBigFaucetWallets creates n new wallets, each wallet is created from one faucet request and contains 1000 outputs. -func (e *EvilWallet) RequestFreshBigFaucetWallets(numberOfWallets int) { - // channel to block the number of concurrent goroutines - semaphore := make(chan bool, 1) - wg := sync.WaitGroup{} - - for reqNum := 0; reqNum < numberOfWallets; reqNum++ { - wg.Add(1) - // block if full - semaphore <- true - go func() { - defer wg.Done() - defer func() { - // release - <-semaphore - }() - - err := e.RequestFreshBigFaucetWallet() - if err != nil { - return - } - }() - } - wg.Wait() -} - -// RequestFreshBigFaucetWallet creates a new wallet and fills the wallet with 1000 outputs created from funds -// requested from the Faucet. -func (e *EvilWallet) RequestFreshBigFaucetWallet() error { - initWallet := NewWallet() - receiveWallet := e.NewWallet(Fresh) - - txIDs := make(iotago.TransactionIDs, 0) - for i := 0; i < 1; i++ { - txID, err := e.requestAndSplitFaucetFunds(initWallet, receiveWallet) - if err != nil { - return ierrors.Wrap(err, "failed to request big funds from faucet") - } - - txIDs = append(txIDs, txID) - } - - e.outputManager.AwaitTransactionsConfirmation(txIDs...) - - e.wallets.SetWalletReady(receiveWallet) - - return nil -} - -// RequestFreshFaucetWallet creates a new wallet and fills the wallet with 100 outputs created from funds -// requested from the Faucet. -func (e *EvilWallet) RequestFreshFaucetWallet() error { - initWallet := NewWallet() - receiveWallet := e.NewWallet(Fresh) - txID, err := e.requestAndSplitFaucetFunds(initWallet, receiveWallet) - if err != nil { - return ierrors.Wrap(err, "failed to request funds from faucet") - } - - e.outputManager.AwaitTransactionsConfirmation(txID) - - e.wallets.SetWalletReady(receiveWallet) - - return err -} - -func (e *EvilWallet) requestAndSplitFaucetFunds(initWallet, receiveWallet *Wallet) (txID iotago.TransactionID, err error) { - splitOutput, err := e.requestFaucetFunds(initWallet) - if err != nil { - return iotago.EmptyTransactionID, err - } - - e.log.Debugf("Faucet funds received, continue spliting output: %s", splitOutput.OutputID.ToHex()) - // first split 1 to FaucetRequestSplitNumber outputs - return e.splitOutputs(splitOutput, initWallet, receiveWallet) -} - -func (e *EvilWallet) requestFaucetFunds(wallet *Wallet) (outputID *models.Output, err error) { - receiveAddr := wallet.AddressOnIndex(0) - clt := e.connector.GetClient() - - output, err := e.accWallet.RequestFaucetFunds(clt, receiveAddr, faucetTokensPerRequest) - if err != nil { - return nil, ierrors.Wrap(err, "failed to request funds from faucet") - } - - // update wallet with newly created output - e.outputManager.createOutputFromAddress(wallet, receiveAddr, faucetTokensPerRequest, output.OutputID, output.OutputStruct) - - return output, nil -} - -// splitOutputs splits faucet input to 100 outputs. -func (e *EvilWallet) splitOutputs(splitOutput *models.Output, inputWallet, outputWallet *Wallet) (iotago.TransactionID, error) { - if inputWallet.IsEmpty() { - return iotago.EmptyTransactionID, ierrors.New("inputWallet is empty") - } - - input, outputs := e.handleInputOutputDuringSplitOutputs(splitOutput, FaucetRequestSplitNumber, outputWallet) - - faucetAccount, err := e.accWallet.GetAccount(accountwallet.FaucetAccountAlias) - if err != nil { - return iotago.EmptyTransactionID, err - } - txData, err := e.CreateTransaction( - WithInputs(input), - WithOutputs(outputs), - WithInputWallet(inputWallet), - WithOutputWallet(outputWallet), - WithIssuanceStrategy(models.AllotmentStrategyAll, faucetAccount.Account.ID()), - ) - - if err != nil { - return iotago.EmptyTransactionID, err - } - - _, err = e.PrepareAndPostBlock(e.connector.GetClient(), txData.Payload, txData.CongestionResponse, faucetAccount.Account) - if err != nil { - return iotago.TransactionID{}, err - } - - if txData.Payload.PayloadType() != iotago.PayloadSignedTransaction { - return iotago.EmptyTransactionID, ierrors.New("payload type is not signed transaction") - } - - txID := lo.PanicOnErr(txData.Payload.(*iotago.SignedTransaction).Transaction.ID()) - - e.log.Debugf("Splitting output %s finished with tx: %s", splitOutput.OutputID.ToHex(), txID.ToHex()) - - return txID, nil -} - -func (e *EvilWallet) handleInputOutputDuringSplitOutputs(splitOutput *models.Output, splitNumber int, receiveWallet *Wallet) (input *models.Output, outputs []*OutputOption) { - input = splitOutput - - balances := SplitBalanceEqually(splitNumber, input.Balance) - for _, bal := range balances { - outputs = append(outputs, &OutputOption{amount: bal, address: receiveWallet.Address(), outputType: iotago.OutputBasic}) - } - - return -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region EvilWallet functionality /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// ClearAliases remove only provided aliases from AliasManager. -func (e *EvilWallet) ClearAliases(aliases ScenarioAlias) { - e.aliasManager.ClearAliases(aliases) -} - -// ClearAllAliases remove all registered alias names. -func (e *EvilWallet) ClearAllAliases() { - e.aliasManager.ClearAllAliases() -} - -func (e *EvilWallet) PrepareCustomConflicts(conflictsMaps []ConflictSlice) (conflictBatch [][]*models.PayloadIssuanceData, err error) { - for _, conflictMap := range conflictsMaps { - var txsData []*models.PayloadIssuanceData - for _, conflictOptions := range conflictMap { - txData, err2 := e.CreateTransaction(conflictOptions...) - if err2 != nil { - return nil, err2 - } - txsData = append(txsData, txData) - } - conflictBatch = append(conflictBatch, txsData) - } - - return conflictBatch, nil -} - -// CreateTransaction creates a transaction based on provided options. If no input wallet is provided, the next non-empty faucet wallet is used. -// Inputs of the transaction are determined in three ways: -// 1 - inputs are provided directly without associated alias, 2- alias is provided, and input is already stored in an alias manager, -// 3 - alias is provided, and there are no inputs assigned in Alias manager, so aliases are assigned to next ready inputs from input wallet. -func (e *EvilWallet) CreateTransaction(options ...Option) (*models.PayloadIssuanceData, error) { - buildOptions, err := NewOptions(options...) - if err != nil { - return nil, err - } - // wallet used only for outputs in the middle of the batch, that will never be reused outside custom conflict batch creation. - tempWallet := e.NewWallet() - - err = e.updateInputWallet(buildOptions) - if err != nil { - return nil, err - } - - inputs, err := e.prepareInputs(buildOptions) - if err != nil { - return nil, err - } - - outputs, addrAliasMap, tempAddresses, err := e.prepareOutputs(buildOptions, tempWallet) - if err != nil { - return nil, err - } - - alias, remainder, remainderAddr, hasRemainder := e.prepareRemainderOutput(buildOptions, outputs) - if hasRemainder { - outputs = append(outputs, remainder) - if alias != "" && addrAliasMap != nil { - addrAliasMap[remainderAddr.String()] = alias - } - } - - var congestionResp *apimodels.CongestionResponse - // request congestion endpoint if allotment strategy configured - if buildOptions.allotmentStrategy == models.AllotmentStrategyMinCost { - congestionResp, err = e.connector.GetClient().GetCongestion(buildOptions.issuerAccountID) - if err != nil { - return nil, err - } - } - - signedTx, err := e.makeTransaction(inputs, outputs, buildOptions.inputWallet, congestionResp, buildOptions.allotmentStrategy, buildOptions.issuerAccountID) - if err != nil { - return nil, err - } - txData := &models.PayloadIssuanceData{ - Payload: signedTx, - CongestionResponse: congestionResp, - } - - e.addOutputsToOutputManager(signedTx, buildOptions.outputWallet, tempWallet, tempAddresses) - e.registerOutputAliases(signedTx, addrAliasMap) - - return txData, nil -} - -// addOutputsToOutputManager adds output to the OutputManager if. -func (e *EvilWallet) addOutputsToOutputManager(signedTx *iotago.SignedTransaction, outWallet, tmpWallet *Wallet, tempAddresses map[string]types.Empty) { - for idx, o := range signedTx.Transaction.Outputs { - if o.UnlockConditionSet().Address() == nil { - continue - } - - // register UnlockConditionAddress only (skip account outputs) - addr := o.UnlockConditionSet().Address().Address - out := &models.Output{ - OutputID: iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), uint16(idx)), - Address: addr, - Balance: o.BaseTokenAmount(), - OutputStruct: o, - } - - if _, ok := tempAddresses[addr.String()]; ok { - e.outputManager.AddOutput(tmpWallet, out) - } else { - out.Index = outWallet.AddrIndexMap(addr.String()) - e.outputManager.AddOutput(outWallet, out) - } - } -} - -// updateInputWallet if input wallet is not specified, or aliases were provided without inputs (batch inputs) use Fresh faucet wallet. -func (e *EvilWallet) updateInputWallet(buildOptions *Options) error { - for alias := range buildOptions.aliasInputs { - // inputs provided for aliases (middle inputs in a batch) - _, ok := e.aliasManager.GetInput(alias) - if ok { - // leave nil, wallet will be selected based on OutputIDWalletMap - buildOptions.inputWallet = nil - return nil - } - - break - } - wallet, err := e.useFreshIfInputWalletNotProvided(buildOptions) - if err != nil { - return err - } - buildOptions.inputWallet = wallet - - return nil -} - -func (e *EvilWallet) registerOutputAliases(signedTx *iotago.SignedTransaction, addrAliasMap map[string]string) { - if len(addrAliasMap) == 0 { - return - } - - for idx := range signedTx.Transaction.Outputs { - id := iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), uint16(idx)) - out := e.outputManager.GetOutput(id) - if out == nil { - continue - } - - // register output alias - e.aliasManager.AddOutputAlias(out, addrAliasMap[out.Address.String()]) - - // register output as unspent output(input) - e.aliasManager.AddInputAlias(out, addrAliasMap[out.Address.String()]) - } -} - -func (e *EvilWallet) prepareInputs(buildOptions *Options) (inputs []*models.Output, err error) { - if buildOptions.areInputsProvidedWithoutAliases() { - inputs = append(inputs, buildOptions.inputs...) - - return - } - // append inputs with alias - aliasInputs, err := e.matchInputsWithAliases(buildOptions) - if err != nil { - return nil, err - } - inputs = append(inputs, aliasInputs...) - - return inputs, nil -} - -// prepareOutputs creates outputs for different scenarios, if no aliases were provided, new empty outputs are created from buildOptions.outputs balances. -func (e *EvilWallet) prepareOutputs(buildOptions *Options, tempWallet *Wallet) (outputs []iotago.Output, - addrAliasMap map[string]string, tempAddresses map[string]types.Empty, err error, -) { - if buildOptions.areOutputsProvidedWithoutAliases() { - outputs = append(outputs, buildOptions.outputs...) - } else { - // if outputs were provided with aliases - outputs, addrAliasMap, tempAddresses, err = e.matchOutputsWithAliases(buildOptions, tempWallet) - } - - return -} - -// matchInputsWithAliases gets input from the alias manager. if input was not assigned to an alias before, -// it assigns a new Fresh faucet output. -func (e *EvilWallet) matchInputsWithAliases(buildOptions *Options) (inputs []*models.Output, err error) { - // get inputs by alias - for inputAlias := range buildOptions.aliasInputs { - in, ok := e.aliasManager.GetInput(inputAlias) - if !ok { - wallet, err2 := e.useFreshIfInputWalletNotProvided(buildOptions) - if err2 != nil { - err = err2 - return - } - // No output found for given alias, use internal Fresh output if wallets are non-empty. - in = e.wallets.GetUnspentOutput(wallet) - if in == nil { - return nil, ierrors.New("could not get unspent output") - } - e.aliasManager.AddInputAlias(in, inputAlias) - } - inputs = append(inputs, in) - } - - return inputs, nil -} - -func (e *EvilWallet) useFreshIfInputWalletNotProvided(buildOptions *Options) (*Wallet, error) { - // if input wallet is not specified, use Fresh faucet wallet - if buildOptions.inputWallet == nil { - // deep spam enabled and no input reuse wallet provided, use evil wallet reuse wallet if enough outputs are available - if buildOptions.reuse { - outputsNeeded := len(buildOptions.inputs) - if wallet := e.wallets.reuseWallet(outputsNeeded); wallet != nil { - return wallet, nil - } - } - - wallet, err := e.wallets.freshWallet() - if err != nil { - return nil, ierrors.Wrap(err, "no Fresh wallet is available") - } - - return wallet, nil - } - - return buildOptions.inputWallet, nil -} - -// matchOutputsWithAliases creates outputs based on balances provided via options. -// Outputs are not yet added to the Alias Manager, as they have no ID before the transaction is created. -// Thus, they are tracker in address to alias map. If the scenario is used, the outputBatchAliases map is provided -// that indicates which outputs should be saved to the outputWallet.All other outputs are created with temporary wallet, -// and their addresses are stored in tempAddresses. -func (e *EvilWallet) matchOutputsWithAliases(buildOptions *Options, tempWallet *Wallet) (outputs []iotago.Output, - addrAliasMap map[string]string, tempAddresses map[string]types.Empty, err error, -) { - err = e.updateOutputBalances(buildOptions) - if err != nil { - return nil, nil, nil, err - } - - tempAddresses = make(map[string]types.Empty) - addrAliasMap = make(map[string]string) - for alias, output := range buildOptions.aliasOutputs { - var addr *iotago.Ed25519Address - if _, ok := buildOptions.outputBatchAliases[alias]; ok { - addr = buildOptions.outputWallet.Address() - } else { - addr = tempWallet.Address() - tempAddresses[addr.String()] = types.Void - } - - switch output.Type() { - case iotago.OutputBasic: - outputBuilder := builder.NewBasicOutputBuilder(addr, output.BaseTokenAmount()) - outputs = append(outputs, outputBuilder.MustBuild()) - case iotago.OutputAccount: - outputBuilder := builder.NewAccountOutputBuilder(addr, addr, output.BaseTokenAmount()) - outputs = append(outputs, outputBuilder.MustBuild()) - } - - addrAliasMap[addr.String()] = alias - } - - return -} - -func (e *EvilWallet) prepareRemainderOutput(buildOptions *Options, outputs []iotago.Output) (alias string, remainderOutput iotago.Output, remainderAddress iotago.Address, added bool) { - inputBalance := iotago.BaseToken(0) - - for inputAlias := range buildOptions.aliasInputs { - in, _ := e.aliasManager.GetInput(inputAlias) - inputBalance += in.Balance - - if alias == "" { - remainderAddress = in.Address - alias = inputAlias - } - } - - for _, input := range buildOptions.inputs { - // get balance from output manager - in := e.outputManager.GetOutput(input.OutputID) - inputBalance += in.Balance - - if remainderAddress == nil { - remainderAddress = in.Address - } - } - - outputBalance := iotago.BaseToken(0) - for _, o := range outputs { - outputBalance += o.BaseTokenAmount() - } - - // remainder balances is sent to one of the address in inputs - if outputBalance < inputBalance { - remainderOutput = &iotago.BasicOutput{ - Amount: inputBalance - outputBalance, - Conditions: iotago.BasicOutputUnlockConditions{ - &iotago.AddressUnlockCondition{Address: remainderAddress}, - }, - } - - added = true - } - - return -} - -func (e *EvilWallet) updateOutputBalances(buildOptions *Options) (err error) { - // when aliases are not used for outputs, the balance had to be provided in options, nothing to do - if buildOptions.areOutputsProvidedWithoutAliases() { - return - } - totalBalance := iotago.BaseToken(0) - if !buildOptions.isBalanceProvided() { - if buildOptions.areInputsProvidedWithoutAliases() { - for _, input := range buildOptions.inputs { - // get balance from output manager - inputDetails := e.outputManager.GetOutput(input.OutputID) - totalBalance += inputDetails.Balance - } - } else { - for inputAlias := range buildOptions.aliasInputs { - in, ok := e.aliasManager.GetInput(inputAlias) - if !ok { - err = ierrors.New("could not get input by input alias") - return - } - totalBalance += in.Balance - } - } - balances := SplitBalanceEqually(len(buildOptions.outputs)+len(buildOptions.aliasOutputs), totalBalance) - i := 0 - for out, output := range buildOptions.aliasOutputs { - switch output.Type() { - case iotago.OutputBasic: - buildOptions.aliasOutputs[out] = &iotago.BasicOutput{ - Amount: balances[i], - } - case iotago.OutputAccount: - buildOptions.aliasOutputs[out] = &iotago.AccountOutput{ - Amount: balances[i], - } - } - i++ - } - } - - return -} - -func (e *EvilWallet) makeTransaction(inputs []*models.Output, outputs iotago.Outputs[iotago.Output], w *Wallet, congestionResponse *apimodels.CongestionResponse, allotmentStrategy models.AllotmentStrategy, issuerAccountID iotago.AccountID) (tx *iotago.SignedTransaction, err error) { - clt := e.Connector().GetClient() - currentTime := time.Now() - targetSlot := clt.LatestAPI().TimeProvider().SlotFromTime(currentTime) - targetAPI := clt.APIForSlot(targetSlot) - - txBuilder := builder.NewTransactionBuilder(targetAPI) - - for _, input := range inputs { - txBuilder.AddInput(&builder.TxInput{UnlockTarget: input.Address, InputID: input.OutputID, Input: input.OutputStruct}) - } - - for _, output := range outputs { - txBuilder.AddOutput(output) - } - - randomPayload := tpkg.Rand12ByteArray() - txBuilder.AddTaggedDataPayload(&iotago.TaggedData{Tag: randomPayload[:], Data: randomPayload[:]}) - - walletKeys := make([]iotago.AddressKeys, len(inputs)) - for i, input := range inputs { - addr := input.Address - var wallet *Wallet - if w == nil { // aliases provided with inputs, use wallet saved in outputManager - wallet = e.outputManager.OutputIDWalletMap(input.OutputID.ToHex()) - } else { - wallet = w - } - index := wallet.AddrIndexMap(addr.String()) - inputPrivateKey, _ := wallet.KeyPair(index) - walletKeys[i] = iotago.AddressKeys{Address: addr, Keys: inputPrivateKey} - } - - txBuilder.SetCreationSlot(targetSlot) - // no allotment strategy - if congestionResponse == nil { - return txBuilder.Build(iotago.NewInMemoryAddressSigner(walletKeys...)) - } - switch allotmentStrategy { - case models.AllotmentStrategyAll: - txBuilder.AllotAllMana(targetSlot, issuerAccountID) - case models.AllotmentStrategyMinCost: - txBuilder.AllotRequiredManaAndStoreRemainingManaInOutput(targetSlot, congestionResponse.ReferenceManaCost, issuerAccountID, 0) - } - - return txBuilder.Build(iotago.NewInMemoryAddressSigner(walletKeys...)) -} - -func (e *EvilWallet) PrepareCustomConflictsSpam(scenario *EvilScenario, strategy *models.IssuancePaymentStrategy) (txsData [][]*models.PayloadIssuanceData, allAliases ScenarioAlias, err error) { - conflicts, allAliases := e.prepareConflictSliceForScenario(scenario, strategy) - txsData, err = e.PrepareCustomConflicts(conflicts) - - return txsData, allAliases, err -} - -func (e *EvilWallet) PrepareAccountSpam(scenario *EvilScenario, strategy *models.IssuancePaymentStrategy) (*models.PayloadIssuanceData, ScenarioAlias, error) { - accountSpamOptions, allAliases := e.prepareFlatOptionsForAccountScenario(scenario, strategy) - - txData, err := e.CreateTransaction(accountSpamOptions...) - - return txData, allAliases, err -} - -func (e *EvilWallet) evaluateIssuanceStrategy(strategy *models.IssuancePaymentStrategy) (models.AllotmentStrategy, iotago.AccountID) { - var issuerAccountID iotago.AccountID - if strategy.AllotmentStrategy != models.AllotmentStrategyNone { - // get issuer accountID - accData, err := e.accWallet.GetAccount(strategy.IssuerAlias) - if err != nil { - panic("could not get issuer accountID while preparing conflicts") - } - issuerAccountID = accData.Account.ID() - } - return strategy.AllotmentStrategy, issuerAccountID -} - -func (e *EvilWallet) prepareConflictSliceForScenario(scenario *EvilScenario, strategy *models.IssuancePaymentStrategy) (conflictSlice []ConflictSlice, allAliases ScenarioAlias) { - genOutputOptions := func(aliases []string) []*OutputOption { - outputOptions := make([]*OutputOption, 0) - for _, o := range aliases { - outputOptions = append(outputOptions, &OutputOption{aliasName: o, outputType: iotago.OutputBasic}) - } - - return outputOptions - } - - // make conflictSlice - prefixedBatch, allAliases, batchOutputs := scenario.ConflictBatchWithPrefix() - conflictSlice = make([]ConflictSlice, 0) - for _, conflictMap := range prefixedBatch { - conflicts := make([][]Option, 0) - for _, aliases := range conflictMap { - outs := genOutputOptions(aliases.Outputs) - option := []Option{WithInputs(aliases.Inputs), WithOutputs(outs), WithOutputBatchAliases(batchOutputs)} - if scenario.OutputWallet != nil { - option = append(option, WithOutputWallet(scenario.OutputWallet)) - } - if scenario.RestrictedInputWallet != nil { - option = append(option, WithInputWallet(scenario.RestrictedInputWallet)) - } - if scenario.Reuse { - option = append(option, WithReuseOutputs()) - } - option = append(option, WithIssuanceStrategy(e.evaluateIssuanceStrategy(strategy))) - conflicts = append(conflicts, option) - } - conflictSlice = append(conflictSlice, conflicts) - } - - return -} - -func (e *EvilWallet) prepareFlatOptionsForAccountScenario(scenario *EvilScenario, strategy *models.IssuancePaymentStrategy) ([]Option, ScenarioAlias) { - // we do not care about batchedOutputs, because we do not support saving account spam result in evil wallet for now - prefixedBatch, allAliases, _ := scenario.ConflictBatchWithPrefix() - if len(prefixedBatch) != 1 { - panic("invalid scenario, cannot prepare flat option structure with deep scenario, EvilBatch should have only one element") - } - evilBatch := prefixedBatch[0] - if len(evilBatch) != 1 { - panic("invalid scenario, cannot prepare flat option structure with deep scenario, EvilBatch should have only one element") - } - - genOutputOptions := func(aliases []string) []*OutputOption { - outputOptions := make([]*OutputOption, 0) - for _, o := range aliases { - outputOptions = append(outputOptions, &OutputOption{ - aliasName: o, - outputType: iotago.OutputAccount, - }) - } - - return outputOptions - } - scenarioAlias := evilBatch[0] - outs := genOutputOptions(scenarioAlias.Outputs) - - return []Option{ - WithInputs(scenarioAlias.Inputs), - WithOutputs(outs), - WithIssuanceStrategy(e.evaluateIssuanceStrategy(strategy)), - }, allAliases -} - -// AwaitInputsSolidity waits for all inputs to be solid for client clt. -// func (e *EvilWallet) AwaitInputsSolidity(inputs devnetvm.Inputs, clt Client) (allSolid bool) { -// awaitSolid := make([]string, 0) -// for _, in := range inputs { -// awaitSolid = append(awaitSolid, in.Base58()) -// } -// allSolid = e.outputManager.AwaitOutputsToBeSolid(awaitSolid, clt, maxGoroutines) -// return -// } - -// SetTxOutputsSolid marks all outputs as solid in OutputManager for clientID. -func (e *EvilWallet) SetTxOutputsSolid(outputs iotago.OutputIDs, clientID string) { - for _, out := range outputs { - e.outputManager.SetOutputIDSolidForIssuer(out, clientID) - } -} - -// AddReuseOutputsToThePool adds all addresses corresponding to provided outputs to the reuse pool. -// func (e *EvilWallet) AddReuseOutputsToThePool(outputs devnetvm.Outputs) { -// for _, out := range outputs { -// evilOutput := e.outputManager.GetOutput(out.ID()) -// if evilOutput != nil { -// wallet := e.outputManager.OutputIDWalletMap(out.ID().Base58()) -// wallet.AddReuseAddress(evilOutput.Address.Base58()) -// } -// } -// } - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -func WithClients(urls ...string) options.Option[EvilWallet] { - return func(opts *EvilWallet) { - opts.optsClientURLs = urls - } -} - -func WithAccountsWallet(wallet *accountwallet.AccountWallet) options.Option[EvilWallet] { - return func(opts *EvilWallet) { - opts.accWallet = wallet - } -} diff --git a/tools/evil-spammer/evilwallet/options.go b/tools/evil-spammer/evilwallet/options.go deleted file mode 100644 index 7b0b581f8..000000000 --- a/tools/evil-spammer/evilwallet/options.go +++ /dev/null @@ -1,295 +0,0 @@ -package evilwallet - -import ( - "time" - - "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/builder" -) - -// region Options /////////////////////////////////////////////////////////////////////////// - -// Options is a struct that represents a collection of options that can be set when creating a block. -type Options struct { - aliasInputs map[string]types.Empty - inputs []*models.Output - aliasOutputs map[string]iotago.Output - outputs []iotago.Output - inputWallet *Wallet - outputWallet *Wallet - outputBatchAliases map[string]types.Empty - reuse bool - issuingTime time.Time - allotmentStrategy models.AllotmentStrategy - issuerAccountID iotago.AccountID - // maps input alias to desired output type, used to create account output types - specialOutputTypes map[string]iotago.OutputType -} - -type OutputOption struct { - aliasName string - amount iotago.BaseToken - address *iotago.Ed25519Address - outputType iotago.OutputType -} - -// NewOptions is the constructor for the tx creation. -func NewOptions(options ...Option) (option *Options, err error) { - option = &Options{ - aliasInputs: make(map[string]types.Empty), - inputs: make([]*models.Output, 0), - aliasOutputs: make(map[string]iotago.Output), - outputs: make([]iotago.Output, 0), - specialOutputTypes: make(map[string]iotago.OutputType), - } - - for _, opt := range options { - opt(option) - } - - // check if alias and non-alias are mixed in use. - if err := option.checkInputsAndOutputs(); err != nil { - return nil, err - } - - // input and output wallets must be provided if inputs/outputs are not aliases. - if err := option.isWalletProvidedForInputsOutputs(); err != nil { - return nil, err - } - - if option.outputWallet == nil { - option.outputWallet = NewWallet() - } - - return -} - -// Option is the type that is used for options that can be passed into the CreateBlock method to configure its -// behavior. -type Option func(*Options) - -func (o *Options) isBalanceProvided() bool { - provided := false - - for _, output := range o.aliasOutputs { - if output.BaseTokenAmount() > 0 { - provided = true - } - } - - return provided -} - -func (o *Options) isWalletProvidedForInputsOutputs() error { - if o.areInputsProvidedWithoutAliases() { - if o.inputWallet == nil { - return ierrors.New("no input wallet provided for inputs without aliases") - } - } - if o.areOutputsProvidedWithoutAliases() { - if o.outputWallet == nil { - return ierrors.New("no output wallet provided for outputs without aliases") - } - } - - return nil -} - -func (o *Options) areInputsProvidedWithoutAliases() bool { - return len(o.inputs) > 0 -} - -func (o *Options) areOutputsProvidedWithoutAliases() bool { - return len(o.outputs) > 0 -} - -// checkInputsAndOutputs checks if either all provided inputs/outputs are with aliases or all are without, -// we do not allow for mixing those two possibilities. -func (o *Options) checkInputsAndOutputs() error { - inLength, outLength, aliasInLength, aliasOutLength := len(o.inputs), len(o.outputs), len(o.aliasInputs), len(o.aliasOutputs) - - if (inLength == 0 && aliasInLength == 0) || (outLength == 0 && aliasOutLength == 0) { - return ierrors.New("no inputs or outputs provided") - } - - inputsOk := (inLength > 0 && aliasInLength == 0) || (aliasInLength > 0 && inLength == 0) - outputsOk := (outLength > 0 && aliasOutLength == 0) || (aliasOutLength > 0 && outLength == 0) - if !inputsOk || !outputsOk { - return ierrors.New("mixing providing inputs/outputs with and without aliases is not allowed") - } - - return nil -} - -// WithInputs returns an Option that is used to provide the Inputs of the Transaction. -func WithInputs(inputs interface{}) Option { - return func(options *Options) { - switch in := inputs.(type) { - case string: - options.aliasInputs[in] = types.Void - case []string: - for _, input := range in { - options.aliasInputs[input] = types.Void - } - case *models.Output: - options.inputs = append(options.inputs, in) - case []*models.Output: - options.inputs = append(options.inputs, in...) - } - } -} - -// WithOutputs returns an Option that is used to define a non-colored Outputs for the Transaction in the Block. -func WithOutputs(outputsOptions []*OutputOption) Option { - return func(options *Options) { - for _, outputOptions := range outputsOptions { - var output iotago.Output - switch outputOptions.outputType { - case iotago.OutputBasic: - outputBuilder := builder.NewBasicOutputBuilder(outputOptions.address, outputOptions.amount) - output = outputBuilder.MustBuild() - case iotago.OutputAccount: - outputBuilder := builder.NewAccountOutputBuilder(outputOptions.address, outputOptions.address, outputOptions.amount) - output = outputBuilder.MustBuild() - } - - if outputOptions.aliasName != "" { - options.aliasOutputs[outputOptions.aliasName] = output - } else { - options.outputs = append(options.outputs, output) - } - } - } -} - -func WithIssuanceStrategy(strategy models.AllotmentStrategy, issuerID iotago.AccountID) Option { - return func(options *Options) { - options.allotmentStrategy = strategy - options.issuerAccountID = issuerID - } -} - -// WithInputWallet returns a BlockOption that is used to define the inputWallet of the Block. -func WithInputWallet(issuer *Wallet) Option { - return func(options *Options) { - options.inputWallet = issuer - } -} - -// WithOutputWallet returns a BlockOption that is used to define the inputWallet of the Block. -func WithOutputWallet(wallet *Wallet) Option { - return func(options *Options) { - options.outputWallet = wallet - } -} - -// WithOutputBatchAliases returns a BlockOption that is used to determine which outputs should be added to the outWallet. -func WithOutputBatchAliases(outputAliases map[string]types.Empty) Option { - return func(options *Options) { - options.outputBatchAliases = outputAliases - } -} - -// WithReuseOutputs returns a BlockOption that is used to enable deep spamming with Reuse wallet outputs. -func WithReuseOutputs() Option { - return func(options *Options) { - options.reuse = true - } -} - -// WithIssuingTime returns a BlockOption that is used to set issuing time of the Block. -func WithIssuingTime(issuingTime time.Time) Option { - return func(options *Options) { - options.issuingTime = issuingTime - } -} - -// ConflictSlice represents a set of conflict transactions. -type ConflictSlice [][]Option - -// endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region FaucetRequestOptions ///////////////////////////////////////////////////////////////////////////////////////// - -// FaucetRequestOptions is options for faucet request. -type FaucetRequestOptions struct { - outputAliasName string -} - -// NewFaucetRequestOptions creates options for a faucet request. -func NewFaucetRequestOptions(options ...FaucetRequestOption) *FaucetRequestOptions { - reqOptions := &FaucetRequestOptions{ - outputAliasName: "", - } - - for _, option := range options { - option(reqOptions) - } - - return reqOptions -} - -// FaucetRequestOption is an option for faucet request. -type FaucetRequestOption func(*FaucetRequestOptions) - -// WithOutputAlias returns an Option that is used to provide the Output of the Transaction. -func WithOutputAlias(aliasName string) FaucetRequestOption { - return func(options *FaucetRequestOptions) { - options.outputAliasName = aliasName - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region EvilScenario Options ///////////////////////////////////////////////////////////////////////////////////////// - -type ScenarioOption func(scenario *EvilScenario) - -// WithScenarioCustomConflicts specifies the EvilBatch that describes the UTXO structure that should be used for the spam. -func WithScenarioCustomConflicts(batch EvilBatch) ScenarioOption { - return func(options *EvilScenario) { - if batch != nil { - options.ConflictBatch = batch - } - } -} - -// WithScenarioDeepSpamEnabled enables deep spam, the outputs from available Reuse wallets or RestrictedReuse wallet -// if provided with WithReuseInputWalletForDeepSpam option will be used for spam instead fresh faucet outputs. -func WithScenarioDeepSpamEnabled() ScenarioOption { - return func(options *EvilScenario) { - options.Reuse = true - } -} - -// WithScenarioReuseOutputWallet the outputs from the spam will be saved into this wallet, accepted types of wallet: Reuse, RestrictedReuse. -func WithScenarioReuseOutputWallet(wallet *Wallet) ScenarioOption { - return func(options *EvilScenario) { - if wallet != nil { - if wallet.walletType == Reuse || wallet.walletType == RestrictedReuse { - options.OutputWallet = wallet - } - } - } -} - -// WithScenarioInputWalletForDeepSpam reuse set to true, outputs from this wallet will be used for deep spamming, -// allows for controllable building of UTXO deep structures. Accepts only RestrictedReuse wallet type. -func WithScenarioInputWalletForDeepSpam(wallet *Wallet) ScenarioOption { - return func(options *EvilScenario) { - if wallet.walletType == RestrictedReuse { - options.RestrictedInputWallet = wallet - } - } -} - -func WithCreateAccounts() ScenarioOption { - return func(options *EvilScenario) { - options.OutputType = iotago.OutputAccount - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/evilwallet/output_manager.go b/tools/evil-spammer/evilwallet/output_manager.go deleted file mode 100644 index e86ac7448..000000000 --- a/tools/evil-spammer/evilwallet/output_manager.go +++ /dev/null @@ -1,367 +0,0 @@ -package evilwallet - -import ( - "sync" - "sync/atomic" - "time" - - "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/logger" - "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" -) - -const ( - awaitOutputToBeConfirmed = 10 * time.Second -) - -// OutputManager keeps track of the output statuses. -type OutputManager struct { - connector models.Connector - - wallets *Wallets - outputIDWalletMap map[string]*Wallet - outputIDAddrMap map[string]string - // stores solid outputs per node - issuerSolidOutIDMap map[string]map[iotago.OutputID]types.Empty - - log *logger.Logger - - syncutils.RWMutex -} - -// NewOutputManager creates an OutputManager instance. -func NewOutputManager(connector models.Connector, wallets *Wallets, log *logger.Logger) *OutputManager { - return &OutputManager{ - connector: connector, - wallets: wallets, - outputIDWalletMap: make(map[string]*Wallet), - outputIDAddrMap: make(map[string]string), - issuerSolidOutIDMap: make(map[string]map[iotago.OutputID]types.Empty), - log: log, - } -} - -// setOutputIDWalletMap sets wallet for the provided outputID. -func (o *OutputManager) setOutputIDWalletMap(outputID string, wallet *Wallet) { - o.Lock() - defer o.Unlock() - - o.outputIDWalletMap[outputID] = wallet -} - -// setOutputIDAddrMap sets address for the provided outputID. -func (o *OutputManager) setOutputIDAddrMap(outputID string, addr string) { - o.Lock() - defer o.Unlock() - - o.outputIDAddrMap[outputID] = addr -} - -// OutputIDWalletMap returns wallet corresponding to the outputID stored in OutputManager. -func (o *OutputManager) OutputIDWalletMap(outputID string) *Wallet { - o.RLock() - defer o.RUnlock() - - return o.outputIDWalletMap[outputID] -} - -// OutputIDAddrMap returns address corresponding to the outputID stored in OutputManager. -func (o *OutputManager) OutputIDAddrMap(outputID string) (addr string) { - o.RLock() - defer o.RUnlock() - - addr = o.outputIDAddrMap[outputID] - - return -} - -// SetOutputIDSolidForIssuer sets solid flag for the provided outputID and issuer. -func (o *OutputManager) SetOutputIDSolidForIssuer(outputID iotago.OutputID, issuer string) { - o.Lock() - defer o.Unlock() - - if _, ok := o.issuerSolidOutIDMap[issuer]; !ok { - o.issuerSolidOutIDMap[issuer] = make(map[iotago.OutputID]types.Empty) - } - o.issuerSolidOutIDMap[issuer][outputID] = types.Void -} - -// IssuerSolidOutIDMap checks whether output was marked as solid for a given node. -func (o *OutputManager) IssuerSolidOutIDMap(issuer string, outputID iotago.OutputID) (isSolid bool) { - o.RLock() - defer o.RUnlock() - - if solidOutputs, ok := o.issuerSolidOutIDMap[issuer]; ok { - if _, isSolid = solidOutputs[outputID]; isSolid { - return - } - } - - return -} - -// Track the confirmed statuses of the given outputIDs, it returns true if all of them are confirmed. -func (o *OutputManager) Track(outputIDs ...iotago.OutputID) (allConfirmed bool) { - var ( - wg sync.WaitGroup - unconfirmedOutputFound atomic.Bool - ) - - for _, ID := range outputIDs { - wg.Add(1) - - go func(id iotago.OutputID) { - defer wg.Done() - - if !o.AwaitOutputToBeAccepted(id, awaitOutputToBeConfirmed) { - unconfirmedOutputFound.Store(true) - } - }(ID) - } - wg.Wait() - - return !unconfirmedOutputFound.Load() -} - -// createOutputFromAddress creates output, retrieves outputID, and adds it to the wallet. -// Provided address should be generated from provided wallet. Considers only first output found on address. -func (o *OutputManager) createOutputFromAddress(w *Wallet, addr *iotago.Ed25519Address, balance iotago.BaseToken, outputID iotago.OutputID, outputStruct iotago.Output) *models.Output { - index := w.AddrIndexMap(addr.String()) - out := &models.Output{ - Address: addr, - Index: index, - OutputID: outputID, - Balance: balance, - OutputStruct: outputStruct, - } - w.AddUnspentOutput(out) - o.setOutputIDWalletMap(outputID.ToHex(), w) - o.setOutputIDAddrMap(outputID.ToHex(), addr.String()) - - return out -} - -// AddOutput adds existing output from wallet w to the OutputManager. -func (o *OutputManager) AddOutput(w *Wallet, output *models.Output) *models.Output { - idx := w.AddrIndexMap(output.Address.String()) - out := &models.Output{ - Address: output.Address, - Index: idx, - OutputID: output.OutputID, - Balance: output.Balance, - OutputStruct: output.OutputStruct, - } - w.AddUnspentOutput(out) - o.setOutputIDWalletMap(out.OutputID.ToHex(), w) - o.setOutputIDAddrMap(out.OutputID.ToHex(), output.Address.String()) - - return out -} - -// GetOutput returns the Output of the given outputID. -// Firstly checks if output can be retrieved by outputManager from wallet, if not does an API call. -func (o *OutputManager) GetOutput(outputID iotago.OutputID) (output *models.Output) { - output = o.getOutputFromWallet(outputID) - - // get output info via web api - if output == nil { - clt := o.connector.GetClient() - out := clt.GetOutput(outputID) - if out == nil { - return nil - } - - basicOutput, isBasic := out.(*iotago.BasicOutput) - if !isBasic { - return nil - } - - output = &models.Output{ - OutputID: outputID, - Address: basicOutput.UnlockConditionSet().Address().Address, - Balance: basicOutput.BaseTokenAmount(), - OutputStruct: basicOutput, - } - } - - return output -} - -func (o *OutputManager) getOutputFromWallet(outputID iotago.OutputID) (output *models.Output) { - o.RLock() - defer o.RUnlock() - w, ok := o.outputIDWalletMap[outputID.ToHex()] - if ok { - addr := o.outputIDAddrMap[outputID.ToHex()] - output = w.UnspentOutput(addr) - } - - return -} - -// RequestOutputsByAddress finds the unspent outputs of a given address and updates the provided output status map. -// func (o *OutputManager) RequestOutputsByAddress(address string) (outputIDs []iotago.OutputID) { -// s := time.Now() -// clt := o.connector.GetClient() -// for ; time.Since(s) < awaitOutputsByAddress; time.Sleep(1 * time.Second) { -// outputIDs, err := clt.GetAddressUnspentOutputs(address) -// if err == nil && len(outputIDs) > 0 { -// return outputIDs -// } -// } - -// 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{} - for _, output := range wallet.UnspentOutputs() { - wg.Add(1) - if output == nil { - continue - } - - var outs iotago.OutputIDs - outs = append(outs, output.OutputID) - - go func(outs iotago.OutputIDs) { - defer wg.Done() - - o.Track(outs...) - }(outs) - } - 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(awaitConfirmationSleep) { - confirmationState := clt.GetOutputConfirmationState(outputID) - if confirmationState == "confirmed" { - accepted = true - break - } - } - - return accepted -} - -// AwaitTransactionsConfirmation awaits for transaction confirmation and updates wallet with outputIDs. -func (o *OutputManager) AwaitTransactionsConfirmation(txIDs ...iotago.TransactionID) { - wg := sync.WaitGroup{} - semaphore := make(chan bool, 1) - - o.log.Debugf("Awaiting confirmation of %d transactions", len(txIDs)) - - for _, txID := range txIDs { - wg.Add(1) - go func(txID iotago.TransactionID) { - defer wg.Done() - semaphore <- true - defer func() { - <-semaphore - }() - err := o.AwaitTransactionToBeAccepted(txID, waitForConfirmation) - if err != nil { - return - } - }(txID) - } - wg.Wait() -} - -// AwaitTransactionToBeAccepted awaits for acceptance of a single transaction. -func (o *OutputManager) AwaitTransactionToBeAccepted(txID iotago.TransactionID, waitFor time.Duration) error { - s := time.Now() - clt := o.connector.GetClient() - var accepted bool - for ; time.Since(s) < waitFor; time.Sleep(awaitConfirmationSleep) { - confirmationState := clt.GetTransactionConfirmationState(txID) - o.log.Debugf("Tx %s confirmationState: %s", txID.ToHex(), confirmationState) - if 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 -} - -// AwaitOutputToBeSolid awaits for solidification of a single output by provided clt. -func (o *OutputManager) AwaitOutputToBeSolid(outID iotago.OutputID, clt models.Client, waitFor time.Duration) error { - s := time.Now() - var solid bool - - for ; time.Since(s) < waitFor; time.Sleep(awaitSolidificationSleep) { - solid = o.IssuerSolidOutIDMap(clt.URL(), outID) - if solid { - break - } - if output := clt.GetOutput(outID); output != nil { - o.SetOutputIDSolidForIssuer(outID, clt.URL()) - solid = true - - break - } - } - if !solid { - return ierrors.Errorf("output %s not solidified in time", outID) - } - - return nil -} - -// AwaitOutputsToBeSolid awaits for all provided outputs are solid for a provided client. -func (o *OutputManager) AwaitOutputsToBeSolid(outputs iotago.OutputIDs, clt models.Client, maxGoroutines int) (allSolid bool) { - wg := sync.WaitGroup{} - semaphore := make(chan bool, maxGoroutines) - allSolid = true - - for _, outID := range outputs { - wg.Add(1) - go func(outID iotago.OutputID) { - defer wg.Done() - semaphore <- true - defer func() { - <-semaphore - }() - err := o.AwaitOutputToBeSolid(outID, clt, waitForSolidification) - if err != nil { - allSolid = false - return - } - }(outID) - } - wg.Wait() - - return -} diff --git a/tools/evil-spammer/evilwallet/utils.go b/tools/evil-spammer/evilwallet/utils.go deleted file mode 100644 index 733d86987..000000000 --- a/tools/evil-spammer/evilwallet/utils.go +++ /dev/null @@ -1,25 +0,0 @@ -package evilwallet - -import iotago "github.com/iotaledger/iota.go/v4" - -// region utxo/tx realted functions //////////////////////////////////////////////////////////////////////////////////////////// - -// 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/tools/evil-spammer/evilwallet/wallet.go b/tools/evil-spammer/evilwallet/wallet.go deleted file mode 100644 index 349c97b97..000000000 --- a/tools/evil-spammer/evilwallet/wallet.go +++ /dev/null @@ -1,259 +0,0 @@ -package evilwallet - -import ( - "crypto/ed25519" - - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/iota-core/pkg/testsuite/mock" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/tpkg" -) - -// region Wallet /////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Wallet is the definition of a wallet. -type Wallet struct { - ID walletID - walletType WalletType - unspentOutputs map[string]*models.Output // maps addr to its unspentOutput - indexAddrMap map[uint64]string - addrIndexMap map[string]uint64 - inputTransactions map[string]types.Empty - reuseAddressPool map[string]types.Empty - seed [32]byte - - lastAddrIdxUsed atomic.Int64 // used during filling in wallet with new outputs - lastAddrSpent atomic.Int64 // used during spamming with outputs one by one - - *syncutils.RWMutex -} - -// NewWallet creates a wallet of a given type. -func NewWallet(wType ...WalletType) *Wallet { - walletType := Other - if len(wType) > 0 { - walletType = wType[0] - } - idxSpent := atomic.NewInt64(-1) - addrUsed := atomic.NewInt64(-1) - - wallet := &Wallet{ - walletType: walletType, - ID: -1, - seed: tpkg.RandEd25519Seed(), - unspentOutputs: make(map[string]*models.Output), - indexAddrMap: make(map[uint64]string), - addrIndexMap: make(map[string]uint64), - inputTransactions: make(map[string]types.Empty), - lastAddrSpent: *idxSpent, - lastAddrIdxUsed: *addrUsed, - RWMutex: &syncutils.RWMutex{}, - } - - if walletType == Reuse { - wallet.reuseAddressPool = make(map[string]types.Empty) - } - - return wallet -} - -// Type returns the wallet type. -func (w *Wallet) Type() WalletType { - return w.walletType -} - -// Address returns a new and unused address of a given wallet. -func (w *Wallet) Address() *iotago.Ed25519Address { - w.Lock() - defer w.Unlock() - - index := uint64(w.lastAddrIdxUsed.Add(1)) - keyManager := mock.NewKeyManager(w.seed[:], index) - //nolint:forcetypeassert - addr := keyManager.Address(iotago.AddressEd25519).(*iotago.Ed25519Address) - w.indexAddrMap[index] = addr.String() - w.addrIndexMap[addr.String()] = index - - return addr -} - -// AddressOnIndex returns a new and unused address of a given wallet. -func (w *Wallet) AddressOnIndex(index uint64) *iotago.Ed25519Address { - w.Lock() - defer w.Unlock() - - hdWallet := mock.NewKeyManager(w.seed[:], index) - //nolint:forcetypeassert - addr := hdWallet.Address(iotago.AddressEd25519).(*iotago.Ed25519Address) - - return addr -} - -// UnspentOutput returns the unspent output on the address. -func (w *Wallet) UnspentOutput(addr string) *models.Output { - w.RLock() - defer w.RUnlock() - - return w.unspentOutputs[addr] -} - -// UnspentOutputs returns all unspent outputs on the wallet. -func (w *Wallet) UnspentOutputs() (outputs map[string]*models.Output) { - w.RLock() - defer w.RUnlock() - outputs = make(map[string]*models.Output) - for addr, outs := range w.unspentOutputs { - outputs[addr] = outs - } - - return outputs -} - -// IndexAddrMap returns the address for the index specified. -func (w *Wallet) IndexAddrMap(outIndex uint64) string { - w.RLock() - defer w.RUnlock() - - return w.indexAddrMap[outIndex] -} - -// AddrIndexMap returns the index for the address specified. -func (w *Wallet) AddrIndexMap(address string) uint64 { - w.RLock() - defer w.RUnlock() - - return w.addrIndexMap[address] -} - -// AddUnspentOutput adds an unspentOutput of a given wallet. -func (w *Wallet) AddUnspentOutput(output *models.Output) { - w.Lock() - defer w.Unlock() - w.unspentOutputs[output.Address.String()] = output -} - -// UnspentOutputBalance returns the balance on the unspent output sitting on the address specified. -func (w *Wallet) UnspentOutputBalance(addr string) iotago.BaseToken { - w.RLock() - defer w.RUnlock() - - total := iotago.BaseToken(0) - if out, ok := w.unspentOutputs[addr]; ok { - total += out.Balance - } - - return total -} - -// IsEmpty returns true if the wallet is empty. -func (w *Wallet) IsEmpty() (empty bool) { - switch w.walletType { - case Reuse: - empty = len(w.reuseAddressPool) == 0 - default: - empty = w.UnspentOutputsLength() == 0 - } - - return -} - -// UnspentOutputsLeft returns how many unused outputs are available in wallet. -func (w *Wallet) UnspentOutputsLeft() (left int) { - switch w.walletType { - case Reuse: - left = len(w.reuseAddressPool) - default: - left = int(w.lastAddrIdxUsed.Load() - w.lastAddrSpent.Load()) - } - - return -} - -// AddReuseAddress adds address to the reuse ready outputs' addresses pool for a Reuse wallet. -func (w *Wallet) AddReuseAddress(addr string) { - w.Lock() - defer w.Unlock() - - if w.walletType == Reuse { - w.reuseAddressPool[addr] = types.Void - } -} - -// GetReuseAddress get random address from reuse addresses reuseOutputsAddresses pool. Address is removed from the pool after selecting. -func (w *Wallet) GetReuseAddress() string { - w.Lock() - defer w.Unlock() - - if w.walletType == Reuse { - if len(w.reuseAddressPool) > 0 { - for addr := range w.reuseAddressPool { - delete(w.reuseAddressPool, addr) - return addr - } - } - } - - return "" -} - -// GetUnspentOutput returns an unspent output on the oldest address ordered by index. -func (w *Wallet) GetUnspentOutput() *models.Output { - switch w.walletType { - case Reuse: - addr := w.GetReuseAddress() - return w.UnspentOutput(addr) - default: - if w.lastAddrSpent.Load() < w.lastAddrIdxUsed.Load() { - idx := w.lastAddrSpent.Add(1) - addr := w.IndexAddrMap(uint64(idx)) - outs := w.UnspentOutput(addr) - - return outs - } - } - - return nil -} - -// Sign signs the tx essence. -func (w *Wallet) AddressSigner(addr *iotago.Ed25519Address) iotago.AddressSigner { - w.RLock() - defer w.RUnlock() - index := w.AddrIndexMap(addr.String()) - hdWallet := mock.NewKeyManager(w.seed[:], index) - - return hdWallet.AddressSigner() -} - -func (w *Wallet) KeyPair(index uint64) (ed25519.PrivateKey, ed25519.PublicKey) { - w.RLock() - defer w.RUnlock() - hdWallet := mock.NewKeyManager(w.seed[:], index) - - return hdWallet.KeyPair() -} - -// UpdateUnspentOutputID updates the unspent output on the address specified. -// func (w *Wallet) UpdateUnspentOutputID(addr string, outputID utxo.OutputID) error { -// w.RLock() -// walletOutput, ok := w.unspentOutputs[addr] -// w.RUnlock() -// if !ok { -// return errors.Errorf("could not find unspent output under provided address in the wallet, outID:%s, addr: %s", outputID.Base58(), addr) -// } -// w.Lock() -// walletOutput.OutputID = outputID -// w.Unlock() -// return nil -// } - -// UnspentOutputsLength returns the number of unspent outputs on the wallet. -func (w *Wallet) UnspentOutputsLength() int { - return len(w.unspentOutputs) -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/evilwallet/wallets.go b/tools/evil-spammer/evilwallet/wallets.go deleted file mode 100644 index 31e5c0366..000000000 --- a/tools/evil-spammer/evilwallet/wallets.go +++ /dev/null @@ -1,226 +0,0 @@ -package evilwallet - -import ( - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" -) - -type walletID int - -type ( - // WalletType is the type of the wallet. - //nolint:revive - WalletType int8 - //nolint:revive - WalletStatus int8 -) - -const ( - Other WalletType = iota - // Fresh is used for automatic Faucet Requests, outputs are returned one by one. - Fresh - // Reuse stores resulting outputs of double spends or transactions issued by the evilWallet, - // outputs from this wallet are reused in spamming scenario with flag reuse set to true and no RestrictedReuse wallet provided. - // Reusing spammed outputs allows for creation of deeper UTXO DAG structure. - Reuse - // RestrictedReuse it is a reuse wallet, that will be available only to spamming scenarios that - // will provide RestrictedWallet for the reuse spamming. - RestrictedReuse -) - -// region Wallets /////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Wallets is a container of wallets. -type Wallets struct { - wallets map[walletID]*Wallet - // we store here non-empty wallets ids of wallets with Fresh faucet outputs. - faucetWallets []walletID - // reuse wallets are stored without an order, so they are picked up randomly. - // Boolean flag indicates if wallet is ready - no new addresses will be generated, so empty wallets can be deleted. - reuseWallets map[walletID]bool - mu syncutils.RWMutex - - lastWalletID atomic.Int64 -} - -// NewWallets creates and returns a new Wallets container. -func NewWallets() *Wallets { - return &Wallets{ - wallets: make(map[walletID]*Wallet), - faucetWallets: make([]walletID, 0), - reuseWallets: make(map[walletID]bool), - lastWalletID: *atomic.NewInt64(-1), - } -} - -// NewWallet adds a new wallet to Wallets and returns the created wallet. -func (w *Wallets) NewWallet(walletType WalletType) *Wallet { - wallet := NewWallet(walletType) - wallet.ID = walletID(w.lastWalletID.Add(1)) - - w.addWallet(wallet) - if walletType == Reuse { - w.addReuseWallet(wallet) - } - - return wallet -} - -// GetWallet returns the wallet with the specified ID. -func (w *Wallets) GetWallet(walletID walletID) *Wallet { - return w.wallets[walletID] -} - -// GetNextWallet get next non-empty wallet based on provided type. -func (w *Wallets) GetNextWallet(walletType WalletType, minOutputsLeft int) (*Wallet, error) { - w.mu.Lock() - defer w.mu.Unlock() - - switch walletType { - case Fresh: - if !w.IsFaucetWalletAvailable() { - return nil, ierrors.New("no faucet wallets available, need to request more funds") - } - - wallet := w.wallets[w.faucetWallets[0]] - if wallet.IsEmpty() { - return nil, ierrors.New("wallet is empty, need to request more funds") - } - - return wallet, nil - case Reuse: - for id, ready := range w.reuseWallets { - wal := w.wallets[id] - if wal.UnspentOutputsLeft() > minOutputsLeft { - // if are solid - - return wal, nil - } - // no outputs will be ever added to this wallet, so it can be deleted - if wal.IsEmpty() && ready { - w.removeReuseWallet(id) - } - } - - return nil, ierrors.New("no reuse wallets available") - } - - return nil, ierrors.New("wallet type not supported for ordered usage, use GetWallet by ID instead") -} - -func (w *Wallets) UnspentOutputsLeft(walletType WalletType) int { - w.mu.RLock() - defer w.mu.RUnlock() - outputsLeft := 0 - - switch walletType { - case Fresh: - for _, wID := range w.faucetWallets { - outputsLeft += w.wallets[wID].UnspentOutputsLeft() - } - case Reuse: - for wID := range w.reuseWallets { - outputsLeft += w.wallets[wID].UnspentOutputsLeft() - } - } - - return outputsLeft -} - -// addWallet stores newly created wallet. -func (w *Wallets) addWallet(wallet *Wallet) { - w.mu.Lock() - defer w.mu.Unlock() - - w.wallets[wallet.ID] = wallet -} - -// addReuseWallet stores newly created wallet. -func (w *Wallets) addReuseWallet(wallet *Wallet) { - w.mu.Lock() - defer w.mu.Unlock() - - w.reuseWallets[wallet.ID] = false -} - -// GetUnspentOutput gets first found unspent output for a given walletType. -func (w *Wallets) GetUnspentOutput(wallet *Wallet) *models.Output { - if wallet == nil { - return nil - } - - return wallet.GetUnspentOutput() -} - -// IsFaucetWalletAvailable checks if there is any faucet wallet left. -func (w *Wallets) IsFaucetWalletAvailable() bool { - return len(w.faucetWallets) > 0 -} - -// freshWallet returns the first non-empty wallet from the faucetWallets queue. If current wallet is empty, -// it is removed and the next enqueued one is returned. -func (w *Wallets) freshWallet() (*Wallet, error) { - wallet, err := w.GetNextWallet(Fresh, 1) - if err != nil { - w.removeFreshWallet() - wallet, err = w.GetNextWallet(Fresh, 1) - if err != nil { - return nil, err - } - } - - return wallet, nil -} - -// reuseWallet returns the first non-empty wallet from the reuseWallets queue. If current wallet is empty, -// it is removed and the next enqueued one is returned. -func (w *Wallets) reuseWallet(outputsNeeded int) *Wallet { - wallet, err := w.GetNextWallet(Reuse, outputsNeeded) - if err != nil { - return nil - } - - return wallet -} - -// removeWallet removes wallet, for Fresh wallet it will be the first wallet in a queue. -func (w *Wallets) removeFreshWallet() { - w.mu.Lock() - defer w.mu.Unlock() - - if w.IsFaucetWalletAvailable() { - removedID := w.faucetWallets[0] - w.faucetWallets = w.faucetWallets[1:] - delete(w.wallets, removedID) - } -} - -// removeWallet removes wallet, for Fresh wallet it will be the first wallet in a queue. -func (w *Wallets) removeReuseWallet(walletID walletID) { - if _, ok := w.reuseWallets[walletID]; ok { - delete(w.reuseWallets, walletID) - delete(w.wallets, walletID) - } -} - -// SetWalletReady makes wallet ready to use, Fresh wallet is added to freshWallets queue. -func (w *Wallets) SetWalletReady(wallet *Wallet) { - w.mu.Lock() - defer w.mu.Unlock() - - if wallet.IsEmpty() { - return - } - wType := wallet.walletType - switch wType { - case Fresh: - w.faucetWallets = append(w.faucetWallets, wallet.ID) - case Reuse: - w.reuseWallets[wallet.ID] = true - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/go.mod b/tools/evil-spammer/go.mod deleted file mode 100644 index 91ec4b3b6..000000000 --- a/tools/evil-spammer/go.mod +++ /dev/null @@ -1,95 +0,0 @@ -module github.com/iotaledger/iota-core/tools/evil-spammer - -go 1.21 - -replace github.com/iotaledger/iota-core => ../../ - -replace github.com/iotaledger/iota-core/tools/genesis-snapshot => ../genesis-snapshot/ - -require ( - github.com/AlecAivazis/survey/v2 v2.3.7 - github.com/ethereum/go-ethereum v1.13.4 - 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-20231020115340-13da292c580b - github.com/iotaledger/hive.go/lo v0.0.0-20231020115340-13da292c580b - github.com/iotaledger/hive.go/logger v0.0.0-20231020115340-13da292c580b - github.com/iotaledger/hive.go/runtime v0.0.0-20231020115340-13da292c580b - github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000 - github.com/iotaledger/iota-core/tools/genesis-snapshot v0.0.0-00010101000000-000000000000 - github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 - github.com/mr-tron/base58 v1.2.0 - go.uber.org/atomic v1.11.0 -) - -require ( - filippo.io/edwards25519 v1.0.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/eclipse/paho.mqtt.golang v1.4.3 // indirect - github.com/fatih/structs v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-stack/stack v1.8.1 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/holiman/uint256 v1.2.3 // indirect - github.com/iancoleman/orderedmap v0.3.0 // indirect - 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/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 - github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b // indirect - github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b // indirect - github.com/ipfs/go-cid v0.4.1 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/knadh/koanf v1.5.0 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-libp2p v0.31.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/multiformats/go-base32 v0.1.0 // indirect - github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr v0.12.0 // indirect - github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.9.0 // indirect - github.com/multiformats/go-multihash v0.2.3 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect - github.com/otiai10/copy v1.14.0 // indirect - github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/pokt-network/smt v0.6.1 // indirect - github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.8.4 // indirect - github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 // indirect - github.com/zyedidia/generic v1.2.1 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.2.1 // indirect -) diff --git a/tools/evil-spammer/go.sum b/tools/evil-spammer/go.sum deleted file mode 100644 index f66d3f1bb..000000000 --- a/tools/evil-spammer/go.sum +++ /dev/null @@ -1,581 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= -filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= -github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= -github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= -github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= -github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= -github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= -github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= -github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= -github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= -github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= -github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7 h1:dTrD7X2PTNgli6EbS4tV9qu3QAm/kBU3XaYZV2xdzys= -github.com/iotaledger/grocksdb v1.7.5-0.20230220105546-5162e18885c7/go.mod h1:ZRdPu684P0fQ1z8sXz4dj9H5LWHhz4a9oCtvjunkSrw= -github.com/iotaledger/hive.go/ads v0.0.0-20231020115340-13da292c580b h1:D68khiAFb9DwTvjZc2nc4R0E6wUdKyYCUXkmdaMzuoQ= -github.com/iotaledger/hive.go/ads v0.0.0-20231020115340-13da292c580b/go.mod h1:IFh0gDfeMgZtfCo+5afK59IDR4xXh+cTR9YtLnZPcbY= -github.com/iotaledger/hive.go/app v0.0.0-20231020115340-13da292c580b h1:mX3NXaTMLEwZnEs4IlxEvXY0YZo8qbb8M1xM39FS6qY= -github.com/iotaledger/hive.go/app v0.0.0-20231020115340-13da292c580b/go.mod h1:8ZbIKR84oQd/3iQ5eeT7xpudO9/ytzXP7veIYnk7Orc= -github.com/iotaledger/hive.go/constraints v0.0.0-20231020115340-13da292c580b h1:HF4e0wz0JMIT4m3saqdQ//T9nWHV9d5sLMtEwNDuykM= -github.com/iotaledger/hive.go/constraints v0.0.0-20231020115340-13da292c580b/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s= -github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231020115340-13da292c580b h1:ZERXxhQBUBV1AqTE6cUI4vTxSx4JrnsMuLZFgj32xLM= -github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231020115340-13da292c580b/go.mod h1:Mc+ACqBGPxrPMIPUBOm6/HL0J6m0iVMwjtIEKW3uow8= -github.com/iotaledger/hive.go/crypto v0.0.0-20231020115340-13da292c580b h1:ZUUqRRO6XnQmVcXlXyx07vqySn28+bln6jp9KagYCjY= -github.com/iotaledger/hive.go/crypto v0.0.0-20231020115340-13da292c580b/go.mod h1:h3o6okvMSEK3KOX6pOp3yq1h9ohTkTfo6X8MzEadeb0= -github.com/iotaledger/hive.go/ds v0.0.0-20231020115340-13da292c580b h1:8b2sH+2Vf0y5BDYTMwKa09iQr3JF9JrzTI64DkXb+9U= -github.com/iotaledger/hive.go/ds v0.0.0-20231020115340-13da292c580b/go.mod h1:3XkUSKfHaVxGbT0XAvjNlVYqPzhfLTGhDtdNA5UBPco= -github.com/iotaledger/hive.go/ierrors v0.0.0-20231020115340-13da292c580b h1:JJPnr231djUTgTnE4oGz847WE9VA7Py6E6fgZwT5TQo= -github.com/iotaledger/hive.go/ierrors v0.0.0-20231020115340-13da292c580b/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8= -github.com/iotaledger/hive.go/kvstore v0.0.0-20231020115340-13da292c580b h1:LusmtjpfG/q8lc15Fp9W3kABbN3tArKx/zw2ibdY1DU= -github.com/iotaledger/hive.go/kvstore v0.0.0-20231020115340-13da292c580b/go.mod h1:O/U3jtiUDeqqM0MZQFu2UPqS9fUm0C5hNISxlmg/thE= -github.com/iotaledger/hive.go/lo v0.0.0-20231020115340-13da292c580b h1:UvFWI8wQJS/XQOeWHpPsaFVeS2nxJ7nIGFr+IFjrnVw= -github.com/iotaledger/hive.go/lo v0.0.0-20231020115340-13da292c580b/go.mod h1:s4kzx9QY1MVWHJralj+3q5kI0eARtrJhphYD/iBbPfo= -github.com/iotaledger/hive.go/log v0.0.0-20231020115340-13da292c580b h1:IwhoeOeRu25mBdrimuOOvbbhHYX0QipibV69ubn8nX0= -github.com/iotaledger/hive.go/log v0.0.0-20231020115340-13da292c580b/go.mod h1:JvokzmpmFZPDskMlUqqjgHtD8usVJU4nAY/TNMGge8M= -github.com/iotaledger/hive.go/logger v0.0.0-20231020115340-13da292c580b h1:EhVgAU/f2J3VYZwP60dRdyfAeDU3c/gBzX9blKtQGKA= -github.com/iotaledger/hive.go/logger v0.0.0-20231020115340-13da292c580b/go.mod h1:aBfAfIB2GO/IblhYt5ipCbyeL9bXSNeAwtYVA3hZaHg= -github.com/iotaledger/hive.go/runtime v0.0.0-20231020115340-13da292c580b h1:O68POYIqBLnoHN+HIszc58QwAI2qocYq0WKGfVrXmMg= -github.com/iotaledger/hive.go/runtime v0.0.0-20231020115340-13da292c580b/go.mod h1:jRw8yFipiPaqmTPHh7hTcxAP9u6pjRGpByS3REJKkbY= -github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b h1:zaXZn9yV/95SRDkgCZQeBbSbmcJTKSZbCB7oBd71Qwg= -github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b/go.mod h1:SdK26z8/VhWtxaqCuQrufm80SELgowQPmu9T/8eUQ8g= -github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b h1:MDZhTZTVDiydXcW5j4TA7HixVCyAdToIMPhHfJee7cE= -github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 h1:81aWESXFC04iKI9I140eDrBb9zBWXfVoAUMp9berk0c= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= -github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= -github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= -github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-libp2p v0.31.0 h1:LFShhP8F6xthWiBBq3euxbKjZsoRajVEyBS9snfHxYg= -github.com/libp2p/go-libp2p v0.31.0/go.mod h1:W/FEK1c/t04PbRH3fA9i5oucu5YcgrG0JVoBWT1B7Eg= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= -github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= -github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.12.0 h1:1QlibTFkoXJuDjjYsMHhE73TnzJQl8FSWatk/0gxGzE= -github.com/multiformats/go-multiaddr v0.12.0/go.mod h1:WmZXgObOQOYp9r3cslLlppkrz1FYSHmE834dfz/lWu8= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= -github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= -github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= -github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= -github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= -github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c h1:Gcce/r5tSQeprxswXXOwQ/RBU1bjQWVd9dB7QKoPXBE= -github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c/go.mod h1:1iCZ0433JJMecYqCa+TdWA9Pax8MGl4ByuNDZ7eSnQY= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc h1:8bQZVK1X6BJR/6nYUPxQEP+ReTsceJTKizeuwjWOPUA= -github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pokt-network/smt v0.6.1 h1:u5yTGNNND6edXv3vMQrAcjku1Ig4osehdu+EMYSXHUU= -github.com/pokt-network/smt v0.6.1/go.mod h1:CWgC9UzDxXJNkL7TEADnJXutZVMYzK/+dmBb37RWkeQ= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= -github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 h1:i7k63xHOX2ntuHrhHewfKro67c834jug2DIk599fqAA= -github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98/go.mod h1:Knu2XMRWe8SkwTlHc/+ghP+O9DEaZRQQEyTjvLJ5Cck= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zyedidia/generic v1.2.1 h1:Zv5KS/N2m0XZZiuLS82qheRG4X1o5gsWreGb0hR7XDc= -github.com/zyedidia/generic v1.2.1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis= -go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= -gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= -lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/tools/evil-spammer/identity/config.go b/tools/evil-spammer/identity/config.go deleted file mode 100644 index 4529b5055..000000000 --- a/tools/evil-spammer/identity/config.go +++ /dev/null @@ -1,72 +0,0 @@ -package identity - -import ( - "encoding/json" - "fmt" - "os" -) - -type Params map[string]Identities -type Identities map[string]privKeyURLPair -type privKeyURLPair [2]string - -var Config = Params{} - -var identityConfigJSON = `{ - "docker": { - "validator-1": [ - "8q491c3YWjbPwLmF2WD95YmCgh61j2kenCKHfGfByoWi", - "http://localhost:8080" - ], - "validator-2": [ - "4ata8GcTRMJ5sSv2jQJWmSYYTHct748p3tXmCFYm7wjA", - "http://localhost:8070" - ], - "validator-3": [ - "3YX6e7AL28hHihZewKdq6CMkEYVsTJBLgRiprUNiNq5E", - "http://localhost:8090" - ] - } -}` - -func LoadIdentity(networkName, alias string) (privKey, url string) { - fmt.Println("Loading identity", alias, "for network", networkName) - if networkIdentities, exists := Config[networkName]; exists { - if keyURLPair, ok := networkIdentities[alias]; ok { - privateKey := keyURLPair[0] - urlAPI := keyURLPair[1] - fmt.Println("Loaded identity", alias, "API url:", url, "for network", networkName, "...DONE") - - return privateKey, urlAPI - } - - return "", "" - } - - return "", "" -} - -func LoadConfig() Params { - // open config file - fname := "keys-config.json" - file, err := os.Open(fname) - if err != nil { - if !os.IsNotExist(err) { - panic(err) - } - if err = os.WriteFile(fname, []byte(identityConfigJSON), 0o600); err != nil { - panic(err) - } - } - defer file.Close() - // decode config file - fbytes, err := os.ReadFile(fname) - if err != nil { - panic(err) - } - if err = json.Unmarshal(fbytes, &Config); err != nil { - panic(err) - } - - return Config -} diff --git a/tools/evil-spammer/interactive/interactive.go b/tools/evil-spammer/interactive/interactive.go deleted file mode 100644 index 620f1c9b7..000000000 --- a/tools/evil-spammer/interactive/interactive.go +++ /dev/null @@ -1,899 +0,0 @@ -package interactive - -import ( - "encoding/json" - "fmt" - "io" - "os" - "strconv" - "strings" - "text/tabwriter" - "time" - - "github.com/AlecAivazis/survey/v2" - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/programs" - "github.com/iotaledger/iota-core/tools/evil-spammer/spammer" - "github.com/iotaledger/iota.go/v4/nodeclient" -) - -const ( - faucetFundsCheck = time.Minute / 12 - maxConcurrentSpams = 5 - lastSpamsShowed = 15 - timeFormat = "2006/01/02 15:04:05" - configFilename = "interactive_config.json" -) - -const ( - AnswerEnable = "enable" - AnswerDisable = "disable" -) - -var ( - faucetTicker *time.Ticker - printer *Printer - minSpamOutputs int -) - -type InteractiveConfig struct { - //nolint:tagliatelle - WebAPI []string `json:"webAPI"` - Rate int `json:"rate"` - DurationStr string `json:"duration"` - TimeUnitStr string `json:"timeUnit"` - Deep bool `json:"deepEnabled"` - Reuse bool `json:"reuseEnabled"` - Scenario string `json:"scenario"` - AutoRequesting bool `json:"autoRequestingEnabled"` - AutoRequestingAmount string `json:"autoRequestingAmount"` - UseRateSetter bool `json:"useRateSetter"` - - duration time.Duration - timeUnit time.Duration - clientURLs map[string]types.Empty -} - -var configJSON = fmt.Sprintf(`{ - "webAPI": ["http://localhost:8080","http://localhost:8090"], - "rate": 2, - "duration": "20s", - "timeUnit": "1s", - "deepEnabled": false, - "reuseEnabled": true, - "scenario": "%s", - "autoRequestingEnabled": false, - "autoRequestingAmount": "100", - "useRateSetter": true -}`, spammer.TypeTx) - -var defaultConfig = InteractiveConfig{ - clientURLs: map[string]types.Empty{ - "http://localhost:8080": types.Void, - "http://localhost:8090": types.Void, - }, - Rate: 2, - duration: 20 * time.Second, - timeUnit: time.Second, - Deep: false, - Reuse: true, - Scenario: spammer.TypeTx, - AutoRequesting: false, - AutoRequestingAmount: "100", - UseRateSetter: true, -} - -const ( - requestAmount100 = "100" - requestAmount10k = "10000" -) - -// region survey selections /////////////////////////////////////////////////////////////////////////////////////////////////////// - -type action int - -const ( - actionWalletDetails action = iota - actionPrepareFunds - actionSpamMenu - actionCurrent - actionHistory - actionSettings - shutdown -) - -var actions = []string{"Evil wallet details", "Prepare faucet funds", "New spam", "Active spammers", "Spam history", "Settings", "Close"} - -const ( - spamScenario = "Change scenario" - spamType = "Update spam options" - spamDetails = "Update spam rate and duration" - startSpam = "Start the spammer" - back = "Go back" -) - -var spamMenuOptions = []string{startSpam, spamScenario, spamDetails, spamType, back} - -const ( - settingPreparation = "Auto funds requesting" - settingAddURLs = "Add client API url" - settingRemoveURLs = "Remove client API urls" - settingUseRateSetter = "Enable/disable rate setter" -) - -var settingsMenuOptions = []string{settingPreparation, settingAddURLs, settingRemoveURLs, settingUseRateSetter, back} - -const ( - currentSpamRemove = "Cancel spam" -) - -var currentSpamOptions = []string{currentSpamRemove, back} - -const ( - mpm string = "Minute, rate is [mpm]" - mps string = "Second, rate is [mps]" -) - -var ( - scenarios = []string{spammer.TypeBlock, spammer.TypeTx, spammer.TypeDs, "conflict-circle", "guava", "orange", "mango", "pear", "lemon", "banana", "kiwi", "peace"} - confirms = []string{AnswerEnable, AnswerDisable} - outputNumbers = []string{"100", "1000", "5000", "cancel"} - timeUnits = []string{mpm, mps} -) - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region interactive /////////////////////////////////////////////////////////////////////////////////////////////////////// - -func Run() { - mode := NewInteractiveMode() - - printer = NewPrinter(mode) - - printer.printBanner() - mode.loadConfig() - time.Sleep(time.Millisecond * 100) - configure(mode) - go mode.runBackgroundTasks() - mode.menu() - - for { - select { - case id := <-mode.spamFinished: - mode.summarizeSpam(id) - case <-mode.mainMenu: - mode.menu() - case <-mode.shutdown: - printer.FarewellBlock() - mode.saveConfigsToFile() - os.Exit(0) - - return - } - } -} - -func configure(mode *Mode) { - faucetTicker = time.NewTicker(faucetFundsCheck) - switch mode.Config.AutoRequestingAmount { - case requestAmount100: - minSpamOutputs = 40 - case requestAmount10k: - minSpamOutputs = 2000 - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region Mode ///////////////////////////////////////////////////////////////////////////////////////////////////////// - -type Mode struct { - evilWallet *evilwallet.EvilWallet - shutdown chan types.Empty - mainMenu chan types.Empty - spamFinished chan int - action chan action - - nextAction string - - preparingFunds bool - - Config InteractiveConfig - blkSent *atomic.Uint64 - txSent *atomic.Uint64 - scenariosSent *atomic.Uint64 - - activeSpammers map[int]*spammer.Spammer - spammerLog *SpammerLog - spamMutex syncutils.Mutex - - stdOutMutex syncutils.Mutex -} - -func NewInteractiveMode() *Mode { - return &Mode{ - evilWallet: evilwallet.NewEvilWallet(), - action: make(chan action), - shutdown: make(chan types.Empty), - mainMenu: make(chan types.Empty), - spamFinished: make(chan int), - - Config: defaultConfig, - blkSent: atomic.NewUint64(0), - txSent: atomic.NewUint64(0), - scenariosSent: atomic.NewUint64(0), - - spammerLog: NewSpammerLog(), - activeSpammers: make(map[int]*spammer.Spammer), - } -} - -func (m *Mode) runBackgroundTasks() { - for { - select { - case <-faucetTicker.C: - m.prepareFundsIfNeeded() - case act := <-m.action: - switch act { - case actionSpamMenu: - go m.spamMenu() - case actionWalletDetails: - m.walletDetails() - m.mainMenu <- types.Void - case actionPrepareFunds: - m.prepareFunds() - m.mainMenu <- types.Void - case actionHistory: - m.history() - m.mainMenu <- types.Void - case actionCurrent: - go m.currentSpams() - case actionSettings: - go m.settingsMenu() - case shutdown: - m.shutdown <- types.Void - } - } - } -} - -func (m *Mode) walletDetails() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - - printer.EvilWalletStatus() -} - -func (m *Mode) menu() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - err := survey.AskOne(actionQuestion, &m.nextAction) - if err != nil { - fmt.Println(err.Error()) - return - } - m.onMenuAction() -} - -func (m *Mode) onMenuAction() { - switch m.nextAction { - case actions[actionWalletDetails]: - m.action <- actionWalletDetails - case actions[actionPrepareFunds]: - m.action <- actionPrepareFunds - case actions[actionSpamMenu]: - m.action <- actionSpamMenu - case actions[actionSettings]: - m.action <- actionSettings - case actions[actionCurrent]: - m.action <- actionCurrent - case actions[actionHistory]: - m.action <- actionHistory - case actions[shutdown]: - m.action <- shutdown - } -} - -func (m *Mode) prepareFundsIfNeeded() { - if m.evilWallet.UnspentOutputsLeft(evilwallet.Fresh) < minSpamOutputs { - if !m.preparingFunds && m.Config.AutoRequesting { - m.preparingFunds = true - go func() { - switch m.Config.AutoRequestingAmount { - case requestAmount100: - _ = m.evilWallet.RequestFreshFaucetWallet() - case requestAmount10k: - _ = m.evilWallet.RequestFreshBigFaucetWallet() - } - m.preparingFunds = false - }() - } - } -} - -func (m *Mode) prepareFunds() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - printer.DevNetFundsWarning() - - if m.preparingFunds { - printer.FundsCurrentlyPreparedWarning() - return - } - if len(m.Config.clientURLs) == 0 { - printer.NotEnoughClientsWarning(1) - } - numToPrepareStr := "" - err := survey.AskOne(fundsQuestion, &numToPrepareStr) - if err != nil { - fmt.Println(err.Error()) - return - } - switch numToPrepareStr { - case "100": - go func() { - m.preparingFunds = true - err = m.evilWallet.RequestFreshFaucetWallet() - m.preparingFunds = false - }() - case "1000": - go func() { - m.preparingFunds = true - _ = m.evilWallet.RequestFreshBigFaucetWallet() - m.preparingFunds = false - }() - case "cancel": - return - case "5000": - go func() { - m.preparingFunds = true - m.evilWallet.RequestFreshBigFaucetWallets(5) - m.preparingFunds = false - }() - } - - printer.StartedPreparingBlock(numToPrepareStr) -} - -func (m *Mode) spamMenu() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - printer.SpammerSettings() - var submenu string - err := survey.AskOne(spamMenuQuestion, &submenu) - if err != nil { - fmt.Println(err.Error()) - return - } - - m.spamSubMenu(submenu) -} - -func (m *Mode) spamSubMenu(menuType string) { - switch menuType { - case spamDetails: - defaultTimeUnit := timeUnitToString(m.Config.duration) - var spamSurvey spamDetailsSurvey - err := survey.Ask(spamDetailsQuestions(strconv.Itoa(int(m.Config.duration.Seconds())), strconv.Itoa(m.Config.Rate), defaultTimeUnit), &spamSurvey) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.parseSpamDetails(spamSurvey) - - case spamType: - var spamSurvey spamTypeSurvey - err := survey.Ask(spamTypeQuestions(boolToEnable(m.Config.Deep), boolToEnable(m.Config.Reuse)), &spamSurvey) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.parseSpamType(spamSurvey) - - case spamScenario: - scenario := "" - err := survey.AskOne(spamScenarioQuestion(m.Config.Scenario), &scenario) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.parseScenario(scenario) - - case startSpam: - if m.areEnoughFundsAvailable() { - printer.FundsWarning() - m.mainMenu <- types.Void - - return - } - if len(m.activeSpammers) >= maxConcurrentSpams { - printer.MaxSpamWarning() - m.mainMenu <- types.Void - - return - } - m.startSpam() - - case back: - m.mainMenu <- types.Void - return - } - m.action <- actionSpamMenu -} - -func (m *Mode) areEnoughFundsAvailable() bool { - outputsNeeded := m.Config.Rate * int(m.Config.duration.Seconds()) - if m.Config.timeUnit == time.Minute { - outputsNeeded = int(float64(m.Config.Rate) * m.Config.duration.Minutes()) - } - - return m.evilWallet.UnspentOutputsLeft(evilwallet.Fresh) < outputsNeeded && m.Config.Scenario != spammer.TypeBlock -} - -func (m *Mode) startSpam() { - m.spamMutex.Lock() - defer m.spamMutex.Unlock() - - var s *spammer.Spammer - if m.Config.Scenario == spammer.TypeBlock { - s = programs.SpamBlocks(m.evilWallet, m.Config.Rate, time.Second, m.Config.duration, 0, m.Config.UseRateSetter, "") - } else { - scenario, _ := evilwallet.GetScenario(m.Config.Scenario) - s = programs.SpamNestedConflicts(m.evilWallet, m.Config.Rate, time.Second, m.Config.duration, scenario, m.Config.Deep, m.Config.Reuse, m.Config.UseRateSetter, "") - if s == nil { - return - } - } - spamID := m.spammerLog.AddSpam(m.Config) - m.activeSpammers[spamID] = s - go func(id int) { - s.Spam() - m.spamFinished <- id - }(spamID) - printer.SpammerStartedBlock() -} - -func (m *Mode) settingsMenu() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - printer.Settings() - var submenu string - err := survey.AskOne(settingsQuestion, &submenu) - if err != nil { - fmt.Println(err.Error()) - return - } - - m.settingsSubMenu(submenu) -} - -func (m *Mode) settingsSubMenu(menuType string) { - switch menuType { - case settingPreparation: - answer := "" - err := survey.AskOne(autoCreationQuestion, &answer) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.onFundsCreation(answer) - - case settingAddURLs: - var url string - err := survey.AskOne(addURLQuestion, &url) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.validateAndAddURL(url) - - case settingRemoveURLs: - answer := make([]string, 0) - urlsList := m.urlMapToList() - err := survey.AskOne(removeURLQuestion(urlsList), &answer) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.removeUrls(answer) - - case settingUseRateSetter: - answer := "" - err := survey.AskOne(enableRateSetterQuestion, &answer) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.onEnableRateSetter(answer) - - case back: - m.mainMenu <- types.Void - return - } - m.action <- actionSettings -} - -func (m *Mode) validateAndAddURL(url string) { - url = "http://" + url - ok := validateURL(url) - if !ok { - printer.URLWarning() - } else { - if _, ok := m.Config.clientURLs[url]; ok { - printer.URLExists() - return - } - m.Config.clientURLs[url] = types.Void - m.evilWallet.AddClient(url) - } -} - -func (m *Mode) onFundsCreation(answer string) { - if answer == AnswerEnable { - m.Config.AutoRequesting = true - printer.AutoRequestingEnabled() - m.prepareFundsIfNeeded() - } else { - m.Config.AutoRequesting = false - } -} - -func (m *Mode) onEnableRateSetter(answer string) { - if answer == AnswerEnable { - m.Config.UseRateSetter = true - printer.RateSetterEnabled() - } else { - m.Config.UseRateSetter = false - } -} - -func (m *Mode) history() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - printer.History() -} - -func (m *Mode) currentSpams() { - m.stdOutMutex.Lock() - defer m.stdOutMutex.Unlock() - - if len(m.activeSpammers) == 0 { - printer.Println(printer.colorString("There are no currently running spammers.", "red"), 1) - fmt.Println("") - m.mainMenu <- types.Void - - return - } - printer.CurrentSpams() - answer := "" - err := survey.AskOne(currentMenuQuestion, &answer) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - - m.currentSpamsSubMenu(answer) -} - -func (m *Mode) currentSpamsSubMenu(menuType string) { - switch menuType { - case currentSpamRemove: - if len(m.activeSpammers) == 0 { - printer.NoActiveSpammer() - } else { - answer := "" - err := survey.AskOne(removeSpammer, &answer) - if err != nil { - fmt.Println(err.Error()) - m.mainMenu <- types.Void - - return - } - m.parseIDToRemove(answer) - } - - m.action <- actionCurrent - - case back: - m.mainMenu <- types.Void - return - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region parsers ///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -func (m *Mode) parseSpamDetails(details spamDetailsSurvey) { - d, _ := strconv.Atoi(details.SpamDuration) - dur := time.Second * time.Duration(d) - rate, err := strconv.Atoi(details.SpamRate) - if err != nil { - return - } - switch details.TimeUnit { - case mpm: - m.Config.timeUnit = time.Minute - case mps: - m.Config.timeUnit = time.Second - } - m.Config.Rate = rate - m.Config.duration = dur - fmt.Println(details) -} - -func (m *Mode) parseSpamType(spamType spamTypeSurvey) { - deep := enableToBool(spamType.DeepSpamEnabled) - reuse := enableToBool(spamType.ReuseLaterEnabled) - m.Config.Deep = deep - m.Config.Reuse = reuse -} - -func (m *Mode) parseScenario(scenario string) { - m.Config.Scenario = scenario -} - -func (m *Mode) removeUrls(urls []string) { - for _, url := range urls { - if _, ok := m.Config.clientURLs[url]; ok { - delete(m.Config.clientURLs, url) - m.evilWallet.RemoveClient(url) - } - } -} - -func (m *Mode) urlMapToList() (list []string) { - for url := range m.Config.clientURLs { - list = append(list, url) - } - - return -} - -func (m *Mode) parseIDToRemove(answer string) { - m.spamMutex.Lock() - defer m.spamMutex.Unlock() - - id, err := strconv.Atoi(answer) - if err != nil { - return - } - m.summarizeSpam(id) -} - -func (m *Mode) summarizeSpam(id int) { - if s, ok := m.activeSpammers[id]; ok { - m.updateSentStatistic(s, id) - m.spammerLog.SetSpamEndTime(id) - delete(m.activeSpammers, id) - } else { - printer.ClientNotFoundWarning(id) - } -} - -func (m *Mode) updateSentStatistic(s *spammer.Spammer, id int) { - blkSent := s.BlocksSent() - scenariosCreated := s.BatchesPrepared() - if m.spammerLog.SpamDetails(id).Scenario == spammer.TypeBlock { - m.blkSent.Add(blkSent) - } else { - m.txSent.Add(blkSent) - } - m.scenariosSent.Add(scenariosCreated) -} - -// load the config file. -func (m *Mode) loadConfig() { - // open config file - file, err := os.Open(configFilename) - if err != nil { - if !os.IsNotExist(err) { - panic(err) - } - - //nolint:gosec // users should be able to read the file - if err = os.WriteFile("config.json", []byte(configJSON), 0o644); err != nil { - panic(err) - } - if file, err = os.Open("config.json"); err != nil { - panic(err) - } - } - defer file.Close() - - // decode config file - if err = json.NewDecoder(file).Decode(&m.Config); err != nil { - panic(err) - } - // convert urls array to map - if len(m.Config.WebAPI) > 0 { - // rewrite default value - for url := range m.Config.clientURLs { - m.evilWallet.RemoveClient(url) - } - m.Config.clientURLs = make(map[string]types.Empty) - } - for _, url := range m.Config.WebAPI { - m.Config.clientURLs[url] = types.Void - m.evilWallet.AddClient(url) - } - // parse duration - d, err := time.ParseDuration(m.Config.DurationStr) - if err != nil { - d = time.Minute - } - u, err := time.ParseDuration(m.Config.TimeUnitStr) - if err != nil { - u = time.Second - } - m.Config.duration = d - m.Config.timeUnit = u -} - -func (m *Mode) saveConfigsToFile() { - // open config file - file, err := os.Open("config.json") - if err != nil { - panic(err) - } - defer file.Close() - - // update client urls - m.Config.WebAPI = []string{} - for url := range m.Config.clientURLs { - m.Config.WebAPI = append(m.Config.WebAPI, url) - } - - // update duration - m.Config.DurationStr = m.Config.duration.String() - - // update time unit - m.Config.TimeUnitStr = m.Config.timeUnit.String() - - jsonConfigs, err := json.MarshalIndent(m.Config, "", " ") - if err != nil { - panic(err) - } - //nolint:gosec // users should be able to read the file - if err = os.WriteFile("config.json", jsonConfigs, 0o644); err != nil { - panic(err) - } -} - -func enableToBool(e string) bool { - return e == AnswerEnable -} - -func boolToEnable(b bool) string { - if b { - return AnswerEnable - } - - return AnswerDisable -} - -func validateURL(url string) (ok bool) { - _, err := nodeclient.New(url) - if err != nil { - return - } - - return true -} - -func timeUnitToString(d time.Duration) string { - durStr := d.String() - - if strings.Contains(durStr, "s") { - return mps - } - - return mpm -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region SpammerLog /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -var ( - historyHeader = "scenario\tstart\tstop\tdeep\treuse\trate\tduration" - historyLineFmt = "%s\t%s\t%s\t%v\t%v\t%d\t%d\n" -) - -type SpammerLog struct { - spamDetails []InteractiveConfig - spamStartTime []time.Time - spamStopTime []time.Time - mu syncutils.Mutex -} - -func NewSpammerLog() *SpammerLog { - return &SpammerLog{ - spamDetails: make([]InteractiveConfig, 0), - spamStartTime: make([]time.Time, 0), - spamStopTime: make([]time.Time, 0), - } -} - -func (s *SpammerLog) SpamDetails(spamID int) *InteractiveConfig { - return &s.spamDetails[spamID] -} - -func (s *SpammerLog) StartTime(spamID int) time.Time { - return s.spamStartTime[spamID] -} - -func (s *SpammerLog) AddSpam(config InteractiveConfig) (spamID int) { - s.mu.Lock() - defer s.mu.Unlock() - - s.spamDetails = append(s.spamDetails, config) - s.spamStartTime = append(s.spamStartTime, time.Now()) - s.spamStopTime = append(s.spamStopTime, time.Time{}) - - return len(s.spamDetails) - 1 -} - -func (s *SpammerLog) SetSpamEndTime(spamID int) { - s.mu.Lock() - defer s.mu.Unlock() - - s.spamStopTime[spamID] = time.Now() -} - -func newTabWriter(writer io.Writer) *tabwriter.Writer { - return tabwriter.NewWriter(writer, 0, 0, 1, ' ', tabwriter.Debug|tabwriter.TabIndent) -} - -func (s *SpammerLog) LogHistory(lastLines int, writer io.Writer) { - s.mu.Lock() - defer s.mu.Unlock() - - w := newTabWriter(writer) - fmt.Fprintln(w, historyHeader) - idx := len(s.spamDetails) - lastLines + 1 - if idx < 0 { - idx = 0 - } - for i, spam := range s.spamDetails[idx:] { - fmt.Fprintf(w, historyLineFmt, spam.Scenario, s.spamStartTime[i].Format(timeFormat), s.spamStopTime[i].Format(timeFormat), - spam.Deep, spam.Deep, spam.Rate, int(spam.duration.Seconds())) - } - w.Flush() -} - -func (s *SpammerLog) LogSelected(lines []int, writer io.Writer) { - s.mu.Lock() - defer s.mu.Unlock() - - w := newTabWriter(writer) - fmt.Fprintln(w, historyHeader) - for _, idx := range lines { - spam := s.spamDetails[idx] - fmt.Fprintf(w, historyLineFmt, spam.Scenario, s.spamStartTime[idx].Format(timeFormat), s.spamStopTime[idx].Format(timeFormat), - spam.Deep, spam.Deep, spam.Rate, int(spam.duration.Seconds())) - } - w.Flush() -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/interactive/menu.go b/tools/evil-spammer/interactive/menu.go deleted file mode 100644 index 3f91c8c00..000000000 --- a/tools/evil-spammer/interactive/menu.go +++ /dev/null @@ -1,258 +0,0 @@ -package interactive - -import ( - "fmt" - "os" - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" -) - -// region Printer ///////////////////////////////////////////////////////////////////////////////////////////////////////// - -type Printer struct { - mode *Mode -} - -func NewPrinter(mode *Mode) *Printer { - return &Printer{ - mode: mode, - } -} - -func (p *Printer) Println(s string, indent int) { - pre := "█" - for i := 0; i < indent; i++ { - pre += "▓" - } - fmt.Println(pre, s) -} - -func (p *Printer) PrintlnPoint(s string, indent int) { - pre := "" - for i := 0; i < indent; i++ { - pre += " " - } - fmt.Println(pre, "▀▄", s) -} - -func (p *Printer) PrintlnInput(s string) { - fmt.Println("█▓>>", s) -} - -func (p *Printer) PrintThickLine() { - fmt.Println("\n ooo▄▄▓░░▀▀▀▀▄▓▓░░▄▄▄▓▓░░▄▒▄▀█▒▓▄▓▓░░▄▄▒▄▄█▒▓▄▄▀▀▄▓▒▄▄█▒▓▓▀▓▓░░░░█▒▄▄█▒▓░▄▄ooo") - fmt.Println() -} - -func (p *Printer) PrintTopLine() { - fmt.Println("▀▄---------------------------------------------------------------------------▄▀") -} - -func (p *Printer) PrintLine() { - fmt.Println("▄▀___________________________________________________________________________▀▄") -} - -func (p *Printer) printBanner() { - fmt.Println("▓█████ ██▒ █▓ ██▓ ██▓ \n▓█ ▀ ▓██░ █▒▓██▒▓██▒ \n▒███ ▓██ █▒░▒██▒▒██░ \n▒▓█ ▄ ▒██ █░░░██░▒██░ \n░▒████▒ ▒▀█░ ░██░░██████▒ \n░░ ▒░ ░ ░ ▐░ ░▓ ░ ▒░▓ ░ \n ░ ░ ░ ░ ░░ ▒ ░░ ░ ▒ ░ \n ░ ░░ ▒ ░ ░ ░ \n ░ ░ ░ ░ ░ ░ \n ░ \n ██████ ██▓███ ▄▄▄ ███▄ ▄███▓ ███▄ ▄███▓▓█████ ██▀███ \n ▒██ ▒ ▓██░ ██▒▒████▄ ▓██▒▀█▀ ██▒▓██▒▀█▀ ██▒▓█ ▀ ▓██ ▒ ██▒\n ░ ▓██▄ ▓██░ ██▓▒▒██ ▀█▄ ▓██ ▓██░▓██ ▓██░▒███ ▓██ ░▄█ ▒\n ▒ ██▒▒██▄█▓▒ ▒░██▄▄▄▄██ ▒██ ▒██ ▒██ ▒██ ▒▓█ ▄ ▒██▀▀█▄ \n ▒██████▒▒▒██▒ ░ ░ ▓█ ▓██▒▒██▒ ░██▒▒██▒ ░██▒░▒████▒░██▓ ▒██▒\n ▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ▒▒ ▓▒█░░ ▒░ ░ ░░ ▒░ ░ ░░░ ▒░ ░░ ▒▓ ░▒▓░\n ░ ░▒ ░ ░░▒ ░ ▒ ▒▒ ░░ ░ ░░ ░ ░ ░ ░ ░ ░▒ ░ ▒░\n ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░░ ░ \n ░ ░ ░ ░ ░ ░ ░ ░ \n ") - p.PrintThickLine() - p.Println("Interactive mode enabled", level1) - fmt.Println() -} - -func (p *Printer) EvilWalletStatus() { - p.PrintTopLine() - p.Println(p.colorString("Evil Wallet status:", "cyan"), level2) - p.PrintlnPoint(fmt.Sprintf("Available faucet outputs: %d", p.mode.evilWallet.UnspentOutputsLeft(evilwallet.Fresh)), level2) - p.PrintlnPoint(fmt.Sprintf("Available reuse outputs: %d", p.mode.evilWallet.UnspentOutputsLeft(evilwallet.Reuse)), level2) - p.PrintlnPoint(fmt.Sprintf("Spammed blocks: %d", p.mode.blkSent.Load()), level2) - p.PrintlnPoint(fmt.Sprintf("Spammed transactions: %d", p.mode.txSent.Load()), level2) - p.PrintlnPoint(fmt.Sprintf("Spammed scenario batches: %d", p.mode.scenariosSent.Load()), level2) - - p.PrintLine() - fmt.Println() -} - -func (p *Printer) SpammerSettings() { - rateUnit := "[mpm]" - if p.mode.Config.timeUnit == time.Second { - rateUnit = "[mps]" - } - p.PrintTopLine() - p.Println(p.colorString("Current settings:", "cyan"), level1) - p.PrintlnPoint(fmt.Sprintf("Scenario: %s", p.mode.Config.Scenario), level2) - p.PrintlnPoint(fmt.Sprintf("Deep: %v, Reuse: %v", p.mode.Config.Deep, p.mode.Config.Reuse), level2) - p.PrintlnPoint(fmt.Sprintf("Use rate-setter: %v", p.mode.Config.UseRateSetter), level2) - p.PrintlnPoint(fmt.Sprintf("Rate: %d%s, Duration: %d[s]", p.mode.Config.Rate, rateUnit, int(p.mode.Config.duration.Seconds())), level2) - p.PrintLine() - fmt.Println() -} - -func (p *Printer) FarewellBlock() { - p.PrintTopLine() - fmt.Println(" ups... we're forgetting all your private keys ;)") - p.PrintLine() -} - -func (p *Printer) FundsWarning() { - p.Println(p.colorString("Not enough fresh faucet outputs in the wallet to spam!", "red"), level1) - if p.mode.preparingFunds { - p.PrintlnPoint(p.colorString("Funds are currently prepared, wait until outputs will be available.", "yellow"), level2) - } else { - p.PrintlnPoint(p.colorString("Request more outputs manually with 'Prepare faucet funds' option in main menu.", "yellow"), level2) - p.PrintlnPoint(p.colorString("You can also enable auto funds requesting in the settings.", "yellow"), level2) - } - fmt.Println() -} - -func (p *Printer) URLWarning() { - p.Println(p.colorString("Could not connect to provided API endpoint, client not added.", "yellow"), level2) - fmt.Println() -} - -func (p *Printer) URLExists() { - p.Println(p.colorString("The url already exists.", "red"), level2) - fmt.Println() -} - -func (p *Printer) DevNetFundsWarning() { - p.Println(p.colorString("Warning: Preparing 10k outputs and more could take looong time in the DevNet due to high PoW and congestion.", "yellow"), level1) - p.Println(p.colorString("We advice to use 100 option only.", "yellow"), level1) - fmt.Println() -} - -func (p *Printer) NotEnoughClientsWarning(numOfClient int) { - p.Println(p.colorString(fmt.Sprintf("Warning: At least %d clients is recommended if double spends are not allowed from the same node.", numOfClient), "red"), level2) - fmt.Println() -} - -func (p *Printer) clients() { - p.Println(p.colorString("Provided clients:", "cyan"), level1) - for url := range p.mode.Config.clientURLs { - p.PrintlnPoint(url, level2) - } -} - -func (p *Printer) colorString(s string, color string) string { - colorStringReset := "\033[0m" - colorString := "" - switch color { - case "red": - colorString = "\033[31m" - case "cyan": - colorString = "\033[36m" - case "green": - colorString = "\033[32m" - case "yellow": - colorString = "\033[33m" - } - - return colorString + s + colorStringReset -} - -func (p *Printer) Settings() { - p.PrintTopLine() - p.Println(p.colorString("Current settings:", "cyan"), 0) - p.Println(fmt.Sprintf("Auto requesting enabled: %v", p.mode.Config.AutoRequesting), level1) - p.Println(fmt.Sprintf("Use rate-setter: %v", p.mode.Config.UseRateSetter), level1) - p.clients() - p.PrintLine() - fmt.Println() -} - -func (p *Printer) MaxSpamWarning() { - p.Println("", level2) - p.Println(p.colorString("Cannot spam. Maximum number of concurrent spams achieved.", "red"), level1) - p.Println("", level2) - fmt.Println() -} - -func (p *Printer) CurrentSpams() { - p.mode.spamMutex.Lock() - defer p.mode.spamMutex.Unlock() - - lines := make([]string, 0) - for id := range p.mode.activeSpammers { - details := p.mode.spammerLog.SpamDetails(id) - startTime := p.mode.spammerLog.StartTime(id) - endTime := startTime.Add(details.duration) - timeLeft := int(time.Until(endTime).Seconds()) - lines = append(lines, fmt.Sprintf("ID: %d, scenario: %s, time left: %d [s]", id, details.Scenario, timeLeft)) - } - if len(lines) == 0 { - p.Println(p.colorString("There are no currently running spams.", "red"), level1) - return - } - p.Println(p.colorString("Currently active spammers:", "green"), level1) - for _, line := range lines { - p.PrintlnPoint(line, level2) - } - p.PrintLine() - fmt.Println() -} - -func (p *Printer) History() { - p.PrintTopLine() - p.Println(fmt.Sprintf(p.colorString("List of last %d started spams.", "cyan"), lastSpamsShowed), level1) - p.mode.spammerLog.LogHistory(lastSpamsShowed, os.Stdout) - p.PrintLine() - fmt.Println() -} - -func (p *Printer) ClientNotFoundWarning(id int) { - p.Println("", level2) - p.Println(p.colorString(fmt.Sprintf("No spam with id %d found. Nothing removed.", id), "red"), level1) - p.Println("", level2) - - fmt.Println() -} - -func (p *Printer) NoActiveSpammer() { - p.Println("", level2) - p.Println(p.colorString("No active spammers.", "red"), level1) - p.Println("", level2) - - fmt.Println() -} - -func (p *Printer) FundsCurrentlyPreparedWarning() { - p.Println("", level2) - p.Println(p.colorString("Funds are currently prepared. Try again later.", "red"), level1) - p.Println("", level2) - fmt.Println() -} - -func (p *Printer) StartedPreparingBlock(numToPrepareStr string) { - p.Println("", level2) - p.Println(p.colorString("Start preparing "+numToPrepareStr+" faucet outputs.", "green"), level1) - p.Println("", level2) - fmt.Println() -} - -func (p *Printer) SpammerStartedBlock() { - p.Println("", level2) - p.Println(p.colorString("Spammer started", "green"), level1) - p.Println("", level2) - fmt.Println() -} - -func (p *Printer) AutoRequestingEnabled() { - p.Println("", level2) - p.Println(p.colorString(fmt.Sprintf("Automatic funds requesting enabled. %s outputs will be requested whenever output amout will go below %d.", p.mode.Config.AutoRequestingAmount, minSpamOutputs), "green"), level1) - p.Println(p.colorString("The size of the request can be changed in the config file. Possible values: '100', '10000'", "yellow"), level1) - p.Println("", level2) -} - -func (p *Printer) RateSetterEnabled() { - p.Println("", level2) - p.Println(p.colorString("Enable waiting the rate-setter estimate.", "green"), level1) - p.Println(p.colorString(" Enabling this will force the spammer to sleep certain amount of time based on node's rate-setter estimate.", "yellow"), level1) - p.Println("", level2) -} - -const ( - level1 = 1 - level2 = 2 -) - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/interactive/survey.go b/tools/evil-spammer/interactive/survey.go deleted file mode 100644 index 04d0530b4..000000000 --- a/tools/evil-spammer/interactive/survey.go +++ /dev/null @@ -1,151 +0,0 @@ -package interactive - -import ( - "strconv" - - "github.com/AlecAivazis/survey/v2" - - "github.com/iotaledger/hive.go/ierrors" -) - -// region survey ////////////////////////////////////////////////////////////////////////////////////////////// - -var actionQuestion = &survey.Select{ - Message: "Choose an action", - Options: actions, - Default: "Evil wallet details", -} - -var fundsQuestion = &survey.Select{ - Message: "How many fresh outputs you want to create?", - Options: outputNumbers, - Default: "100", -} - -var settingsQuestion = &survey.Select{ - Message: "Available settings:", - Options: settingsMenuOptions, - Default: settingPreparation, -} - -var autoCreationQuestion = &survey.Select{ - Message: "Enable automatic faucet output creation", - Options: confirms, - Default: "enable", -} - -var enableRateSetterQuestion = &survey.Select{ - Message: "Enable using rate-setter estimate", - Options: confirms, - Default: "enable", -} - -var addURLQuestion = &survey.Input{ - Message: "http://", - Default: "enable", -} - -var removeURLQuestion = func(urls []string) *survey.MultiSelect { - return &survey.MultiSelect{ - Message: "Select urls that should be removed.", - Options: urls, - } -} - -type spamTypeSurvey struct { - DeepSpamEnabled string - ReuseLaterEnabled string -} - -var spamTypeQuestions = func(defaultDeep, defaultReuse string) []*survey.Question { - return []*survey.Question{ - { - Name: "deepSpamEnabled", - Prompt: &survey.Select{ - Message: "Deep spam", - Options: confirms, - Default: defaultDeep, - Help: "Uses outputs generated during the spam, to create deep UTXO and conflict structures.", - }, - }, - { - Name: "reuseLaterEnabled", - Prompt: &survey.Select{ - Message: "Reuse outputs", - Options: confirms, - Default: defaultReuse, - Help: "Remember created outputs (add them to reuse outputs and use in future deep spams).", - }, - }, - } -} - -type spamDetailsSurvey struct { - SpamDuration string - SpamRate string - TimeUnit string -} - -var spamDetailsQuestions = func(defaultDuration, defaultRate, defaultTimeUnit string) []*survey.Question { - return []*survey.Question{ - { - Name: "spamDuration", - Prompt: &survey.Input{ - Message: "Spam duration in [s].", - Default: defaultDuration, - }, - }, - { - Name: "timeUnit", - Prompt: &survey.Select{ - Message: "Choose time unit for the spam", - Options: timeUnits, - Default: defaultTimeUnit, - }, - }, - { - Name: "spamRate", - Prompt: &survey.Input{ - Message: "Spam rate", - Default: defaultRate, - }, - Validate: func(val interface{}) error { - if str, ok := val.(string); ok { - _, err := strconv.Atoi(str) - if err == nil { - return nil - } - - return ierrors.New("Incorrect spam rate") - } - - return nil - }, - }, - } -} - -var spamScenarioQuestion = func(defaultScenario string) *survey.Select { - return &survey.Select{ - Message: "Choose a spam scenario", - Options: scenarios, - Default: defaultScenario, - } -} - -var spamMenuQuestion = &survey.Select{ - Message: "Spam settings", - Options: spamMenuOptions, - Default: startSpam, -} - -var currentMenuQuestion = &survey.Select{ - Options: currentSpamOptions, - Default: back, -} - -var removeSpammer = &survey.Input{ - Message: "Type in id of the spammer you wish to stop.", -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/logger/logger.go b/tools/evil-spammer/logger/logger.go deleted file mode 100644 index 20ce465a3..000000000 --- a/tools/evil-spammer/logger/logger.go +++ /dev/null @@ -1,24 +0,0 @@ -package logger - -import ( - "fmt" - - "github.com/iotaledger/hive.go/app/configuration" - appLogger "github.com/iotaledger/hive.go/app/logger" - "github.com/iotaledger/hive.go/logger" -) - -var New = logger.NewLogger - -func init() { - config := configuration.New() - err := config.Set(logger.ConfigurationKeyOutputPaths, []string{"evil-spammer.log", "stdout"}) - if err != nil { - fmt.Println(err) - return - } - if err = appLogger.InitGlobalLogger(config); err != nil { - panic(err) - } - logger.SetLevel(logger.LevelDebug) -} diff --git a/tools/evil-spammer/main.go b/tools/evil-spammer/main.go deleted file mode 100644 index b94687059..000000000 --- a/tools/evil-spammer/main.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "github.com/iotaledger/iota-core/tools/evil-spammer/accountwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/interactive" - "github.com/iotaledger/iota-core/tools/evil-spammer/logger" - "github.com/iotaledger/iota-core/tools/evil-spammer/programs" -) - -var ( - log = logger.New("main") - optionFlagSet = flag.NewFlagSet("script flag set", flag.ExitOnError) -) - -func main() { - help := parseFlags() - - if help { - fmt.Println("Usage of the Evil Spammer tool, provide the first argument for the selected mode:\n" + - "'interactive' - enters the interactive mode.\n" + - "'basic' - can be parametrized with additional flags to run one time spammer. Run 'evil-wallet basic -h' for the list of possible flags.\n" + - "'accounts' - tool for account creation and transition. Run 'evil-wallet accounts -h' for the list of possible flags.\n" + - "'quick' - runs simple stress test: tx spam -> blk spam -> ds spam. Run 'evil-wallet quick -h' for the list of possible flags.") - - return - } - // init account wallet - var accWallet *accountwallet.AccountWallet - var err error - if Script == "basic" || Script == "accounts" { - // read config here - config := accountwallet.LoadConfiguration() - // load wallet - accWallet, err = accountwallet.Run(config) - if err != nil { - log.Error(err) - log.Errorf("Failed to init account wallet, exitting...") - - return - } - - // save wallet and latest faucet output - defer func() { - err = accountwallet.SaveState(accWallet) - if err != nil { - log.Errorf("Error while saving wallet state: %v", err) - } - accountwallet.SaveConfiguration(config) - - }() - } - // run selected test scenario - switch Script { - case "interactive": - interactive.Run() - case "basic": - programs.CustomSpam(&customSpamParams, accWallet) - case "accounts": - accountsSubcommands(accWallet, accountsSubcommandsFlags) - case "quick": - programs.QuickTest(&quickTestParams) - // case SpammerTypeCommitments: - // CommitmentsSpam(&commitmentsSpamParams) - default: - log.Warnf("Unknown parameter for script, possible values: interactive, basic, accounts, quick") - } -} - -func accountsSubcommands(wallet *accountwallet.AccountWallet, subcommands []accountwallet.AccountSubcommands) { - for _, sub := range subcommands { - accountsSubcommand(wallet, sub) - } -} - -func accountsSubcommand(wallet *accountwallet.AccountWallet, sub accountwallet.AccountSubcommands) { - switch sub.Type() { - case accountwallet.OperationCreateAccount: - log.Infof("Run subcommand: %s, with parametetr set: %v", accountwallet.OperationCreateAccount.String(), sub) - params := sub.(*accountwallet.CreateAccountParams) - accountID, err := wallet.CreateAccount(params) - if err != nil { - log.Errorf("Error creating account: %v", err) - - return - } - log.Infof("Created account %s with %d tokens", accountID, params.Amount) - case accountwallet.OperationDestroyAccound: - log.Infof("Run subcommand: %s, with parametetr set: %v", accountwallet.OperationDestroyAccound, sub) - params := sub.(*accountwallet.DestroyAccountParams) - err := wallet.DestroyAccount(params) - if err != nil { - log.Errorf("Error destroying account: %v", err) - - return - } - case accountwallet.OperationListAccounts: - err := wallet.ListAccount() - if err != nil { - log.Errorf("Error listing accounts: %v", err) - - return - } - case accountwallet.OperationAllotAccount: - log.Infof("Run subcommand: %s, with parametetr set: %v", accountwallet.OperationAllotAccount, sub) - params := sub.(*accountwallet.AllotAccountParams) - err := wallet.AllotToAccount(params) - if err != nil { - log.Errorf("Error allotting account: %v", err) - - return - } - } -} diff --git a/tools/evil-spammer/models/connector.go b/tools/evil-spammer/models/connector.go deleted file mode 100644 index 3fd408cf3..000000000 --- a/tools/evil-spammer/models/connector.go +++ /dev/null @@ -1,328 +0,0 @@ -package models - -import ( - "context" - "time" - - "github.com/google/martian/log" - - "github.com/iotaledger/hive.go/runtime/options" - "github.com/iotaledger/hive.go/runtime/syncutils" - "github.com/iotaledger/iota-core/pkg/model" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/builder" - "github.com/iotaledger/iota.go/v4/nodeclient" - "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" -) - -type ServerInfo struct { - Healthy bool - Version string -} - -type ServerInfos []*ServerInfo - -type Connector interface { - // ServersStatuses retrieves the connected server status for each client. - ServersStatuses() ServerInfos - // ServerStatus retrieves the connected server status. - ServerStatus(cltIdx int) (status *ServerInfo, err error) - // Clients returns list of all clients. - Clients(...bool) []Client - // GetClients returns the numOfClt client instances that were used the longest time ago. - GetClients(numOfClt int) []Client - // AddClient adds a client to WebClients based on provided GoShimmerAPI url. - AddClient(url string, setters ...options.Option[WebClient]) - // RemoveClient removes a client with the provided url from the WebClients. - RemoveClient(url string) - // GetClient returns the client instance that was used the longest time ago. - GetClient() Client -} - -// WebClients is responsible for handling connections via GoShimmerAPI. -type WebClients struct { - clients []*WebClient - urls []string - - // helper variable indicating which clt was recently used, useful for double, triple,... spends - lastUsed int - - mu syncutils.Mutex -} - -// NewWebClients creates Connector from provided GoShimmerAPI urls. -func NewWebClients(urls []string, setters ...options.Option[WebClient]) *WebClients { - clients := make([]*WebClient, len(urls)) - var err error - for i, url := range urls { - clients[i], err = NewWebClient(url, setters...) - if err != nil { - log.Errorf("failed to create client for url %s: %s", url, err) - - return nil - } - } - - return &WebClients{ - clients: clients, - urls: urls, - lastUsed: -1, - } -} - -// ServersStatuses retrieves the connected server status for each client. -func (c *WebClients) ServersStatuses() ServerInfos { - status := make(ServerInfos, len(c.clients)) - - for i := range c.clients { - status[i], _ = c.ServerStatus(i) - } - - return status -} - -// ServerStatus retrieves the connected server status. -func (c *WebClients) ServerStatus(cltIdx int) (status *ServerInfo, err error) { - response, err := c.clients[cltIdx].client.Info(context.Background()) - if err != nil { - return nil, err - } - - return &ServerInfo{ - Healthy: response.Status.IsHealthy, - Version: response.Version, - }, nil -} - -// Clients returns list of all clients. -func (c *WebClients) Clients(...bool) []Client { - clients := make([]Client, len(c.clients)) - for i, c := range c.clients { - clients[i] = c - } - - return clients -} - -// GetClients returns the numOfClt client instances that were used the longest time ago. -func (c *WebClients) GetClients(numOfClt int) []Client { - c.mu.Lock() - defer c.mu.Unlock() - - clts := make([]Client, numOfClt) - - for i := range clts { - clts[i] = c.getClient() - } - - return clts -} - -// getClient returns the client instance that was used the longest time ago, not protected by mutex. -func (c *WebClients) getClient() Client { - if c.lastUsed >= len(c.clients)-1 { - c.lastUsed = 0 - } else { - c.lastUsed++ - } - - return c.clients[c.lastUsed] -} - -// GetClient returns the client instance that was used the longest time ago. -func (c *WebClients) GetClient() Client { - c.mu.Lock() - defer c.mu.Unlock() - - return c.getClient() -} - -// AddClient adds client to WebClients based on provided GoShimmerAPI url. -func (c *WebClients) AddClient(url string, setters ...options.Option[WebClient]) { - c.mu.Lock() - defer c.mu.Unlock() - - clt, err := NewWebClient(url, setters...) - if err != nil { - log.Errorf("failed to create client for url %s: %s", url, err) - - return - } - c.clients = append(c.clients, clt) - c.urls = append(c.urls, url) -} - -// RemoveClient removes client with the provided url from the WebClients. -func (c *WebClients) RemoveClient(url string) { - c.mu.Lock() - defer c.mu.Unlock() - - indexToRemove := -1 - for i, u := range c.urls { - if u == url { - indexToRemove = i - break - } - } - if indexToRemove == -1 { - return - } - c.clients = append(c.clients[:indexToRemove], c.clients[indexToRemove+1:]...) - c.urls = append(c.urls[:indexToRemove], c.urls[indexToRemove+1:]...) -} - -type Client interface { - Client() *nodeclient.Client - Indexer() (nodeclient.IndexerClient, error) - // URL returns a client API url. - URL() (cltID string) - // PostBlock sends a block to the Tangle via a given client. - PostBlock(block *iotago.ProtocolBlock) (iotago.BlockID, error) - // PostData sends the given data (payload) by creating a block in the backend. - PostData(data []byte) (blkID string, err error) - // GetTransactionConfirmationState returns the AcceptanceState of a given transaction ID. - GetTransactionConfirmationState(txID iotago.TransactionID) string - // GetOutput gets the output of a given outputID. - GetOutput(outputID iotago.OutputID) iotago.Output - // GetOutputConfirmationState gets the first unspent outputs of a given address. - GetOutputConfirmationState(outputID iotago.OutputID) string - // GetTransaction gets the transaction. - GetTransaction(txID iotago.TransactionID) (resp *iotago.SignedTransaction, err error) - // GetBlockIssuance returns the latest commitment and data needed to create a new block. - GetBlockIssuance(...iotago.SlotIndex) (resp *apimodels.IssuanceBlockHeaderResponse, err error) - // GetCongestion returns congestion data such as rmc or issuing readiness. - GetCongestion(id iotago.AccountID) (resp *apimodels.CongestionResponse, err error) - - iotago.APIProvider -} - -// WebClient contains a GoShimmer web API to interact with a node. -type WebClient struct { - client *nodeclient.Client - url string -} - -func (c *WebClient) Client() *nodeclient.Client { - return c.client -} - -func (c *WebClient) Indexer() (nodeclient.IndexerClient, error) { - return c.client.Indexer(context.Background()) -} - -func (c *WebClient) APIForVersion(version iotago.Version) (iotago.API, error) { - return c.client.APIForVersion(version) -} - -func (c *WebClient) APIForTime(t time.Time) iotago.API { - return c.client.APIForTime(t) -} - -func (c *WebClient) APIForSlot(index iotago.SlotIndex) iotago.API { - return c.client.APIForSlot(index) -} - -func (c *WebClient) APIForEpoch(index iotago.EpochIndex) iotago.API { - return c.client.APIForEpoch(index) -} - -func (c *WebClient) CommittedAPI() iotago.API { - return c.client.CommittedAPI() -} - -func (c *WebClient) LatestAPI() iotago.API { - return c.client.LatestAPI() -} - -// URL returns a client API Url. -func (c *WebClient) URL() string { - return c.url -} - -// NewWebClient creates Connector from provided iota-core API urls. -func NewWebClient(url string, opts ...options.Option[WebClient]) (*WebClient, error) { - var initErr error - return options.Apply(&WebClient{ - url: url, - }, opts, func(w *WebClient) { - w.client, initErr = nodeclient.New(w.url) - }), initErr -} - -func (c *WebClient) PostBlock(block *iotago.ProtocolBlock) (blockID iotago.BlockID, err error) { - return c.client.SubmitBlock(context.Background(), block) -} - -// PostData sends the given data (payload) by creating a block in the backend. -func (c *WebClient) PostData(data []byte) (blkID string, err error) { - blockBuilder := builder.NewBasicBlockBuilder(c.client.CommittedAPI()) - blockBuilder.IssuingTime(time.Time{}) - - blockBuilder.Payload(&iotago.TaggedData{ - Tag: data, - }) - - blk, err := blockBuilder.Build() - if err != nil { - return iotago.EmptyBlockID.ToHex(), err - } - - id, err := c.client.SubmitBlock(context.Background(), blk) - if err != nil { - return - } - - return id.ToHex(), nil -} - -// GetOutputConfirmationState gets the first unspent outputs of a given address. -func (c *WebClient) GetOutputConfirmationState(outputID iotago.OutputID) string { - txID := outputID.TransactionID() - - return c.GetTransactionConfirmationState(txID) -} - -// GetOutput gets the output of a given outputID. -func (c *WebClient) GetOutput(outputID iotago.OutputID) iotago.Output { - res, err := c.client.OutputByID(context.Background(), outputID) - if err != nil { - return nil - } - - return res -} - -// GetTransactionConfirmationState returns the AcceptanceState of a given transaction ID. -func (c *WebClient) GetTransactionConfirmationState(txID iotago.TransactionID) string { - resp, err := c.client.TransactionIncludedBlockMetadata(context.Background(), txID) - if err != nil { - return "" - } - - return resp.TransactionState -} - -// GetTransaction gets the transaction. -func (c *WebClient) GetTransaction(txID iotago.TransactionID) (tx *iotago.SignedTransaction, err error) { - resp, err := c.client.TransactionIncludedBlock(context.Background(), txID) - if err != nil { - return - } - - modelBlk, err := model.BlockFromBlock(resp) - if err != nil { - return - } - - tx, _ = modelBlk.SignedTransaction() - - return tx, nil -} - -func (c *WebClient) GetBlockIssuance(slotIndex ...iotago.SlotIndex) (resp *apimodels.IssuanceBlockHeaderResponse, err error) { - return c.client.BlockIssuance(context.Background(), slotIndex...) -} - -func (c *WebClient) GetCongestion(accountID iotago.AccountID) (resp *apimodels.CongestionResponse, err error) { - return c.client.Congestion(context.Background(), accountID) -} diff --git a/tools/evil-spammer/models/output.go b/tools/evil-spammer/models/output.go deleted file mode 100644 index 539da3a19..000000000 --- a/tools/evil-spammer/models/output.go +++ /dev/null @@ -1,88 +0,0 @@ -package models - -import ( - "crypto/ed25519" - - "github.com/iotaledger/iota-core/pkg/blockhandler" - iotago "github.com/iotaledger/iota.go/v4" - "github.com/iotaledger/iota.go/v4/nodeclient/apimodels" -) - -// Input contains details of an input. -type Input struct { - OutputID iotago.OutputID - Address iotago.Address -} - -// Output contains details of an output ID. -type Output struct { - OutputID iotago.OutputID - Address iotago.Address - Index uint64 - Balance iotago.BaseToken - - OutputStruct iotago.Output -} - -// Outputs is a list of Output. -type Outputs []*Output - -type AccountStatus uint8 - -const ( - AccountPending AccountStatus = iota - AccountReady -) - -type AccountData struct { - Alias string - Status AccountStatus - Account blockhandler.Account - OutputID iotago.OutputID - Index uint64 -} - -type AccountState struct { - Alias string `serix:"0,lengthPrefixType=uint8"` - AccountID iotago.AccountID `serix:"2"` - PrivateKey ed25519.PrivateKey `serix:"3,lengthPrefixType=uint8"` - OutputID iotago.OutputID `serix:"4"` - Index uint64 `serix:"5"` -} - -func AccountStateFromAccountData(acc *AccountData) *AccountState { - return &AccountState{ - Alias: acc.Alias, - AccountID: acc.Account.ID(), - PrivateKey: acc.Account.PrivateKey(), - OutputID: acc.OutputID, - Index: acc.Index, - } -} - -func (a *AccountState) ToAccountData() *AccountData { - return &AccountData{ - Alias: a.Alias, - Account: blockhandler.NewEd25519Account(a.AccountID, a.PrivateKey), - OutputID: a.OutputID, - Index: a.Index, - } -} - -type PayloadIssuanceData struct { - Payload iotago.Payload - CongestionResponse *apimodels.CongestionResponse -} - -type AllotmentStrategy uint8 - -const ( - AllotmentStrategyNone AllotmentStrategy = iota - AllotmentStrategyMinCost - AllotmentStrategyAll -) - -type IssuancePaymentStrategy struct { - AllotmentStrategy AllotmentStrategy - IssuerAlias string -} diff --git a/tools/evil-spammer/parse.go b/tools/evil-spammer/parse.go deleted file mode 100644 index b62b85292..000000000 --- a/tools/evil-spammer/parse.go +++ /dev/null @@ -1,494 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "strconv" - "strings" - "time" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/iota-core/tools/evil-spammer/accountwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" -) - -func parseFlags() (help bool) { - if len(os.Args) <= 1 { - return true - } - script := os.Args[1] - - Script = script - log.Infof("script %s", Script) - - switch Script { - case "basic": - parseBasicSpamFlags() - case "accounts": - // pass subcommands - subcommands := make([]string, 0) - if len(os.Args) > 2 { - subcommands = os.Args[2:] - } - splitedCmds := readSubcommandsAndFlagSets(subcommands) - accountsSubcommandsFlags = parseAccountTestFlags(splitedCmds) - - case "quick": - parseQuickTestFlags() - // case SpammerTypeCommitments: - // parseCommitmentsSpamFlags() - } - if Script == "help" || Script == "-h" || Script == "--help" { - return true - } - - return -} - -func parseOptionFlagSet(flagSet *flag.FlagSet, args ...[]string) { - commands := os.Args[2:] - if len(args) > 0 { - commands = args[0] - } - err := flagSet.Parse(commands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - return - } -} - -func parseBasicSpamFlags() { - urls := optionFlagSet.String("urls", "", "API urls for clients used in test separated with commas") - spamTypes := optionFlagSet.String("spammer", "", "Spammers used during test. Format: strings separated with comma, available options: 'blk' - block,"+ - " 'tx' - transaction, 'ds' - double spends spammers, 'nds' - n-spends spammer, 'custom' - spams with provided scenario") - rate := optionFlagSet.String("rate", "", "Spamming rate for provided 'spammer'. Format: numbers separated with comma, e.g. 10,100,1 if three spammers were provided for 'spammer' parameter.") - duration := optionFlagSet.String("duration", "", "Spam duration. Cannot be combined with flag 'blkNum'. Format: separated by commas list of decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'.\n Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'.") - blkNum := optionFlagSet.String("blkNum", "", "Spam duration in seconds. Cannot be combined with flag 'duration'. Format: numbers separated with comma, e.g. 10,100,1 if three spammers were provided for 'spammer' parameter.") - timeunit := optionFlagSet.Duration("tu", customSpamParams.TimeUnit, "Time unit for the spamming rate. Format: decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'.\n Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'.") - delayBetweenConflicts := optionFlagSet.Duration("dbc", customSpamParams.DelayBetweenConflicts, "delayBetweenConflicts - Time delay between conflicts in double spend spamming") - scenario := optionFlagSet.String("scenario", "", "Name of the EvilBatch that should be used for the spam. By default uses Scenario1. Possible scenarios can be found in evilwallet/customscenarion.go.") - deepSpam := optionFlagSet.Bool("deep", customSpamParams.DeepSpam, "Enable the deep spam, by reusing outputs created during the spam.") - nSpend := optionFlagSet.Int("nSpend", customSpamParams.NSpend, "Number of outputs to be spent in n-spends spammer for the spammer type needs to be set to 'ds'. Default value is 2 for double-spend.") - account := optionFlagSet.String("account", "", "Account alias to be used for the spam. Account should be created first with accounts tool.") - - parseOptionFlagSet(optionFlagSet) - - if *urls != "" { - parsedUrls := parseCommaSepString(*urls) - quickTestParams.ClientURLs = parsedUrls - customSpamParams.ClientURLs = parsedUrls - } - if *spamTypes != "" { - parsedSpamTypes := parseCommaSepString(*spamTypes) - customSpamParams.SpamTypes = parsedSpamTypes - } - if *rate != "" { - parsedRates := parseCommaSepInt(*rate) - customSpamParams.Rates = parsedRates - } - if *duration != "" { - parsedDurations := parseDurations(*duration) - customSpamParams.Durations = parsedDurations - } - if *blkNum != "" { - parsedBlkNums := parseCommaSepInt(*blkNum) - customSpamParams.BlkToBeSent = parsedBlkNums - } - if *scenario != "" { - conflictBatch, ok := evilwallet.GetScenario(*scenario) - if ok { - customSpamParams.Scenario = conflictBatch - } - } - - customSpamParams.NSpend = *nSpend - customSpamParams.DeepSpam = *deepSpam - customSpamParams.TimeUnit = *timeunit - customSpamParams.DelayBetweenConflicts = *delayBetweenConflicts - if *account != "" { - customSpamParams.AccountAlias = *account - } - - // fill in unused parameter: blkNum or duration with zeros - if *duration == "" && *blkNum != "" { - customSpamParams.Durations = make([]time.Duration, len(customSpamParams.BlkToBeSent)) - } - if *blkNum == "" && *duration != "" { - customSpamParams.BlkToBeSent = make([]int, len(customSpamParams.Durations)) - } -} - -func parseQuickTestFlags() { - urls := optionFlagSet.String("urls", "", "API urls for clients used in test separated with commas") - rate := optionFlagSet.Int("rate", quickTestParams.Rate, "The spamming rate") - duration := optionFlagSet.Duration("duration", quickTestParams.Duration, "Duration of the spam. Format: decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'.\n Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'.") - timeunit := optionFlagSet.Duration("tu", quickTestParams.TimeUnit, "Time unit for the spamming rate. Format: decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'.\n Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'.") - delayBetweenConflicts := optionFlagSet.Duration("dbc", quickTestParams.DelayBetweenConflicts, "delayBetweenConflicts - Time delay between conflicts in double spend spamming") - verifyLedger := optionFlagSet.Bool("verify", quickTestParams.VerifyLedger, "Set to true if verify ledger script should be run at the end of the test") - - parseOptionFlagSet(optionFlagSet) - - if *urls != "" { - parsedUrls := parseCommaSepString(*urls) - quickTestParams.ClientURLs = parsedUrls - } - quickTestParams.Rate = *rate - quickTestParams.Duration = *duration - quickTestParams.TimeUnit = *timeunit - quickTestParams.DelayBetweenConflicts = *delayBetweenConflicts - quickTestParams.VerifyLedger = *verifyLedger -} - -// readSubcommandsAndFlagSets splits the subcommands on multiple flag sets. -func readSubcommandsAndFlagSets(subcommands []string) [][]string { - prevSplitIndex := 0 - subcommandsSplit := make([][]string, 0) - if len(subcommands) == 0 { - return nil - } - - // mainCmd := make([]string, 0) - for index := 0; index < len(subcommands); index++ { - validCommand := accountwallet.AvailableCommands(subcommands[index]) - - if !strings.HasPrefix(subcommands[index], "--") && validCommand { - if index != 0 { - subcommandsSplit = append(subcommandsSplit, subcommands[prevSplitIndex:index]) - } - prevSplitIndex = index - } - } - subcommandsSplit = append(subcommandsSplit, subcommands[prevSplitIndex:]) - - return subcommandsSplit -} - -func parseAccountTestFlags(splitedCmds [][]string) []accountwallet.AccountSubcommands { - parsedCmds := make([]accountwallet.AccountSubcommands, 0) - - for _, cmds := range splitedCmds { - switch cmds[0] { - case "create": - createAccountParams, err := parseCreateAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, createAccountParams) - case "convert": - convertAccountParams, err := parseConvertAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, convertAccountParams) - case "destroy": - destroyAccountParams, err := parseDestroyAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, destroyAccountParams) - case "allot": - allotAccountParams, err := parseAllotAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, allotAccountParams) - case "delegate": - delegatingAccountParams, err := parseDelegateAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, delegatingAccountParams) - case "stake": - stakingAccountParams, err := parseStakeAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, stakingAccountParams) - case "update": - updateAccountParams, err := parseUpdateAccountFlags(cmds[1:]) - if err != nil { - continue - } - - parsedCmds = append(parsedCmds, updateAccountParams) - case "list": - parsedCmds = append(parsedCmds, &accountwallet.NoAccountParams{ - Operation: accountwallet.OperationListAccounts, - }) - default: - accountUsage() - return nil - } - } - - return parsedCmds -} - -func accountUsage() { - fmt.Println("Usage for accounts [COMMAND] [FLAGS], multiple commands can be chained together.") - fmt.Printf("COMMAND: %s\n", accountwallet.CmdNameCreateAccount) - parseCreateAccountFlags(nil) - - fmt.Printf("COMMAND: %s\n", accountwallet.CmdNameConvertAccount) - parseConvertAccountFlags(nil) - - fmt.Printf("COMMAND: %s\n", accountwallet.CmdNameDestroyAccount) - parseDestroyAccountFlags(nil) - - fmt.Printf("COMMAND: %s\n", accountwallet.CmdNameAllotAccount) - parseAllotAccountFlags(nil) - - fmt.Printf("COMMAND: %s\n", accountwallet.CmdNameDelegateAccount) - parseDelegateAccountFlags(nil) - - fmt.Printf("COMMAND: %s\n", accountwallet.CmdNameStakeAccount) - parseStakeAccountFlags(nil) -} - -func parseCreateAccountFlags(subcommands []string) (*accountwallet.CreateAccountParams, error) { - flagSet := flag.NewFlagSet("create", flag.ExitOnError) - alias := flagSet.String("alias", "", "The alias name of new created account") - amount := flagSet.Int64("amount", 1000, "The amount to be transfered to the new account") - noBIF := flagSet.Bool("noBIF", false, "Create account without Block Issuer Feature") - implicit := flagSet.Bool("implicit", false, "Create an implicit account") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing create account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.CreateAccountParams{ - Alias: *alias, - Amount: uint64(*amount), - NoBIF: *noBIF, - Implicit: *implicit, - }, nil -} - -func parseConvertAccountFlags(subcommands []string) (*accountwallet.ConvertAccountParams, error) { - flagSet := flag.NewFlagSet("convert", flag.ExitOnError) - alias := flagSet.String("alias", "", "The implicit account to be converted to full account") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing convert account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.ConvertAccountParams{ - AccountAlias: *alias, - }, nil -} - -func parseDestroyAccountFlags(subcommands []string) (*accountwallet.DestroyAccountParams, error) { - flagSet := flag.NewFlagSet("destroy", flag.ExitOnError) - alias := flagSet.String("alias", "", "The alias name of the account to be destroyed") - expirySlot := flagSet.Int64("expirySlot", 0, "The expiry slot of the account to be destroyed") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing destroy account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.DestroyAccountParams{ - AccountAlias: *alias, - ExpirySlot: uint64(*expirySlot), - }, nil -} - -func parseAllotAccountFlags(subcommands []string) (*accountwallet.AllotAccountParams, error) { - flagSet := flag.NewFlagSet("allot", flag.ExitOnError) - from := flagSet.String("from", "", "The alias name of the account to allot mana from") - to := flagSet.String("to", "", "The alias of the account to allot mana to") - amount := flagSet.Int64("amount", 1000, "The amount of mana to allot") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing allot account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.AllotAccountParams{ - From: *from, - To: *to, - Amount: uint64(*amount), - }, nil -} - -func parseStakeAccountFlags(subcommands []string) (*accountwallet.StakeAccountParams, error) { - flagSet := flag.NewFlagSet("stake", flag.ExitOnError) - alias := flagSet.String("alias", "", "The alias name of the account to stake") - amount := flagSet.Int64("amount", 100, "The amount of tokens to stake") - fixedCost := flagSet.Int64("fixedCost", 0, "The fixed cost of the account to stake") - startEpoch := flagSet.Int64("startEpoch", 0, "The start epoch of the account to stake") - endEpoch := flagSet.Int64("endEpoch", 0, "The end epoch of the account to stake") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing staking account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.StakeAccountParams{ - Alias: *alias, - Amount: uint64(*amount), - FixedCost: uint64(*fixedCost), - StartEpoch: uint64(*startEpoch), - EndEpoch: uint64(*endEpoch), - }, nil -} - -func parseDelegateAccountFlags(subcommands []string) (*accountwallet.DelegateAccountParams, error) { - flagSet := flag.NewFlagSet("delegate", flag.ExitOnError) - from := flagSet.String("from", "", "The alias name of the account to delegate mana from") - to := flagSet.String("to", "", "The alias of the account to delegate mana to") - amount := flagSet.Int64("amount", 100, "The amount of mana to delegate") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing delegate account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.DelegateAccountParams{ - From: *from, - To: *to, - Amount: uint64(*amount), - }, nil -} - -func parseUpdateAccountFlags(subcommands []string) (*accountwallet.UpdateAccountParams, error) { - flagSet := flag.NewFlagSet("update", flag.ExitOnError) - alias := flagSet.String("alias", "", "The alias name of the account to update") - bik := flagSet.String("bik", "", "The block issuer key (in hex) to add") - amount := flagSet.Int64("addamount", 100, "The amount of token to add") - mana := flagSet.Int64("addmana", 100, "The amount of mana to add") - expirySlot := flagSet.Int64("expirySlot", 0, "Update the expiry slot of the account") - - if subcommands == nil { - flagSet.Usage() - - return nil, ierrors.Errorf("no subcommands") - } - - log.Infof("Parsing update account flags, subcommands: %v", subcommands) - err := flagSet.Parse(subcommands) - if err != nil { - log.Errorf("Cannot parse first `script` parameter") - - return nil, ierrors.Wrap(err, "cannot parse first `script` parameter") - } - - return &accountwallet.UpdateAccountParams{ - Alias: *alias, - BlockIssuerKey: *bik, - Amount: uint64(*amount), - Mana: uint64(*mana), - ExpirySlot: uint64(*expirySlot), - }, nil -} - -// func parseCommitmentsSpamFlags() { -// commitmentType := optionFlagSet.String("type", commitmentsSpamParams.CommitmentType, "Type of commitment spam. Possible values: 'latest' - valid commitment spam, 'random' - completely new, invalid cahin, 'fork' - forked chain, combine with 'forkAfter' parameter.") -// rate := optionFlagSet.Int("rate", commitmentsSpamParams.Rate, "Commitment spam rate") -// duration := optionFlagSet.Duration("duration", commitmentsSpamParams.Duration, "Duration of the spam. Format: decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'.\n Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'.") -// timeUnit := optionFlagSet.Duration("tu", commitmentsSpamParams.TimeUnit, "Time unit for the spamming rate. Format: decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '-1.5h' or '2h45m'.\n Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'.") -// networkAlias := optionFlagSet.String("network", commitmentsSpamParams.NetworkAlias, "Network alias for the test. Check your keys-config.json file for possible values.") -// identityAlias := optionFlagSet.String("spammerAlias", commitmentsSpamParams.SpammerAlias, "Identity alias for the node identity and its private keys that will be used to spam. Check your keys-config.json file for possible values.") -// validAlias := optionFlagSet.String("validAlias", commitmentsSpamParams.ValidAlias, "Identity alias for the honest node and its private keys, will be used to request valid commitment and block data. Check your keys-config.json file for possible values.") -// forkAfter := optionFlagSet.Int("forkAfter", commitmentsSpamParams.Rate, "Indicates how many slots after spammer startup should fork be placed in the created commitment chain. Works only for 'fork' commitment spam type.") - -// parseOptionFlagSet(optionFlagSet) - -// commitmentsSpamParams.CommitmentType = *commitmentType -// commitmentsSpamParams.Rate = *rate -// commitmentsSpamParams.Duration = *duration -// commitmentsSpamParams.TimeUnit = *timeUnit -// commitmentsSpamParams.NetworkAlias = *networkAlias -// commitmentsSpamParams.SpammerAlias = *identityAlias -// commitmentsSpamParams.ValidAlias = *validAlias -// commitmentsSpamParams.ForkAfter = *forkAfter -// } - -func parseCommaSepString(urls string) []string { - split := strings.Split(urls, ",") - - return split -} - -func parseCommaSepInt(nums string) []int { - split := strings.Split(nums, ",") - parsed := make([]int, len(split)) - for i, num := range split { - parsed[i], _ = strconv.Atoi(num) - } - - return parsed -} - -func parseDurations(durations string) []time.Duration { - split := strings.Split(durations, ",") - parsed := make([]time.Duration, len(split)) - for i, dur := range split { - parsed[i], _ = time.ParseDuration(dur) - } - - return parsed -} diff --git a/tools/evil-spammer/programs/commitments.go b/tools/evil-spammer/programs/commitments.go deleted file mode 100644 index 5083b8552..000000000 --- a/tools/evil-spammer/programs/commitments.go +++ /dev/null @@ -1,41 +0,0 @@ -package programs - -// import ( -// "time" - -// "github.com/iotaledger/goshimmer/client/evilspammer" -// "github.com/iotaledger/goshimmer/tools/evil-spammer/identity" -// ) - -// type CommitmentsSpamParams struct { -// CommitmentType string -// Rate int -// Duration time.Duration -// TimeUnit time.Duration -// NetworkAlias string -// SpammerAlias string -// ValidAlias string -// ForkAfter int // optional, will be used only with CommitmentType = "fork" -// } - -// func CommitmentsSpam(params *CommitmentsSpamParams) { -// identity.LoadConfig() -// SpamCommitments(*params) -// } - -// func SpamCommitments(params CommitmentsSpamParams) { -// privateKey, urlAPI := identity.LoadIdentity(params.NetworkAlias, params.SpammerAlias) -// _, validAPI := identity.LoadIdentity(params.NetworkAlias, params.ValidAlias) -// options := []evilspammer.Options{ -// evilspammer.WithClientURL(urlAPI), -// evilspammer.WithValidClientURL(validAPI), -// evilspammer.WithSpamRate(params.Rate, params.TimeUnit), -// evilspammer.WithSpamDuration(params.Duration), -// evilspammer.WithSpammingFunc(evilspammer.CommitmentsSpammingFunction), -// evilspammer.WithIdentity(params.SpammerAlias, privateKey), -// evilspammer.WithCommitmentType(params.CommitmentType), -// evilspammer.WithForkAfter(params.ForkAfter), -// } -// spammer := evilspammer.NewSpammer(options...) -// spammer.Spam() -// } diff --git a/tools/evil-spammer/programs/params.go b/tools/evil-spammer/programs/params.go deleted file mode 100644 index f0e1d2070..000000000 --- a/tools/evil-spammer/programs/params.go +++ /dev/null @@ -1,22 +0,0 @@ -package programs - -import ( - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" -) - -type CustomSpamParams struct { - ClientURLs []string - SpamTypes []string - Rates []int - Durations []time.Duration - BlkToBeSent []int - TimeUnit time.Duration - DelayBetweenConflicts time.Duration - NSpend int - Scenario evilwallet.EvilBatch - DeepSpam bool - EnableRateSetter bool - AccountAlias string -} diff --git a/tools/evil-spammer/programs/quick-test.go b/tools/evil-spammer/programs/quick-test.go deleted file mode 100644 index e17725594..000000000 --- a/tools/evil-spammer/programs/quick-test.go +++ /dev/null @@ -1,68 +0,0 @@ -package programs - -import ( - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/spammer" -) - -type QuickTestParams struct { - ClientURLs []string - Rate int - Duration time.Duration - TimeUnit time.Duration - DelayBetweenConflicts time.Duration - VerifyLedger bool - EnableRateSetter bool -} - -// QuickTest runs short spamming periods with stable mps. -func QuickTest(params *QuickTestParams) { - evilWallet := evilwallet.NewEvilWallet(evilwallet.WithClients(params.ClientURLs...)) - counter := spammer.NewErrorCount() - log.Info("Starting quick test") - - nWallets := 2 * spammer.BigWalletsNeeded(params.Rate, params.TimeUnit, params.Duration) - - log.Info("Start preparing funds") - evilWallet.RequestFreshBigFaucetWallets(nWallets) - - // define spammers - baseOptions := []spammer.Options{ - spammer.WithSpamRate(params.Rate, params.TimeUnit), - spammer.WithSpamDuration(params.Duration), - spammer.WithErrorCounter(counter), - spammer.WithEvilWallet(evilWallet), - } - - //nolint:gocritic // we want a copy here - blkOptions := append(baseOptions, - spammer.WithSpammingFunc(spammer.DataSpammingFunction), - ) - - dsScenario := evilwallet.NewEvilScenario( - evilwallet.WithScenarioCustomConflicts(evilwallet.NSpendBatch(2)), - ) - - //nolint:gocritic // we want a copy here - dsOptions := append(baseOptions, - spammer.WithEvilScenario(dsScenario), - ) - - blkSpammer := spammer.NewSpammer(blkOptions...) - txSpammer := spammer.NewSpammer(baseOptions...) - dsSpammer := spammer.NewSpammer(dsOptions...) - - // start test - txSpammer.Spam() - time.Sleep(5 * time.Second) - - blkSpammer.Spam() - time.Sleep(5 * time.Second) - - dsSpammer.Spam() - - log.Info(counter.GetErrorsSummary()) - log.Info("Quick Test finished") -} diff --git a/tools/evil-spammer/programs/spammers.go b/tools/evil-spammer/programs/spammers.go deleted file mode 100644 index b8ca4845f..000000000 --- a/tools/evil-spammer/programs/spammers.go +++ /dev/null @@ -1,230 +0,0 @@ -package programs - -import ( - "sync" - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/accountwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/logger" - "github.com/iotaledger/iota-core/tools/evil-spammer/spammer" -) - -var log = logger.New("customSpam") - -func CustomSpam(params *CustomSpamParams, accWallet *accountwallet.AccountWallet) { - w := evilwallet.NewEvilWallet(evilwallet.WithClients(params.ClientURLs...), evilwallet.WithAccountsWallet(accWallet)) - wg := sync.WaitGroup{} - - // funds are requested fro all spam types except SpammerTypeBlock - fundsNeeded := false - for _, st := range params.SpamTypes { - if st != spammer.TypeBlock { - fundsNeeded = true - } - } - if fundsNeeded { - err := w.RequestFreshBigFaucetWallet() - if err != nil { - panic(err) - } - } - - for i, sType := range params.SpamTypes { - log.Infof("Start spamming with rate: %d, time unit: %s, and spamming type: %s.", params.Rates[i], params.TimeUnit.String(), sType) - - switch sType { - case spammer.TypeBlock: - wg.Add(1) - go func(i int) { - defer wg.Done() - s := SpamBlocks(w, params.Rates[i], params.TimeUnit, params.Durations[i], params.BlkToBeSent[i], params.EnableRateSetter, params.AccountAlias) - if s == nil { - return - } - s.Spam() - }(i) - case spammer.TypeTx: - wg.Add(1) - go func(i int) { - defer wg.Done() - SpamTransaction(w, params.Rates[i], params.TimeUnit, params.Durations[i], params.DeepSpam, params.EnableRateSetter, params.AccountAlias) - }(i) - case spammer.TypeDs: - wg.Add(1) - go func(i int) { - defer wg.Done() - SpamDoubleSpends(w, params.Rates[i], params.NSpend, params.TimeUnit, params.Durations[i], params.DelayBetweenConflicts, params.DeepSpam, params.EnableRateSetter, params.AccountAlias) - }(i) - case spammer.TypeCustom: - wg.Add(1) - go func(i int) { - defer wg.Done() - s := SpamNestedConflicts(w, params.Rates[i], params.TimeUnit, params.Durations[i], params.Scenario, params.DeepSpam, false, params.EnableRateSetter, params.AccountAlias) - if s == nil { - return - } - s.Spam() - }(i) - case spammer.TypeCommitments: - wg.Add(1) - go func() { - defer wg.Done() - }() - case spammer.TypeAccounts: - wg.Add(1) - go func() { - defer wg.Done() - - s := SpamAccounts(w, params.Rates[i], params.TimeUnit, params.Durations[i], params.EnableRateSetter, params.AccountAlias) - if s == nil { - return - } - s.Spam() - }() - - default: - log.Warn("Spamming type not recognized. Try one of following: tx, ds, blk, custom, commitments") - } - } - - wg.Wait() - log.Info("Basic spamming finished!") -} - -func SpamTransaction(w *evilwallet.EvilWallet, rate int, timeUnit, duration time.Duration, deepSpam, enableRateSetter bool, accountAlias string) { - if w.NumOfClient() < 1 { - log.Infof("Warning: At least one client is needed to spam.") - } - - scenarioOptions := []evilwallet.ScenarioOption{ - evilwallet.WithScenarioCustomConflicts(evilwallet.SingleTransactionBatch()), - } - if deepSpam { - outWallet := evilwallet.NewWallet(evilwallet.Reuse) - scenarioOptions = append(scenarioOptions, - evilwallet.WithScenarioDeepSpamEnabled(), - evilwallet.WithScenarioReuseOutputWallet(outWallet), - evilwallet.WithScenarioInputWalletForDeepSpam(outWallet), - ) - } - scenarioTx := evilwallet.NewEvilScenario(scenarioOptions...) - - options := []spammer.Options{ - spammer.WithSpamRate(rate, timeUnit), - spammer.WithSpamDuration(duration), - spammer.WithRateSetter(enableRateSetter), - spammer.WithEvilWallet(w), - spammer.WithEvilScenario(scenarioTx), - spammer.WithAccountAlias(accountAlias), - } - - s := spammer.NewSpammer(options...) - s.Spam() -} - -func SpamDoubleSpends(w *evilwallet.EvilWallet, rate, nSpent int, timeUnit, duration, delayBetweenConflicts time.Duration, deepSpam, enableRateSetter bool, accountAlias string) { - log.Debugf("Setting up double spend spammer with rate: %d, time unit: %s, and duration: %s.", rate, timeUnit.String(), duration.String()) - if w.NumOfClient() < 2 { - log.Infof("Warning: At least two client are needed to spam, and %d was provided", w.NumOfClient()) - } - - scenarioOptions := []evilwallet.ScenarioOption{ - evilwallet.WithScenarioCustomConflicts(evilwallet.NSpendBatch(nSpent)), - } - if deepSpam { - outWallet := evilwallet.NewWallet(evilwallet.Reuse) - scenarioOptions = append(scenarioOptions, - evilwallet.WithScenarioDeepSpamEnabled(), - evilwallet.WithScenarioReuseOutputWallet(outWallet), - evilwallet.WithScenarioInputWalletForDeepSpam(outWallet), - ) - } - scenarioDs := evilwallet.NewEvilScenario(scenarioOptions...) - options := []spammer.Options{ - spammer.WithSpamRate(rate, timeUnit), - spammer.WithSpamDuration(duration), - spammer.WithEvilWallet(w), - spammer.WithRateSetter(enableRateSetter), - spammer.WithTimeDelayForDoubleSpend(delayBetweenConflicts), - spammer.WithEvilScenario(scenarioDs), - spammer.WithAccountAlias(accountAlias), - } - - s := spammer.NewSpammer(options...) - s.Spam() -} - -func SpamNestedConflicts(w *evilwallet.EvilWallet, rate int, timeUnit, duration time.Duration, conflictBatch evilwallet.EvilBatch, deepSpam, reuseOutputs, enableRateSetter bool, accountAlias string) *spammer.Spammer { - scenarioOptions := []evilwallet.ScenarioOption{ - evilwallet.WithScenarioCustomConflicts(conflictBatch), - } - if deepSpam { - outWallet := evilwallet.NewWallet(evilwallet.Reuse) - scenarioOptions = append(scenarioOptions, - evilwallet.WithScenarioDeepSpamEnabled(), - evilwallet.WithScenarioReuseOutputWallet(outWallet), - evilwallet.WithScenarioInputWalletForDeepSpam(outWallet), - ) - } else if reuseOutputs { - outWallet := evilwallet.NewWallet(evilwallet.Reuse) - scenarioOptions = append(scenarioOptions, evilwallet.WithScenarioReuseOutputWallet(outWallet)) - } - scenario := evilwallet.NewEvilScenario(scenarioOptions...) - if scenario.NumOfClientsNeeded > w.NumOfClient() { - log.Infof("Warning: At least %d client are needed to spam, and %d was provided", scenario.NumOfClientsNeeded, w.NumOfClient()) - } - - options := []spammer.Options{ - spammer.WithSpamRate(rate, timeUnit), - spammer.WithSpamDuration(duration), - spammer.WithEvilWallet(w), - spammer.WithRateSetter(enableRateSetter), - spammer.WithEvilScenario(scenario), - spammer.WithAccountAlias(accountAlias), - } - - return spammer.NewSpammer(options...) -} - -func SpamBlocks(w *evilwallet.EvilWallet, rate int, timeUnit, duration time.Duration, numBlkToSend int, enableRateSetter bool, accountAlias string) *spammer.Spammer { - if w.NumOfClient() < 1 { - log.Infof("Warning: At least one client is needed to spam.") - } - - options := []spammer.Options{ - spammer.WithSpamRate(rate, timeUnit), - spammer.WithSpamDuration(duration), - spammer.WithBatchesSent(numBlkToSend), - spammer.WithRateSetter(enableRateSetter), - spammer.WithEvilWallet(w), - spammer.WithSpammingFunc(spammer.DataSpammingFunction), - spammer.WithAccountAlias(accountAlias), - } - - return spammer.NewSpammer(options...) -} - -func SpamAccounts(w *evilwallet.EvilWallet, rate int, timeUnit, duration time.Duration, enableRateSetter bool, accountAlias string) *spammer.Spammer { - if w.NumOfClient() < 1 { - log.Infof("Warning: At least one client is needed to spam.") - } - scenarioOptions := []evilwallet.ScenarioOption{ - evilwallet.WithScenarioCustomConflicts(evilwallet.SingleTransactionBatch()), - evilwallet.WithCreateAccounts(), - } - - scenarioAccount := evilwallet.NewEvilScenario(scenarioOptions...) - - options := []spammer.Options{ - spammer.WithSpamRate(rate, timeUnit), - spammer.WithSpamDuration(duration), - spammer.WithRateSetter(enableRateSetter), - spammer.WithEvilWallet(w), - spammer.WithSpammingFunc(spammer.AccountSpammingFunction), - spammer.WithEvilScenario(scenarioAccount), - spammer.WithAccountAlias(accountAlias), - } - - return spammer.NewSpammer(options...) -} diff --git a/tools/evil-spammer/spammer/clock.go b/tools/evil-spammer/spammer/clock.go deleted file mode 100644 index 87a3cc069..000000000 --- a/tools/evil-spammer/spammer/clock.go +++ /dev/null @@ -1,76 +0,0 @@ -package spammer - -// import ( -// "time" - -// iotago "github.com/iotaledger/iota.go/v4" -// "github.com/iotaledger/iota-core/tools/evil-spammer/wallet" -// ) - -// // region ClockSync //////////////evilspammerpkg///////////////////////////////////////////////////////////////////////////////// - -// // ClockSync is used to synchronize with connected nodes. -// type ClockSync struct { -// LatestCommittedSlotClock *SlotClock - -// syncTicker *time.Ticker -// clt wallet.Client -// } - -// func NewClockSync(slotDuration time.Duration, syncInterval time.Duration, clientList wallet.Client) *ClockSync { -// updateTicker := time.NewTicker(syncInterval) -// return &ClockSync{ -// LatestCommittedSlotClock: &SlotClock{slotDuration: slotDuration}, - -// syncTicker: updateTicker, -// clt: clientList, -// } -// } - -// // Start starts the clock synchronization in the background after the first sync is done.. -// func (c *ClockSync) Start() { -// c.Synchronize() -// go func() { -// for range c.syncTicker.C { -// c.Synchronize() -// } -// }() -// } - -// func (c *ClockSync) Shutdown() { -// c.syncTicker.Stop() -// } - -// func (c *ClockSync) Synchronize() { -// si, err := c.clt.GetLatestCommittedSlot() -// if err != nil { -// return -// } -// c.LatestCommittedSlotClock.Update(si) -// } - -// // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// - -// // region SlotClock /////////////////////////////////////////////////////////////////////////////////////////////// - -// type SlotClock struct { -// lastUpdated time.Time -// updatedSlot slot.Index - -// slotDuration time.Duration -// } - -// func (c *SlotClock) Update(value slot.Index) { -// c.lastUpdated = time.Now() -// c.updatedSlot = value -// } - -// func (c *SlotClock) Get() slot.Index { -// return c.updatedSlot -// } - -// func (c *SlotClock) GetRelative() slot.Index { -// return c.updatedSlot + slot.Index(time.Since(c.lastUpdated)/c.slotDuration) -// } - -// // endregion /////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/evil-spammer/spammer/commitmentmanager.go b/tools/evil-spammer/spammer/commitmentmanager.go deleted file mode 100644 index dc9181f04..000000000 --- a/tools/evil-spammer/spammer/commitmentmanager.go +++ /dev/null @@ -1,245 +0,0 @@ -package spammer - -// import ( -// "crypto/sha256" -// "math/rand" -// "time" - -// "github.com/iotaledger/hive.go/core/slot" -// "github.com/iotaledger/iota-core/tools/evil-spammer/wallet" -// iotago "github.com/iotaledger/iota.go/v4" - -// "github.com/pkg/errors" -// ) - -// type CommitmentManagerParams struct { -// CommitmentType string -// ValidClientURL string -// ParentRefsCount int -// ClockResyncTime time.Duration -// GenesisTime time.Time -// SlotDuration time.Duration - -// OptionalForkAfter int -// } -// type CommitmentManager struct { -// Params *CommitmentManagerParams -// // we store here only the valid commitments to not request them again through API -// validChain map[slot.Index]*iotago.Commitment -// // commitments used to spam -// commitmentChain map[slot.Index]*iotago.Commitment - -// initiationSlot slot.Index -// forkIndex slot.Index -// latestCommitted slot.Index - -// clockSync *ClockSync -// validClient wallet.Client - -// log Logger -// } - -// func NewCommitmentManager() *CommitmentManager { -// return &CommitmentManager{ -// Params: &CommitmentManagerParams{ -// ParentRefsCount: 2, -// ClockResyncTime: 30 * time.Second, -// GenesisTime: time.Now(), -// SlotDuration: 5 * time.Second, -// }, -// validChain: make(map[slot.Index]*iotago.Commitment), -// commitmentChain: make(map[slot.Index]*iotago.Commitment), -// } -// } - -// func (c *CommitmentManager) Setup(l Logger) { -// c.log = l - -// c.log.Infof("Commitment Manager will be based on the valid client: %s", c.Params.ValidClientURL) -// c.validClient = wallet.NewWebClient(c.Params.ValidClientURL) -// c.setupTimeParams(c.validClient) - -// c.clockSync = NewClockSync(c.Params.SlotDuration, c.Params.ClockResyncTime, c.validClient) -// c.clockSync.Start() - -// c.setupForkingPoint() -// c.setupInitCommitment() -// } - -// // SetupInitCommitment sets the initiation commitment which is the current valid commitment requested from validClient. -// func (c *CommitmentManager) setupInitCommitment() { -// c.initiationSlot = c.clockSync.LatestCommittedSlotClock.Get() -// comm, err := c.getValidCommitment(c.initiationSlot) -// if err != nil { -// panic(errors.Wrapf(err, "failed to get initiation commitment")) -// } -// c.commitmentChain[comm.Slot()] = comm -// c.latestCommitted = comm.Slot() -// } - -// // SetupTimeParams requests through API and sets the genesis time and slot duration for the commitment manager. -// func (c *CommitmentManager) setupTimeParams(clt wallet.Client) { -// genesisTime, slotDuration, err := clt.GetTimeProvider() -// if err != nil { -// panic(errors.Wrapf(err, "failed to get time provider for the commitment manager setup")) -// } -// c.Params.GenesisTime = genesisTime -// c.Params.SlotDuration = slotDuration -// } - -// func (c *CommitmentManager) SetCommitmentType(commitmentType string) { -// c.Params.CommitmentType = commitmentType -// } - -// func (c *CommitmentManager) SetForkAfter(forkAfter int) { -// c.Params.OptionalForkAfter = forkAfter -// } - -// // SetupForkingPoint sets the forking point for the commitment manager. It uses ForkAfter parameter so need to be called after params are read. -// func (c *CommitmentManager) setupForkingPoint() { -// c.forkIndex = c.clockSync.LatestCommittedSlotClock.Get() + slot.Index(c.Params.OptionalForkAfter) -// } - -// func (c *CommitmentManager) Shutdown() { -// c.clockSync.Shutdown() -// } - -// func (c *CommitmentManager) commit(comm *iotago.Commitment) { -// c.commitmentChain[comm.Slot()] = comm -// if comm.Slot() > c.latestCommitted { -// if comm.Slot()-c.latestCommitted != 1 { -// panic("next committed slot is not sequential, lastCommitted: " + c.latestCommitted.String() + " nextCommitted: " + comm.Slot().String()) -// } -// c.latestCommitted = comm.Slot() -// } -// } - -// func (c *CommitmentManager) getLatestCommitment() *iotago.Commitment { -// return c.commitmentChain[c.latestCommitted] -// } - -// // GenerateCommitment generates a commitment based on the commitment type provided in spam details. -// func (c *CommitmentManager) GenerateCommitment(clt wallet.Client) (*iotago.Commitment, slot.Index, error) { -// switch c.Params.CommitmentType { -// // todo refactor this to work with chainsA -// case "latest": -// comm, err := clt.GetLatestCommitment() -// if err != nil { -// return nil, 0, errors.Wrap(err, "failed to get latest commitment") -// } -// index, err := clt.GetLatestConfirmedIndex() -// if err != nil { -// return nil, 0, errors.Wrap(err, "failed to get latest confirmed index") -// } -// return comm, index, err -// case "random": -// slot := c.clockSync.LatestCommittedSlotClock.Get() -// newCommitment := randomCommitmentChain(slot) - -// return newCommitment, slot - 10, nil - -// case "fork": -// // it should request time periodically, and be relative -// slot := c.clockSync.LatestCommittedSlotClock.Get() -// // make sure chain is upto date to the forking point -// uptoSlot := c.forkIndex -// // get minimum -// if slot < c.forkIndex { -// uptoSlot = slot -// } -// err := c.updateChainWithValidCommitment(uptoSlot) -// if err != nil { -// return nil, 0, errors.Wrap(err, "failed to update chain with valid commitment") -// } -// if c.isAfterForkPoint(slot) { -// c.updateForkedChain(slot) -// } -// comm := c.getLatestCommitment() -// index, err := clt.GetLatestConfirmedIndex() -// if err != nil { -// return nil, 0, errors.Wrap(err, "failed to get latest confirmed index") -// } -// return comm, index - 1, nil -// } -// return nil, 0, nil -// } - -// func (c *CommitmentManager) isAfterForkPoint(slot slot.Index) bool { -// return c.forkIndex != 0 && slot > c.forkIndex -// } - -// // updateChainWithValidCommitment commits the chain up to the given slot with the valid commitments. -// func (c *CommitmentManager) updateChainWithValidCommitment(s slot.Index) error { -// for i := c.latestCommitted + 1; i <= s; i++ { -// comm, err := c.getValidCommitment(i) -// if err != nil { -// return errors.Wrapf(err, "failed to get valid commitment for slot %d", i) -// } -// c.commit(comm) -// } -// return nil -// } - -// func (c *CommitmentManager) updateForkedChain(slot slot.Index) { -// for i := c.latestCommitted + 1; i <= slot; i++ { -// comm, err := c.getForkedCommitment(i) -// if err != nil { -// panic(errors.Wrapf(err, "failed to get forked commitment for slot %d", i)) -// } -// c.commit(comm) -// } -// } - -// // getValidCommitment returns the valid commitment for the given slot if not exists it requests it from the node and update the validChain. -// func (c *CommitmentManager) getValidCommitment(slot slot.Index) (*commitment.Commitment, error) { -// if comm, ok := c.validChain[slot]; ok { -// return comm, nil -// } -// // if not requested before then get it from the node -// comm, err := c.validClient.GetCommitment(int(slot)) -// if err != nil { -// return nil, errors.Wrapf(err, "failed to get commitment for slot %d", slot) -// } -// c.validChain[slot] = comm - -// return comm, nil -// } - -// func (c *CommitmentManager) getForkedCommitment(slot slot.Index) (*commitment.Commitment, error) { -// validComm, err := c.getValidCommitment(slot) -// if err != nil { -// return nil, errors.Wrapf(err, "failed to get valid commitment for slot %d", slot) -// } -// prevComm := c.commitmentChain[slot-1] -// forkedComm := commitment.New( -// validComm.Slot(), -// prevComm.ID(), -// randomRoot(), -// validComm.CumulativeWeight(), -// ) -// return forkedComm, nil -// } - -// func randomCommitmentChain(currSlot slot.Index) *commitment.Commitment { -// chain := make([]*commitment.Commitment, currSlot+1) -// chain[0] = commitment.NewEmptyCommitment() -// for i := slot.Index(0); i < currSlot-1; i++ { -// prevComm := chain[i] -// newCommitment := commitment.New( -// i, -// prevComm.ID(), -// randomRoot(), -// 100, -// ) -// chain[i+1] = newCommitment -// } -// return chain[currSlot-1] -// } - -// func randomRoot() [32]byte { -// data := make([]byte, 10) -// for i := range data { -// data[i] = byte(rand.Intn(256)) -// } -// return sha256.Sum256(data) -// } diff --git a/tools/evil-spammer/spammer/errors.go b/tools/evil-spammer/spammer/errors.go deleted file mode 100644 index 4c056ffaa..000000000 --- a/tools/evil-spammer/spammer/errors.go +++ /dev/null @@ -1,69 +0,0 @@ -package spammer - -import ( - "fmt" - - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/runtime/syncutils" -) - -var ( - ErrFailPostBlock = ierrors.New("failed to post block") - ErrFailSendDataBlock = ierrors.New("failed to send a data block") - ErrFailGetReferences = ierrors.New("failed to get references") - ErrTransactionIsNil = ierrors.New("provided transaction is nil") - ErrTransactionInvalid = ierrors.New("provided transaction is invalid") - ErrPayloadIsNil = ierrors.New("provided payload is nil") - ErrFailToPrepareBatch = ierrors.New("custom conflict batch could not be prepared") - ErrInsufficientClients = ierrors.New("insufficient clients to send conflicts") - ErrInputsNotSolid = ierrors.New("not all inputs are solid") - ErrFailPrepareBlock = ierrors.New("failed to prepare block") - ErrFailGetAccount = ierrors.New("failed to get account from the account wallet") -) - -// ErrorCounter counts errors that appeared during the spam, -// as during the spam they are ignored and allows to print the summary (might be useful for debugging). -type ErrorCounter struct { - errorsMap map[error]*atomic.Int64 - errInTotalCount *atomic.Int64 - mutex syncutils.RWMutex -} - -func NewErrorCount() *ErrorCounter { - e := &ErrorCounter{ - errorsMap: make(map[error]*atomic.Int64), - errInTotalCount: atomic.NewInt64(0), - } - - return e -} - -func (e *ErrorCounter) CountError(err error) { - e.mutex.Lock() - defer e.mutex.Unlock() - - // check if error is already in the map - if _, ok := e.errorsMap[err]; !ok { - e.errorsMap[err] = atomic.NewInt64(0) - } - e.errInTotalCount.Add(1) - e.errorsMap[err].Add(1) -} - -func (e *ErrorCounter) GetTotalErrorCount() int64 { - return e.errInTotalCount.Load() -} - -func (e *ErrorCounter) GetErrorsSummary() string { - if len(e.errorsMap) == 0 { - return "No errors encountered" - } - blk := "Errors encountered during spam:\n" - for key, value := range e.errorsMap { - blk += fmt.Sprintf("%s: %d\n", key.Error(), value.Load()) - } - - return blk -} diff --git a/tools/evil-spammer/spammer/options.go b/tools/evil-spammer/spammer/options.go deleted file mode 100644 index e42eca8a1..000000000 --- a/tools/evil-spammer/spammer/options.go +++ /dev/null @@ -1,165 +0,0 @@ -package spammer - -import ( - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" -) - -type Options func(*Spammer) - -// region Spammer general options /////////////////////////////////////////////////////////////////////////////////////////////////// - -// WithSpamRate provides spammer with options regarding rate, time unit, and finishing spam criteria. Provide 0 to one of max parameters to skip it. -func WithSpamRate(rate int, timeUnit time.Duration) Options { - return func(s *Spammer) { - if s.SpamDetails == nil { - s.SpamDetails = &SpamDetails{ - Rate: rate, - TimeUnit: timeUnit, - } - } else { - s.SpamDetails.Rate = rate - s.SpamDetails.TimeUnit = timeUnit - } - } -} - -// WithSpamDuration provides spammer with options regarding rate, time unit, and finishing spam criteria. Provide 0 to one of max parameters to skip it. -func WithSpamDuration(maxDuration time.Duration) Options { - return func(s *Spammer) { - if s.SpamDetails == nil { - s.SpamDetails = &SpamDetails{ - MaxDuration: maxDuration, - } - } else { - s.SpamDetails.MaxDuration = maxDuration - } - } -} - -// WithErrorCounter allows for setting an error counter object, if not provided a new instance will be created. -func WithErrorCounter(errCounter *ErrorCounter) Options { - return func(s *Spammer) { - s.ErrCounter = errCounter - } -} - -// WithLogTickerInterval allows for changing interval between progress spamming logs, default is 30s. -func WithLogTickerInterval(interval time.Duration) Options { - return func(s *Spammer) { - s.State.logTickTime = interval - } -} - -// WithSpammingFunc sets core function of the spammer with spamming logic, needs to use done spammer's channel to communicate. -// end of spamming and errors. Default one is the CustomConflictSpammingFunc. -func WithSpammingFunc(spammerFunc func(s *Spammer)) Options { - return func(s *Spammer) { - s.spamFunc = spammerFunc - } -} - -// WithAccountAlias sets the alias of the account that will be used to pay with mana for sent blocks. -func WithAccountAlias(alias string) Options { - return func(s *Spammer) { - s.IssuerAlias = alias - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region Spammer EvilWallet options /////////////////////////////////////////////////////////////////////////////////////////////////// - -// WithRateSetter enables setting rate of spammer. -func WithRateSetter(enable bool) Options { - return func(s *Spammer) { - s.UseRateSetter = enable - } -} - -// WithBatchesSent provides spammer with options regarding rate, time unit, and finishing spam criteria. Provide 0 to one of max parameters to skip it. -func WithBatchesSent(maxBatchesSent int) Options { - return func(s *Spammer) { - if s.SpamDetails == nil { - s.SpamDetails = &SpamDetails{ - MaxBatchesSent: maxBatchesSent, - } - } else { - s.SpamDetails.MaxBatchesSent = maxBatchesSent - } - } -} - -// WithEvilWallet provides evil wallet instance, that will handle all spam logic according to provided EvilScenario. -func WithEvilWallet(initWallets *evilwallet.EvilWallet) Options { - return func(s *Spammer) { - s.EvilWallet = initWallets - } -} - -// WithEvilScenario provides initWallet of spammer, if omitted spammer will prepare funds based on maxBlkSent parameter. -func WithEvilScenario(scenario *evilwallet.EvilScenario) Options { - return func(s *Spammer) { - s.EvilScenario = scenario - } -} - -func WithTimeDelayForDoubleSpend(timeDelay time.Duration) Options { - return func(s *Spammer) { - s.TimeDelayBetweenConflicts = timeDelay - } -} - -// WithNumberOfSpends sets how many transactions should be created with the same input, e.g 3 for triple spend, -// 2 for double spend. For this to work user needs to make sure that there is enough number of clients. -func WithNumberOfSpends(n int) Options { - return func(s *Spammer) { - s.NumberOfSpends = n - } -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// region Spammer Commitment options /////////////////////////////////////////////////////////////////////////////////////////////////// - -func WithClientURL(clientURL string) Options { - return func(s *Spammer) { - s.Clients = models.NewWebClients([]string{clientURL}) - } -} - -// func WithValidClientURL(validClient string) Options { -// return func(s *Spammer) { -// s.CommitmentManager.Params.ValidClientURL = validClient -// } -// } - -// WithCommitmentType provides commitment type for the spammer, allowed types: fork, valid, random. Enables commitment spam and disables the wallet functionality. -// func WithCommitmentType(commitmentType string) Options { -// return func(s *Spammer) { -// s.SpamType = SpamCommitments -// s.CommitmentManager.SetCommitmentType(commitmentType) -// } -// } - -// WithForkAfter provides after how many slots from the spammer setup should fork bee created, this option can be used with CommitmentType: fork. -// func WithForkAfter(forkingAfter int) Options { -// return func(s *Spammer) { -// s.CommitmentManager.SetForkAfter(forkingAfter) -// } -// } - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type SpamDetails struct { - Rate int - TimeUnit time.Duration - MaxDuration time.Duration - MaxBatchesSent int -} - -type CommitmentSpamDetails struct { - CommitmentType string -} diff --git a/tools/evil-spammer/spammer/spammer.go b/tools/evil-spammer/spammer/spammer.go deleted file mode 100644 index 2be0e01c8..000000000 --- a/tools/evil-spammer/spammer/spammer.go +++ /dev/null @@ -1,293 +0,0 @@ -package spammer - -import ( - "time" - - "go.uber.org/atomic" - - "github.com/iotaledger/hive.go/app/configuration" - appLogger "github.com/iotaledger/hive.go/app/logger" - "github.com/iotaledger/hive.go/ds/types" - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/hive.go/logger" - "github.com/iotaledger/iota-core/pkg/testsuite/snapshotcreator" - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - "github.com/iotaledger/iota-core/tools/genesis-snapshot/presets" - iotago "github.com/iotaledger/iota.go/v4" -) - -const ( - TypeBlock = "blk" - TypeTx = "tx" - TypeDs = "ds" - TypeCustom = "custom" - TypeCommitments = "commitments" - TypeAccounts = "accounts" -) - -// region Spammer ////////////////////////////////////////////////////////////////////////////////////////////////////// - -//nolint:revive -type SpammerFunc func(*Spammer) - -type State struct { - spamTicker *time.Ticker - logTicker *time.Ticker - spamStartTime time.Time - txSent *atomic.Int64 - batchPrepared *atomic.Int64 - - logTickTime time.Duration - spamDuration time.Duration -} - -type SpamType int - -const ( - SpamEvilWallet SpamType = iota - SpamCommitments -) - -// Spammer is a utility object for new spammer creations, can be modified by passing options. -// Mandatory options: WithClients, WithSpammingFunc -// Not mandatory options, if not provided spammer will use default settings: -// WithSpamDetails, WithEvilWallet, WithErrorCounter, WithLogTickerInterval. -type Spammer struct { - SpamDetails *SpamDetails - State *State - UseRateSetter bool - SpamType SpamType - Clients models.Connector - EvilWallet *evilwallet.EvilWallet - EvilScenario *evilwallet.EvilScenario - // CommitmentManager *CommitmentManager - ErrCounter *ErrorCounter - IssuerAlias string - - log Logger - api iotago.API - - // accessed from spamming functions - done chan bool - shutdown chan types.Empty - spamFunc SpammerFunc - - TimeDelayBetweenConflicts time.Duration - NumberOfSpends int -} - -// NewSpammer is a constructor of Spammer. -func NewSpammer(options ...Options) *Spammer { - protocolParams := snapshotcreator.NewOptions(presets.Docker...).ProtocolParameters - api := iotago.V3API(protocolParams) - - state := &State{ - txSent: atomic.NewInt64(0), - batchPrepared: atomic.NewInt64(0), - logTickTime: time.Second * 30, - } - s := &Spammer{ - SpamDetails: &SpamDetails{}, - spamFunc: CustomConflictSpammingFunc, - State: state, - SpamType: SpamEvilWallet, - EvilScenario: evilwallet.NewEvilScenario(), - // CommitmentManager: NewCommitmentManager(), - UseRateSetter: true, - done: make(chan bool), - shutdown: make(chan types.Empty), - NumberOfSpends: 2, - api: api, - } - - for _, opt := range options { - opt(s) - } - - s.setup() - - return s -} - -func (s *Spammer) BlocksSent() uint64 { - return uint64(s.State.txSent.Load()) -} - -func (s *Spammer) BatchesPrepared() uint64 { - return uint64(s.State.batchPrepared.Load()) -} - -func (s *Spammer) setup() { - if s.log == nil { - s.initLogger() - } - - switch s.SpamType { - case SpamEvilWallet: - if s.EvilWallet == nil { - s.EvilWallet = evilwallet.NewEvilWallet() - } - s.Clients = s.EvilWallet.Connector() - // case SpamCommitments: - // s.CommitmentManager.Setup(s.log) - } - s.setupSpamDetails() - - s.State.spamTicker = s.initSpamTicker() - s.State.logTicker = s.initLogTicker() - - if s.ErrCounter == nil { - s.ErrCounter = NewErrorCount() - } -} - -func (s *Spammer) setupSpamDetails() { - if s.SpamDetails.Rate <= 0 { - s.SpamDetails.Rate = 1 - } - if s.SpamDetails.TimeUnit == 0 { - s.SpamDetails.TimeUnit = time.Second - } - // provided only maxBlkSent, calculating the default max for maxDuration - if s.SpamDetails.MaxDuration == 0 && s.SpamDetails.MaxBatchesSent > 0 { - s.SpamDetails.MaxDuration = time.Hour * 100 - } - // provided only maxDuration, calculating the default max for maxBlkSent - if s.SpamDetails.MaxBatchesSent == 0 && s.SpamDetails.MaxDuration > 0 { - s.SpamDetails.MaxBatchesSent = int(s.SpamDetails.MaxDuration.Seconds()/s.SpamDetails.TimeUnit.Seconds()*float64(s.SpamDetails.Rate)) + 1 - } -} - -func (s *Spammer) initLogger() { - config := configuration.New() - _ = appLogger.InitGlobalLogger(config) - logger.SetLevel(logger.LevelDebug) - s.log = logger.NewLogger("Spammer") -} - -func (s *Spammer) initSpamTicker() *time.Ticker { - tickerTime := float64(s.SpamDetails.TimeUnit) / float64(s.SpamDetails.Rate) - return time.NewTicker(time.Duration(tickerTime)) -} - -func (s *Spammer) initLogTicker() *time.Ticker { - return time.NewTicker(s.State.logTickTime) -} - -// Spam runs the spammer. Function will stop after maxDuration time will pass or when maxBlkSent will be exceeded. -func (s *Spammer) Spam() { - s.log.Infof("Start spamming transactions with %d rate", s.SpamDetails.Rate) - - s.State.spamStartTime = time.Now() - timeExceeded := time.After(s.SpamDetails.MaxDuration) - - go func() { - goroutineCount := atomic.NewInt32(0) - for { - select { - case <-s.State.logTicker.C: - s.log.Infof("Blocks issued so far: %d, errors encountered: %d", s.State.txSent.Load(), s.ErrCounter.GetTotalErrorCount()) - case <-timeExceeded: - s.log.Infof("Maximum spam duration exceeded, stopping spammer....") - s.StopSpamming() - - return - case <-s.done: - s.StopSpamming() - return - case <-s.State.spamTicker.C: - if goroutineCount.Load() > 100 { - break - } - go func() { - goroutineCount.Inc() - defer goroutineCount.Dec() - s.spamFunc(s) - }() - } - } - }() - <-s.shutdown - s.log.Info(s.ErrCounter.GetErrorsSummary()) - s.log.Infof("Finishing spamming, total txns sent: %v, TotalTime: %v, Rate: %f", s.State.txSent.Load(), s.State.spamDuration.Seconds(), float64(s.State.txSent.Load())/s.State.spamDuration.Seconds()) -} - -func (s *Spammer) CheckIfAllSent() { - if s.State.batchPrepared.Load() >= int64(s.SpamDetails.MaxBatchesSent) { - s.log.Infof("Maximum number of blocks sent, stopping spammer...") - s.done <- true - } -} - -// StopSpamming finishes tasks before shutting down the spammer. -func (s *Spammer) StopSpamming() { - s.State.spamDuration = time.Since(s.State.spamStartTime) - s.State.spamTicker.Stop() - s.State.logTicker.Stop() - // s.CommitmentManager.Shutdown() - s.shutdown <- types.Void -} - -func (s *Spammer) PrepareAndPostBlock(txData *models.PayloadIssuanceData, issuerAlias string, clt models.Client) { - if txData.Payload == nil { - s.log.Debug(ErrPayloadIsNil) - s.ErrCounter.CountError(ErrPayloadIsNil) - - return - } - issuerAccount, err := s.EvilWallet.GetAccount(issuerAlias) - if err != nil { - s.log.Debug(ierrors.Wrapf(ErrFailGetAccount, err.Error())) - s.ErrCounter.CountError(ierrors.Wrapf(ErrFailGetAccount, err.Error())) - - return - } - blockID, err := s.EvilWallet.PrepareAndPostBlock(clt, txData.Payload, txData.CongestionResponse, issuerAccount) - if err != nil { - s.log.Debug(ierrors.Wrapf(ErrFailPostBlock, err.Error())) - s.ErrCounter.CountError(ierrors.Wrapf(ErrFailPostBlock, err.Error())) - - return - } - - if txData.Payload.PayloadType() != iotago.PayloadSignedTransaction { - return - } - - signedTx := txData.Payload.(*iotago.SignedTransaction) - - txID, err := signedTx.Transaction.ID() - if err != nil { - s.log.Debug(ierrors.Wrapf(ErrTransactionInvalid, err.Error())) - s.ErrCounter.CountError(ierrors.Wrapf(ErrTransactionInvalid, err.Error())) - - return - } - - // reuse outputs - if txData.Payload.PayloadType() == iotago.PayloadSignedTransaction { - if s.EvilScenario.OutputWallet.Type() == evilwallet.Reuse { - var outputIDs iotago.OutputIDs - for index := range signedTx.Transaction.Outputs { - outputIDs = append(outputIDs, iotago.OutputIDFromTransactionIDAndIndex(txID, uint16(index))) - } - s.EvilWallet.SetTxOutputsSolid(outputIDs, clt.URL()) - } - } - count := s.State.txSent.Add(1) - s.log.Debugf("Last block sent, ID: %s, txCount: %d", blockID.ToHex(), count) -} - -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type Logger interface { - Infof(template string, args ...interface{}) - Info(args ...interface{}) - Debugf(template string, args ...interface{}) - Debug(args ...interface{}) - Warn(args ...interface{}) - Warnf(template string, args ...interface{}) - Error(args ...interface{}) - Errorf(template string, args ...interface{}) -} diff --git a/tools/evil-spammer/spammer/spamming_functions.go b/tools/evil-spammer/spammer/spamming_functions.go deleted file mode 100644 index 2462c07ca..000000000 --- a/tools/evil-spammer/spammer/spamming_functions.go +++ /dev/null @@ -1,138 +0,0 @@ -package spammer - -import ( - "math/rand" - "sync" - "time" - - "github.com/iotaledger/hive.go/ierrors" - "github.com/iotaledger/iota-core/tools/evil-spammer/models" - iotago "github.com/iotaledger/iota.go/v4" -) - -func DataSpammingFunction(s *Spammer) { - clt := s.Clients.GetClient() - // sleep randomly to avoid issuing blocks in different goroutines at once - //nolint:gosec - time.Sleep(time.Duration(rand.Float64()*20) * time.Millisecond) - - s.PrepareAndPostBlock(&models.PayloadIssuanceData{ - Payload: &iotago.TaggedData{ - Tag: []byte("SPAM"), - }, - }, s.IssuerAlias, clt) - - s.State.batchPrepared.Add(1) - s.CheckIfAllSent() -} - -func CustomConflictSpammingFunc(s *Spammer) { - conflictBatch, aliases, err := s.EvilWallet.PrepareCustomConflictsSpam(s.EvilScenario, &models.IssuancePaymentStrategy{ - AllotmentStrategy: models.AllotmentStrategyAll, - IssuerAlias: s.IssuerAlias, - }) - - if err != nil { - s.log.Debugf(ierrors.Wrap(ErrFailToPrepareBatch, err.Error()).Error()) - s.ErrCounter.CountError(ierrors.Wrap(ErrFailToPrepareBatch, err.Error())) - } - - for _, txsData := range conflictBatch { - clients := s.Clients.GetClients(len(txsData)) - if len(txsData) > len(clients) { - s.log.Debug(ErrFailToPrepareBatch) - s.ErrCounter.CountError(ErrInsufficientClients) - } - - // send transactions in parallel - wg := sync.WaitGroup{} - for i, txData := range txsData { - wg.Add(1) - go func(clt models.Client, tx *models.PayloadIssuanceData) { - defer wg.Done() - - // sleep randomly to avoid issuing blocks in different goroutines at once - //nolint:gosec - time.Sleep(time.Duration(rand.Float64()*100) * time.Millisecond) - - s.PrepareAndPostBlock(tx, s.IssuerAlias, clt) - }(clients[i], txData) - } - wg.Wait() - } - s.State.batchPrepared.Add(1) - s.EvilWallet.ClearAliases(aliases) - s.CheckIfAllSent() -} - -func AccountSpammingFunction(s *Spammer) { - clt := s.Clients.GetClient() - // update scenario - txData, aliases, err := s.EvilWallet.PrepareAccountSpam(s.EvilScenario, &models.IssuancePaymentStrategy{ - AllotmentStrategy: models.AllotmentStrategyAll, - IssuerAlias: s.IssuerAlias, - }) - if err != nil { - s.log.Debugf(ierrors.Wrap(ErrFailToPrepareBatch, err.Error()).Error()) - s.ErrCounter.CountError(ierrors.Wrap(ErrFailToPrepareBatch, err.Error())) - } - s.PrepareAndPostBlock(txData, s.IssuerAlias, clt) - - s.State.batchPrepared.Add(1) - s.EvilWallet.ClearAliases(aliases) - s.CheckIfAllSent() -} - -// func CommitmentsSpammingFunction(s *Spammer) { -// clt := s.Clients.GetClient() -// p := payload.NewGenericDataPayload([]byte("SPAM")) -// payloadBytes, err := p.Bytes() -// if err != nil { -// s.ErrCounter.CountError(ErrFailToPrepareBatch) -// } -// parents, err := clt.GetReferences(payloadBytes, s.CommitmentManager.Params.ParentRefsCount) -// if err != nil { -// s.ErrCounter.CountError(ErrFailGetReferences) -// } -// localID := s.IdentityManager.GetIdentity() -// commitment, latestConfIndex, err := s.CommitmentManager.GenerateCommitment(clt) -// if err != nil { -// s.log.Debugf(errors.WithMessage(ErrFailToPrepareBatch, err.Error()).Error()) -// s.ErrCounter.CountError(errors.WithMessage(ErrFailToPrepareBatch, err.Error())) -// } -// block := models.NewBlock( -// models.WithParents(parents), -// models.WithIssuer(localID.PublicKey()), -// models.WithIssuingTime(time.Now()), -// models.WithPayload(p), -// models.WithLatestConfirmedSlot(latestConfIndex), -// models.WithCommitment(commitment), -// models.WithSignature(ed25519.EmptySignature), -// ) -// signature, err := wallet.SignBlock(block, localID) -// if err != nil { -// return -// } -// block.SetSignature(signature) -// timeProvider := slot.NewTimeProvider(s.CommitmentManager.Params.GenesisTime.Unix(), int64(s.CommitmentManager.Params.SlotDuration.Seconds())) -// if err = block.DetermineID(timeProvider); err != nil { -// s.ErrCounter.CountError(ErrFailPrepareBlock) -// } -// blockBytes, err := block.Bytes() -// if err != nil { -// s.ErrCounter.CountError(ErrFailPrepareBlock) -// } - -// blkID, err := clt.PostBlock(blockBytes) -// if err != nil { -// fmt.Println(err) -// s.ErrCounter.CountError(ErrFailSendDataBlock) -// } - -// count := s.State.txSent.Add(1) -// if count%int64(s.SpamDetails.Rate*4) == 0 { -// s.log.Debugf("Last sent block, ID: %s; %s blkCount: %d", blkID, commitment.ID().String(), count) -// } -// s.State.batchPrepared.Add(1) -// s.CheckIfAllSent() -// } diff --git a/tools/evil-spammer/spammer/utils.go b/tools/evil-spammer/spammer/utils.go deleted file mode 100644 index 997945723..000000000 --- a/tools/evil-spammer/spammer/utils.go +++ /dev/null @@ -1,16 +0,0 @@ -package spammer - -import ( - "time" - - "github.com/iotaledger/iota-core/tools/evil-spammer/evilwallet" -) - -// BigWalletsNeeded calculates how many big wallets needs to be prepared for a spam based on provided spam details. -func BigWalletsNeeded(rate int, timeUnit, duration time.Duration) int { - bigWalletSize := evilwallet.FaucetRequestSplitNumber * evilwallet.FaucetRequestSplitNumber - outputsNeeded := rate * int(duration/timeUnit) - walletsNeeded := outputsNeeded/bigWalletSize + 1 - - return walletsNeeded -} diff --git a/tools/gendoc/go.mod b/tools/gendoc/go.mod index 65a416839..554b4ebab 100644 --- a/tools/gendoc/go.mod +++ b/tools/gendoc/go.mod @@ -71,9 +71,9 @@ require ( github.com/iotaledger/hive.go/runtime v0.0.0-20231020115340-13da292c580b // indirect github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b // indirect github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b // indirect - github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231023191159-38919c4705e0 // indirect - github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231023190837-6e7b2cdfd4fd // indirect - github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 // indirect + github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231026154618-820b8eaed2cc // indirect + github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231026154523-9ee0c47a283a // indirect + github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d // indirect github.com/ipfs/boxo v0.13.1 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect @@ -156,6 +156,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect + github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 // indirect github.com/zyedidia/generic v1.2.1 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect diff --git a/tools/gendoc/go.sum b/tools/gendoc/go.sum index c13517eea..b5bc4c338 100644 --- a/tools/gendoc/go.sum +++ b/tools/gendoc/go.sum @@ -309,12 +309,12 @@ github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292 github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b/go.mod h1:SdK26z8/VhWtxaqCuQrufm80SELgowQPmu9T/8eUQ8g= github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b h1:MDZhTZTVDiydXcW5j4TA7HixVCyAdToIMPhHfJee7cE= github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= -github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231023191159-38919c4705e0 h1:/8pbFXhTSroJvjJMfJqfHjzoT9N8B4LUY3SbKruD5MM= -github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231023191159-38919c4705e0/go.mod h1:My1SB4vZj42EgTDNJ/dgW8lUpLNmvtzu8f89J5y2kP0= -github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231023190837-6e7b2cdfd4fd h1:hh5mAnnaZHOYAi4CIqR9K/mv786ex9AQgpisbJ4ZMow= -github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231023190837-6e7b2cdfd4fd/go.mod h1:MK0SHfNicBmcaZb3qS3tA8NEJIWKNbcNtNNKuSDKqXY= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 h1:81aWESXFC04iKI9I140eDrBb9zBWXfVoAUMp9berk0c= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= +github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231026154618-820b8eaed2cc h1:Foz7Q1vNh0Ts+YTEODHO3LSKVGM/uNV3RqbBJS6u7yA= +github.com/iotaledger/inx-app v1.0.0-rc.3.0.20231026154618-820b8eaed2cc/go.mod h1:gaQbe/L+wjjUeQj5N8+o/XdZnSosFmDQfxmfyrK05hc= +github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231026154523-9ee0c47a283a h1:8JbC44pNud1rT091fJA4bDC+35MozksapuCXf8M1kmg= +github.com/iotaledger/inx/go v1.0.0-rc.2.0.20231026154523-9ee0c47a283a/go.mod h1:iXG/tO+GQZQzgIUyITnQDigb6Ny1wSueHFIYne4HBkc= +github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d h1:gcJz0J3xFELIPT7y4xqW+q25oOcK6QMlxNnrfFu8srA= +github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= github.com/ipfs/boxo v0.13.1 h1:nQ5oQzcMZR3oL41REJDcTbrvDvuZh3J9ckc9+ILeRQI= github.com/ipfs/boxo v0.13.1/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= @@ -668,6 +668,8 @@ github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSD github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 h1:i7k63xHOX2ntuHrhHewfKro67c834jug2DIk599fqAA= +github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98/go.mod h1:Knu2XMRWe8SkwTlHc/+ghP+O9DEaZRQQEyTjvLJ5Cck= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= diff --git a/tools/genesis-snapshot/go.mod b/tools/genesis-snapshot/go.mod index 0743c2ff7..4a1a2b7ab 100644 --- a/tools/genesis-snapshot/go.mod +++ b/tools/genesis-snapshot/go.mod @@ -10,7 +10,7 @@ require ( github.com/iotaledger/hive.go/lo v0.0.0-20231020115340-13da292c580b github.com/iotaledger/hive.go/runtime v0.0.0-20231020115340-13da292c580b github.com/iotaledger/iota-core v0.0.0-00010101000000-000000000000 - github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 + github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d github.com/mr-tron/base58 v1.2.0 github.com/spf13/pflag v1.0.5 golang.org/x/crypto v0.14.0 diff --git a/tools/genesis-snapshot/go.sum b/tools/genesis-snapshot/go.sum index f50a589ed..1d01d2de8 100644 --- a/tools/genesis-snapshot/go.sum +++ b/tools/genesis-snapshot/go.sum @@ -52,8 +52,8 @@ github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292 github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231020115340-13da292c580b/go.mod h1:SdK26z8/VhWtxaqCuQrufm80SELgowQPmu9T/8eUQ8g= github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b h1:MDZhTZTVDiydXcW5j4TA7HixVCyAdToIMPhHfJee7cE= github.com/iotaledger/hive.go/stringify v0.0.0-20231020115340-13da292c580b/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8 h1:81aWESXFC04iKI9I140eDrBb9zBWXfVoAUMp9berk0c= -github.com/iotaledger/iota.go/v4 v4.0.0-20231023190719-1a9daaa83ca8/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= +github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d h1:gcJz0J3xFELIPT7y4xqW+q25oOcK6QMlxNnrfFu8srA= +github.com/iotaledger/iota.go/v4 v4.0.0-20231026154111-efd63ff4f03d/go.mod h1:jqbLYq4a/FwuiPBqFfkAwwxU8vs3+kReRq2/tyX5qRA= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= diff --git a/tools/genesis-snapshot/presets/presets.go b/tools/genesis-snapshot/presets/presets.go index 67f67df43..373a52929 100644 --- a/tools/genesis-snapshot/presets/presets.go +++ b/tools/genesis-snapshot/presets/presets.go @@ -21,7 +21,7 @@ var Base = []options.Option[snapshotcreator.Options]{ snapshotcreator.WithProtocolParameters( iotago.NewV3ProtocolParameters( iotago.WithNetworkOptions("default", "rms"), - iotago.WithSupplyOptions(10_000_000_000, 100, 1, 10, 100, 100, 100), + iotago.WithSupplyOptions(4_600_000_000_000_000, 100, 1, 10, 100, 100, 100), iotago.WithTimeProviderOptions(1696841745, 10, 13), iotago.WithLivenessOptions(30, 30, 7, 14, 30), // increase/decrease threshold = fraction * slotDurationInSeconds * schedulerRate @@ -36,25 +36,8 @@ var Base = []options.Option[snapshotcreator.Options]{ var Docker = []options.Option[snapshotcreator.Options]{ snapshotcreator.WithFilePath("docker-network.snapshot"), - snapshotcreator.WithBasicOutputs( - /* - inx-faucet - - ed25519 private key: de52b9964dda96564e9fab362ab16c2669c715c6a2a853bece8a25fc58c599755b938327ea463e0c323c0fd44f6fc1843ed94daecc6909c6043d06b7152e4737 - ed25519 public key: 5b938327ea463e0c323c0fd44f6fc1843ed94daecc6909c6043d06b7152e4737 - ed25519 address: 2f64f9d179991f50542b01e034fa043b195403875b8677efaf196b41c88803d0 - bech32 address: rms1qqhkf7w30xv375z59vq7qd86qsa3j4qrsadcval04uvkkswg3qpaqf4hga2 - - => restricted address with mana enabled: rms19qqz7e8e69uej86s2s4srcp5lgzrkx25qwr4hpnha7h3j66pezyq85qpqgqjjc5k - */ - snapshotcreator.BasicOutputDetails{ - Address: lo.Return2(iotago.ParseBech32("rms19qqz7e8e69uej86s2s4srcp5lgzrkx25qwr4hpnha7h3j66pezyq85qpqgqjjc5k")), - Amount: 5_000_000_000, - Mana: 10_000_000, - }, - ), snapshotcreator.WithAccounts( - snapshotcreator.AccountDetails{ // validator-1 + snapshotcreator.AccountDetails{ // node-1-validator AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x293dc170d9a59474e6d81cfba7f7d924c09b25d7166bcfba606e53114d0a758b"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x293dc170d9a59474e6d81cfba7f7d924c09b25d7166bcfba606e53114d0a758b"))), Amount: mock.MinValidatorAccountAmount, @@ -66,7 +49,7 @@ var Docker = []options.Option[snapshotcreator.Options]{ StakedAmount: mock.MinValidatorAccountAmount, Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, - snapshotcreator.AccountDetails{ // validator-2 + snapshotcreator.AccountDetails{ // node-2-validator AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x05c1de274451db8de8182d64c6ee0dca3ae0c9077e0b4330c976976171d79064"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x05c1de274451db8de8182d64c6ee0dca3ae0c9077e0b4330c976976171d79064"))), Amount: mock.MinValidatorAccountAmount, @@ -78,7 +61,7 @@ var Docker = []options.Option[snapshotcreator.Options]{ StakedAmount: mock.MinValidatorAccountAmount, Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, - snapshotcreator.AccountDetails{ // validator-3 + snapshotcreator.AccountDetails{ // node-3-validator AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x1e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x1e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648"))), Amount: mock.MinValidatorAccountAmount, @@ -108,10 +91,27 @@ var Docker = []options.Option[snapshotcreator.Options]{ Mana: iotago.Mana(mock.MinIssuerAccountAmount), }, ), + snapshotcreator.WithBasicOutputs( + /* + inx-faucet + + ed25519 private key: de52b9964dda96564e9fab362ab16c2669c715c6a2a853bece8a25fc58c599755b938327ea463e0c323c0fd44f6fc1843ed94daecc6909c6043d06b7152e4737 + ed25519 public key: 5b938327ea463e0c323c0fd44f6fc1843ed94daecc6909c6043d06b7152e4737 + ed25519 address: 2f64f9d179991f50542b01e034fa043b195403875b8677efaf196b41c88803d0 + bech32 address: rms1qqhkf7w30xv375z59vq7qd86qsa3j4qrsadcval04uvkkswg3qpaqf4hga2 + + => restricted address with mana enabled: rms1xqqz7e8e69uej86s2s4srcp5lgzrkx25qwr4hpnha7h3j66pezyq85qpqg55v3ur + */ + snapshotcreator.BasicOutputDetails{ + Address: lo.Return2(iotago.ParseBech32("rms1xqqz7e8e69uej86s2s4srcp5lgzrkx25qwr4hpnha7h3j66pezyq85qpqg55v3ur")), + Amount: 1_000_000_000_000_000, + Mana: 10_000_000, + }, + ), snapshotcreator.WithProtocolParameters( iotago.NewV3ProtocolParameters( iotago.WithNetworkOptions("docker", "rms"), - iotago.WithSupplyOptions(10_000_000_000, 1, 1, 10, 100, 100, 100), + iotago.WithSupplyOptions(4_600_000_000_000_000, 1, 1, 10, 100, 100, 100), iotago.WithTimeProviderOptions(time.Now().Unix(), 10, 13), iotago.WithLivenessOptions(30, 30, 7, 14, 30), // increase/decrease threshold = fraction * slotDurationInSeconds * schedulerRate @@ -125,7 +125,7 @@ var Docker = []options.Option[snapshotcreator.Options]{ var Feature = []options.Option[snapshotcreator.Options]{ snapshotcreator.WithFilePath("docker-network.snapshot"), snapshotcreator.WithAccounts( - snapshotcreator.AccountDetails{ + snapshotcreator.AccountDetails{ // node-01 AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x01fb6b9db5d96240aef00bc950d1c67a6494513f6d7cf784e57b4972b96ab2fe"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x01fb6b9db5d96240aef00bc950d1c67a6494513f6d7cf784e57b4972b96ab2fe"))), Amount: mock.MinValidatorAccountAmount, @@ -137,7 +137,7 @@ var Feature = []options.Option[snapshotcreator.Options]{ StakedAmount: mock.MinValidatorAccountAmount, Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, - snapshotcreator.AccountDetails{ + snapshotcreator.AccountDetails{ // node-02 AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x83e7f71a440afd48981a8b4684ddae24434b7182ce5c47cfb56ac528525fd4b6"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x83e7f71a440afd48981a8b4684ddae24434b7182ce5c47cfb56ac528525fd4b6"))), Amount: mock.MinValidatorAccountAmount, @@ -149,7 +149,7 @@ var Feature = []options.Option[snapshotcreator.Options]{ StakedAmount: mock.MinValidatorAccountAmount, Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, - snapshotcreator.AccountDetails{ + snapshotcreator.AccountDetails{ // node-03 AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0xac628986b2ef52a1679f2289fcd7b4198476976dea4c30ae34ff04ae52e14805"))), Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0xac628986b2ef52a1679f2289fcd7b4198476976dea4c30ae34ff04ae52e14805"))), Amount: mock.MinValidatorAccountAmount, @@ -161,11 +161,43 @@ var Feature = []options.Option[snapshotcreator.Options]{ StakedAmount: mock.MinValidatorAccountAmount, Mana: iotago.Mana(mock.MinValidatorAccountAmount), }, + snapshotcreator.AccountDetails{ + /* + inx-blockissuer + + ed25519 public key: 670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25 + ed25519 address: 3b07e3e84c1276f0b9d35cf218b3763e0cbdadaa7ca38588de2170c31b38e9bb + bech32 address: rms1pqas0clgfsf8du9e6dw0yx9nwclqe0dd4f728pvgmcshpscm8r5mkddrrfc + */ + AccountID: blake2b.Sum256(lo.PanicOnErr(hexutil.DecodeHex("0x670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25"))), + Address: iotago.Ed25519AddressFromPubKey(lo.PanicOnErr(hexutil.DecodeHex("0x670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25"))), + Amount: testsuite.MinIssuerAccountAmount, + IssuerKey: iotago.Ed25519PublicKeyBlockIssuerKeyFromPublicKey(ed25519.PublicKey(lo.PanicOnErr(hexutil.DecodeHex("0x670a1a20ddb02a6cec53ec3196bc7d5bd26df2f5a6ca90b5fffd71364f104b25")))), + ExpirySlot: iotago.MaxSlotIndex, + BlockIssuanceCredits: iotago.MaxBlockIssuanceCredits / 4, + Mana: iotago.Mana(testsuite.MinIssuerAccountAmount), + }, + ), + snapshotcreator.WithBasicOutputs( + /* + inx-faucet + + ed25519 public key: dcd760a51cfafe901f4ca0745d399af7146028af643e8a339c7bb82fbb1be7f9 + ed25519 address: 48acd764f626523646d5ccf22f807e96d30b7ab0064f370b66fa811985985ec4 + bech32 address: rms1qpy2e4my7cn9ydjx6hx0ytuq06tdxzm6kqry7dctvmagzxv9np0vg9c55n4 + + => restricted address with mana enabled: rms1xqqy3txhvnmzv53kgm2ueu30splfd5ct02cqvnehpdn04qgeskv9a3qpqgrhlhv3 + */ + snapshotcreator.BasicOutputDetails{ + Address: lo.Return2(iotago.ParseBech32("rms1xqqy3txhvnmzv53kgm2ueu30splfd5ct02cqvnehpdn04qgeskv9a3qpqgrhlhv3")), + Amount: 1_000_000_000_000_000, + Mana: 10_000_000, + }, ), snapshotcreator.WithProtocolParameters( iotago.NewV3ProtocolParameters( iotago.WithNetworkOptions("feature", "rms"), - iotago.WithSupplyOptions(10_000_000_000, 100, 1, 10, 100, 100, 100), + iotago.WithSupplyOptions(4_600_000_000_000_000, 100, 1, 10, 100, 100, 100), iotago.WithTimeProviderOptions(1697631694, 10, 13), iotago.WithLivenessOptions(30, 30, 10, 20, 30), // increase/decrease threshold = fraction * slotDurationInSeconds * schedulerRate