generated from kommitters/.template
-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add continuation command implementation (#149)
* Add continuation command implementation. Co-authored-by: David Roldán <Odraxs@users.noreply.github.com>
- Loading branch information
1 parent
afa61a9
commit 1114a6a
Showing
9 changed files
with
884 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
defmodule Kadena.Pact.ContCommand do | ||
@moduledoc """ | ||
Specifies functions to build PACT continuation command requests. | ||
""" | ||
@behaviour Kadena.Pact.Command | ||
|
||
alias Kadena.Chainweb.Pact.JSONPayload | ||
alias Kadena.Cryptography.Sign | ||
alias Kadena.Pact.Command.Hash | ||
|
||
alias Kadena.Types.{ | ||
Command, | ||
CommandPayload, | ||
ContPayload, | ||
EnvData, | ||
KeyPair, | ||
MetaData, | ||
NetworkID, | ||
PactPayload, | ||
PactTransactionHash, | ||
Proof, | ||
Rollback, | ||
SignaturesList, | ||
SignCommand, | ||
Signer, | ||
SignersList, | ||
Step | ||
} | ||
|
||
@type cmd :: String.t() | ||
@type command :: Command.t() | ||
@type data :: EnvData.t() | nil | ||
@type hash :: String.t() | ||
@type json_string_payload :: String.t() | ||
@type keypair :: KeyPair.t() | ||
@type keypairs :: list(keypair()) | ||
@type meta_data :: MetaData.t() | ||
@type network_id :: NetworkID.t() | ||
@type nonce :: String.t() | ||
@type pact_payload :: PactPayload.t() | ||
@type pact_tx_hash :: PactTransactionHash.t() | ||
@type proof :: Proof.t() | nil | ||
@type rollback :: Rollback.t() | ||
@type signatures :: SignaturesList.t() | ||
@type signers :: SignersList.t() | ||
@type sign_command :: SignCommand.t() | ||
@type sign_commands :: list(sign_command()) | ||
@type step :: Step.t() | ||
@type valid_command :: {:ok, command()} | ||
@type valid_command_json_string :: {:ok, json_string_payload()} | ||
@type valid_payload :: {:ok, pact_payload()} | ||
@type valid_signatures :: {:ok, signatures()} | ||
@type valid_sign_commands :: {:ok, sign_commands()} | ||
|
||
@type t :: %__MODULE__{ | ||
network_id: network_id(), | ||
data: data(), | ||
nonce: nonce(), | ||
meta_data: meta_data(), | ||
pact_tx_hash: pact_tx_hash(), | ||
step: step(), | ||
proof: proof(), | ||
rollback: rollback(), | ||
keypairs: keypairs(), | ||
signers: signers() | ||
} | ||
|
||
defstruct [ | ||
:network_id, | ||
:data, | ||
:nonce, | ||
:meta_data, | ||
:pact_tx_hash, | ||
:step, | ||
:proof, | ||
:rollback, | ||
:signers, | ||
keypairs: [] | ||
] | ||
|
||
@impl true | ||
def new(opts \\ nil) | ||
|
||
def new(opts) when is_list(opts) do | ||
network_id = Keyword.get(opts, :network_id) | ||
data = Keyword.get(opts, :data) | ||
nonce = Keyword.get(opts, :nonce, "") | ||
meta_data = Keyword.get(opts, :meta_data, %MetaData{}) | ||
pact_tx_hash = Keyword.get(opts, :pact_tx_hash, "") | ||
step = Keyword.get(opts, :step, %Step{}) | ||
proof = Keyword.get(opts, :proof) | ||
rollback = Keyword.get(opts, :rollback, %Rollback{}) | ||
keypairs = Keyword.get(opts, :keypairs, []) | ||
signers = Keyword.get(opts, :signers, %SignersList{}) | ||
|
||
%__MODULE__{} | ||
|> set_network(network_id) | ||
|> set_data(data) | ||
|> set_nonce(nonce) | ||
|> set_metadata(meta_data) | ||
|> add_keypairs(keypairs) | ||
|> add_signers(signers) | ||
|> set_pact_tx_hash(pact_tx_hash) | ||
|> set_proof(proof) | ||
|> set_step(step) | ||
|> set_rollback(rollback) | ||
end | ||
|
||
def new(_opts), do: %__MODULE__{} | ||
|
||
@impl true | ||
def set_network(%__MODULE__{} = cmd_request, network) do | ||
case NetworkID.new(network) do | ||
%NetworkID{} = network_id -> %{cmd_request | network_id: network_id} | ||
{:error, reason} -> {:error, [network_id: :invalid] ++ reason} | ||
end | ||
end | ||
|
||
def set_network({:error, reason}, _network), do: {:error, reason} | ||
|
||
@impl true | ||
def set_data(%__MODULE__{} = cmd_request, data) do | ||
case EnvData.new(data) do | ||
%EnvData{} -> %{cmd_request | data: data} | ||
error -> error | ||
end | ||
end | ||
|
||
def set_data({:error, reason}, _data), do: {:error, reason} | ||
|
||
@impl true | ||
def set_nonce(%__MODULE__{} = cmd_request, nonce) when is_binary(nonce), | ||
do: %{cmd_request | nonce: nonce} | ||
|
||
def set_nonce(%__MODULE__{}, _nonce), do: {:error, [nonce: :not_a_string]} | ||
def set_nonce({:error, reason}, _nonce), do: {:error, reason} | ||
|
||
@impl true | ||
def set_metadata(%__MODULE__{} = cmd_request, %MetaData{} = meta_data), | ||
do: %{cmd_request | meta_data: meta_data} | ||
|
||
def set_metadata(%__MODULE__{}, _metadata), do: {:error, [metadata: :invalid]} | ||
def set_metadata({:error, reason}, _metadata), do: {:error, reason} | ||
|
||
@impl true | ||
def add_keypair(%__MODULE__{keypairs: keypairs} = cmd_request, %KeyPair{} = keypair), | ||
do: %{cmd_request | keypairs: keypairs ++ [keypair]} | ||
|
||
def add_keypair(%__MODULE__{}, _keypair), do: {:error, [keypair: :invalid]} | ||
def add_keypair({:error, reason}, _keypair), do: {:error, reason} | ||
|
||
@impl true | ||
def add_keypairs(%__MODULE__{} = cmd_request, []), do: cmd_request | ||
|
||
def add_keypairs(%__MODULE__{} = cmd_request, [keypair | keypairs]) do | ||
cmd_request | ||
|> add_keypair(keypair) | ||
|> add_keypairs(keypairs) | ||
end | ||
|
||
def add_keypairs(%__MODULE__{}, _keypairs), do: {:error, [keypairs: :not_a_list]} | ||
def add_keypairs({:error, reason}, _keypairs), do: {:error, reason} | ||
|
||
@impl true | ||
def add_signer(%__MODULE__{signers: nil} = cmd_request, %Signer{} = signer), | ||
do: %{cmd_request | signers: SignersList.new([signer])} | ||
|
||
def add_signer(%__MODULE__{signers: signer_list} = cmd_request, %Signer{} = signer) do | ||
%SignersList{signers: signers} = signer_list | ||
%{cmd_request | signers: SignersList.new(signers ++ [signer])} | ||
end | ||
|
||
def add_signer(%__MODULE__{}, _signer), do: {:error, [signer: :invalid]} | ||
def add_signer({:error, reason}, _signer), do: {:error, reason} | ||
|
||
@impl true | ||
def add_signers(%__MODULE__{} = cmd_request, %SignersList{} = list), | ||
do: %{cmd_request | signers: list} | ||
|
||
def add_signers(%__MODULE__{}, _signers), do: {:error, [signers: :invalid]} | ||
def add_signers({:error, reason}, _signers), do: {:error, reason} | ||
|
||
@impl true | ||
def set_pact_tx_hash(%__MODULE__{} = cmd_request, pact_tx_hash) when is_binary(pact_tx_hash), | ||
do: %{cmd_request | pact_tx_hash: pact_tx_hash} | ||
|
||
def set_pact_tx_hash(%__MODULE__{}, _pact_tx_hash), do: {:error, [pact_tx_hash: :not_a_string]} | ||
def set_pact_tx_hash({:error, reason}, _pact_tx_hash), do: {:error, reason} | ||
|
||
@impl true | ||
def set_step(%__MODULE__{} = cmd_request, step) when is_integer(step), | ||
do: %{cmd_request | step: step} | ||
|
||
def set_step(%__MODULE__{}, _step), do: {:error, [step: :not_an_integer]} | ||
def set_step({:error, reason}, _step), do: {:error, reason} | ||
|
||
@impl true | ||
def set_proof(%__MODULE__{} = cmd_request, proof) when is_binary(proof), | ||
do: %{cmd_request | proof: proof} | ||
|
||
def set_proof(%__MODULE__{}, _proof), do: {:error, [proof: :not_a_string]} | ||
def set_proof({:error, reason}, _proof), do: {:error, reason} | ||
|
||
@impl true | ||
|
||
def set_rollback(%__MODULE__{} = cmd_request, rollback) when is_boolean(rollback), | ||
do: %{cmd_request | rollback: rollback} | ||
|
||
def set_rollback(%__MODULE__{}, _rollback), do: {:error, [rollback: :not_a_boolean]} | ||
def set_rollback({:error, reason}, _rollback), do: {:error, reason} | ||
|
||
@impl true | ||
def build( | ||
%__MODULE__{ | ||
keypairs: keypairs | ||
} = cmd_request | ||
) do | ||
with {:ok, payload} <- create_payload(cmd_request), | ||
{:ok, cmd} <- command_to_json_string(payload, cmd_request), | ||
{:ok, sig_commands} <- sign_commands([], cmd, keypairs), | ||
{:ok, hash} <- Hash.pull_unique(sig_commands), | ||
{:ok, signatures} <- build_signatures(sig_commands, []), | ||
{:ok, command} <- create_command(hash, signatures, cmd) do | ||
command | ||
end | ||
end | ||
|
||
def build(_module), do: {:error, [exec_command_request: :invalid_payload]} | ||
|
||
@spec create_payload(t()) :: valid_payload() | ||
defp create_payload(%__MODULE__{ | ||
data: data, | ||
pact_tx_hash: pact_tx_hash, | ||
proof: proof, | ||
rollback: rollback, | ||
step: step | ||
}) do | ||
[data: data, pact_id: pact_tx_hash, proof: proof, rollback: rollback, step: step] | ||
|> ContPayload.new() | ||
|> PactPayload.new() | ||
|> (&{:ok, &1}).() | ||
end | ||
|
||
@spec command_to_json_string(payload :: pact_payload(), t()) :: valid_command_json_string() | ||
defp command_to_json_string(payload, %__MODULE__{ | ||
network_id: network_id, | ||
meta_data: meta_data, | ||
signers: signers, | ||
nonce: nonce | ||
}) do | ||
[ | ||
network_id: network_id, | ||
payload: payload, | ||
meta: meta_data, | ||
signers: signers, | ||
nonce: nonce | ||
] | ||
|> CommandPayload.new() | ||
|> JSONPayload.parse() | ||
|> (&{:ok, &1}).() | ||
end | ||
|
||
@spec create_command( | ||
hash :: hash(), | ||
sigs :: signatures(), | ||
cmd :: cmd() | ||
) :: valid_command() | ||
defp create_command(hash, sigs, cmd) do | ||
case Command.new(hash: hash, sigs: sigs, cmd: cmd) do | ||
%Command{} = command -> {:ok, command} | ||
end | ||
end | ||
|
||
@spec sign_commands(signs :: list(), cmd :: json_string_payload(), keypairs()) :: | ||
valid_sign_commands() | ||
defp sign_commands(signs, _cmd, []), do: {:ok, signs} | ||
|
||
defp sign_commands(signs, cmd, [%KeyPair{} = keypair | keypairs]) do | ||
signs | ||
|> sign_command(cmd, keypair) | ||
|> sign_commands(cmd, keypairs) | ||
end | ||
|
||
@spec sign_command(signs :: list(), cmd :: json_string_payload(), keypair()) :: | ||
list() | ||
defp sign_command(signs, cmd, %KeyPair{} = keypair) do | ||
{:ok, sign_command} = Sign.sign(cmd, keypair) | ||
signs ++ [sign_command] | ||
end | ||
|
||
@spec build_signatures(sign_commands :: sign_commands(), result :: list()) :: valid_signatures() | ||
defp build_signatures([], result), do: {:ok, SignaturesList.new(result)} | ||
|
||
defp build_signatures([%SignCommand{sig: sig} | rest], result), | ||
do: build_signatures(rest, result ++ [sig]) | ||
end |
Oops, something went wrong.