Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local endpoint implementation #177

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,84 @@ Chainweb.Pact.send(cmds, network_id: :testnet04, chain_id: 1)
}}
```

### Local endpoint

Executes a single command on the local server and retrieves the transaction result. Useful with code that queries from blockchain. It does not impact the blockchain when returning transaction results.

```elixir
Kadena.Chainweb.Pact.local(cmd, network_opts \\ [network_id: :testnet04, chain_id: 0])
```

**Parameters**

- `cmd`: [PACT command](#pact-commands).
- `network_opts`: Network options. Keyword list with:

- `network_id` (required): Allowed values: `:testnet04` `mainnet01`.
- `chain_id` (required): Allowed values: integer or string-encoded integer from 0 to 19.

Defaults to `[network_id: :testnet04, chain_id: 0]` if not specified.

**Example**

```elixir
alias Kadena.Chainweb
alias Kadena.Cryptography
alias Kadena.Pact

{:ok, keypair} =
Cryptography.KeyPair.from_secret_key(
"28834b7a0d6d1f84ae2c2efcb5b1de28122e07e2e4caad04a32988a3c79c547c"
)

network_id = :testnet04

metadata =
Kadena.Types.MetaData.new(
creation_time: 1_671_462_208,
ttl: 28_800,
gas_limit: 1000,
gas_price: 0.000001,
sender: "k:#{keypair.pub_key}",
chain_id: "1"
)

code = "(+ 1 2)"

cmd =
Pact.ExecCommand.new()
|> Pact.ExecCommand.set_code(code)
|> Pact.ExecCommand.set_metadata(metadata)
|> Pact.ExecCommand.add_keypair(keypair)
|> Pact.ExecCommand.build()

Chainweb.Pact.local(cmd, network_id: :testnet04, chain_id: 1)

{:ok,
%Kadena.Chainweb.Pact.LocalResponse{
continuation: nil,
events: nil,
gas: 5,
logs: "wsATyGqckuIvlm89hhd2j4t6RMkCrcwJe_oeCYr7Th8",
meta_data: %{
block_height: 2833149,
block_time: 1671577178603103,
prev_block_hash: "7aURwajZ0pBMGEKmOUJ9oLq9MK7QiZeiDPGPb0cXs5c",
public_meta: %{
chain_id: "1",
creation_time: 1671462208,
gas_limit: 1000,
gas_price: 1.0e-6,
sender: "k:d1a361d721cf81dbc21f676e6897f7e7a336671c0d5d25f87c10933cac6d8cf7",
ttl: 28800
}
},
req_key: "8qnotzzhbfe_SSmZcDVQGDpALjQjYqzYYrHc6D-2D_g",
result: %{data: 3, status: "success"},
tx_id: nil
}}
```

---

## Roadmap
Expand Down
3 changes: 2 additions & 1 deletion lib/chainweb/pact.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ defmodule Kadena.Chainweb.Pact do
Exposes functions to interact with the Pact API endpoints.
"""

alias Kadena.Chainweb.Pact.Send
alias Kadena.Chainweb.Pact.{Local, Send}

@default_network_opts [network_id: :testnet04, chain_id: 0]

defdelegate send(cmds, network_opts \\ @default_network_opts), to: Send, as: :process
defdelegate local(cmds, network_opts \\ @default_network_opts), to: Local, as: :process
end
38 changes: 38 additions & 0 deletions lib/chainweb/pact/local.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Kadena.Chainweb.Pact.Local do
@moduledoc """
Local endpoint implementation
"""

alias Kadena.Chainweb.Request
alias Kadena.Chainweb.Pact.{LocalRequestBody, LocalResponse, Spec}
alias Kadena.Types.Command

@behaviour Spec

@endpoint "local"

@type cmd :: Command.t()
@type json :: String.t()

@impl true
def process(%Command{} = cmd, network_id: network_id, chain_id: chain_id) do
headers = [{"Content-Type", "application/json"}]
body = json_request_body(cmd)

:post
|> Request.new(pact: [endpoint: @endpoint])
|> Request.set_chain_id(chain_id)
|> Request.set_network(network_id)
|> Request.add_body(body)
|> Request.add_headers(headers)
|> Request.perform()
|> Request.results(as: LocalResponse)
end

@spec json_request_body(cmd :: cmd()) :: json()
defp json_request_body(cmd) do
cmd
|> LocalRequestBody.new()
|> LocalRequestBody.to_json!()
end
end
16 changes: 4 additions & 12 deletions lib/chainweb/pact/local_request_body.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Kadena.Chainweb.Pact.LocalRequestBody do

@behaviour Kadena.Chainweb.Pact.Type

@type command :: String.t()
@type command :: Command.t()
@type hash :: PactTransactionHash.t()
@type sigs :: SignaturesList.t()
@type cmd :: String.t()
Expand All @@ -19,11 +19,8 @@ defmodule Kadena.Chainweb.Pact.LocalRequestBody do
defstruct [:hash, :sigs, :cmd]

@impl true
def new(args) do
args
|> Command.new()
|> build_local_request_body()
end
def new(%Command{} = cmd), do: build_local_request_body(cmd)
def new(_cmd), do: {:error, [arg: :not_a_command]}

@impl true
def to_json!(%__MODULE__{hash: hash, sigs: sigs, cmd: cmd}) do
Expand All @@ -33,17 +30,12 @@ defmodule Kadena.Chainweb.Pact.LocalRequestBody do
end
end

@spec build_local_request_body(command :: command() | error()) :: t() | error()
@spec build_local_request_body(command :: command()) :: t()
defp build_local_request_body(%Command{} = command) do
attrs = Map.from_struct(command)
struct(%__MODULE__{}, attrs)
end

defp build_local_request_body({:error, [command: :not_a_list]}),
do: {:error, [local_request_body: :not_a_list]}

defp build_local_request_body({:error, reason}), do: {:error, reason}

@spec to_signature_list(signatures :: sigs()) :: {:ok, raw_sigs()}
defp to_signature_list(%SignaturesList{signatures: list}) do
sigs = Enum.map(list, fn sig -> Map.from_struct(sig) end)
Expand Down
2 changes: 1 addition & 1 deletion lib/chainweb/pact/spec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Kadena.Chainweb.Pact.Spec do
alias Kadena.Chainweb.Error
alias Kadena.Types.Command

@type data :: list(Command.t())
@type data :: list(Command.t()) | Command.t()
@type error :: {:error, Error.t()}
@type chain_id :: 0..19 | String.t()
@type network_opts :: [network_id: atom(), chain_id: chain_id()]
Expand Down
10 changes: 1 addition & 9 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ defmodule Kadena.MixProject do
Kadena.Chainweb.Client.Spec,
Kadena.Chainweb.Network,
Kadena.Chainweb.Pact,
Kadena.Chainweb.Pact.Local,
Kadena.Chainweb.Pact.Request,
Kadena.Chainweb.Pact.Send,
Kadena.Chainweb.Pact.Spec,
Expand All @@ -119,31 +120,24 @@ defmodule Kadena.MixProject do
Kadena.Types.Cap,
Kadena.Types.CapsList,
Kadena.Types.ChainID,
Kadena.Types.ChainwebResponseMetaData,
Kadena.Types.Command,
Kadena.Types.CommandsList,
Kadena.Types.ContPayload,
Kadena.Types.EnvData,
Kadena.Types.ExecPayload,
Kadena.Types.KeyPair,
Kadena.Types.ListenRequestBody,
Kadena.Types.LocalRequestBody,
Kadena.Types.MetaData,
Kadena.Types.NetworkID,
Kadena.Types.OptionalCapsList,
Kadena.Types.OptionalMetaData,
Kadena.Types.OptionalPactEventsList,
Kadena.Types.PactCode,
Kadena.Types.PactDecimal,
Kadena.Types.PactInt,
Kadena.Types.PactPayload,
Kadena.Types.PactTransactionHash,
Kadena.Types.PactValue,
Kadena.Types.PactValuesList,
Kadena.Types.PollRequestBody,
Kadena.Types.Proof,
Kadena.Types.Rollback,
Kadena.Types.SendRequestBody,
Kadena.Types.SignCommand,
Kadena.Types.SignatureWithHash,
Kadena.Types.Signature,
Expand All @@ -153,8 +147,6 @@ defmodule Kadena.MixProject do
Kadena.Types.SignersList,
Kadena.Types.SigningCap,
Kadena.Types.Spec,
Kadena.Types.SPVProof,
Kadena.Types.SPVRequestBody,
Kadena.Types.Step
],
"Chainweb Pact Types": [
Expand Down
79 changes: 42 additions & 37 deletions test/chainweb/pact/local_request_body_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,67 @@ defmodule Kadena.Chainweb.Pact.LocalRequestBodyTest do

alias Kadena.Chainweb.Pact.LocalRequestBody

alias Kadena.Types.{PactTransactionHash, SignaturesList}
alias Kadena.Types.{
Command,
PactTransactionHash,
Signature,
SignaturesList
}

setup do
cmd =
"{\"meta\":{\"chainId\":\"0\",\"creationTime\":1667249173,\"gasLimit\":1000,\"gasPrice\":1.0e-6,\"sender\":\"k:554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94\",\"ttl\":28800},\"networkId\":\"testnet04\",\"nonce\":\"2023-06-13 17:45:18.211131 UTC\",\"payload\":{\"exec\":{\"code\":\"(+ 5 6)\",\"data\":{}}},\"signers\":[{\"addr\":\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\",\"clist\":[{\"args\":[\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\"],\"name\":\"coin.GAS\"}],\"pubKey\":\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\",\"scheme\":\"ED25519\"}]}"

hash = %PactTransactionHash{
hash: "-1npoTU2Mi71pKE_oteJiJuHuXTXxoObJm8zzVRK2pk"
}

sigs = %SignaturesList{
signatures: [
%Signature{
sig:
"8b234b83570359e52188cceb301036a2a7b255171e856fd550cac687a946f18fbfc0e769fd8393dda44d6d04c31b531eaf35efb3b78b5e40fd857a743133030d"
}
]
}

command = %Command{
cmd: cmd,
hash: hash,
sigs: sigs
}

%{
hash: "-1npoTU2Mi71pKE_oteJiJuHuXTXxoObJm8zzVRK2pk",
sigs: [
"8b234b83570359e52188cceb301036a2a7b255171e856fd550cac687a946f18fbfc0e769fd8393dda44d6d04c31b531eaf35efb3b78b5e40fd857a743133030d"
],
cmd:
"{\"meta\":{\"chainId\":\"0\",\"creationTime\":1667249173,\"gasLimit\":1000,\"gasPrice\":1.0e-6,\"sender\":\"k:554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94\",\"ttl\":28800},\"networkId\":\"testnet04\",\"nonce\":\"2023-06-13 17:45:18.211131 UTC\",\"payload\":{\"exec\":{\"code\":\"(+ 5 6)\",\"data\":{}}},\"signers\":[{\"addr\":\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\",\"clist\":[{\"args\":[\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\"],\"name\":\"coin.GAS\"}],\"pubKey\":\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\",\"scheme\":\"ED25519\"}]}"
command: command,
cmd: cmd,
hash: hash,
sigs: sigs,
json_result:
"{\"cmd\":\"{\\\"meta\\\":{\\\"chainId\\\":\\\"0\\\",\\\"creationTime\\\":1667249173,\\\"gasLimit\\\":1000,\\\"gasPrice\\\":1.0e-6,\\\"sender\\\":\\\"k:554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94\\\",\\\"ttl\\\":28800},\\\"networkId\\\":\\\"testnet04\\\",\\\"nonce\\\":\\\"2023-06-13 17:45:18.211131 UTC\\\",\\\"payload\\\":{\\\"exec\\\":{\\\"code\\\":\\\"(+ 5 6)\\\",\\\"data\\\":{}}},\\\"signers\\\":[{\\\"addr\\\":\\\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\\\",\\\"clist\\\":[{\\\"args\\\":[\\\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\\\"],\\\"name\\\":\\\"coin.GAS\\\"}],\\\"pubKey\\\":\\\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\\\",\\\"scheme\\\":\\\"ED25519\\\"}]}\",\"hash\":\"-1npoTU2Mi71pKE_oteJiJuHuXTXxoObJm8zzVRK2pk\",\"sigs\":[{\"sig\":\"8b234b83570359e52188cceb301036a2a7b255171e856fd550cac687a946f18fbfc0e769fd8393dda44d6d04c31b531eaf35efb3b78b5e40fd857a743133030d\"}]}"
}
end

describe "new/1" do
test "with a valid params list", %{hash: hash, sigs: sigs, cmd: cmd} do
test "with a valid params list", %{command: command, hash: hash, sigs: sigs, cmd: cmd} do
%LocalRequestBody{
cmd: ^cmd,
hash: %PactTransactionHash{hash: ^hash},
sigs: %SignaturesList{}
} = LocalRequestBody.new(hash: hash, sigs: sigs, cmd: cmd)
hash: ^hash,
sigs: ^sigs
} = LocalRequestBody.new(command)
end

test "with an invalid no list params" do
{:error, [local_request_body: :not_a_list]} = LocalRequestBody.new("No list")
end

test "with an invalid cmd", %{hash: hash, sigs: sigs} do
{:error, [cmd: :not_a_string]} = LocalRequestBody.new(hash: hash, sigs: sigs, cmd: 123)
end

test "with an invalid hash", %{sigs: sigs, cmd: cmd} do
{:error, [hash: :invalid]} = LocalRequestBody.new(hash: 123, sigs: sigs, cmd: cmd)
end

test "with an invalid sigs list", %{hash: hash, cmd: cmd} do
{:error, [sigs: :invalid, signatures: :invalid, sig: :invalid]} =
LocalRequestBody.new(hash: hash, sigs: [invalid_signature: :invalid_value], cmd: cmd)
test "with an invalid command", %{hash: hash, sigs: sigs} do
{:error, [arg: :not_a_command]} = LocalRequestBody.new(hash: hash, sigs: sigs, cmd: 123)
end
end

describe "JSONPayload.parse/1" do
setup do
%{
json_result:
"{\"cmd\":\"{\\\"meta\\\":{\\\"chainId\\\":\\\"0\\\",\\\"creationTime\\\":1667249173,\\\"gasLimit\\\":1000,\\\"gasPrice\\\":1.0e-6,\\\"sender\\\":\\\"k:554754f48b16df24b552f6832dda090642ed9658559fef9f3ee1bb4637ea7c94\\\",\\\"ttl\\\":28800},\\\"networkId\\\":\\\"testnet04\\\",\\\"nonce\\\":\\\"2023-06-13 17:45:18.211131 UTC\\\",\\\"payload\\\":{\\\"exec\\\":{\\\"code\\\":\\\"(+ 5 6)\\\",\\\"data\\\":{}}},\\\"signers\\\":[{\\\"addr\\\":\\\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\\\",\\\"clist\\\":[{\\\"args\\\":[\\\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\\\"],\\\"name\\\":\\\"coin.GAS\\\"}],\\\"pubKey\\\":\\\"85bef77ea3570387cac57da34938f246c7460dc533a67823f065823e327b2afd\\\",\\\"scheme\\\":\\\"ED25519\\\"}]}\",\"hash\":\"-1npoTU2Mi71pKE_oteJiJuHuXTXxoObJm8zzVRK2pk\",\"sigs\":[{\"sig\":\"8b234b83570359e52188cceb301036a2a7b255171e856fd550cac687a946f18fbfc0e769fd8393dda44d6d04c31b531eaf35efb3b78b5e40fd857a743133030d\"}]}"
}
end

test "with a valid LocalRequestBody", %{
hash: hash,
sigs: sigs,
cmd: cmd,
command: command,
json_result: json_result
} do
^json_result =
[hash: hash, sigs: sigs, cmd: cmd]
command
|> LocalRequestBody.new()
|> LocalRequestBody.to_json!()
end
Expand Down
Loading