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

Contract Loader: should be able to load a contract & trigger recipients at the same time#866 #900

Merged
merged 1 commit into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
144 changes: 53 additions & 91 deletions lib/archethic/contracts/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,113 +44,75 @@ defmodule Archethic.Contracts.Loader do
Load the smart contracts based on transaction involving smart contract code
"""
@spec load_transaction(Transaction.t(), list()) :: :ok
def load_transaction(tx, opts \\ []) do
from_db = Keyword.get(opts, :from_db, false)
from_self_repair = Keyword.get(opts, :from_self_repair, false)

do_load_transaction(tx, from_db, from_self_repair)
end
def load_transaction(
tx = %Transaction{
address: address,
type: type,
data: %TransactionData{code: code},
validation_stamp: %ValidationStamp{
recipients: recipients,
timestamp: timestamp,
protocol_version: protocol_version
}
},
opts \\ []
) do
from_db? = Keyword.get(opts, :from_db, false)
from_self_repair? = Keyword.get(opts, :from_self_repair, false)
if from_db? and from_self_repair?, do: raise("Cant have tx with db and self repair flag")

# Stop previous transaction contract
unless from_db? do
stop_contract(Transaction.previous_address(tx))
end

defp do_load_transaction(
tx = %Transaction{
address: address,
type: type,
data: %TransactionData{code: code}
},
_from_db,
_from_self_repair
)
when code != "" do
stop_contract(Transaction.previous_address(tx))

%Contract{triggers: triggers} = Contracts.parse!(code)
triggers = Enum.reject(triggers, fn {_, actions} -> actions == {:__block__, [], []} end)

# Create worker only load smart contract which are expecting interactions and where the actions are not empty
if length(triggers) > 0 do
{:ok, _} =
DynamicSupervisor.start_child(
ContractSupervisor,
{Worker, Contract.from_transaction!(tx)}
# If transaction contains code, start a new worker for it
if code != "" do
%Contract{triggers: triggers} = Contracts.parse!(code)
triggers = Enum.reject(triggers, fn {_, actions} -> actions == {:__block__, [], []} end)

# Create worker only load smart contract which are expecting interactions and where the actions are not empty
if length(triggers) > 0 do
{:ok, _} =
DynamicSupervisor.start_child(
ContractSupervisor,
{Worker, Contract.from_transaction!(tx)}
)

Logger.info("Smart contract loaded",
transaction_address: Base.encode16(address),
transaction_type: type
)

Logger.info("Smart contract loaded",
transaction_address: Base.encode16(address),
transaction_type: type
)
else
:ok
end
end
end

defp do_load_transaction(
tx = %Transaction{
address: tx_address,
type: tx_type,
validation_stamp: %ValidationStamp{
timestamp: tx_timestamp,
recipients: recipients,
protocol_version: protocol_version
}
},
_from_db = false,
from_self_repair?
)
when recipients != [] do
# For each recipients, load the transaction in lookup and execute the contract
Enum.each(recipients, fn contract_address ->
Logger.info("Execute transaction on contract #{Base.encode16(contract_address)}",
transaction_address: Base.encode16(tx_address),
transaction_type: tx_type
)

unless from_self_repair? do
# execute asynchronously the contract
Worker.execute(contract_address, tx)
end

TransactionLookup.add_contract_transaction(
contract_address,
tx_address,
tx_timestamp,
address,
timestamp,
protocol_version
)

Worker.execute(contract_address, tx)
unless from_db? or from_self_repair? do
# execute contract asynchronously only if we are in live replication
Logger.info(
"Execute transaction on contract #{Base.encode16(contract_address)}",
transaction_address: Base.encode16(address),
transaction_type: type
)

Worker.execute(contract_address, tx)
end

Logger.info("Transaction towards contract ingested",
transaction_address: Base.encode16(tx_address),
transaction_type: tx_type
transaction_address: Base.encode16(address),
transaction_type: type
)
end)
end

defp do_load_transaction(
%Transaction{
address: address,
type: type,
validation_stamp: %ValidationStamp{
recipients: recipients,
timestamp: timestamp,
protocol_version: protocol_version
}
},
_from_db = true,
false
)
when recipients != [] do
Enum.each(
recipients,
&TransactionLookup.add_contract_transaction(&1, address, timestamp, protocol_version)
)

Logger.info("Transaction towards contract ingested",
transaction_address: Base.encode16(address),
transaction_type: type
)
end

defp do_load_transaction(_tx, _, _), do: :ok

@doc """
Termine a contract execution
"""
Expand Down
44 changes: 25 additions & 19 deletions test/archethic/contracts/loader_test.exs
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
defmodule Archethic.Contracts.LoaderTest do
use ArchethicCase

alias Archethic.ContractRegistry
alias Archethic.Contracts.Contract
alias Archethic.Contracts.ContractConstants, as: Constants
alias Archethic.Contracts.Loader
alias Archethic.Contracts.Worker
alias Archethic.ContractSupervisor

alias Archethic.Crypto

alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData
alias Archethic.{Crypto, ContractRegistry, Contracts, TransactionChain, ContractSupervisor}
alias Contracts.{Contract, ContractConstants, Loader, Worker}
alias TransactionChain.{Transaction, TransactionData, Transaction.ValidationStamp}

import Mox

setup :set_mox_global

describe "load_transaction/1" do
test "should create a supervised worker for the given transaction with contract code" do
{pub0, _} = Crypto.derive_keypair("contract1_seed", 0)
Expand All @@ -41,7 +31,11 @@ defmodule Archethic.Contracts.LoaderTest do
end
"""
},
previous_public_key: pub0
previous_public_key: pub0,
validation_stamp: %ValidationStamp{
recipients: [],
timestamp: DateTime.utc_now()
}
}

assert :ok = Loader.load_transaction(tx)
Expand All @@ -55,7 +49,7 @@ defmodule Archethic.Contracts.LoaderTest do
assert %{
contract: %Contract{
triggers: %{transaction: _},
constants: %Constants{contract: %{"address" => ^contract_address}}
constants: %ContractConstants{contract: %{"address" => ^contract_address}}
}
} = :sys.get_state(pid)
end
Expand All @@ -82,7 +76,11 @@ defmodule Archethic.Contracts.LoaderTest do
end
"""
},
previous_public_key: pub0
previous_public_key: pub0,
validation_stamp: %ValidationStamp{
recipients: [],
timestamp: DateTime.utc_now()
}
}

tx2 = %Transaction{
Expand All @@ -102,7 +100,11 @@ defmodule Archethic.Contracts.LoaderTest do
end
"""
},
previous_public_key: pub1
previous_public_key: pub1,
validation_stamp: %ValidationStamp{
recipients: [],
timestamp: DateTime.utc_now()
}
}

assert :ok = Loader.load_transaction(tx1)
Expand Down Expand Up @@ -149,7 +151,11 @@ defmodule Archethic.Contracts.LoaderTest do
end
"""
},
previous_public_key: pub0
previous_public_key: pub0,
validation_stamp: %ValidationStamp{
recipients: [],
timestamp: DateTime.utc_now()
}
}}
end)

Expand All @@ -164,7 +170,7 @@ defmodule Archethic.Contracts.LoaderTest do
assert %{
contract: %Contract{
triggers: %{transaction: _},
constants: %Constants{contract: %{"address" => ^contract_address}}
constants: %ContractConstants{contract: %{"address" => ^contract_address}}
}
} = :sys.get_state(pid)
end
Expand Down