Skip to content
Snippets Groups Projects
Unverified Commit 4a47fc7c authored by jkrvivian's avatar jkrvivian Committed by GitHub
Browse files

feat: Implement value web api & client library (#438)

* feat: Implement value api: Attachment

* feat: Implement value api: UnspentOutputs

* feat: Implement value api: transactionByID

* feat: Implement client lib & value api: sendTransaction

* fix: minor tweak

* fix: minor fix

* refactor: Refactor sendTransaction api

* Refactor: Fix :dog:

* refactor: Fix :dog:

* refactor: Rename api route of testSendTxn to camel case
parent 7db8e8e1
No related branches found
No related tags found
No related merge requests found
package client
import (
"fmt"
"net/http"
webapi_attachments "github.com/iotaledger/goshimmer/plugins/webapi/value/attachments"
webapi_gettxn "github.com/iotaledger/goshimmer/plugins/webapi/value/gettransactionbyid"
webapi_sendtxn "github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransaction"
webapi_unspentoutputs "github.com/iotaledger/goshimmer/plugins/webapi/value/unspentoutputs"
)
const (
routeAttachments = "value/attachments"
routeGetTxnByID = "value/transactionByID"
routeSendTxn = "value/sendTransaction"
routeUnspentOutputs = "value/unspentOutputs"
)
// GetAttachments gets the attachments of a transaction ID
func (api *GoShimmerAPI) GetAttachments(base58EncodedTxnID string) (*webapi_attachments.Response, error) {
res := &webapi_attachments.Response{}
if err := api.do(http.MethodGet, func() string {
return fmt.Sprintf("%s?txnID=%s", routeAttachments, base58EncodedTxnID)
}(), nil, res); err != nil {
return nil, err
}
return res, nil
}
// GetTransactionByID gets the transaction of a transaction ID
func (api *GoShimmerAPI) GetTransactionByID(base58EncodedTxnID string) (*webapi_gettxn.Response, error) {
res := &webapi_gettxn.Response{}
if err := api.do(http.MethodGet, func() string {
return fmt.Sprintf("%s?txnID=%s", routeGetTxnByID, base58EncodedTxnID)
}(), nil, res); err != nil {
return nil, err
}
return res, nil
}
// GetUnspentOutputs return unspent output IDs of addresses
func (api *GoShimmerAPI) GetUnspentOutputs(addresses []string) (*webapi_unspentoutputs.Response, error) {
res := &webapi_unspentoutputs.Response{}
if err := api.do(http.MethodPost, routeUnspentOutputs,
&webapi_unspentoutputs.Request{Addresses: addresses}, res); err != nil {
return nil, err
}
return res, nil
}
// SendTransaction sends the transaction(bytes) to Tangle and returns transaction ID
func (api *GoShimmerAPI) SendTransaction(txnBytes []byte) (string, error) {
res := &webapi_sendtxn.Response{}
if err := api.do(http.MethodPost, routeSendTxn,
&webapi_sendtxn.Request{TransactionBytes: txnBytes}, res); err != nil {
return "", err
}
return res.TransactionID, nil
}
package transaction
import (
"fmt"
"github.com/mr-tron/base58"
"github.com/iotaledger/hive.go/marshalutil"
......@@ -19,6 +20,27 @@ func NewOutputID(outputAddress address.Address, transactionID ID) (outputID Outp
return
}
// OutputIDFromBase58 creates an output id from a base58 encoded string.
func OutputIDFromBase58(base58String string) (outputid OutputID, err error) {
// decode string
bytes, err := base58.Decode(base58String)
if err != nil {
return
}
// sanitize input
if len(bytes) != OutputIDLength {
err = fmt.Errorf("base58 encoded string does not match the length of a output id")
return
}
// copy bytes to result
copy(outputid[:], bytes)
return
}
// OutputIDFromBytes unmarshals an OutputID from a sequence of bytes.
func OutputIDFromBytes(bytes []byte) (result OutputID, consumedBytes int, err error) {
// parse the bytes
......
......@@ -8,6 +8,7 @@ import (
"github.com/iotaledger/goshimmer/plugins/webapi/info"
"github.com/iotaledger/goshimmer/plugins/webapi/message"
"github.com/iotaledger/goshimmer/plugins/webapi/spammer"
"github.com/iotaledger/goshimmer/plugins/webapi/value"
"github.com/iotaledger/goshimmer/plugins/webauth"
"github.com/iotaledger/hive.go/node"
)
......@@ -21,4 +22,5 @@ var PLUGINS = node.Plugins(
message.Plugin,
autopeering.Plugin,
info.Plugin,
value.Plugin,
)
package attachments
import (
"net/http"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
)
// Handler gets the value attachments.
func Handler(c echo.Context) error {
txnID, err := transaction.IDFromBase58(c.QueryParam("txnID"))
if err != nil {
log.Info(err)
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
var valueObjs []ValueObject
// get txn by txn id
txnObj := valuetransfers.Tangle.Transaction(txnID)
if !txnObj.Exists() {
return c.JSON(http.StatusNotFound, Response{Error: "Transaction not found"})
}
txn := utils.ParseTransaction(txnObj.Unwrap())
// get attachements by txn id
for _, attachmentObj := range valuetransfers.Tangle.Attachments(txnID) {
if !attachmentObj.Exists() {
continue
}
attachment := attachmentObj.Unwrap()
// get payload by payload id
payloadObj := valuetransfers.Tangle.Payload(attachment.PayloadID())
if !payloadObj.Exists() {
continue
}
payload := payloadObj.Unwrap()
// append value object
valueObjs = append(valueObjs, ValueObject{
ID: payload.ID().String(),
ParentID0: payload.TrunkID().String(),
ParentID1: payload.BranchID().String(),
Transaction: txn,
})
}
return c.JSON(http.StatusOK, Response{Attachments: valueObjs})
}
// Response is the HTTP response from retreiving value objects.
type Response struct {
Attachments []ValueObject `json:"attachments,omitempty"`
Error string `json:"error,omitempty"`
}
// ValueObject holds the information of a value object.
type ValueObject struct {
ID string `json:"id"`
ParentID0 string `json:"parent0_id"`
ParentID1 string `json:"parent1_id"`
Transaction utils.Transaction `json:"transaction"`
}
package gettransactionbyid
import (
"net/http"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
)
// Handler gets the transaction by id.
func Handler(c echo.Context) error {
txnID, err := transaction.IDFromBase58(c.QueryParam("txnID"))
if err != nil {
log.Info(err)
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// get txn by txn id
txnObj := valuetransfers.Tangle.Transaction(txnID)
if !txnObj.Exists() {
return c.JSON(http.StatusNotFound, Response{Error: "Transaction not found"})
}
txn := utils.ParseTransaction(txnObj.Unwrap())
// TODO: get inclusion state
return c.JSON(http.StatusOK, Response{
Transaction: txn,
InclusionState: utils.InclusionState{
Confirmed: true,
Conflict: false,
Liked: true,
},
})
}
// Response is the HTTP response from retreiving transaction.
type Response struct {
Transaction utils.Transaction `json:"transaction,omitempty"`
InclusionState utils.InclusionState `json:"inclusion_state,omitempty"`
Error string `json:"error,omitempty"`
}
package value
import (
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/goshimmer/plugins/webapi/value/attachments"
"github.com/iotaledger/goshimmer/plugins/webapi/value/gettransactionbyid"
"github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransaction"
"github.com/iotaledger/goshimmer/plugins/webapi/value/testsendtxn"
"github.com/iotaledger/goshimmer/plugins/webapi/value/unspentoutputs"
"github.com/iotaledger/hive.go/node"
)
// PluginName is the name of the web API DRNG endpoint plugin.
const PluginName = "WebAPI Value Endpoint"
var (
// Plugin is the plugin instance of the web API DRNG endpoint plugin.
Plugin = node.NewPlugin(PluginName, node.Enabled, configure)
)
func configure(_ *node.Plugin) {
webapi.Server.GET("value/attachments", attachments.Handler)
webapi.Server.POST("value/unspentOutputs", unspentoutputs.Handler)
webapi.Server.POST("value/sendTransaction", sendtransaction.Handler)
webapi.Server.POST("value/testSendTxn", testsendtxn.Handler)
webapi.Server.GET("value/transactionByID", gettransactionbyid.Handler)
}
package sendtransaction
import (
"net/http"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/goshimmer/plugins/issuer"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
)
// Handler sends a transaction.
func Handler(c echo.Context) error {
var request Request
if err := c.Bind(&request); err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// prepare transaction
tx, _, err := transaction.FromBytes(request.TransactionBytes)
if err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// Prepare value payload and send the message to tangle
payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
_, err = issuer.IssuePayload(payload)
if err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
return c.JSON(http.StatusOK, Response{TransactionID: tx.ID().String()})
}
// Request holds the transaction object(bytes) to send.
type Request struct {
TransactionBytes []byte `json:"txn_bytes"`
}
// Response is the HTTP response from sending transaction.
type Response struct {
TransactionID string `json:"transaction_id,omitempty"`
Error string `json:"error,omitempty"`
}
package testsendtxn
import (
"net/http"
"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/transaction"
"github.com/iotaledger/goshimmer/plugins/issuer"
"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
)
// Handler sends a transaction.
func Handler(c echo.Context) error {
var request Request
if err := c.Bind(&request); err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// prepare inputs
outputids := []transaction.OutputID{}
for _, in := range request.Inputs {
id, err := transaction.OutputIDFromBase58(in)
if err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
outputids = append(outputids, id)
}
inputs := transaction.NewInputs(outputids...)
// prepare outputs
outmap := map[address.Address][]*balance.Balance{}
for _, out := range request.Outputs {
addr, err := address.FromBase58(out.Address)
if err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// iterate balances
balances := []*balance.Balance{}
for _, b := range out.Balances {
// get token color
color, _, err := balance.ColorFromBytes([]byte(b.Color))
if err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
balances = append(balances, balance.New(color, b.Value))
}
outmap[addr] = balances
}
outputs := transaction.NewOutputs(outmap)
// prepare transaction
// Note: not signed
tx := transaction.New(inputs, outputs)
// Prepare value payload and send the message to tangle
payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
_, err := issuer.IssuePayload(payload)
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
return c.JSON(http.StatusOK, Response{TransactionID: tx.ID().String()})
}
// Request holds the inputs and outputs to send.
type Request struct {
Inputs []string `json:"inputs"`
Outputs []utils.Output `json:"outputs"`
}
// Response is the HTTP response from sending transaction.
type Response struct {
TransactionID string `json:"transaction_id,omitempty"`
Error string `json:"error,omitempty"`
}
package unspentoutputs
import (
"net/http"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
)
// Handler gets the unspent outputs.
func Handler(c echo.Context) error {
var request Request
if err := c.Bind(&request); err != nil {
log.Info(err.Error())
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
var unspents []UnspentOutput
for _, strAddress := range request.Addresses {
address, err := address.FromBase58(strAddress)
if err != nil {
log.Info(err.Error())
continue
}
outputids := make([]OutputID, 0)
// get outputids by address
for id, outputObj := range valuetransfers.Tangle.OutputsOnAddress(address) {
output := outputObj.Unwrap()
// TODO: get inclusion state
if output.ConsumerCount() == 0 {
outputids = append(outputids, OutputID{
ID: id.String(),
InclusionState: utils.InclusionState{
Confirmed: true,
Conflict: false,
Liked: true,
},
})
}
}
unspents = append(unspents, UnspentOutput{
Address: strAddress,
OutputIDs: outputids,
})
}
return c.JSON(http.StatusOK, Response{UnspentOutputs: unspents})
}
// Request holds the addresses to query.
type Request struct {
Addresses []string `json:"addresses,omitempty"`
Error string `json:"error,omitempty"`
}
// Response is the HTTP response from retreiving value objects.
type Response struct {
UnspentOutputs []UnspentOutput `json:"unspent_outputs,omitempty"`
Error string `json:"error,omitempty"`
}
// UnspentOutput holds the address and the corresponding unspent output ids
type UnspentOutput struct {
Address string `json:"address"`
OutputIDs []OutputID `json:"output_ids"`
}
// OutputID holds the output id and its inclusion state
type OutputID struct {
ID string `json:"id"`
InclusionState utils.InclusionState `json:"inclusion_state"`
}
package utils
import (
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
)
// ParseTransaction handle transaction json object.
func ParseTransaction(t *transaction.Transaction) (txn Transaction) {
var inputs []string
var outputs []Output
// process inputs
t.Inputs().ForEachAddress(func(currentAddress address.Address) bool {
inputs = append(inputs, currentAddress.String())
return true
})
// process outputs: address + balance
t.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
var b []Balance
for _, balance := range balances {
b = append(b, Balance{
Value: balance.Value(),
Color: balance.Color().String(),
})
}
t := Output{
Address: address.String(),
Balances: b,
}
outputs = append(outputs, t)
return true
})
return Transaction{
Inputs: inputs,
Outputs: outputs,
Signature: t.SignatureBytes(),
DataPayload: t.GetDataPayload(),
}
}
// Transaction holds the information of a transaction.
type Transaction struct {
Inputs []string `json:"inputs"`
Outputs []Output `json:"outputs"`
Signature []byte `json:"signature"`
DataPayload []byte `json:"data_payload"`
}
// Output consists an address and balances
type Output struct {
Address string `json:"address"`
Balances []Balance `json:"balances"`
}
// Balance holds the value and the color of token
type Balance struct {
Value int64 `json:"value"`
Color string `json:"color"`
}
// InclusionState represents the different states of an OutputID
type InclusionState struct {
Confirmed bool `json:"confirmed,omitempty"`
Conflict bool `json:"conflict,omitempty"`
Liked bool `json:"liked,omitempty"`
}
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