-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: init block.validate * completes parameter validation tests * rename validation method with stronger typing * feat: verify_transactions and and negative test * add: positive test for verify_transactions * chore: remove single pipes * verify_transactions: add @SPEC and @doc * pending * test: Added tests for `verify_merkle_root/1` * remove reverse operation * refactor: simplify tests * refactor: simplify verify_merkle_root/1 tests * refactor: merkle validation tests + entities as module attributes * add dialyzer spec and remove argument pattern matching * move logic to dedicated validator module * test: added test for `block.validate` endpoint * refactor: make tests pass * feat: response for `validate_block` view * feat: error for mismatched_merkle_root * simplify tests * rename error * remove unused method * fix: error name * fix: credo * merge issue: gitmodules * merge master issue: gitmodules [2] * merge master issue: gitmodules [3] * Merge branch 'ripzery/block.validate' of https://github.com/omisego/elixir-omg into ripzery/block.validate * (re)delete specs * return submodule * refactor: decouple verify_merkle_root from transaction recovery * add: negative test for invalid transactions * refactor: improve performance of verify_transactions * use pin operator instead of comparison * refactor: remove use of & &1. for clearer syntax * remove redundant comment * move block validation logic into Watcher * move parameter validation logic into BlockConstraints * reflect file changes in controller * refactor: make verification functions private and shift tests to stateless_validate/1 * fix potential false positive by computing Merkle root correctly. * add: swagger specs * refactor: endpoint to return boolean result * add: nil error * update documentation to reflect boolean result * remove error reason in response Co-authored-by: euro <[email protected]> Co-authored-by: Ayrat Badykov <[email protected]>
- Loading branch information
1 parent
8ddf382
commit 6ba53bd
Showing
18 changed files
with
625 additions
and
4 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 |
---|---|---|
@@ -1,4 +1,4 @@ | ||
[submodule "priv/cabbage"] | ||
path = priv/cabbage | ||
url = [email protected]:omgnetwork/specs.git | ||
branch = master | ||
branch = master |
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,67 @@ | ||
# Copyright 2019-2020 OmiseGO Pte Ltd | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
defmodule OMG.Watcher.BlockValidator do | ||
@moduledoc """ | ||
Operations related to block validation. | ||
""" | ||
|
||
alias OMG.Block | ||
alias OMG.Merkle | ||
alias OMG.State.Transaction | ||
|
||
@doc """ | ||
Executes stateless validation of a submitted block: | ||
- Verifies that transactions are correctly formed. | ||
- Verifies that given Merkle root matches reconstructed Merkle root. | ||
""" | ||
@spec stateless_validate(Block.t()) :: {:ok, boolean()} | {:error, atom()} | ||
def stateless_validate(submitted_block) do | ||
with {:ok, recovered_transactions} <- verify_transactions(submitted_block.transactions), | ||
{:ok, _block} <- verify_merkle_root(submitted_block, recovered_transactions) do | ||
{:ok, true} | ||
end | ||
end | ||
|
||
@spec verify_merkle_root(Block.t(), list(Transaction.Recovered.t())) :: | ||
{:ok, Block.t()} | {:error, :mismatched_merkle_root} | ||
defp verify_merkle_root(block, transactions) do | ||
reconstructed_merkle_hash = | ||
transactions | ||
|> Enum.map(&Transaction.raw_txbytes/1) | ||
|> Merkle.hash() | ||
|
||
case block.hash do | ||
^reconstructed_merkle_hash -> {:ok, block} | ||
_ -> {:error, :invalid_merkle_root} | ||
end | ||
end | ||
|
||
@spec verify_transactions(transactions :: list(Transaction.Recovered.t())) :: | ||
{:ok, list(Transaction.Recovered.t())} | ||
| {:error, Transaction.Recovered.recover_tx_error()} | ||
defp verify_transactions(transactions) do | ||
transactions | ||
|> Enum.reverse() | ||
|> Enum.reduce_while({:ok, []}, fn tx, {:ok, already_recovered} -> | ||
case Transaction.Recovered.recover_from(tx) do | ||
{:ok, recovered} -> | ||
{:cont, {:ok, [recovered | already_recovered]}} | ||
|
||
error -> | ||
{:halt, error} | ||
end | ||
end) | ||
end | ||
end |
124 changes: 124 additions & 0 deletions
124
apps/omg_watcher/test/omg_watcher/block_validator_test.exs
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,124 @@ | ||
# Copyright 2019-2020 OmiseGO Pte Ltd | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
defmodule OMG.WatcherRPC.Web.Validator.BlockValidatorTest do | ||
use ExUnit.Case, async: true | ||
use OMG.WatcherRPC.Web, :controller | ||
|
||
alias OMG.Merkle | ||
alias OMG.State.Transaction | ||
alias OMG.TestHelper | ||
alias OMG.Watcher.BlockValidator | ||
|
||
@alice TestHelper.generate_entity() | ||
@bob TestHelper.generate_entity() | ||
@eth OMG.Eth.zero_address() | ||
@payment_tx_type OMG.WireFormatTypes.tx_type_for(:tx_payment_v1) | ||
|
||
describe "stateless_validate/1" do | ||
test "returns error if a transaction is not correctly formed (e.g. duplicate inputs)" do | ||
input_1 = {1, 0, 0, @alice} | ||
input_2 = {2, 0, 0, @alice} | ||
input_3 = {3, 0, 0, @alice} | ||
|
||
signed_valid_tx = TestHelper.create_signed([input_1, input_2], @eth, [{@bob, 10}]) | ||
signed_invalid_tx = TestHelper.create_signed([input_3, input_3], @eth, [{@bob, 10}]) | ||
|
||
%{sigs: sigs_valid} = signed_valid_tx | ||
%{sigs: sigs_invalid} = signed_invalid_tx | ||
|
||
txbytes_valid = Transaction.raw_txbytes(signed_valid_tx) | ||
txbytes_invalid = Transaction.raw_txbytes(signed_invalid_tx) | ||
|
||
[_, inputs_valid, outputs_valid, _, _] = ExRLP.decode(txbytes_valid) | ||
[_, inputs_invalid, outputs_invalid, _, _] = ExRLP.decode(txbytes_invalid) | ||
|
||
hash_valid = ExRLP.encode([sigs_valid, @payment_tx_type, inputs_valid, outputs_valid, 0, <<0::256>>]) | ||
|
||
hash_invalid = | ||
ExRLP.encode([ | ||
sigs_invalid, | ||
@payment_tx_type, | ||
inputs_invalid, | ||
outputs_invalid, | ||
0, | ||
<<0::256>> | ||
]) | ||
|
||
block = %{ | ||
hash: Merkle.hash([txbytes_valid, txbytes_invalid]), | ||
number: 1000, | ||
transactions: [hash_invalid, hash_valid] | ||
} | ||
|
||
assert {:error, :duplicate_inputs} == BlockValidator.stateless_validate(block) | ||
end | ||
|
||
test "accepts correctly formed transactions" do | ||
recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}, {2, 0, 0, @alice}], @eth, [{@bob, 10}]) | ||
recovered_tx_2 = TestHelper.create_recovered([{3, 0, 0, @alice}, {4, 0, 0, @alice}], @eth, [{@bob, 10}]) | ||
|
||
signed_txbytes_1 = recovered_tx_1.signed_tx_bytes | ||
signed_txbytes_2 = recovered_tx_2.signed_tx_bytes | ||
|
||
merkle_root = | ||
[recovered_tx_1, recovered_tx_2] | ||
|> Enum.map(&Transaction.raw_txbytes/1) | ||
|> Merkle.hash() | ||
|
||
block = %{ | ||
hash: merkle_root, | ||
number: 1000, | ||
transactions: [signed_txbytes_1, signed_txbytes_2] | ||
} | ||
|
||
assert {:ok, true} == BlockValidator.stateless_validate(block) | ||
end | ||
|
||
test "returns error for non-matching Merkle root hash" do | ||
recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}], @eth, [{@bob, 100}]) | ||
recovered_tx_2 = TestHelper.create_recovered([{2, 0, 0, @alice}], @eth, [{@bob, 100}]) | ||
|
||
signed_txbytes = Enum.map([recovered_tx_1, recovered_tx_2], fn tx -> tx.signed_tx_bytes end) | ||
|
||
block = %{ | ||
hash: "0x0", | ||
number: 1000, | ||
transactions: signed_txbytes | ||
} | ||
|
||
assert {:error, :invalid_merkle_root} == BlockValidator.stateless_validate(block) | ||
end | ||
|
||
test "accepts matching Merkle root hash" do | ||
recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}], @eth, [{@bob, 100}]) | ||
recovered_tx_2 = TestHelper.create_recovered([{2, 0, 0, @alice}], @eth, [{@bob, 100}]) | ||
|
||
signed_txbytes = Enum.map([recovered_tx_1, recovered_tx_2], fn tx -> tx.signed_tx_bytes end) | ||
|
||
valid_merkle_root = | ||
[recovered_tx_1, recovered_tx_2] | ||
|> Enum.map(&Transaction.raw_txbytes/1) | ||
|> Merkle.hash() | ||
|
||
block = %{ | ||
hash: valid_merkle_root, | ||
number: 1000, | ||
transactions: signed_txbytes | ||
} | ||
|
||
assert {:ok, true} = BlockValidator.stateless_validate(block) | ||
end | ||
end | ||
end |
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
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
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
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
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
Oops, something went wrong.