diff --git a/clients/chainclient/chainclient.go b/clients/chainclient/chainclient.go index 7a0ad49444..3c72f4faa3 100644 --- a/clients/chainclient/chainclient.go +++ b/clients/chainclient/chainclient.go @@ -5,6 +5,7 @@ import ( "math" iotago "github.com/iotaledger/iota.go/v3" + "github.com/iotaledger/wasp/clients/apiclient" "github.com/iotaledger/wasp/clients/apiextensions" "github.com/iotaledger/wasp/packages/cryptolib" @@ -47,6 +48,7 @@ type PostRequestParams struct { Allowance *isc.Assets gasBudget uint64 AutoAdjustStorageDeposit bool + OnlyUnlockedOutputs bool } func (par *PostRequestParams) GasBudget() uint64 { @@ -69,7 +71,16 @@ func (c *Client) Post1Request( entryPoint isc.Hname, params ...PostRequestParams, ) (*iotago.Transaction, error) { - outputsSet, err := c.Layer1Client.OutputMap(c.KeyPair.Address()) + par := defaultParams(params...) + var outputsSet iotago.OutputSet + var err error + + if par.OnlyUnlockedOutputs { + outputsSet, err = c.Layer1Client.OutputMapNonLocked(c.KeyPair.Address()) + } else { + outputsSet, err = c.Layer1Client.OutputMap(c.KeyPair.Address()) + } + if err != nil { return nil, err } @@ -83,8 +94,16 @@ func (c *Client) PostNRequests( requestsCount int, params ...PostRequestParams, ) ([]*iotago.Transaction, error) { + par := defaultParams(params...) + var outputs iotago.OutputSet var err error - outputs, err := c.Layer1Client.OutputMap(c.KeyPair.Address()) + + if par.OnlyUnlockedOutputs { + outputs, err = c.Layer1Client.OutputMapNonLocked(c.KeyPair.Address()) + } else { + outputs, err = c.Layer1Client.OutputMap(c.KeyPair.Address()) + } + if err != nil { return nil, err } diff --git a/packages/l1connection/l1connection.go b/packages/l1connection/l1connection.go index 46609d614a..c96474b661 100644 --- a/packages/l1connection/l1connection.go +++ b/packages/l1connection/l1connection.go @@ -37,15 +37,17 @@ type Config struct { } type Client interface { - // requests funds from faucet, waits for confirmation + // RequestFunds requests funds from faucet, waits for confirmation RequestFunds(addr iotago.Address, timeout ...time.Duration) error - // sends a tx (including tipselection and local PoW if necessary) and waits for confirmation + // PostTxAndWaitUntilConfirmation sends a tx (including tipselection and local PoW if necessary) and waits for confirmation PostTxAndWaitUntilConfirmation(tx *iotago.Transaction, timeout ...time.Duration) (iotago.BlockID, error) - // returns the outputs owned by a given address + // OutputMap returns the outputs owned by a given address OutputMap(myAddress iotago.Address, timeout ...time.Duration) (iotago.OutputSet, error) - // output + // OutputMapNonLocked returns the outputs owned by a given address, excluding any locked outputs (mainly relevant for sending TXs from clients) + OutputMapNonLocked(myAddress iotago.Address, timeout ...time.Duration) (iotago.OutputSet, error) + // GetAliasOutput output GetAliasOutput(aliasID iotago.AliasID, timeout ...time.Duration) (iotago.OutputID, iotago.Output, error) - // used to query the health endpoint of the node + // Health used to query the health endpoint of the node Health(timeout ...time.Duration) (bool, error) } @@ -87,8 +89,31 @@ func NewClient(config Config, log *logger.Logger, timeout ...time.Duration) Clie } } -// OutputMap implements L1Connection -func (c *l1client) OutputMap(myAddress iotago.Address, timeout ...time.Duration) (iotago.OutputSet, error) { +func (c *l1client) readOutputs(ctx context.Context, queries []nodeclient.IndexerQuery) (iotago.OutputSet, error) { + result := make(map[iotago.OutputID]iotago.Output) + + for _, query := range queries { + res, err := c.indexerClient.Outputs(ctx, query) + if err != nil { + return nil, fmt.Errorf("failed to query address outputs: %w", err) + } + for res.Next() { + outputs, err := res.Outputs() + if err != nil { + return nil, fmt.Errorf("failed to fetch address outputs: %w", err) + } + + outputIDs := res.Response.Items.MustOutputIDs() + for i := range outputs { + result[outputIDs[i]] = outputs[i] + } + } + } + return result, nil +} + +// OutputMapNonLocked implements L1Connection +func (c *l1client) OutputMapNonLocked(myAddress iotago.Address, timeout ...time.Duration) (iotago.OutputSet, error) { ctxWithTimeout, cancelContext := newCtx(c.ctx, timeout...) defer cancelContext() @@ -103,44 +128,36 @@ func (c *l1client) OutputMap(myAddress iotago.Address, timeout ...time.Duration) HasTimelock: &trueParam, TimelockedBefore: uint32(time.Now().Unix()), }, - IndexerNativeTokenParas: nodeclient.IndexerNativeTokenParas{ - HasNativeTokens: &falseParam, - }, }, &nodeclient.BasicOutputsQuery{ AddressBech32: bech32Addr, IndexerTimelockParas: nodeclient.IndexerTimelockParas{ HasTimelock: &falseParam, }, - IndexerNativeTokenParas: nodeclient.IndexerNativeTokenParas{ - HasNativeTokens: &falseParam, - }, }, &nodeclient.FoundriesQuery{AliasAddressBech32: bech32Addr}, &nodeclient.NFTsQuery{AddressBech32: bech32Addr}, &nodeclient.AliasesQuery{GovernorBech32: bech32Addr}, } - result := make(map[iotago.OutputID]iotago.Output) + return c.readOutputs(ctxWithTimeout, queries) +} - for _, query := range queries { - res, err := c.indexerClient.Outputs(ctxWithTimeout, query) - if err != nil { - return nil, fmt.Errorf("failed to query address outputs: %w", err) - } - for res.Next() { - outputs, err := res.Outputs() - if err != nil { - return nil, fmt.Errorf("failed to fetch address outputs: %w", err) - } +// OutputMap implements L1Connection +func (c *l1client) OutputMap(myAddress iotago.Address, timeout ...time.Duration) (iotago.OutputSet, error) { + ctxWithTimeout, cancelContext := newCtx(c.ctx, timeout...) + defer cancelContext() - outputIDs := res.Response.Items.MustOutputIDs() - for i := range outputs { - result[outputIDs[i]] = outputs[i] - } - } + bech32Addr := myAddress.Bech32(parameters.L1().Protocol.Bech32HRP) + + queries := []nodeclient.IndexerQuery{ + &nodeclient.BasicOutputsQuery{AddressBech32: bech32Addr}, + &nodeclient.FoundriesQuery{AliasAddressBech32: bech32Addr}, + &nodeclient.NFTsQuery{AddressBech32: bech32Addr}, + &nodeclient.AliasesQuery{GovernorBech32: bech32Addr}, } - return result, nil + + return c.readOutputs(ctxWithTimeout, queries) } // postBlock sends a block (including tipselection and local PoW if necessary). diff --git a/tools/cluster/tests/wasp-cli_test.go b/tools/cluster/tests/wasp-cli_test.go index c3a7665d2f..cfbc91249e 100644 --- a/tools/cluster/tests/wasp-cli_test.go +++ b/tools/cluster/tests/wasp-cli_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" iotago "github.com/iotaledger/iota.go/v3" + "github.com/iotaledger/wasp/clients/apiclient" "github.com/iotaledger/wasp/packages/kv/codec" "github.com/iotaledger/wasp/packages/parameters" @@ -203,6 +204,14 @@ func TestWaspCLIDeposit(t *testing.T) { ) require.Regexp(t, `.*Error: \(empty\).*`, strings.Join(out, "")) + out = w.MustRun("balance") + for _, line := range out { + if strings.Contains(line, "0x") { + balance := strings.TrimSpace(strings.Split(line, ":")[1]) + require.Equal(t, balance, "2") + } + } + // deposit the native token to the chain (to an ethereum account) w.MustRun( "chain", "deposit", eth.String(), diff --git a/tools/wasp-cli/chain/postrequest.go b/tools/wasp-cli/chain/postrequest.go index 08c16485ce..67b76ee3a5 100644 --- a/tools/wasp-cli/chain/postrequest.go +++ b/tools/wasp-cli/chain/postrequest.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" iotago "github.com/iotaledger/iota.go/v3" + "github.com/iotaledger/wasp/clients/chainclient" "github.com/iotaledger/wasp/packages/isc" "github.com/iotaledger/wasp/packages/transaction" @@ -46,7 +47,7 @@ func postRequest(nodeName, chain, hname, fname string, params chainclient.PostRe } util.WithSCTransaction(config.GetChain(chain), nodeName, func() (*iotago.Transaction, error) { - return scClient.PostRequest(fname, params) + return scClient.PostRequest(fname, params, chainclient.PostRequestParams{OnlyUnlockedOutputs: true}) }) }