Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Request faucet funds from inx-faucet #3

Merged
merged 9 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions accountwallet/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (a *AccountWallet) createAccountImplicitly(params *CreateAccountParams) (io
// An implicit account has an implicitly defined Block Issuer Key, corresponding to the address itself.
// Thus, implicit accounts can issue blocks by signing them with the private key corresponding to the public key
// from which the Implicit Account Creation Address was derived.
implicitAccountOutput, privateKey, err := a.getFunds(params.Amount, iotago.AddressImplicitAccountCreation)
implicitAccountOutput, privateKey, err := a.getFunds(iotago.AddressImplicitAccountCreation)
if err != nil {
return iotago.EmptyAccountID, ierrors.Wrap(err, "Failed to create account")
}
Expand Down Expand Up @@ -58,15 +58,15 @@ func (a *AccountWallet) transitionImplicitAccount(
accAddr iotago.Address,
blockIssuerKeys iotago.BlockIssuerKeys,
_ ed25519.PrivateKey,
params *CreateAccountParams,
_ *CreateAccountParams,
) (iotago.AccountID, error) {
// transition from implicit to regular account
accountOutput := builder.NewAccountOutputBuilder(accAddr, accAddr, implicitAccountOutput.Balance).
Mana(implicitAccountOutput.OutputStruct.StoredMana()).
AccountID(iotago.AccountIDFromOutputID(implicitAccountOutput.OutputID)).
BlockIssuer(blockIssuerKeys, iotago.MaxSlotIndex).MustBuild()

log.Infof("Created account %s with %d tokens\n", accountOutput.AccountID.ToHex(), params.Amount)
log.Infof("Created account %s with %d tokens\n", accountOutput.AccountID.ToHex())
txBuilder := a.createTransactionBuilder(implicitAccountOutput, implicitAccAddr, accountOutput)

// TODO get congestionResponse from API
Expand Down
3 changes: 2 additions & 1 deletion accountwallet/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func AvailableCommands(cmd string) bool {

type Configuration struct {
BindAddress string `json:"bindAddress,omitempty"`
FaucetBindAddress string `json:"faucetBindAddress,omitempty"`
AccountStatesFile string `json:"accountStatesFile,omitempty"`
GenesisSeed string `json:"genesisSeed,omitempty"`
BlockIssuerPrivateKey string `json:"blockIssuerPrivateKey,omitempty"`
Expand All @@ -75,6 +76,7 @@ var accountConfigFile = "config.json"
var (
dockerAccountConfigJSON = `{
"bindAddress": "http://localhost:8080",
"faucetBindAddress": "http://localhost:8088",
"accountStatesFile": "wallet.dat",
"genesisSeed": "7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih",
"blockIssuerPrivateKey": "db39d2fde6301d313b108dc9db1ee724d0f405f6fde966bd776365bc5f4a5fb31e4b21eb51dcddf65c20db1065e1f1514658b23a3ddbf48d30c0efc926a9a648",
Expand Down Expand Up @@ -135,7 +137,6 @@ type AccountSubcommands interface {

type CreateAccountParams struct {
Alias string
Amount uint64
NoBIF bool
Implicit bool
Transition bool
Expand Down
117 changes: 28 additions & 89 deletions accountwallet/faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/mr-tron/base58"

"github.com/iotaledger/evil-tools/models"
"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"
Expand Down Expand Up @@ -39,32 +38,47 @@ func (a *AccountWallet) RequestBlockBuiltData(clt *nodeclient.Client, issuerID i
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())
func (a *AccountWallet) RequestFaucetFunds(clt models.Client, receiveAddr iotago.Address) (*models.Output, error) {
//nolint:all,forcetypassert
err := clt.RequestFaucetFunds(receiveAddr)
if err != nil {
return nil, ierrors.Wrapf(err, "failed to get block built data for issuer %s", a.faucet.account.ID().ToHex())
return nil, ierrors.Wrap(err, "failed to request funds from faucet")
}

signedTx, err := a.faucet.prepareFaucetRequest(receiveAddr, amount, congestionResp.ReferenceManaCost)
indexer, err := clt.Indexer()
if err != nil {
log.Errorf("failed to prepare faucet request: %s", err)

return nil, err
return nil, ierrors.Wrap(err, "failed to get indexer client")
}

_, err = a.PostWithBlock(clt, signedTx, a.faucet.account, congestionResp, issuerResp, version)
addrBech := receiveAddr.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP())

time.Sleep(10 * time.Second)

res, err := indexer.Outputs(context.Background(), &apimodels.BasicOutputsQuery{
AddressBech32: addrBech,
})
if err != nil {
log.Errorf("failed to create block: %s", err)
return nil, ierrors.Wrap(err, "indexer request failed in request faucet funds")
}

var outputStruct iotago.Output
var outputID iotago.OutputID
for res.Next() {
unspents, err := res.Outputs(context.TODO())
if err != nil {
return nil, ierrors.Wrap(err, "failed to get faucet unspent outputs")
}

return nil, err
outputStruct = unspents[0]
outputID = lo.Return1(res.Response.Items.OutputIDs())[0]
}

return &models.Output{
OutputID: iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), 0),
OutputID: outputID,
Address: receiveAddr,
AddressIndex: 0,
Balance: signedTx.Transaction.Outputs[0].BaseTokenAmount(),
OutputStruct: signedTx.Transaction.Outputs[0],
Balance: outputStruct.BaseTokenAmount(),
OutputStruct: outputStruct,
}, nil
}

Expand Down Expand Up @@ -204,78 +218,3 @@ func (f *faucet) getGenesisOutputFromIndexer(clt models.Client, faucetAddr iotag

return faucetUnspentOutput, faucetUnspentOutputID, faucetAmount, nil
}

func (f *faucet) prepareFaucetRequest(receiveAddr iotago.Address, amount iotago.BaseToken, rmc iotago.Mana) (*iotago.SignedTransaction, error) {
txBuilder, remainderIndex, err := f.createFaucetTransactionNoManaHandling(receiveAddr, amount)
if err != nil {
return nil, err
}

rmcAllottedTxBuilder := txBuilder.Clone()
// faucet will allot exact mana to be burnt, rest of the mana is alloted to faucet output remainder
rmcAllottedTxBuilder.AllotRequiredManaAndStoreRemainingManaInOutput(txBuilder.CreationSlot(), rmc, f.account.ID(), remainderIndex)

var signedTx *iotago.SignedTransaction
signedTx, err = rmcAllottedTxBuilder.Build(f.genesisHdWallet.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.genesisHdWallet.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")
}
}

// set remainder output to be reused by the Faucet wallet
//nolint:all,forcetypassert
f.unspentOutput = &models.Output{
OutputID: iotago.OutputIDFromTransactionIDAndIndex(lo.PanicOnErr(signedTx.Transaction.ID()), uint16(remainderIndex)),
Address: f.genesisHdWallet.Address(iotago.AddressEd25519).(*iotago.Ed25519Address),
AddressIndex: 0,
Balance: signedTx.Transaction.Outputs[1].BaseTokenAmount(),
OutputStruct: signedTx.Transaction.Outputs[1],
}

return signedTx, nil
}

func (f *faucet) createFaucetTransactionNoManaHandling(receiveAddr iotago.Address, amount 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)

//nolint:all,forcetypassert
txBuilder.AddInput(&builder.TxInput{
UnlockTarget: f.genesisHdWallet.Address(iotago.AddressEd25519).(*iotago.Ed25519Address),
InputID: f.unspentOutput.OutputID,
Input: f.unspentOutput.OutputStruct,
})

remainderAmount, err := safemath.SafeSub(f.unspentOutput.Balance, amount)
if err != nil {
return nil, 0, ierrors.Errorf("safeSub failed %d - %d: %s", f.unspentOutput.Balance, amount, err)
}

txBuilder.AddOutput(&iotago.BasicOutput{
Amount: amount,
Conditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: receiveAddr},
},
Features: iotago.BasicOutputFeatures{},
})

// remainder output
remainderIndex := 1
//nolint:all,forcetypassert
txBuilder.AddOutput(&iotago.BasicOutput{
Amount: remainderAmount,
Conditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: f.genesisHdWallet.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
}
6 changes: 6 additions & 0 deletions accountwallet/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ func WithClientURL(url string) options.Option[AccountWallet] {
}
}

func WithFaucetURL(url string) options.Option[AccountWallet] {
return func(w *AccountWallet) {
w.optsFaucetURL = url
}
}

func WithAccountStatesFile(fileName string) options.Option[AccountWallet] {
return func(w *AccountWallet) {
w.optsAccountStatesFile = fileName
Expand Down
10 changes: 7 additions & 3 deletions accountwallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ func Run(config *Configuration) (*AccountWallet, error) {
if config.BindAddress != "" {
opts = append(opts, WithClientURL(config.BindAddress))
}
if config.FaucetBindAddress != "" {
opts = append(opts, WithFaucetURL(config.FaucetBindAddress))
}
if config.AccountStatesFile != "" {
opts = append(opts, WithAccountStatesFile(config.AccountStatesFile))
}
Expand Down Expand Up @@ -69,6 +72,7 @@ type AccountWallet struct {
client *models.WebClient

optsClientBindAddress string
optsFaucetURL string
optsAccountStatesFile string
optsFaucetParams *faucetParams
optsRequestTimeout time.Duration
Expand All @@ -83,7 +87,7 @@ func NewAccountWallet(opts ...options.Option[AccountWallet]) (*AccountWallet, er
optsRequestTimeout: time.Second * 120,
optsRequestTicker: time.Second * 5,
}, opts, func(w *AccountWallet) {
w.client, initErr = models.NewWebClient(w.optsClientBindAddress)
w.client, initErr = models.NewWebClient(w.optsClientBindAddress, w.optsFaucetURL)
if initErr != nil {
log.Errorf("failed to create web client: %s", initErr.Error())

Expand Down Expand Up @@ -276,10 +280,10 @@ func (a *AccountWallet) isAccountReady(accData *models.AccountData) bool {
return true
}

func (a *AccountWallet) getFunds(amount uint64, addressType iotago.AddressType) (*models.Output, ed25519.PrivateKey, error) {
func (a *AccountWallet) getFunds(addressType iotago.AddressType) (*models.Output, ed25519.PrivateKey, error) {
receiverAddr, privKey, usedIndex := a.getAddress(addressType)

createdOutput, err := a.RequestFaucetFunds(a.client, receiverAddr, iotago.BaseToken(amount))
createdOutput, err := a.RequestFaucetFunds(a.client, receiverAddr)
if err != nil {
return nil, nil, ierrors.Wrap(err, "failed to request funds from Faucet")
}
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (

customSpamParams = programs.CustomSpamParams{
ClientURLs: urls,
FaucetURL: "http://localhost:8088",
SpamTypes: []string{spammer.TypeBlock},
Rates: []int{1},
Durations: []time.Duration{time.Second * 20},
Expand Down
21 changes: 14 additions & 7 deletions evilwallet/evilwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (

var (
defaultClientsURLs = []string{"http://localhost:8080", "http://localhost:8090"}
defaultFaucetURL = "http://localhost:8088"
)

// region EvilWallet ///////////////////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -46,6 +47,7 @@ type EvilWallet struct {
aliasManager *AliasManager

optsClientURLs []string
optsFaucetURL string
log *logger.Logger
}

Expand All @@ -55,12 +57,12 @@ func NewEvilWallet(opts ...options.Option[EvilWallet]) *EvilWallet {
wallets: NewWallets(),
aliasManager: NewAliasManager(),
optsClientURLs: defaultClientsURLs,
optsFaucetURL: defaultFaucetURL,
log: evillogger.New("EvilWallet"),
}, opts, func(w *EvilWallet) {
connector := models.NewWebClients(w.optsClientURLs)
connector := models.NewWebClients(w.optsClientURLs, w.optsFaucetURL)
w.connector = connector
w.outputManager = NewOutputManager(connector, w.wallets, w.log)

})
}

Expand Down Expand Up @@ -185,7 +187,7 @@ func (e *EvilWallet) RequestFundsFromFaucet(options ...FaucetRequestOption) (ini

// RequestFreshBigFaucetWallets creates n new wallets, each wallet is created from one faucet request and contains 1000 outputs.
func (e *EvilWallet) RequestFreshBigFaucetWallets(numberOfWallets int) bool {
e.log.Debug("Requesting %d wallets from faucet", numberOfWallets)
e.log.Debugf("Requesting %d wallets from faucet", numberOfWallets)
success := true
// channel to block the number of concurrent goroutines
semaphore := make(chan bool, 1)
Expand All @@ -206,7 +208,7 @@ func (e *EvilWallet) RequestFreshBigFaucetWallets(numberOfWallets int) bool {
err := e.RequestFreshBigFaucetWallet()
if err != nil {
success = false
e.log.Error("Failed to request wallet from faucet: %s", err)
e.log.Errorf("Failed to request wallet from faucet: %s", err)

return
}
Expand Down Expand Up @@ -274,17 +276,22 @@ func (e *EvilWallet) requestAndSplitFaucetFunds(initWallet, receiveWallet *Walle
return splitTransactionsID, nil
}

func (e *EvilWallet) requestFaucetFunds(wallet *Wallet) (outputID *models.Output, err error) {
func (e *EvilWallet) requestFaucetFunds(wallet *Wallet) (output *models.Output, err error) {
receiveAddr := wallet.AddressOnIndex(0)
clt := e.connector.GetClient()

output, err := e.accWallet.RequestFaucetFunds(clt, receiveAddr, faucetTokensPerRequest)
err = clt.RequestFaucetFunds(receiveAddr)
if err != nil {
return nil, ierrors.Wrap(err, "failed to request funds from faucet")
}

outputID, iotaOutput, err := e.outputManager.AwaitAddressUnspentOutputToBeAccepted(receiveAddr, 10*time.Second)
if err != nil {
return nil, ierrors.Wrap(err, "failed to await faucet output acceptance")
}

// update wallet with newly created output
e.outputManager.createOutputFromAddress(wallet, receiveAddr, faucetTokensPerRequest, output.OutputID, output.OutputStruct)
output = e.outputManager.createOutputFromAddress(wallet, receiveAddr, iotaOutput.BaseTokenAmount(), outputID, iotaOutput)

return output, nil
}
Expand Down
37 changes: 37 additions & 0 deletions evilwallet/output_manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evilwallet

import (
"context"
"sync"
"time"

Expand Down Expand Up @@ -259,6 +260,42 @@ func (o *OutputManager) AwaitOutputToBeAccepted(outputID iotago.OutputID, waitFo
return accepted
}

func (o *OutputManager) AwaitAddressUnspentOutputToBeAccepted(addr *iotago.Ed25519Address, waitFor time.Duration) (outputID iotago.OutputID, output iotago.Output, err error) {
jkrvivian marked this conversation as resolved.
Show resolved Hide resolved
clt := o.connector.GetIndexerClient()
indexer, err := clt.Indexer()
if err != nil {
return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get indexer client")
}

s := time.Now()
addrBech := addr.Bech32(clt.CommittedAPI().ProtocolParameters().Bech32HRP())

for ; time.Since(s) < waitFor; time.Sleep(awaitAcceptationSleep) {
res, err := indexer.Outputs(context.Background(), &apimodels.BasicOutputsQuery{
AddressBech32: addrBech,
})
if err != nil {
return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "indexer request failed in request faucet funds")
}

for res.Next() {
unspents, err := res.Outputs(context.TODO())
if err != nil {
return iotago.EmptyOutputID, nil, ierrors.Wrap(err, "failed to get faucet unspent outputs")
}

if len(unspents) == 0 {
o.log.Debugf("no unspent outputs found in indexer for address: %s", addrBech)
break
}

return lo.Return1(res.Response.Items.OutputIDs())[0], unspents[0], nil
}
}

return iotago.EmptyOutputID, nil, ierrors.Errorf("no unspent outputs found for address %s due to timeout", addrBech)
}

// AwaitTransactionsAcceptance awaits for transaction confirmation and updates wallet with outputIDs.
func (o *OutputManager) AwaitTransactionsAcceptance(txIDs ...iotago.TransactionID) {
wg := sync.WaitGroup{}
Expand Down
Loading
Loading