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

Implement simplified faucet #2391

Merged
merged 21 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 18 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
102 changes: 102 additions & 0 deletions plugins/faucet/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package faucet

import (
"github.com/iotaledger/goshimmer/client/wallet"
"github.com/iotaledger/goshimmer/client/wallet/packages/address"
"github.com/iotaledger/goshimmer/packages/core/ledger"
"github.com/iotaledger/goshimmer/packages/core/ledger/utxo"
"github.com/iotaledger/goshimmer/packages/core/ledger/vm/devnetvm"
"github.com/iotaledger/goshimmer/packages/core/ledger/vm/devnetvm/indexer"
"github.com/iotaledger/goshimmer/packages/core/mana"
"github.com/iotaledger/goshimmer/packages/core/tangleold"
"github.com/iotaledger/goshimmer/plugins/blocklayer"
"github.com/iotaledger/hive.go/core/types/confirmation"
"github.com/pkg/errors"
)

type FaucetConnector struct {
tangle *tangleold.Tangle
indexer *indexer.Indexer
}

func NewConnector(t *tangleold.Tangle, indexer *indexer.Indexer) *FaucetConnector {
return &FaucetConnector{
tangle: t,
indexer: indexer,
}
}

func (f *FaucetConnector) UnspentOutputs(addresses ...address.Address) (unspentOutputs wallet.OutputsByAddressAndOutputID, err error) {
unspentOutputs = make(map[address.Address]map[utxo.OutputID]*wallet.Output)

for _, addr := range addresses {
f.indexer.CachedAddressOutputMappings(addr.Address()).Consume(func(mapping *indexer.AddressOutputMapping) {
f.tangle.Ledger.Storage.CachedOutput(mapping.OutputID()).Consume(func(output utxo.Output) {
if typedOutput, ok := output.(devnetvm.Output); ok {
f.tangle.Ledger.Storage.CachedOutputMetadata(typedOutput.ID()).Consume(func(outputMetadata *ledger.OutputMetadata) {
if !outputMetadata.IsSpent() {
addr := address.Address{AddressBytes: typedOutput.Address().Array()}
walletOutput := &wallet.Output{
Address: addr,
Object: typedOutput,
ConfirmationStateReached: outputMetadata.ConfirmationState().IsAccepted(),
Spent: false,
Metadata: wallet.OutputMetadata{
Timestamp: outputMetadata.CreationTime(),
},
}

// store output in result
if _, addressExists := unspentOutputs[addr]; !addressExists {
unspentOutputs[addr] = make(map[utxo.OutputID]*wallet.Output)
}
unspentOutputs[addr][typedOutput.ID()] = walletOutput
}
})
}
})
})
}

return
}

func (f *FaucetConnector) SendTransaction(tx *devnetvm.Transaction) (err error) {
// attach to block layer
issueTransaction := func() (*tangleold.Block, error) {
block, e := deps.Tangle.IssuePayload(tx)
if e != nil {
return nil, e
}
return block, nil
}

_, err = blocklayer.AwaitBlockToBeBooked(issueTransaction, tx.ID(), Parameters.MaxTransactionBookedAwaitTime)
if err != nil {
return errors.Errorf("%v: tx %s", err, tx.ID().String())
}
return nil
}

func (f *FaucetConnector) RequestFaucetFunds(address address.Address, powTarget int) (err error) {
panic("RequestFaucetFunds is not implemented in faucet connector.")
}

func (f *FaucetConnector) GetAllowedPledgeIDs() (pledgeIDMap map[mana.Type][]string, err error) {
jkrvivian marked this conversation as resolved.
Show resolved Hide resolved
pledgeIDMap = make(map[mana.Type][]string)
pledgeIDMap[mana.AccessMana] = []string{deps.Local.ID().EncodeBase58()}
pledgeIDMap[mana.ConsensusMana] = []string{deps.Local.ID().EncodeBase58()}

return
}

func (f *FaucetConnector) GetTransactionConfirmationState(txID utxo.TransactionID) (confirmationState confirmation.State, err error) {
f.tangle.Ledger.Storage.CachedTransactionMetadata(txID).Consume(func(tm *ledger.TransactionMetadata) {
confirmationState = tm.ConfirmationState()
})
return
}

func (f *FaucetConnector) GetUnspentAliasOutput(address *devnetvm.AliasAddress) (output *devnetvm.AliasOutput, err error) {
panic("GetUnspentAliasOutput is not implemented in faucet connector.")
}
119 changes: 119 additions & 0 deletions plugins/faucet/faucet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package faucet

import (
"context"
"time"

"github.com/iotaledger/goshimmer/client/wallet"
"github.com/iotaledger/goshimmer/client/wallet/packages/address"
"github.com/iotaledger/goshimmer/client/wallet/packages/seed"
"github.com/iotaledger/goshimmer/client/wallet/packages/sendoptions"
"github.com/iotaledger/goshimmer/packages/app/faucet"
"github.com/iotaledger/goshimmer/packages/core/ledger"
"github.com/iotaledger/goshimmer/packages/core/ledger/utxo"
"github.com/iotaledger/goshimmer/packages/core/ledger/vm/devnetvm"
"github.com/iotaledger/hive.go/core/bitmask"
"github.com/iotaledger/hive.go/core/generics/event"
"github.com/iotaledger/hive.go/core/identity"
"github.com/pkg/errors"
)

// remainder stays on index 0
type Faucet struct {
*wallet.Wallet
}

// NewFaucet creates a new Faucet instance.
func NewFaucet(faucetSeed *seed.Seed) *Faucet {
connector := NewConnector(deps.Tangle, deps.Indexer)

return &Faucet{wallet.New(
wallet.GenericConnector(connector),
wallet.Import(faucetSeed, 0, []bitmask.BitMask{}, nil),
wallet.ReusableAddress(true),
wallet.FaucetPowDifficulty(Parameters.PowDifficulty),
)}
}

// Start starts the faucet to fulfill faucet requests.
func (f *Faucet) Start(ctx context.Context, requestChan <-chan *faucet.Payload) {
for {
select {
case p := <-requestChan:
tx, err := f.handleFaucetRequest(p)
if err != nil {
Plugin.LogErrorf("fail to send funds to %s: %v", p.Address().Base58(), err)
return
}
Plugin.LogInfof("sent funds to %s: TXID: %s", p.Address().Base58(), tx.ID().Base58())

case <-ctx.Done():
return
}
}
}

// handleFaucetRequest sends funds to the requested address and waits for the transaction to become accepted.
func (f *Faucet) handleFaucetRequest(p *faucet.Payload) (*devnetvm.Transaction, error) {
// send funds to faucet in order to pledge mana
totalBalances := uint64(0)
confirmed, _, err := f.Balance(true)
if err != nil {
return nil, err
}
for _, b := range confirmed {
totalBalances += b
}

_, err = f.sendTransaction(
f.Seed().Address(0), // we only reuse the address at index 0 for the wallet
totalBalances-uint64(Parameters.TokensPerRequest),
deps.Local.ID(),
identity.ID{},
)
if err != nil {
return nil, err
}

// send funds to requester
tx, err := f.sendTransaction(
address.Address{AddressBytes: p.Address().Array()},
uint64(Parameters.TokensPerRequest),
p.AccessManaPledgeID(),
p.ConsensusManaPledgeID(),
)
return tx, err
}

func (f *Faucet) sendTransaction(destAddr address.Address, balance uint64, aManaPledgeID, cManaPledgeID identity.ID) (*devnetvm.Transaction, error) {
txAccepted := make(chan utxo.TransactionID, 10)
monitorTxAcceptance := event.NewClosure(func(event *ledger.TransactionAcceptedEvent) {
txAccepted <- event.TransactionID
})

// listen on confirmation
deps.Tangle.Ledger.Events.TransactionAccepted.Attach(monitorTxAcceptance)
defer deps.Tangle.Ledger.Events.TransactionAccepted.Detach(monitorTxAcceptance)

tx, err := f.SendFunds(
sendoptions.Destination(destAddr, balance),
sendoptions.AccessManaPledgeID(aManaPledgeID.EncodeBase58()),
sendoptions.ConsensusManaPledgeID(cManaPledgeID.EncodeBase58()),
)
if err != nil {
return nil, err
}

ticker := time.NewTicker(Parameters.MaxAwait)
defer ticker.Stop()
for {
select {
case txID := <-txAccepted:
if tx.ID() == txID {
return tx, nil
}
case <-ticker.C:
return nil, errors.Errorf("TX %s is not confirmed in time", tx.ID())
}
}
}
15 changes: 2 additions & 13 deletions plugins/faucet/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,8 @@ type ParametersDefinition struct {
// PowDifficulty defines the PoW difficulty for faucet payloads.
PowDifficulty int `default:"22" usage:"defines the PoW difficulty for faucet payloads"`

// BlacklistCapacity holds the maximum amount the address blacklist holds.
// An address for which a funding was done in the past is added to the blacklist and eventually is removed from it.
BlacklistCapacity int `default:"10000" usage:"holds the maximum amount the address blacklist holds"`

// SupplyOutputsCount is the number of supply outputs, and splitting transactions accordingly, the faucet prepares.
SupplyOutputsCount int `default:"20" usage:"the number of supply outputs, and splitting transactions accordingly, the faucet prepares."`

// SplittingMultiplier defines how many outputs each splitting transaction will have.
// SplittingMultiplier * SupplyOutputsCount indicates how many funding outputs during funds replenishment.
SplittingMultiplier int `default:"25" usage:"SplittingMultiplier defines how many outputs each supply transaction will have."`

// GenesisTokenAmount is the total supply.
GenesisTokenAmount uint64 `default:"1000000000000000" usage:"GenesisTokenAmount is the total supply."`
// MaxWaitAttempts defines the maximum time to wait for a transaction to be accepted.
MaxAwait time.Duration `default:"60s" usage:"the maximum time to wait for a transaction to be accepted"`
}

// Parameters contains the configuration parameters of the faucet plugin.
Expand Down
Loading