-
Notifications
You must be signed in to change notification settings - Fork 2
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
feat: return blknum and transaction index on transaction submit #133
Conversation
3fcb21d
to
0b542b2
Compare
a3ff177
to
3d9b6a8
Compare
41e08e1
to
620e3b0
Compare
apps/engine/lib/engine/db/block.ex
Outdated
|> Multi.run("new-block", &insert_block/2) | ||
|> Multi.run("form-block", &attach_transactions_to_block/2) | ||
|> Multi.run("hash-block", &generate_block_hash/2) | ||
|> Multi.run("block", &get_or_insert_forming_block/2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that has to be used carefully.
If there is no forming
block, then each concurrent transaction could insert a block.
That should not be the case becausewe won't be probably forming empty blocks and after each inserting a transaction via Transaction.insert
we'll always have a forming block in the db.
620e3b0
to
1f13a7b
Compare
apps/engine/lib/engine/db/block.ex
Outdated
# basic version, checking transaction limit is postponed until we get transction fees implemented | ||
case last_tx_index >= @max_transaction_in_block do | ||
true -> | ||
{:ok, _} = prepare_for_submission(repo, %{"block" => block}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @InoMurko pointed in discussion in the issue - this should not be done on insert, especially calculating block's hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah and you are missing fee transactions. This should just transition the block to a new state so it can be picked by an other service to process the finalization (add fee tx / calculate hash and stuff).
16a708c
to
075e989
Compare
apps/api/test/api/v1/controllers/transaction_controller_test.exs
Outdated
Show resolved
Hide resolved
assert block.hash == hash | ||
end | ||
|
||
test "correctly generates block hash for empty txs list" do | ||
assert {:ok, %{"hash-block" => block}} = Block.form() | ||
assert {:ok, %{block_for_submission: block}} = Block.form() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we disallow empty blocks? Empty blocks have the same hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes! we shouldn't form empty blocks, but I guess this can be done in your next PR when you tackle the block finalization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right
apps/engine/test/engine/db/transaction/transaction_async_test.exs
Outdated
Show resolved
Hide resolved
assert tx1.tx_index + 1 == tx2.tx_index | ||
end | ||
|
||
test "does not create conflicts when inserting multiple transaction concurrently" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that only this test was causing an issue when using async: true
because it was doing async stuff.
Other tests in this file could go to the other test file that runs with async: true
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that too, but there was 1 more insert
test failing, so I decided to move them all here. I think we do not have to be concerned much about those tests running long, and there is a benefit of having all insert
tests in one place.
assert block.hash == hash | ||
end | ||
|
||
test "correctly generates block hash for empty txs list" do | ||
assert {:ok, %{"hash-block" => block}} = Block.form() | ||
assert {:ok, %{block_for_submission: block}} = Block.form() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes! we shouldn't form empty blocks, but I guess this can be done in your next PR when you tackle the block finalization?
apps/api/test/api/v1/controllers/transaction_controller_test.exs
Outdated
Show resolved
Hide resolved
{:error, changeset} | ||
|
||
error -> | ||
_ = Logger.error("Error when inserting transaction #{inspect(error)}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would be a good datadog counter
|> Multi.run(:current_forming_block, &Block.get_forming_block_for_update/2) | ||
|> Multi.run(:block_with_next_tx_index, &Block.get_block_and_tx_index_for_transaction/2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems to me that perhaps these two reads could be just one.
get the block and it's max transaction index:
last_tx_index = repo.one(TransactionQuery.select_max_tx_index_for_block(block.id)) || -1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea to check if it improves performance. I'll dig into this and if it speeds things up, I'll create a separate PR for that.
|> changeset(params) | ||
|> repo.insert() | ||
|> BlockChangeset.new_block_changeset(params) | ||
|> repo.insert(on_conflict: :nothing) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we have a problem when this conflict happens? Last time I tried, the block returned didn't have an id
so the transaction was failing. I could fix it by adding a
|> case do
{:ok, %{id: nil} -> re-query pending forming block until we get iit.
.....
But I don't know if it's a valid solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would happen if a block is inserted by another transaction, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method is called only when:
Block.formed
-> we insert a new forming block, we can do nothing on conflict, forming block is in the databaseTransaction.insert
-> we insert a new block which we will associate a transaction with. In that case alwaysBlock.get_or_insert_forming_block
is called, which selects a forming block from db after insert.
So it should be fine.
@@ -1,98 +1,102 @@ | |||
defmodule Engine.DB.TransactionTest do | |||
use Engine.DB.DataCase, async: true | |||
use Engine.DB.DataCase, async: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done on purpose
I think all tests using Shared mode Sandbox should not be run sync.
https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html#module-shared-mode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_So Ecto.Adapters.SQL.Sandbox provides :shared mode, which a process can switch to after checkout/1. Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, pid}) means “all processes running concurrently with pid should use the connection which pid owns.”
As the docs for SQL.Sandbox say, using :shared mode means that “tests can no longer run concurrently.” With the understanding we’ve built, this makes sense. If shared mode means “all concurrent processes share this connection,” we only want one one test process to be in that concurrent group. If any other test ran at the same time, the two tests would be working with the database within the same transaction and the changes that one test made would leak to the other test. This would destroy our illusion that each test has a database to itself.
So any test using :shared mode must be in a test module marked async: false. This means no tests from other modules will run concurrently with it. Since tests in the same module run serially, it will be the only test running until it completes and may share its database connection fearlessly with the processes whose behavior it tests._
yeah, seems correct!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job! Let's start with this and see how we can improve it later.
@@ -51,7 +51,7 @@ defmodule Engine.DB.Transaction do | |||
tx_hash: <<_::256>>, | |||
tx_type: pos_integer(), | |||
updated_at: DateTime.t(), | |||
witnesses: DateTime.t() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😱
""" | ||
def insert(tx_bytes) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I think it makes more sense like this!
14fcee5
to
5e648a3
Compare
…tting a transaction
c23be95
to
47c8215
Compare
Returns block number an index for a submitted transaction.
To determine transaction index on submission I had to lock transaction on currently forming block.
Changes:
Engine.DB.Transaction.insert
inserts a transaction and new block if current forming block exceeds transaction limitEngine.DB.Block.form
no longer inserts a block. It calculates merkle root hash and sets block state topending_submission
.Engine.DB.Transaction.insert
required that there is a forming block when inserting a transaction