-
Angelo Capossele authored
*
Add sendTransactionByJson API * Add SendTransactionByJson to the client lib * Fix linter warnings * Make data field a slice of bytes * Add signatures getter * Fix typoAngelo Capossele authored*
Add sendTransactionByJson API * Add SendTransactionByJson to the client lib * Fix linter warnings * Make data field a slice of bytes * Add signatures getter * Fix typo
handler.go 6.96 KiB
package sendtransactionbyjson
import (
"fmt"
"net/http"
"sync"
"time"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"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"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
"github.com/iotaledger/goshimmer/plugins/issuer"
"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/marshalutil"
"github.com/labstack/echo"
"github.com/mr-tron/base58/base58"
)
var (
sendTxMu sync.Mutex
maxBookedAwaitTime = 5 * time.Second
// ErrMalformedInputs defines a malformed inputs error.
ErrMalformedInputs = fmt.Errorf("malformed inputs")
// ErrMalformedOutputs defines a malformed outputs error.
ErrMalformedOutputs = fmt.Errorf("malformed outputs")
// ErrMalformedData defines a malformed data error.
ErrMalformedData = fmt.Errorf("malformed data")
// ErrMalformedColor defines a malformed color error.
ErrMalformedColor = fmt.Errorf("malformed color")
// ErrMalformedPublicKey defines a malformed publicKey error.
ErrMalformedPublicKey = fmt.Errorf("malformed publicKey")
// ErrMalformedSignature defines a malformed signature error.
ErrMalformedSignature = fmt.Errorf("malformed signature")
// ErrWrongSignature defines a wrong signature error.
ErrWrongSignature = fmt.Errorf("wrong signature")
// ErrSignatureVersion defines a unsupported signature version error.
ErrSignatureVersion = fmt.Errorf("unsupported signature version")
)
// Handler sends a transaction.
func Handler(c echo.Context) error {
sendTxMu.Lock()
defer sendTxMu.Unlock()
var request Request
if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
tx, err := NewTransactionFromJSON(request)
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// validate transaction
err = valuetransfers.Tangle().ValidateTransactionToAttach(tx)
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
// Prepare value payload and send the message to tangle
payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
_, err = issuer.IssuePayload(payload)
if err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
if err := valuetransfers.AwaitTransactionToBeBooked(tx.ID(), maxBookedAwaitTime); err != nil {
return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
}
return c.JSON(http.StatusOK, Response{TransactionID: tx.ID().String()})
}
// NewTransactionFromJSON returns a new transaction from a given JSON request or an error.
func NewTransactionFromJSON(request Request) (*transaction.Transaction, error) {
// prepare inputs
inputs := make([]transaction.OutputID, len(request.Inputs))
for i, input := range request.Inputs {
b, err := base58.Decode(input)
if err != nil || len(b) != transaction.OutputIDLength {
return nil, ErrMalformedInputs
}
copy(inputs[i][:], b)
}
// prepare ouputs
outputs := make(map[address.Address][]*balance.Balance)
for _, output := range request.Outputs {
address, err := address.FromBase58(output.Address)
if err != nil {
return nil, ErrMalformedOutputs
}
balances := []*balance.Balance{}
for _, b := range output.Balances {
var color balance.Color
if b.Color == "IOTA" {
color = balance.ColorIOTA
} else {
colorBytes, err := base58.Decode(b.Color)
if err != nil || len(colorBytes) != balance.ColorLength {
return nil, ErrMalformedColor
}
copy(color[:], colorBytes)
}
balances = append(balances, &balance.Balance{
Value: b.Value,
Color: color,
})
}
outputs[address] = balances
}
// prepare transaction
tx := transaction.New(transaction.NewInputs(inputs...), transaction.NewOutputs(outputs))
// add data payload
if request.Data != nil {
tx.SetDataPayload(request.Data)
}
// add signatures
for _, signature := range request.Signatures {
switch signature.Version {
case address.VersionED25519:
pubKeyBytes, err := base58.Decode(signature.PublicKey)
if err != nil || len(pubKeyBytes) != ed25519.PublicKeySize {
return nil, ErrMalformedPublicKey
}
signatureBytes, err := base58.Decode(signature.Signature)
if err != nil || len(signatureBytes) != ed25519.SignatureSize {
return nil, ErrMalformedSignature
}
marshalUtil := marshalutil.New(1 + ed25519.PublicKeySize + ed25519.SignatureSize)
marshalUtil.WriteByte(address.VersionED25519)
marshalUtil.WriteBytes(pubKeyBytes[:])
marshalUtil.WriteBytes(signatureBytes[:])
sign, _, err := signaturescheme.Ed25519SignatureFromBytes(marshalUtil.Bytes())
if err != nil {
return nil, ErrWrongSignature
}
err = tx.PutSignature(sign)
if err != nil {
return nil, ErrWrongSignature
}
case address.VersionBLS:
pubKeyBytes, err := base58.Decode(signature.PublicKey)
if err != nil || len(pubKeyBytes) != signaturescheme.BLSPublicKeySize {
return nil, ErrMalformedPublicKey
}
signatureBytes, err := base58.Decode(signature.Signature)
if err != nil || len(signatureBytes) != signaturescheme.BLSSignatureSize {
return nil, ErrMalformedSignature
}
marshalUtil := marshalutil.New(signaturescheme.BLSFullSignatureSize)
marshalUtil.WriteByte(address.VersionBLS)
marshalUtil.WriteBytes(pubKeyBytes[:])
marshalUtil.WriteBytes(signatureBytes[:])
sign, _, err := signaturescheme.BLSSignatureFromBytes(marshalUtil.Bytes())
if err != nil {
return nil, ErrWrongSignature
}
err = tx.PutSignature(sign)
if err != nil {
return nil, ErrWrongSignature
}
default:
return nil, ErrSignatureVersion
}
}
return tx, nil
}
// Request holds the transaction object(json) to send.
// e.g.,
// {
// "inputs": string[],
// "outputs": {
// "address": string,
// "balances": {
// "value": number,
// "color": string
// }[];
// }[],
// "data": []byte,
// "signatures": {
// "version": number,
// "publicKey": string,
// "signature": string
// }[]
// }
type Request struct {
Inputs []string `json:"inputs"`
Outputs []Output `json:"outputs"`
Data []byte `json:"data,omitempty"`
Signatures []Signature `json:"signatures"`
}
// Output defines the struct of an output.
type Output struct {
Address string `json:"address"`
Balances []utils.Balance `json:"balances"`
}
// Signature defines the struct of a signature.
type Signature struct {
Version byte `json:"version"`
PublicKey string `json:"publicKey"`
Signature string `json:"signature"`
}
// Response is the HTTP response from sending transaction.
type Response struct {
TransactionID string `json:"transaction_id,omitempty"`
Error string `json:"error,omitempty"`
}