Skip to content
Snippets Groups Projects
Unverified Commit 8d9cdf6e authored by Luca Moser's avatar Luca Moser Committed by GitHub
Browse files

Adds blacklist and PoW requirement to the faucet (#609)

* adds blacklist and PoW requirement to the faucet

* fixes wrong condition to check pow

* fix faucet payload marhsaling

* don't test payload data if not payload data set
parent 081f7bb5
No related branches found
No related tags found
No related merge requests found
package faucet
import (
"crypto"
"runtime"
"sync"
"time"
......@@ -9,6 +10,7 @@ import (
faucetpayload "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/tangle"
"github.com/iotaledger/goshimmer/packages/pow"
"github.com/iotaledger/goshimmer/packages/shutdown"
"github.com/iotaledger/goshimmer/plugins/config"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
......@@ -32,12 +34,19 @@ const (
// CfgFaucetMaxTransactionBookedAwaitTimeSeconds defines the time to await for the transaction fulfilling a funding request
// to become booked in the value layer.
CfgFaucetMaxTransactionBookedAwaitTimeSeconds = "faucet.maxTransactionBookedAwaitTimeSeconds"
// CfgFaucetPoWDifficulty defines the PoW difficulty for faucet payloads.
CfgFaucetPoWDifficulty = "faucet.powDifficulty"
// CfgFaucetBlacklistCapacity 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.
CfgFaucetBlacklistCapacity = "faucet.blacklistCapacity"
)
func init() {
flag.String(CfgFaucetSeed, "", "the base58 encoded seed of the faucet, must be defined if this dApp is enabled")
flag.Int(CfgFaucetTokensPerRequest, 1337, "the amount of tokens the faucet should send for each request")
flag.Int(CfgFaucetMaxTransactionBookedAwaitTimeSeconds, 5, "the max amount of time for a funding transaction to become booked in the value layer.")
flag.Int(CfgFaucetMaxTransactionBookedAwaitTimeSeconds, 5, "the max amount of time for a funding transaction to become booked in the value layer")
flag.Int(CfgFaucetPoWDifficulty, 25, "defines the PoW difficulty for faucet payloads")
flag.Int(CfgFaucetBlacklistCapacity, 10000, "holds the maximum amount the address blacklist holds")
}
var (
......@@ -47,6 +56,7 @@ var (
_faucet *faucet.Faucet
faucetOnce sync.Once
log *logger.Logger
powVerifier = pow.New(crypto.BLAKE2b_512)
fundingWorkerPool *workerpool.WorkerPool
fundingWorkerCount = runtime.GOMAXPROCS(0)
fundingWorkerQueueSize = 500
......@@ -79,7 +89,8 @@ func Faucet() *faucet.Faucet {
if maxTxBookedAwaitTime <= 0 {
log.Fatalf("the max transaction booked await time must be more than 0")
}
_faucet = faucet.New(seedBytes, tokensPerRequest, time.Duration(maxTxBookedAwaitTime)*time.Second)
blacklistCapacity := config.Node().GetInt(CfgFaucetBlacklistCapacity)
_faucet = faucet.New(seedBytes, tokensPerRequest, blacklistCapacity, time.Duration(maxTxBookedAwaitTime)*time.Second)
})
return _faucet
}
......@@ -93,7 +104,7 @@ func configure(*node.Plugin) {
addr := msg.Payload().(*faucetpayload.Payload).Address()
msg, txID, err := Faucet().SendFunds(msg)
if err != nil {
log.Errorf("couldn't fulfill funding request to %s: %s", addr, err)
log.Warnf("couldn't fulfill funding request to %s: %s", addr, err)
return
}
log.Infof("sent funds to address %s via tx %s and msg %s", addr, txID, msg.Id().String())
......@@ -122,7 +133,26 @@ func configureEvents() {
return
}
addr := msg.Payload().(*faucetpayload.Payload).Address()
fundingRequest := msg.Payload().(*faucetpayload.Payload)
addr := fundingRequest.Address()
if Faucet().IsAddressBlacklisted(addr) {
log.Debugf("can't fund address %s since it is blacklisted", addr)
return
}
// verify PoW
leadingZeroes, err := powVerifier.LeadingZeros(fundingRequest.Bytes())
if err != nil {
log.Warnf("couldn't verify PoW of funding request for address %s", addr)
return
}
targetPoWDifficulty := config.Node().GetInt(CfgFaucetPoWDifficulty)
if leadingZeroes < targetPoWDifficulty {
log.Debugf("funding request for address %s doesn't fulfill PoW requirement %d vs. %d", addr, targetPoWDifficulty, leadingZeroes)
return
}
// finally add it to the faucet to be processed
_, added := fundingWorkerPool.TrySubmit(msg)
if !added {
log.Info("dropped funding request for address %s as queue is full", addr)
......
package faucet
import (
"errors"
"fmt"
"sync"
"time"
......@@ -13,16 +14,27 @@ import (
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
"github.com/iotaledger/goshimmer/packages/binary/datastructure/orderedmap"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/goshimmer/plugins/issuer"
)
var (
// ErrFundingTxNotBookedInTime is returned when a funding transaction didn't get booked
// by this node in the maximum defined await time for it to get booked.
ErrFundingTxNotBookedInTime = errors.New("funding transaction didn't get booked in time")
// ErrAddressIsBlacklisted is returned if a funding can't be processed since the address is blacklisted.
ErrAddressIsBlacklisted = errors.New("can't fund address as it is blacklisted")
)
// New creates a new faucet using the given seed and tokensPerRequest config.
func New(seed []byte, tokensPerRequest int64, maxTxBookedAwaitTime time.Duration) *Faucet {
func New(seed []byte, tokensPerRequest int64, blacklistCapacity int, maxTxBookedAwaitTime time.Duration) *Faucet {
return &Faucet{
tokensPerRequest: tokensPerRequest,
wallet: wallet.New(seed),
maxTxBookedAwaitTime: maxTxBookedAwaitTime,
blacklist: orderedmap.New(),
blacklistCapacity: blacklistCapacity,
}
}
......@@ -36,6 +48,28 @@ type Faucet struct {
// the time to await for the transaction fulfilling a funding request
// to become booked in the value layer
maxTxBookedAwaitTime time.Duration
blacklistCapacity int
blacklist *orderedmap.OrderedMap
}
// IsAddressBlacklisted checks whether the given address is currently blacklisted.
func (f *Faucet) IsAddressBlacklisted(addr address.Address) bool {
_, blacklisted := f.blacklist.Get(addr)
return blacklisted
}
// adds the given address to the blacklist and removes the oldest blacklist entry
// if it would go over capacity.
func (f *Faucet) addAddressToBlacklist(addr address.Address) {
f.blacklist.Set(addr, true)
if f.blacklist.Size() > f.blacklistCapacity {
var headKey interface{}
f.blacklist.ForEach(func(key, value interface{}) bool {
headKey = key
return false
})
f.blacklist.Delete(headKey)
}
}
// SendFunds sends IOTA tokens to the address from faucet request.
......@@ -46,6 +80,10 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin
addr := msg.Payload().(*faucetpayload.Payload).Address()
if f.IsAddressBlacklisted(addr) {
return nil, "", ErrAddressIsBlacklisted
}
// get the output ids for the inputs and remainder balance
outputIds, addrsIndices, remainder := f.collectUTXOsForFunding()
......@@ -90,6 +128,8 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin
return nil, "", fmt.Errorf("%w: tx %s", err, tx.ID().String())
}
f.addAddressToBlacklist(addr)
return msg, tx.ID().String(), nil
}
......
......@@ -5,6 +5,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/identity"
......@@ -19,13 +20,18 @@ func TestIsFaucetReq(t *testing.T) {
keyPair := ed25519.GenerateKeyPair()
local := identity.NewLocalIdentity(keyPair.PublicKey, keyPair.PrivateKey)
faucetPayload, err := faucet.New(address.Random(), 4)
if err != nil {
require.NoError(t, err)
return
}
faucetMsg := message.New(
message.EmptyId,
message.EmptyId,
time.Now(),
local.PublicKey(),
0,
faucet.New(address.Random()),
faucetPayload,
0,
ed25519.EmptySignature,
)
......
package faucetpayload
import (
"context"
"crypto"
_ "golang.org/x/crypto/blake2b"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/goshimmer/packages/pow"
"github.com/iotaledger/hive.go/marshalutil"
"github.com/iotaledger/hive.go/stringify"
......@@ -18,17 +24,28 @@ const (
type Payload struct {
payloadType payload.Type
address address.Address
nonce uint64
}
// Type represents the identifier for the faucet Payload type.
var Type = payload.Type(2)
var powWorker = pow.New(crypto.BLAKE2b_512, 1)
// New is the constructor of a Payload and creates a new Payload object from the given details.
func New(addr address.Address) *Payload {
return &Payload{
func New(addr address.Address, powTarget int) (*Payload, error) {
p := &Payload{
payloadType: Type,
address: addr,
}
payloadBytes := p.Bytes()
powRelevantBytes := payloadBytes[:len(payloadBytes)-pow.NonceBytes]
nonce, err := powWorker.Mine(context.Background(), powRelevantBytes, powTarget)
if err != nil {
return nil, err
}
p.nonce = nonce
return p, nil
}
func init() {
......@@ -56,15 +73,22 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Payload) (result *Payload,
if err != nil {
return
}
payloadBytes, err := marshalUtil.ReadUint32()
if _, err = marshalUtil.ReadUint32(); err != nil {
return
}
addr, err := marshalUtil.ReadBytes(address.Length)
if err != nil {
return
}
addr, err := marshalUtil.ReadBytes(int(payloadBytes))
result.address, _, err = address.FromBytes(addr)
if err != nil {
return
}
result.nonce, err = marshalUtil.ReadUint64()
if err != nil {
return
}
result.address, _, _ = address.FromBytes(addr)
// return the number of bytes we processed
consumedBytes = marshalUtil.ReadOffset()
......@@ -89,8 +113,9 @@ func (faucetPayload *Payload) Bytes() []byte {
// marshal the payload specific information
marshalUtil.WriteUint32(faucetPayload.Type())
marshalUtil.WriteUint32(uint32(len(faucetPayload.address)))
marshalUtil.WriteUint32(uint32(address.Length + pow.NonceBytes))
marshalUtil.WriteBytes(faucetPayload.address.Bytes())
marshalUtil.WriteUint64(faucetPayload.nonce)
// return result
return marshalUtil.Bytes()
......
......@@ -5,10 +5,9 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/identity"
"github.com/stretchr/testify/assert"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
......@@ -19,10 +18,10 @@ func ExamplePayload() {
local := identity.NewLocalIdentity(keyPair.PublicKey, keyPair.PrivateKey)
// 1. create faucet payload
faucetPayload := New(
// request address
address.Random(),
)
faucetPayload, err := New(address.Random(), 4)
if err != nil {
panic(err)
}
// 2. build actual message
tx := message.New(
......@@ -39,7 +38,10 @@ func ExamplePayload() {
}
func TestPayload(t *testing.T) {
originalPayload := New(address.Random())
originalPayload, err := New(address.Random(), 4)
if err != nil {
panic(err)
}
clonedPayload1, err, _ := FromBytes(originalPayload.Bytes())
if err != nil {
......
......@@ -2,9 +2,12 @@ package dashboard
import (
"net/http"
"sync"
"github.com/iotaledger/goshimmer/dapps/faucet"
faucetpayload "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/plugins/config"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
"github.com/labstack/echo"
......@@ -32,8 +35,16 @@ func setupFaucetRoutes(routeGroup *echo.Group) {
})
}
var fundingReqMu = sync.Mutex{}
func sendFaucetReq(addr address.Address) (res *ReqMsg, err error) {
msg := messagelayer.MessageFactory().IssuePayload(faucetpayload.New(addr))
fundingReqMu.Lock()
defer fundingReqMu.Unlock()
faucetPayload, err := faucetpayload.New(addr, config.Node().GetInt(faucet.CfgFaucetPoWDifficulty))
if err != nil {
return nil, err
}
msg := messagelayer.MessageFactory().IssuePayload(faucetPayload)
if msg == nil {
return nil, errors.Wrapf(ErrInternalError, "Fail to send faucet request")
}
......
......@@ -4,8 +4,10 @@ import (
"net/http"
goSync "sync"
"github.com/iotaledger/goshimmer/dapps/faucet"
faucetpayload "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/plugins/config"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/logger"
......@@ -23,6 +25,7 @@ var (
plugin *node.Plugin
once goSync.Once
log *logger.Logger
fundingMu goSync.Mutex
)
// Plugin gets the plugin instance.
......@@ -41,6 +44,8 @@ func configure(plugin *node.Plugin) {
// requestFunds creates a faucet request (0-value) message with the given destination address and
// broadcasts it to the node's neighbors. It returns the message ID if successful.
func requestFunds(c echo.Context) error {
fundingMu.Lock()
defer fundingMu.Unlock()
var request Request
var addr address.Address
if err := c.Bind(&request); err != nil {
......@@ -55,8 +60,11 @@ func requestFunds(c echo.Context) error {
return c.JSON(http.StatusBadRequest, Response{Error: "Invalid address"})
}
// build faucet message with transaction factory
msg := messagelayer.MessageFactory().IssuePayload(faucetpayload.New(addr))
faucetPayload, err := faucetpayload.New(addr, config.Node().GetInt(faucet.CfgFaucetPoWDifficulty))
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
msg := messagelayer.MessageFactory().IssuePayload(faucetPayload)
if msg == nil {
return c.JSON(http.StatusInternalServerError, Response{Error: "Fail to send faucetrequest"})
}
......
......@@ -85,6 +85,7 @@ func (d *DockerContainer) CreateGoShimmerPeer(config GoShimmerConfig) error {
fmt.Sprintf("--valueLayer.fcob.averageNetworkDelay=%d", ParaFCoBAverageNetworkDelay),
fmt.Sprintf("--node.disablePlugins=%s", config.DisabledPlugins),
fmt.Sprintf("--pow.difficulty=%d", ParaPoWDifficulty),
fmt.Sprintf("--faucet.powDifficulty=%d", ParaPoWFaucetDifficulty),
fmt.Sprintf("--gracefulshutdown.waitToKillTime=%d", ParaWaitToKill),
fmt.Sprintf("--node.enablePlugins=%s", func() string {
var plugins []string
......
......@@ -37,6 +37,8 @@ var (
ParaPoWDifficulty = 2
// ParaWaitToKill defines the time to wait before killing the node.
ParaWaitToKill = 60
// ParaPoWFaucetDifficulty defines the PoW difficulty for faucet payloads.
ParaPoWFaucetDifficulty = 2
)
var (
......
......@@ -9,7 +9,6 @@ import (
"testing"
"time"
faucet_payload "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
......@@ -81,33 +80,28 @@ func SendDataMessage(t *testing.T, peer *framework.Peer, data []byte, number int
func SendFaucetRequestOnRandomPeer(t *testing.T, peers []*framework.Peer, numMessages int) (ids map[string]DataMessageSent, addrBalance map[string]map[balance.Color]int64) {
ids = make(map[string]DataMessageSent, numMessages)
addrBalance = make(map[string]map[balance.Color]int64)
for _, p := range peers {
addr := p.Seed().Address(0).String()
addrBalance[addr] = make(map[balance.Color]int64)
addrBalance[addr][balance.ColorIOTA] = 0
}
for i := 0; i < numMessages; i++ {
peer := peers[rand.Intn(len(peers))]
id, sent := SendFaucetRequest(t, peer)
addr := peer.Seed().Address(uint64(i))
id, sent := SendFaucetRequest(t, peer, addr)
ids[id] = sent
addrBalance[peer.Seed().Address(0).String()][balance.ColorIOTA] += framework.ParaFaucetTokensPerRequest
addrBalance[addr.String()] = map[balance.Color]int64{
balance.ColorIOTA: framework.ParaFaucetTokensPerRequest,
}
}
return ids, addrBalance
}
// SendFaucetRequest sends a data message on a given peer and returns the id and a DataMessageSent struct.
func SendFaucetRequest(t *testing.T, peer *framework.Peer) (string, DataMessageSent) {
addr := peer.Seed().Address(0)
func SendFaucetRequest(t *testing.T, peer *framework.Peer, addr address.Address) (string, DataMessageSent) {
resp, err := peer.SendFaucetRequest(addr.String())
require.NoErrorf(t, err, "Could not send faucet request on %s", peer.String())
sent := DataMessageSent{
id: resp.ID,
// save payload to be able to compare API response
data: faucet_payload.New(addr).Bytes(),
data: nil,
issuerPublicKey: peer.Identity.PublicKey().String(),
}
return resp.ID, sent
......@@ -143,7 +137,9 @@ func CheckForMessageIds(t *testing.T, peers []*framework.Peer, ids map[string]Da
msgSent := ids[msg.ID]
assert.Equalf(t, msgSent.issuerPublicKey, msg.IssuerPublicKey, "messageID=%s, issuer=%s not correct issuer in %s.", msgSent.id, msgSent.issuerPublicKey, peer.String())
if msgSent.data != nil {
assert.Equalf(t, msgSent.data, msg.Payload, "messageID=%s, issuer=%s data not equal in %s.", msgSent.id, msgSent.issuerPublicKey, peer.String())
}
assert.Truef(t, msg.Metadata.Solid, "messageID=%s, issuer=%s not solid in %s.", msgSent.id, msgSent.issuerPublicKey, peer.String())
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment