qubic / docs

Official Qubic Docs
https://docs.qubic.org
0 stars 9 forks source link

Requested Go examples #8

Open LINCKODE opened 3 weeks ago

LINCKODE commented 3 weeks ago

Transaction creation and broadcasting:

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "github.com/pkg/errors"
    "github.com/qubic/go-node-connector/types"
    "github.com/qubic/go-schnorrq"
    "io"
    "net/http"
    "time"
)

// Create simple transaction and broadcast it to the network

const ApiURL = "https://testapi.qubic.org"

const SenderSeed = ""
const SenderID = ""
const TransactionAmount = 5
const DestinationID = ""

func CreateAndBroadcastTransaction() error {

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
    defer cancel()

    subSeed, err := types.GetSubSeed(SenderSeed)
    if err != nil {
        return errors.Wrap(err, "getting sub-seed from seed")
    }

    latestTick, err := GetLatestTick(ctx, ApiURL)
    if err != nil {
        return errors.Wrap(err, "getting latest tick")
    }

    targetTick := latestTick + 10

    transaction, err := CreateTransaction(SenderID, DestinationID, TransactionAmount, targetTick, subSeed)
    if err != nil {
        return errors.Wrap(err, "creating transaction")
    }

    err = BroadCastTransaction(ctx, transaction, ApiURL)
    if err != nil {
        return errors.Wrap(err, "broadcasting transaction")
    }

    return nil
}

func CreateTransaction(sourceID, destinationID string, amount int64, targetTick uint32, subSeed [32]byte) (*types.Transaction, error) {
    transaction, err := types.NewSimpleTransferTransaction(sourceID, destinationID, amount, targetTick)
    if err != nil {
        return nil, errors.Wrap(err, "creating transaction")
    }

    unsignedDigest, err := transaction.GetUnsignedDigest()
    if err != nil {
        return nil, errors.Wrap(err, "getting transaction unsigned digest")
    }

    signature, err := schnorrq.Sign(subSeed, transaction.SourcePublicKey, unsignedDigest)
    if err != nil {
        return nil, errors.Wrap(err, "failed to sign transaction")
    }
    transaction.Signature = signature

    return &transaction, nil

}

func GetLatestTick(ctx context.Context, apiUrl string) (uint32, error) {

    request, err := http.NewRequestWithContext(ctx, http.MethodGet, apiUrl+"/v1/latestTick", nil)
    if err != nil {
        return 0, errors.Wrap(err, "creating latest tick request")
    }

    response, err := http.DefaultClient.Do(request)
    if err != nil {
        return 0, errors.Wrap(err, "performing latest tick request")
    }
    defer response.Body.Close()

    data, err := io.ReadAll(response.Body)
    if err != nil {
        return 0, errors.Wrap(err, "reading latest tick response")
    }

    if response.StatusCode != http.StatusOK {
        return 0, errors.New(fmt.Sprintf("Status not 200! Info: %s", data))
    }

    type LatestTickStruct struct {
        LatestTick uint32 `json:"latestTick"`
    }

    var latestTick LatestTickStruct
    err = json.Unmarshal(data, &latestTick)
    if err != nil {
        return 0, errors.Wrap(err, "un-marshalling latest tick response")
    }

    return latestTick.LatestTick, nil
}

func BroadCastTransaction(ctx context.Context, transaction *types.Transaction, apiUrl string) error {

    transactionID, err := transaction.ID()
    if err != nil {
        return errors.Wrap(err, "getting transaction ID")
    }

    finalUrl := apiUrl + "/v1/broadcast-transaction"

    // Encode transaction
    encodedTransaction, err := transaction.EncodeToBase64()
    if err != nil {
        return errors.Wrap(err, "encoding transaction")
    }

    // Create request payload from encoded transaction
    requestPayload := struct {
        EncodedTransaction string `json:"encodedTransaction"`
    }{
        EncodedTransaction: encodedTransaction,
    }
    buffer := new(bytes.Buffer)

    err = json.NewEncoder(buffer).Encode(requestPayload)
    if err != nil {
        return errors.Wrap(err, "encoding request payload")
    }

    //Create and send request
    request, err := http.NewRequestWithContext(ctx, http.MethodPost, finalUrl, buffer)
    if err != nil {
        return errors.Wrap(err, "creating broadcast request")
    }

    response, err := http.DefaultClient.Do(request)
    if err != nil {
        return errors.Wrap(err, "performing broadcast request")
    }
    defer response.Body.Close()

    data, err := io.ReadAll(response.Body)
    if err != nil {
        return errors.Wrap(err, "reading broadcast response")
    }

    if response.StatusCode != http.StatusOK {
        fmt.Printf("DEBUG: %s\n", finalUrl)
        return errors.New(fmt.Sprintf("Status not 200! Info: %s", data))
    }

    type ResponseInfo struct {
        PeersBroadcasted   uint32 `json:"peersBroadcasted"`
        EncodedTransaction string `json:"encodedTransaction"`
    }

    var info ResponseInfo
    err = json.Unmarshal(data, &info)
    if err != nil {
        return errors.Wrap(err, "un-marshalling broadcast response")
    }

    fmt.Printf("Broadcasted to %d peers.\n", info.PeersBroadcasted)
    fmt.Printf("Transaction ID: %s\n", transactionID)
    fmt.Printf("Target tick: %d\n", transaction.Tick)

    return nil
}
LINCKODE commented 3 weeks ago

Query last tick and it's transactions

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/pkg/errors"
    "io"
    "net/http"
    "time"
)

// Query the last network tick and the transactions in it

const archiverHTTPAddress = "https://testapi.qubic.org"

func RunHTTPExample() error {

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
    defer cancel()

    httpClient := http.DefaultClient

    // Get Status
    status, err := FetchStatusHTTP(ctx, httpClient, archiverHTTPAddress)
    if err != nil {
        return errors.Wrap(err, "fetching archiver status")
    }

    fmt.Printf("Last processed tick: %d\n", status.LastProcessedTick.TickNumber)
    fmt.Printf("Current epoch: %d\n", status.LastProcessedTick.Epoch)

    // Get transactions in the latest tick

    transactions, err := GetTickTransactionsHTTP(ctx, httpClient, archiverHTTPAddress, status.LastProcessedTick.TickNumber)
    if err != nil {
        return errors.Wrap(err, "fetching tick transactions")
    }

    fmt.Printf("Found %d transactions\n", len(transactions.Transactions))

    for _, transaction := range transactions.Transactions {
        fmt.Printf("  Transaction ID: %s\n", transaction.Transaction.TxId)
        fmt.Printf("  Transfered amount: %s\n", transaction.Transaction.Amount)
        fmt.Printf("  From: %s\n", transaction.Transaction.SourceId)
        fmt.Printf("  To: %s\n", transaction.Transaction.DestId)
        println("-------------------------------")

    }

    return nil

}

func FetchStatusHTTP(ctx context.Context, httpClient *http.Client, baseURL string) (*Status, error) {

    statusRequest, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/v1/status", nil)
    if err != nil {
        return nil, errors.Wrap(err, "creating status request")
    }

    response, err := httpClient.Do(statusRequest)
    if err != nil {
        return nil, errors.Wrap(err, "performing status request")
    }
    defer response.Body.Close()

    data, err := io.ReadAll(response.Body)
    if err != nil {
        return nil, errors.Wrap(err, "reading status response")
    }

    if response.StatusCode != http.StatusOK {
        return nil, errors.New(fmt.Sprintf("Status not 200! Info: %s", data))
    }

    var status Status

    err = json.Unmarshal(data, &status)
    if err != nil {
        return nil, errors.Wrap(err, "un-marshalling status response")
    }

    return &status, nil
}

func GetTickTransactionsHTTP(ctx context.Context, httpClient *http.Client, baseURL string, tickNumber uint32) (*TickTransactions, error) {

    finalURL := fmt.Sprintf(baseURL+"/v2/ticks/%d/transactions?transfers=true", tickNumber)

    tickTransactionsRequest, err := http.NewRequestWithContext(ctx, "GET", finalURL, nil)
    if err != nil {
        return nil, errors.Wrap(err, "creating tick transactions request")
    }

    response, err := httpClient.Do(tickTransactionsRequest)
    if err != nil {
        return nil, errors.Wrap(err, "performing tick transactions request")
    }
    defer response.Body.Close()

    data, err := io.ReadAll(response.Body)
    if err != nil {
        return nil, errors.Wrap(err, "reading tick transactions response")
    }

    if response.StatusCode != http.StatusOK {
        return nil, errors.New(fmt.Sprintf("Status not 200! Info: %s", data))
    }

    var transactions TickTransactions

    err = json.Unmarshal(data, &transactions)
    if err != nil {
        return nil, errors.Wrap(err, "un-marshalling tick transactions response")
    }

    return &transactions, nil

}

type Status struct {
    LastProcessedTick struct {
        TickNumber uint32 `json:"tickNumber"`
        Epoch      uint32 `json:"epoch"`
    } `json:"lastProcessedTick"`
    LastProcessedTicksPerEpoch map[string]uint32 `json:"lastProcessedTicksPerEpoch"`
    SkippedTicks               []struct {
        StartTick uint32 `json:"startTick"`
        EndTick   uint32 `json:"endTick"`
    } `json:"skippedTicks"`
    ProcessedTickIntervalsPerEpoch []struct {
        Epoch     uint32 `json:"epoch"`
        Intervals []struct {
            InitialProcessedTick uint32 `json:"initialProcessedTick"`
            LastProcessedTick    uint32 `json:"lastProcessedTick"`
        } `json:"intervals"`
    }
    EmptyTicksPerEpoch map[string]uint32 `json:"emptyTicksPerEpoch"`
}

type TickTransactions struct {
    Transactions []struct {
        Transaction struct {
            SourceId     string `json:"sourceId"`
            DestId       string `json:"destId"`
            Amount       string `json:"amount"`
            TickNumber   uint32 `json:"tickNumber"`
            InputType    uint32 `json:"inputType"`
            InputSize    uint32 `json:"inputSize"`
            InputHex     string `json:"inputHex"`
            SignatureHex string `json:"signatureHex"`
            TxId         string `json:"txId"`
        } `json:"transaction"`
        Timestamp string `json:"timestamp"`
        MoneyFlew bool   `json:"moneyFlew"`
    } `json:"transactions"`
}
LINCKODE commented 3 weeks ago

GRPC version of above example

const archiverGRPCAddress = "213.170.135.5:8003"

func RunGRPCExample() error {

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
    defer cancel()

    // GRPC client creation
    connection, err := grpc.NewClient(archiverGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        return errors.Wrap(err, "creating grpc connection")
    }
    defer connection.Close()

    archiverClient := protobuff.NewArchiveServiceClient(connection)

    // Get status
    status, err := FetchStatusGRPC(ctx, archiverClient)
    if err != nil {
        return errors.Wrap(err, "fetching status")
    }

    fmt.Printf("Last processed tick: %d\n", status.LastProcessedTick.TickNumber)
    fmt.Printf("Current epoch: %d\n", status.LastProcessedTick.Epoch)

    // Get transactions in the latest tick

    transactions, err := GetTickTransactionsGRPC(ctx, archiverClient, status.LastProcessedTick.TickNumber, true)
    if err != nil {
        return errors.Wrap(err, "fetching tick transactions")
    }

    fmt.Printf("Found %d transactions\n", len(transactions.Transactions))

    for _, transaction := range transactions.Transactions {
        fmt.Printf("  Transaction ID: %s\n", transaction.Transaction.TxId)
        fmt.Printf("  Transfered amount: %d\n", transaction.Transaction.Amount)
        fmt.Printf("  From: %s\n", transaction.Transaction.SourceId)
        fmt.Printf("  To: %s\n", transaction.Transaction.DestId)
        println("-------------------------------")

    }

    return nil
}

func FetchStatusGRPC(ctx context.Context, archiverClient protobuff.ArchiveServiceClient) (*protobuff.GetStatusResponse, error) {

    result, err := archiverClient.GetStatus(ctx, nil)
    if err != nil {
        return nil, errors.Wrap(err, "performing request")
    }

    return result, nil
}

func GetTickTransactionsGRPC(ctx context.Context, archiverClient protobuff.ArchiveServiceClient, tickNumber uint32, transfersOnly bool) (*protobuff.GetTickTransactionsResponseV2, error) {

    request := protobuff.GetTickTransactionsRequestV2{
        TickNumber: tickNumber,
        // Do not filter, we want to see all transactions
        Approved:  false,
        Transfers: transfersOnly,
    }

    result, err := archiverClient.GetTickTransactionsV2(ctx, &request)
    if err != nil {
        return nil, errors.Wrap(err, "performing request")
    }

    return result, nil

}