Skip to content
Snippets Groups Projects
Unverified Commit f5bfc7f7 authored by Angelo Capossele's avatar Angelo Capossele Committed by GitHub
Browse files

Add sendTransactionByJson API (#641)

* :sparkles: Add sendTransactionByJson API

* :sparkles: Add SendTransactionByJson to the client lib

* :rotating_light: Fix linter warnings

* :recycle: Make data field a slice of bytes

* :sparkles: Add signatures getter

* :pencil2: Fix typo
parent 439e8ab8
No related branches found
No related tags found
No related merge requests found
Pipeline #453 failed
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
webapi_attachments "github.com/iotaledger/goshimmer/plugins/webapi/value/attachments" webapi_attachments "github.com/iotaledger/goshimmer/plugins/webapi/value/attachments"
webapi_gettxn "github.com/iotaledger/goshimmer/plugins/webapi/value/gettransactionbyid" webapi_gettxn "github.com/iotaledger/goshimmer/plugins/webapi/value/gettransactionbyid"
webapi_sendtxn "github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransaction" webapi_sendtxn "github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransaction"
webapi_sendtxnbyjson "github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransactionbyjson"
webapi_unspentoutputs "github.com/iotaledger/goshimmer/plugins/webapi/value/unspentoutputs" webapi_unspentoutputs "github.com/iotaledger/goshimmer/plugins/webapi/value/unspentoutputs"
) )
...@@ -14,6 +15,7 @@ const ( ...@@ -14,6 +15,7 @@ const (
routeAttachments = "value/attachments" routeAttachments = "value/attachments"
routeGetTxnByID = "value/transactionByID" routeGetTxnByID = "value/transactionByID"
routeSendTxn = "value/sendTransaction" routeSendTxn = "value/sendTransaction"
routeSendTxnByJSON = "value/sendTransactionByJson"
routeUnspentOutputs = "value/unspentOutputs" routeUnspentOutputs = "value/unspentOutputs"
) )
...@@ -52,7 +54,7 @@ func (api *GoShimmerAPI) GetUnspentOutputs(addresses []string) (*webapi_unspento ...@@ -52,7 +54,7 @@ func (api *GoShimmerAPI) GetUnspentOutputs(addresses []string) (*webapi_unspento
return res, nil return res, nil
} }
// SendTransaction sends the transaction(bytes) to Tangle and returns transaction ID // SendTransaction sends the transaction(bytes) to the Value Tangle and returns transaction ID.
func (api *GoShimmerAPI) SendTransaction(txnBytes []byte) (string, error) { func (api *GoShimmerAPI) SendTransaction(txnBytes []byte) (string, error) {
res := &webapi_sendtxn.Response{} res := &webapi_sendtxn.Response{}
if err := api.do(http.MethodPost, routeSendTxn, if err := api.do(http.MethodPost, routeSendTxn,
...@@ -62,3 +64,19 @@ func (api *GoShimmerAPI) SendTransaction(txnBytes []byte) (string, error) { ...@@ -62,3 +64,19 @@ func (api *GoShimmerAPI) SendTransaction(txnBytes []byte) (string, error) {
return res.TransactionID, nil return res.TransactionID, nil
} }
// SendTransactionByJSON sends the transaction(JSON) to the Value Tangle and returns transaction ID.
func (api *GoShimmerAPI) SendTransactionByJSON(txn webapi_sendtxnbyjson.Request) (string, error) {
res := &webapi_sendtxn.Response{}
if err := api.do(http.MethodPost, routeSendTxnByJSON,
&webapi_sendtxnbyjson.Request{
Inputs: txn.Inputs,
Outputs: txn.Outputs,
Data: txn.Data,
Signatures: txn.Signatures,
}, res); err != nil {
return "", err
}
return res.TransactionID, nil
}
...@@ -227,5 +227,15 @@ func AggregateBLSSignatures(sigs ...Signature) (Signature, error) { ...@@ -227,5 +227,15 @@ func AggregateBLSSignatures(sigs ...Signature) (Signature, error) {
return NewBLSSignature(pubKeyBin, sigBin), nil return NewBLSSignature(pubKeyBin, sigBin), nil
} }
// PublicKeySize returns the size of the public key.
func (sig *BLSSignature) PublicKeySize() int {
return BLSPublicKeySize
}
// SignatureSize returns the size of the signature.
func (sig *BLSSignature) SignatureSize() int {
return BLSPrivateKeySize
}
// interface contract (allow the compiler to check if the implementation has all of the required methods). // interface contract (allow the compiler to check if the implementation has all of the required methods).
var _ Signature = &BLSSignature{} var _ Signature = &BLSSignature{}
...@@ -124,4 +124,14 @@ func (signature *ED25519Signature) Address() address.Address { ...@@ -124,4 +124,14 @@ func (signature *ED25519Signature) Address() address.Address {
return address.FromED25519PubKey(signature.publicKey) return address.FromED25519PubKey(signature.publicKey)
} }
// PublicKeySize returns the size of the public key.
func (signature *ED25519Signature) PublicKeySize() int {
return ed25519.PublicKeySize
}
// SignatureSize returns the size of the signature.
func (signature *ED25519Signature) SignatureSize() int {
return ed25519.SignatureSize
}
// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
...@@ -12,4 +12,10 @@ type Signature interface { ...@@ -12,4 +12,10 @@ type Signature interface {
// Address returns the address that this signature signs. // Address returns the address that this signature signs.
Address() address.Address Address() address.Address
// PublicKeySize returns the size of the public key.
PublicKeySize() int
// SignatureSize returns the size of the signature.
SignatureSize() int
} }
...@@ -177,6 +177,20 @@ func (transaction *Transaction) SignaturesValid() bool { ...@@ -177,6 +177,20 @@ func (transaction *Transaction) SignaturesValid() bool {
return signaturesValid return signaturesValid
} }
// Signatures returns all the signatures in this transaction.
func (transaction *Transaction) Signatures() (signatures []signaturescheme.Signature) {
transaction.inputs.ForEachAddress(func(address address.Address) bool {
signature, exists := transaction.signatures.Get(address)
if !exists || !signature.IsValid(transaction.EssenceBytes()) {
return false
}
signatures = append(signatures, signature)
return true
})
return signatures
}
// InputsCountValid returns true if the number of inputs in this transaction is not greater than MaxTransactionInputCount. // InputsCountValid returns true if the number of inputs in this transaction is not greater than MaxTransactionInputCount.
func (transaction *Transaction) InputsCountValid() bool { func (transaction *Transaction) InputsCountValid() bool {
return transaction.inputs.Size() <= MaxTransactionInputCount return transaction.inputs.Size() <= MaxTransactionInputCount
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/iotaledger/goshimmer/plugins/webapi/value/attachments" "github.com/iotaledger/goshimmer/plugins/webapi/value/attachments"
"github.com/iotaledger/goshimmer/plugins/webapi/value/gettransactionbyid" "github.com/iotaledger/goshimmer/plugins/webapi/value/gettransactionbyid"
"github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransaction" "github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransaction"
"github.com/iotaledger/goshimmer/plugins/webapi/value/sendtransactionbyjson"
"github.com/iotaledger/goshimmer/plugins/webapi/value/testsendtxn" "github.com/iotaledger/goshimmer/plugins/webapi/value/testsendtxn"
"github.com/iotaledger/goshimmer/plugins/webapi/value/unspentoutputs" "github.com/iotaledger/goshimmer/plugins/webapi/value/unspentoutputs"
"github.com/iotaledger/hive.go/node" "github.com/iotaledger/hive.go/node"
...@@ -33,6 +34,7 @@ func configure(_ *node.Plugin) { ...@@ -33,6 +34,7 @@ func configure(_ *node.Plugin) {
webapi.Server().GET("value/attachments", attachments.Handler) webapi.Server().GET("value/attachments", attachments.Handler)
webapi.Server().POST("value/unspentOutputs", unspentoutputs.Handler) webapi.Server().POST("value/unspentOutputs", unspentoutputs.Handler)
webapi.Server().POST("value/sendTransaction", sendtransaction.Handler) webapi.Server().POST("value/sendTransaction", sendtransaction.Handler)
webapi.Server().POST("value/sendTransactionByJson", sendtransactionbyjson.Handler)
webapi.Server().POST("value/testSendTxn", testsendtxn.Handler) webapi.Server().POST("value/testSendTxn", testsendtxn.Handler)
webapi.Server().GET("value/transactionByID", gettransactionbyid.Handler) webapi.Server().GET("value/transactionByID", gettransactionbyid.Handler)
} }
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"`
}
package sendtransactionbyjson
import (
"testing"
"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/webapi/value/utils"
"github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/mr-tron/base58/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewTransactionFromJSON(t *testing.T) {
// generate ed25519 keypair
keyPair1 := ed25519.GenerateKeyPair()
sigScheme1 := signaturescheme.ED25519(keyPair1)
// generate BLS keypair
sigScheme2 := signaturescheme.RandBLS()
addr1 := sigScheme1.Address()
addr2 := sigScheme2.Address()
// create first input
o1 := transaction.NewOutputID(addr1, transaction.RandomID())
// create second input
o2 := transaction.NewOutputID(addr2, transaction.RandomID())
inputs := transaction.NewInputs(o1, o2)
bal := balance.New(balance.ColorIOTA, 1)
outputs := transaction.NewOutputs(map[address.Address][]*balance.Balance{address.Random(): {bal}})
tx := transaction.New(inputs, outputs)
tx.SetDataPayload([]byte("TEST"))
// sign with ed25519
tx.Sign(sigScheme1)
// sign with BLS
tx.Sign(sigScheme2)
// Parse inputs to base58
inputsBase58 := []string{}
inputs.ForEach(func(outputId transaction.OutputID) bool {
inputsBase58 = append(inputsBase58, base58.Encode(outputId.Bytes()))
return true
})
// Parse outputs to base58
outputsBase58 := []Output{}
outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
var b []utils.Balance
for _, balance := range balances {
b = append(b, utils.Balance{
Value: balance.Value,
Color: balance.Color.String(),
})
}
t := Output{
Address: address.String(),
Balances: b,
}
outputsBase58 = append(outputsBase58, t)
return true
})
// Parse signatures to base58
signaturesBase58 := []Signature{}
for _, signature := range tx.Signatures() {
signaturesBase58 = append(signaturesBase58, Signature{
Version: signature.Bytes()[0],
PublicKey: base58.Encode(signature.Bytes()[1 : signature.PublicKeySize()+1]),
Signature: base58.Encode(signature.Bytes()[1+signature.PublicKeySize():]),
})
}
// create tx JSON
jsonRequest := Request{
Inputs: inputsBase58,
Outputs: outputsBase58,
Data: []byte("TEST"),
Signatures: signaturesBase58,
}
txFromJSON, err := NewTransactionFromJSON(jsonRequest)
require.NoError(t, err)
// compare signatures
assert.Equal(t, tx.SignatureBytes(), txFromJSON.SignatureBytes())
// conmpare transactions
assert.Equal(t, tx, txFromJSON)
}
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