Skip to content
Snippets Groups Projects
Unverified Commit a15f2729 authored by Ching-Hua (Vivian) Lin's avatar Ching-Hua (Vivian) Lin Committed by GitHub
Browse files

feat: Implement faucet dApp (#531)

* Feat: Integrate faucet page to SPA plugin

* Feat: Add Link to get txn of issued address

* Feat: Add faucet payload

* Feat: Add faucet plugin and minor tweaks

* Fix: Check payload type after the txn is solid

* Fix: Update package name

* Fix: Fix payload test

* Test: Add faucet test

* Refactor: Minor tweaks in plugin/faucet.go

* Feat: Add faucet webapi

* Feat: Apply faucet plugins

* Feat: Apply messagefactory and update to latest develop

* Fix: Fix import error

* Fix: Fix tests in binary/faucet

* Feat: Integrate faucet page to SPA plugin

* Feat: Add Link to get txn of issued address

* Feat: Add faucet payload

* Feat: Add faucet plugin and minor tweaks

* Fix: Check payload type after the txn is solid

* Fix: Update package name

* Fix: Fix payload test

* Test: Add faucet test

* Refactor: Minor tweaks in plugin/faucet.go

* Feat: Add faucet webapi

* Feat: Apply faucet plugins

* Feat: Apply messagefactory and update to latest develop

* Fix: Fix import error

* Fix: Fix tests in binary/faucet

* refactor: Update to latest usage

* fix: Update go.mod, go.sum

* refactor: Disable faucet plugin by default

* fix: Update to latest changes

* feat: Add faucet payload layout

* refactor: Move faucet to dapps

* feat: Enable the faucet to send funds :sparkles:

* Fix: fix tests

* fix: Fix test

* fix: Initiate LedgerState

* Update packr on dashboard

* refactor: refactor SendFunds in faucet dapp

* feat: Add faucet integration test

* feat: Add faucet integration test to script

* doc: Add function descriptions

* fix: fix wrong parameter in CheckBalances

* fix: fix :dog:

* fix some stuff

* make the faucet configurable via CLI flags

* make the faucet seed a parameter in the integration tests

* activate the faucet on the peer master in docker-network

* fixes wrong address route in faucet view

* improves faucet processing log message

* fix log messages in faucet dapp

* improve error message further

* fixes unit tests

* adds tool to auto. gen. address

* dog

* wait for faucet funding tx to get booked before processing the next request

* make the dog stay silent

* decrease pow difficulty to 1 in int. tests

* use 4 as the pow difficulty in integration tests

* :dog:



* fix integration tests

* dec. pow diff to 1

* use 0 for the faucet integration test

* use a worker pool to serve faucet funding requests

Co-authored-by: default avatarLuca Moser <moser.luca@gmail.com>
parent 9696c899
No related branches found
No related tags found
No related merge requests found
Showing
with 10601 additions and 291 deletions
......@@ -213,3 +213,37 @@ jobs:
with:
name: ${{ env.TEST_NAME }}
path: tools/integration-tests/logs
faucet:
name: faucet
env:
TEST_NAME: faucet
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Build GoShimmer image
run: docker build -t iotaledger/goshimmer .
- name: Pull additional Docker images
run: |
docker pull angelocapossele/drand:latest
docker pull gaiaadm/pumba:latest
docker pull gaiadocker/iproute2:latest
- name: Run integration tests
run: docker-compose -f tools/integration-tests/tester/docker-compose.yml up --abort-on-container-exit --exit-code-from tester --build
- name: Create logs from tester
if: always()
run: |
docker logs tester &> tools/integration-tests/logs/tester.log
- name: Save logs as artifacts
if: always()
uses: actions/upload-artifact@v1
with:
name: ${{ env.TEST_NAME }}
path: tools/integration-tests/logs
package client
import (
"net/http"
webapi_faucet "github.com/iotaledger/goshimmer/plugins/webapi/faucet"
)
const (
routeFaucet = "faucet"
)
// SendFaucetRequest requests funds from faucet nodes by sending a faucet request payload message.
func (api *GoShimmerAPI) SendFaucetRequest(base58EncodedAddr string) (*webapi_faucet.Response, error) {
res := &webapi_faucet.Response{}
if err := api.do(http.MethodPost, routeFaucet,
&webapi_faucet.Request{Address: base58EncodedAddr}, res); err != nil {
return nil, err
}
return res, nil
}
package faucet
import (
"runtime"
"sync"
"time"
faucet "github.com/iotaledger/goshimmer/dapps/faucet/packages"
"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/shutdown"
"github.com/iotaledger/goshimmer/plugins/config"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
"github.com/iotaledger/hive.go/daemon"
"github.com/iotaledger/hive.go/events"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/iotaledger/hive.go/workerpool"
"github.com/mr-tron/base58"
flag "github.com/spf13/pflag"
)
const (
// PluginName is the name of the faucet dApp.
PluginName = "Faucet"
// CfgFaucetSeed defines the base58 encoded seed the faucet uses.
CfgFaucetSeed = "faucet.seed"
// CfgFaucetTokensPerRequest defines the amount of tokens the faucet should send for each request.
CfgFaucetTokensPerRequest = "faucet.tokensPerRequest"
// CfgFaucetMaxTransactionBookedAwaitTimeSeconds defines the time to await for the transaction fulfilling a funding request
// to become booked in the value layer.
CfgFaucetMaxTransactionBookedAwaitTimeSeconds = "faucet.maxTransactionBookedAwaitTimeSeconds"
)
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.")
}
var (
// App is the "plugin" instance of the faucet application.
plugin *node.Plugin
pluginOnce sync.Once
_faucet *faucet.Faucet
faucetOnce sync.Once
log *logger.Logger
fundingWorkerPool *workerpool.WorkerPool
fundingWorkerCount = runtime.GOMAXPROCS(0)
fundingWorkerQueueSize = 500
)
// App returns the plugin instance of the faucet dApp.
func App() *node.Plugin {
pluginOnce.Do(func() {
plugin = node.NewPlugin(PluginName, node.Disabled, configure, run)
})
return plugin
}
// Faucet gets the faucet instance the faucet dApp has initialized.
func Faucet() *faucet.Faucet {
faucetOnce.Do(func() {
base58Seed := config.Node().GetString(CfgFaucetSeed)
if len(base58Seed) == 0 {
log.Fatal("a seed must be defined when enabling the faucet dApp")
}
seedBytes, err := base58.Decode(base58Seed)
if err != nil {
log.Fatalf("configured seed for the faucet is invalid: %s", err)
}
tokensPerRequest := config.Node().GetInt64(CfgFaucetTokensPerRequest)
if tokensPerRequest <= 0 {
log.Fatalf("the amount of tokens to fulfill per request must be above zero")
}
maxTxBookedAwaitTime := config.Node().GetInt64(CfgFaucetMaxTransactionBookedAwaitTimeSeconds)
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)
})
return _faucet
}
func configure(*node.Plugin) {
log = logger.NewLogger(PluginName)
Faucet()
fundingWorkerPool = workerpool.New(func(task workerpool.Task) {
msg := task.Param(0).(*message.Message)
addr := msg.Payload().(*faucetpayload.Payload).Address()
_, txID, err := Faucet().SendFunds(msg)
if err != nil {
log.Errorf("couldn't fulfill funding request to %s: %s", addr, err)
return
}
log.Infof("sent funds to address %s via tx %s", addr, txID)
}, workerpool.WorkerCount(fundingWorkerCount), workerpool.QueueSize(fundingWorkerQueueSize))
configureEvents()
}
func run(*node.Plugin) {
if err := daemon.BackgroundWorker("[Faucet]", func(shutdownSignal <-chan struct{}) {
fundingWorkerPool.Start()
defer fundingWorkerPool.Stop()
<-shutdownSignal
}, shutdown.PriorityFaucet); err != nil {
log.Panicf("Failed to start daemon: %s", err)
}
}
func configureEvents() {
messagelayer.Tangle().Events.MessageSolid.Attach(events.NewClosure(func(cachedMessage *message.CachedMessage, cachedMessageMetadata *tangle.CachedMessageMetadata) {
defer cachedMessage.Release()
defer cachedMessageMetadata.Release()
msg := cachedMessage.Unwrap()
if msg == nil || !faucetpayload.IsFaucetReq(msg) {
return
}
addr := msg.Payload().(*faucetpayload.Payload).Address()
_, added := fundingWorkerPool.TrySubmit(msg)
if !added {
log.Info("dropped funding request for address %s as queue is full", addr)
return
}
log.Infof("enqueued funding request for address %s", addr)
}))
}
package faucet
import "errors"
var (
// ErrInvalidAddr represents an error that is triggered when an invalid address is detected.
ErrInvalidAddr = errors.New("invalid address")
)
package faucet
import (
"errors"
"fmt"
"sync"
"time"
faucetpayload "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/hive.go/events"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
"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/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")
)
// New creates a new faucet using the given seed and tokensPerRequest config.
func New(seed []byte, tokensPerRequest int64, maxTxBookedAwaitTime time.Duration) *Faucet {
return &Faucet{
tokensPerRequest: tokensPerRequest,
wallet: wallet.New(seed),
maxTxBookedAwaitTime: maxTxBookedAwaitTime,
}
}
// The Faucet implements a component which will send tokens to actors requesting tokens.
type Faucet struct {
sync.Mutex
// the amount of tokens to send to every request
tokensPerRequest int64
// the wallet instance of the faucet holding the tokens
wallet *wallet.Wallet
// the time to await for the transaction fulfilling a funding request
// to become booked in the value layer
maxTxBookedAwaitTime time.Duration
}
// SendFunds sends IOTA tokens to the address from faucet request.
func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID string, err error) {
// ensure that only one request is being processed any given time
f.Lock()
defer f.Unlock()
addr := msg.Payload().(*faucetpayload.Payload).Address()
// get the output ids for the inputs and remainder balance
outputIds, remainder := f.collectUTXOsForFunding()
tx := transaction.New(
// inputs
transaction.NewInputs(outputIds...),
// outputs
transaction.NewOutputs(map[address.Address][]*balance.Balance{
addr: {
balance.New(balance.ColorIOTA, f.tokensPerRequest),
},
}),
)
// add remainder address if needed
if remainder > 0 {
remainAddr := f.nextUnusedAddress()
tx.Outputs().Add(remainAddr, []*balance.Balance{balance.New(balance.ColorIOTA, remainder)})
}
// prepare value payload with value factory
payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
// attach to message layer
msg, err = issuer.IssuePayload(payload)
if err != nil {
return nil, "", err
}
// block for a certain amount of time until we know that the transaction
// actually got booked by this node itself
// TODO: replace with an actual more reactive way
bookedInTime := f.awaitTransactionBooked(tx.ID(), f.maxTxBookedAwaitTime)
if !bookedInTime {
return nil, "", fmt.Errorf("%w: tx %s", ErrFundingTxNotBookedInTime, tx.ID().String())
}
return msg, tx.ID().String(), nil
}
// awaitTransactionBooked awaits maxAwait for the given transaction to get booked.
func (f *Faucet) awaitTransactionBooked(txID transaction.ID, maxAwait time.Duration) bool {
booked := make(chan struct{}, 1)
// exit is used to let the caller exit if for whatever
// reason the same transaction gets booked multiple times
exit := make(chan struct{})
defer close(exit)
closure := events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, decisionPending bool) {
defer cachedTransaction.Release()
defer cachedTransactionMetadata.Release()
if cachedTransaction.Unwrap().ID() != txID {
return
}
select {
case booked <- struct{}{}:
case <-exit:
}
})
valuetransfers.Tangle().Events.TransactionBooked.Attach(closure)
defer valuetransfers.Tangle().Events.TransactionBooked.Detach(closure)
select {
case <-time.After(maxAwait):
return false
case <-booked:
return true
}
}
// collectUTXOsForFunding iterates over the faucet's UTXOs until the token threshold is reached.
// this function also returns the remainder balance for the given outputs.
func (f *Faucet) collectUTXOsForFunding() (outputIds []transaction.OutputID, remainder int64) {
var total = f.tokensPerRequest
var i uint64
// get a list of address for inputs
for i = 0; total > 0; i++ {
addr := f.wallet.Seed().Address(i)
valuetransfers.Tangle().OutputsOnAddress(addr).Consume(func(output *tangle.Output) {
if output.ConsumerCount() > 0 || total == 0 {
return
}
var val int64
for _, coloredBalance := range output.Balances() {
val += coloredBalance.Value
}
// get unspent output ids and check if it's conflict
if val <= total {
total -= val
} else {
remainder = val - total
total = 0
}
outputIds = append(outputIds, output.ID())
})
}
return
}
// nextUnusedAddress generates an unused address from the faucet seed.
func (f *Faucet) nextUnusedAddress() address.Address {
var index uint64
for index = 0; ; index++ {
addr := f.wallet.Seed().Address(index)
cachedOutputs := valuetransfers.Tangle().OutputsOnAddress(addr)
if len(cachedOutputs) == 0 {
// unused address
cachedOutputs.Release()
return addr
}
cachedOutputs.Release()
}
}
package faucet
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/identity"
faucet "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
data "github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload"
)
func TestIsFaucetReq(t *testing.T) {
keyPair := ed25519.GenerateKeyPair()
local := identity.NewLocalIdentity(keyPair.PublicKey, keyPair.PrivateKey)
faucetMsg := message.New(
message.EmptyId,
message.EmptyId,
time.Now(),
local.PublicKey(),
0,
faucet.New(address.Random()),
0,
ed25519.EmptySignature,
)
dataMsg := message.New(
message.EmptyId,
message.EmptyId,
time.Now(),
local.PublicKey(),
0,
data.NewData([]byte("data")),
0,
ed25519.EmptySignature,
)
assert.Equal(t, true, faucet.IsFaucetReq(faucetMsg))
assert.Equal(t, false, faucet.IsFaucetReq(dataMsg))
}
package faucetpayload
import (
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/hive.go/marshalutil"
"github.com/iotaledger/hive.go/stringify"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload"
)
// Payload represents a request which contains an address for the faucet to send funds to.
type Payload struct {
payloadType payload.Type
address address.Address
}
// Type represents the identifier for the faucet Payload type.
var Type = payload.Type(2)
// 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{
payloadType: Type,
address: addr,
}
}
func init() {
payload.RegisterType(Type, GenericPayloadUnmarshalerFactory(Type))
}
// FromBytes parses the marshaled version of a Payload into an object.
// It either returns a new Payload or fills an optionally provided Payload with the parsed information.
func FromBytes(bytes []byte, optionalTargetObject ...*Payload) (result *Payload, err error, consumedBytes int) {
// determine the target object that will hold the unmarshaled information
switch len(optionalTargetObject) {
case 0:
result = &Payload{}
case 1:
result = optionalTargetObject[0]
default:
panic("too many arguments in call to FromBytes")
}
// initialize helper
marshalUtil := marshalutil.New(bytes)
// read data
result.payloadType, err = marshalUtil.ReadUint32()
if err != nil {
return
}
payloadBytes, err := marshalUtil.ReadUint32()
if err != nil {
return
}
addr, err := marshalUtil.ReadBytes(int(payloadBytes))
if err != nil {
return
}
result.address, _, _ = address.FromBytes(addr)
// return the number of bytes we processed
consumedBytes = marshalUtil.ReadOffset()
return
}
// Type returns the type of the faucet Payload.
func (faucetPayload *Payload) Type() payload.Type {
return faucetPayload.payloadType
}
// Address returns the address of the faucet Payload.
func (faucetPayload *Payload) Address() address.Address {
return faucetPayload.address
}
// Bytes marshals the data payload into a sequence of bytes.
func (faucetPayload *Payload) Bytes() []byte {
// initialize helper
marshalUtil := marshalutil.New()
// marshal the payload specific information
marshalUtil.WriteUint32(faucetPayload.Type())
marshalUtil.WriteUint32(uint32(len(faucetPayload.address)))
marshalUtil.WriteBytes(faucetPayload.address.Bytes())
// return result
return marshalUtil.Bytes()
}
// Unmarshal unmarshals a given slice of bytes and fills the object.
func (faucetPayload *Payload) Unmarshal(data []byte) (err error) {
_, err, _ = FromBytes(data, faucetPayload)
return
}
// String returns a human readable version of faucet payload (for debug purposes).
func (faucetPayload *Payload) String() string {
return stringify.Struct("FaucetPayload",
stringify.StructField("address", faucetPayload.Address().String()),
)
}
// GenericPayloadUnmarshalerFactory sets the generic unmarshaler.
func GenericPayloadUnmarshalerFactory(payloadType payload.Type) payload.Unmarshaler {
return func(data []byte) (payload payload.Payload, err error) {
payload = &Payload{
payloadType: payloadType,
}
err = payload.Unmarshal(data)
return
}
}
// IsFaucetReq checks if the message is faucet payload.
func IsFaucetReq(msg *message.Message) bool {
return msg.Payload().Type() == Type
}
package faucetpayload
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/identity"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
)
func ExamplePayload() {
keyPair := ed25519.GenerateKeyPair()
local := identity.NewLocalIdentity(keyPair.PublicKey, keyPair.PrivateKey)
// 1. create faucet payload
faucetPayload := New(
// request address
address.Random(),
)
// 2. build actual message
tx := message.New(
message.EmptyId,
message.EmptyId,
time.Now(),
local.PublicKey(),
0,
faucetPayload,
0,
ed25519.EmptySignature,
)
fmt.Println(tx.String())
}
func TestPayload(t *testing.T) {
originalPayload := New(address.Random())
clonedPayload1, err, _ := FromBytes(originalPayload.Bytes())
if err != nil {
panic(err)
}
assert.Equal(t, originalPayload.Address(), clonedPayload1.Address())
clonedPayload2, err, _ := FromBytes(clonedPayload1.Bytes())
if err != nil {
panic(err)
}
assert.Equal(t, originalPayload.Address(), clonedPayload2.Address())
}
......@@ -104,6 +104,9 @@ func configure(_ *node.Plugin) {
// configure Tangle
_tangle = Tangle()
// configure LedgerState
ledgerState = tangle.NewLedgerState(Tangle())
// read snapshot file
snapshotFilePath := config.Node().GetString(CfgValueLayerSnapshotFile)
if len(snapshotFilePath) != 0 {
......
......@@ -5,6 +5,7 @@ const (
PriorityFPC
PriorityTangle
PriorityMissingMessagesMonitoring
PriorityFaucet
PriorityRemoteLog
PriorityAnalysis
PriorityPrometheus
......
package core
import (
"github.com/iotaledger/goshimmer/dapps/faucet"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/plugins/autopeering"
"github.com/iotaledger/goshimmer/plugins/banner"
......@@ -41,5 +42,6 @@ var PLUGINS = node.Plugins(
gracefulshutdown.Plugin(),
metrics.Plugin(),
drng.Plugin(),
faucet.App(),
valuetransfers.App(),
)
......@@ -5,6 +5,7 @@ import (
"github.com/iotaledger/goshimmer/plugins/webapi/autopeering"
"github.com/iotaledger/goshimmer/plugins/webapi/data"
"github.com/iotaledger/goshimmer/plugins/webapi/drng"
"github.com/iotaledger/goshimmer/plugins/webapi/faucet"
"github.com/iotaledger/goshimmer/plugins/webapi/healthz"
"github.com/iotaledger/goshimmer/plugins/webapi/info"
"github.com/iotaledger/goshimmer/plugins/webapi/message"
......@@ -20,6 +21,7 @@ var PLUGINS = node.Plugins(
spammer.Plugin(),
data.Plugin(),
drng.Plugin(),
faucet.Plugin(),
healthz.Plugin(),
message.Plugin(),
autopeering.Plugin(),
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package dashboard
import (
"net/http"
faucetpayload "github.com/iotaledger/goshimmer/dapps/faucet/packages/payload"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
"github.com/labstack/echo"
"github.com/pkg/errors"
)
// ReqMsg defines the struct of the faucet request message ID.
type ReqMsg struct {
ID string `json:"MsgId"`
}
func setupFaucetRoutes(routeGroup *echo.Group) {
routeGroup.GET("/faucet/:hash", func(c echo.Context) (err error) {
addr, err := address.FromBase58(c.Param("hash"))
if err != nil {
return errors.Wrapf(ErrInvalidParameter, "faucet request address invalid: %s", addr)
}
t, err := sendFaucetReq(addr)
if err != nil {
return
}
return c.JSON(http.StatusOK, t)
})
}
func sendFaucetReq(addr address.Address) (res *ReqMsg, err error) {
msg := messagelayer.MessageFactory().IssuePayload(faucetpayload.New(addr))
if msg == nil {
return nil, errors.Wrapf(ErrInternalError, "Fail to send faucet request")
}
r := &ReqMsg{
ID: msg.Id().String(),
}
return r, nil
}
......@@ -20,7 +20,7 @@ export class BasicPayload extends React.Component<Props, any> {
<Row className={"mb-3"}>
<Col>
{payload.content_title}: {' '}
{payload.bytes}
{payload.content}
</Col>
</Row>
</React.Fragment>
......
......@@ -50,6 +50,8 @@ export class ExplorerMessageQueryResult extends React.Component<Props, any> {
return "Value"
case PayloadType.Drng:
return "Drng"
case PayloadType.Faucet:
return "Faucet"
default:
return "Unknown"
}
......@@ -62,6 +64,7 @@ export class ExplorerMessageQueryResult extends React.Component<Props, any> {
case PayloadType.Value:
return <ValuePayload/>
case PayloadType.Data:
case PayloadType.Faucet:
default:
console.log(this.props.explorerStore.msg.payload.bytes)
return <BasicPayload/>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment