GetInterchain: perChain + chains options (default first anchor per chain)
Transaction.GetInterchain and Block.GetInterchain take variadic options (client.WithPerChain / client.WithChains, re-exported as sdk.WithPerChain / sdk.WithChains) that map to the new prime-node ?perChain=&chains= query params. Backward compatible: existing no-option calls get the default (one anchor per chain). client.InterchainQuery builds the suffix; unit-tested.
This commit is contained in:
19
README.md
19
README.md
@@ -128,8 +128,27 @@ All API methods accept a `context.Context` as their first parameter for timeout
|
|||||||
- `Create(ctx, req)` - Create a new transaction
|
- `Create(ctx, req)` - Create a new transaction
|
||||||
- `CreateBulk(ctx, req)` - Create multiple transactions
|
- `CreateBulk(ctx, req)` - Create multiple transactions
|
||||||
- `Get(ctx, transactionID)` - Get transaction by ID
|
- `Get(ctx, transactionID)` - Get transaction by ID
|
||||||
|
- `GetInterchain(ctx, transactionID, opts...)` - Trace a transaction's interchain anchors
|
||||||
- `List(ctx)` - List all transactions
|
- `List(ctx)` - List all transactions
|
||||||
|
|
||||||
|
#### Interchain trace options
|
||||||
|
|
||||||
|
`Transaction.GetInterchain` and `Block.GetInterchain` trace a prime block to the
|
||||||
|
public-chain anchors covering it. By default they return the **first anchor per
|
||||||
|
chain** (anchor proofs are chained, so the earliest per chain is the meaningful
|
||||||
|
one). Tune with functional options:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Default: first anchor per chain (ETH, BTC, …)
|
||||||
|
trace, _ := client.Block.GetInterchain(ctx, "42")
|
||||||
|
|
||||||
|
// Up to 3 anchors per chain
|
||||||
|
trace, _ = client.Block.GetInterchain(ctx, "42", sdk.WithPerChain(3))
|
||||||
|
|
||||||
|
// All anchors, only the ETH-mainnet chain ("1"); "0" = BTC
|
||||||
|
trace, _ = client.Block.GetInterchain(ctx, "42", sdk.WithPerChain(0), sdk.WithChains("1"))
|
||||||
|
```
|
||||||
|
|
||||||
### Transaction Type
|
### Transaction Type
|
||||||
- `Create(ctx, req)` - Create a new transaction type
|
- `Create(ctx, req)` - Create a new transaction type
|
||||||
- `Get(ctx, txnType)` - Get transaction type by name
|
- `Get(ctx, txnType)` - Get transaction type by name
|
||||||
|
|||||||
@@ -29,9 +29,14 @@ func (bc *BlockClient) Get(ctx context.Context, blockID string) (*models.Block,
|
|||||||
// GetInterchain traces a block to the validator (verification) blocks that
|
// GetInterchain traces a block to the validator (verification) blocks that
|
||||||
// validated it and the public-chain interchain anchors those validator blocks
|
// validated it and the public-chain interchain anchors those validator blocks
|
||||||
// were bundled into.
|
// were bundled into.
|
||||||
func (bc *BlockClient) GetInterchain(ctx context.Context, blockID string) (*models.InterchainTrace, error) {
|
//
|
||||||
|
// By default it returns the first anchor per public chain (anchor proofs are
|
||||||
|
// chained, so the earliest per chain is the meaningful one). Use
|
||||||
|
// client.WithPerChain / client.WithChains to return more anchors per chain or
|
||||||
|
// restrict to specific chains.
|
||||||
|
func (bc *BlockClient) GetInterchain(ctx context.Context, blockID string, opts ...client.InterchainOption) (*models.InterchainTrace, error) {
|
||||||
var resp models.InterchainTrace
|
var resp models.InterchainTrace
|
||||||
path := fmt.Sprintf("/api/v1/block/%s/interchain", blockID)
|
path := fmt.Sprintf("/api/v1/block/%s/interchain%s", blockID, client.InterchainQuery(opts...))
|
||||||
err := bc.client.Get(ctx, path, &resp)
|
err := bc.client.Get(ctx, path, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
57
client/interchain.go
Normal file
57
client/interchain.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InterchainOption configures an interchain-trace request (Transaction.GetInterchain
|
||||||
|
// / Block.GetInterchain). Anchor proofs are chained, so by default the trace
|
||||||
|
// returns the first anchor per public chain; these options change how many and
|
||||||
|
// which chains are returned.
|
||||||
|
type InterchainOption func(*interchainConfig)
|
||||||
|
|
||||||
|
type interchainConfig struct {
|
||||||
|
perChain *int
|
||||||
|
chains []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPerChain caps how many interchain anchors are returned per public chain,
|
||||||
|
// earliest-first: 1 is the first anchor per chain (the service default), 0
|
||||||
|
// returns all anchors for each chain.
|
||||||
|
func WithPerChain(n int) InterchainOption {
|
||||||
|
return func(c *interchainConfig) { c.perChain = &n }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChains restricts the trace to these interchain chain ids ("1" = ETH
|
||||||
|
// mainnet, "0" = BTC; testnet ids differ).
|
||||||
|
func WithChains(chains ...string) InterchainOption {
|
||||||
|
return func(c *interchainConfig) { c.chains = chains }
|
||||||
|
}
|
||||||
|
|
||||||
|
// InterchainQuery builds the "?perChain=...&chains=..." suffix for the interchain
|
||||||
|
// trace endpoints. Returns "" when no options are set (the server then applies
|
||||||
|
// its defaults: one anchor per chain, all chains).
|
||||||
|
func InterchainQuery(opts ...InterchainOption) string {
|
||||||
|
cfg := &interchainConfig{}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts []string
|
||||||
|
if cfg.perChain != nil {
|
||||||
|
parts = append(parts, "perChain="+strconv.Itoa(*cfg.perChain))
|
||||||
|
}
|
||||||
|
if len(cfg.chains) > 0 {
|
||||||
|
escaped := make([]string, len(cfg.chains))
|
||||||
|
for i, c := range cfg.chains {
|
||||||
|
escaped[i] = url.QueryEscape(c)
|
||||||
|
}
|
||||||
|
parts = append(parts, "chains="+strings.Join(escaped, ","))
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "?" + strings.Join(parts, "&")
|
||||||
|
}
|
||||||
22
client/interchain_test.go
Normal file
22
client/interchain_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestInterchainQuery(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
opts []InterchainOption
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"none", nil, ""},
|
||||||
|
{"perChain1", []InterchainOption{WithPerChain(1)}, "?perChain=1"},
|
||||||
|
{"perChain0 (all)", []InterchainOption{WithPerChain(0)}, "?perChain=0"},
|
||||||
|
{"chains", []InterchainOption{WithChains("1", "0")}, "?chains=1,0"},
|
||||||
|
{"both", []InterchainOption{WithPerChain(2), WithChains("1", "0")}, "?perChain=2&chains=1,0"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := InterchainQuery(c.opts...); got != c.want {
|
||||||
|
t.Errorf("%s: InterchainQuery = %q, want %q", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,3 +113,15 @@ func NewDragonchainSDKWithHTTPClient(publicID, authKeyID, authKey, baseURL strin
|
|||||||
func (sdk *DragonchainSDK) GetClient() *client.Client {
|
func (sdk *DragonchainSDK) GetClient() *client.Client {
|
||||||
return sdk.client
|
return sdk.client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InterchainOption configures Transaction.GetInterchain / Block.GetInterchain.
|
||||||
|
// Re-exported from the client package for convenience (e.g. sdk.WithPerChain).
|
||||||
|
type InterchainOption = client.InterchainOption
|
||||||
|
|
||||||
|
// WithPerChain caps interchain anchors returned per public chain (1 = first per
|
||||||
|
// chain, the default; 0 = all). See client.WithPerChain.
|
||||||
|
var WithPerChain = client.WithPerChain
|
||||||
|
|
||||||
|
// WithChains restricts an interchain trace to specific chain ids ("1" = ETH
|
||||||
|
// mainnet, "0" = BTC). See client.WithChains.
|
||||||
|
var WithChains = client.WithChains
|
||||||
|
|||||||
38
transaction/interchain_test.go
Normal file
38
transaction/interchain_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.dragonchain.com/dragonchain/prime-sdk-go/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGetInterchainQuery confirms GetInterchain builds the request URI (path +
|
||||||
|
// the perChain/chains query) end-to-end through the client.
|
||||||
|
func TestGetInterchainQuery(t *testing.T) {
|
||||||
|
var gotURI string
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
gotURI = r.URL.RequestURI()
|
||||||
|
_, _ = io.WriteString(w, `{"blockId":"42","validatorBlocks":[],"interchainTransactions":[]}`)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
tc := NewTransactionClient(client.NewClient("pid", "kid", "key", srv.URL))
|
||||||
|
|
||||||
|
if _, err := tc.GetInterchain(context.Background(), "tx1", client.WithPerChain(2), client.WithChains("1", "0")); err != nil {
|
||||||
|
t.Fatalf("GetInterchain with opts: %v", err)
|
||||||
|
}
|
||||||
|
if want := "/api/v1/transaction/tx1/interchain?perChain=2&chains=1,0"; gotURI != want {
|
||||||
|
t.Errorf("with opts: URI = %q, want %q", gotURI, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tc.GetInterchain(context.Background(), "tx1"); err != nil {
|
||||||
|
t.Fatalf("GetInterchain default: %v", err)
|
||||||
|
}
|
||||||
|
if want := "/api/v1/transaction/tx1/interchain"; gotURI != want {
|
||||||
|
t.Errorf("default (no opts): URI = %q, want %q", gotURI, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,9 +56,14 @@ func (tc *TransactionClient) Get(ctx context.Context, transactionID string) (*mo
|
|||||||
// validated its prime block and the public-chain interchain anchors those
|
// validated its prime block and the public-chain interchain anchors those
|
||||||
// validator blocks were bundled into. If the transaction is still pending (not
|
// validator blocks were bundled into. If the transaction is still pending (not
|
||||||
// yet in a block) the trace's slices are empty.
|
// yet in a block) the trace's slices are empty.
|
||||||
func (tc *TransactionClient) GetInterchain(ctx context.Context, transactionID string) (*models.InterchainTrace, error) {
|
//
|
||||||
|
// By default it returns the first anchor per public chain (anchor proofs are
|
||||||
|
// chained, so the earliest per chain is the meaningful one). Use
|
||||||
|
// client.WithPerChain / client.WithChains to return more anchors per chain or
|
||||||
|
// restrict to specific chains.
|
||||||
|
func (tc *TransactionClient) GetInterchain(ctx context.Context, transactionID string, opts ...client.InterchainOption) (*models.InterchainTrace, error) {
|
||||||
var resp models.InterchainTrace
|
var resp models.InterchainTrace
|
||||||
path := fmt.Sprintf("/api/v1/transaction/%s/interchain", transactionID)
|
path := fmt.Sprintf("/api/v1/transaction/%s/interchain%s", transactionID, client.InterchainQuery(opts...))
|
||||||
err := tc.client.Get(ctx, path, &resp)
|
err := tc.client.Get(ctx, path, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user