Skip to content

Commit

Permalink
Merge pull request #216 from aeternity/gh-45_coinbase_serialization
Browse files Browse the repository at this point in the history
GH-45 Coinbase transaction serialization
  • Loading branch information
velzevur committed Oct 4, 2017
2 parents f16dfce + 0ee2e22 commit 386608e
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 49 deletions.
7 changes: 2 additions & 5 deletions apps/aecore/src/aec_blocks.erl
Expand Up @@ -119,7 +119,6 @@ serialize_for_network(B = #block{}) ->
{ok, jsx:encode(serialize_to_map(B))}.

serialize_to_map(B = #block{}) ->
EncodeTx = fun(Tx) -> base64:encode(term_to_binary(Tx)) end,
#{<<"height">> => height(B),
<<"prev_hash">> => base64:encode(prev_hash(B)),
<<"state_hash">> => base64:encode(B#block.root_hash),
Expand All @@ -129,8 +128,7 @@ serialize_to_map(B = #block{}) ->
<<"time">> => B#block.time,
<<"version">> => B#block.version,
<<"pow">> => aec_headers:serialize_pow_evidence(B#block.pow_evidence),
%% TODO: txs serialization
<<"transactions">> => lists:map(EncodeTx, B#block.txs)
<<"transactions">> => lists:map(fun aec_tx_sign:serialize/1, B#block.txs)
}.

-spec deserialize_from_network(block_serialized_for_network()) ->
Expand All @@ -148,7 +146,6 @@ deserialize_from_map(#{<<"height">> := Height,
<<"version">> := Version,
<<"pow">> := PowEvidence,
<<"transactions">> := Txs}) ->
DecodeTx = fun(Tx) -> binary_to_term(base64:decode(Tx)) end,
{ok, #block{
height = Height,
prev_hash = base64:decode(PrevHash),
Expand All @@ -158,7 +155,7 @@ deserialize_from_map(#{<<"height">> := Height,
nonce = Nonce,
time = Time,
version = Version,
txs = lists:map(DecodeTx, Txs),
txs = lists:map(fun aec_tx_sign:deserialize/1, Txs),
pow_evidence = aec_headers:deserialize_pow_evidence(PowEvidence)}}.

-spec hash_internal_representation(block()) -> {ok, block_header_hash()}.
Expand Down
44 changes: 31 additions & 13 deletions apps/aecore/src/aec_tx_sign.erl
Expand Up @@ -4,7 +4,9 @@
-export([data/1,
verify/1]).
-export([serialize/1,
deserialize/1]).
serialize_to_binary/1,
deserialize/1,
deserialize_from_binary/1]).

-include("common.hrl").
-include("txs.hrl").
Expand All @@ -18,15 +20,31 @@ verify(failed_tx) ->
verify(_Tx) ->
ok.

%% TODO This is meant to be the deterministic canonical serialization.
serialize(SignedTx = #signed_tx{data = Tx, signatures = [_]}) when
is_tuple(Tx) ->
term_to_binary(SignedTx).

%% TODO This is meant to be the deserialization of the deterministic
%% canonical serialization.
deserialize(B) ->
case binary_to_term(B) of
#signed_tx{signatures = [_]} = SignedTx ->
SignedTx
end.
-spec serialize(#signed_tx{}) -> map().
serialize(#signed_tx{data = Tx, signatures = Sigs}) when is_tuple(Tx) ->
Mod = tx_dispatcher:handler(Tx),
Data = Mod:serialize(Tx),
Type = Mod:type(),
#{<<"type">> => Type,
<<"data">> => Data,
<<"signatures">> => lists:map(fun base64:encode/1, Sigs)}.


-spec deserialize(map()) -> #signed_tx{}.
deserialize(#{<<"type">> := Type,
<<"signatures">> := Sigs,
<<"data">> := Data}) ->
Mod = tx_dispatcher:handler_by_type(Type),
Tx = Mod:deserialize(Data),
#signed_tx{data = Tx,
signatures = lists:map(fun base64:decode/1, Sigs)}.

%% deterministic canonical serialization.
-spec serialize_to_binary(#signed_tx{}) -> binary().
serialize_to_binary(#signed_tx{} = SignedTx) ->
jsx:encode(serialize(SignedTx)).

-spec deserialize_from_binary(binary()) -> #signed_tx{}.
deserialize_from_binary(SignedTxBin) when is_binary(SignedTxBin) ->
deserialize(jsx:decode(SignedTxBin)).

2 changes: 1 addition & 1 deletion apps/aecore/src/aec_txs_trees.erl
Expand Up @@ -16,7 +16,7 @@ new(Txs = [_|_]) ->
{ok, TxsTree}.

put_signed_tx(SignedTx, TxsTree) ->
V = aec_tx_sign:serialize(SignedTx),
V = aec_tx_sign:serialize_to_binary(SignedTx),
K = aec_sha256:hash(V),
{ok, _NewTxsTree} =
aec_trees:put(K, V, TxsTree).
Expand Down
16 changes: 15 additions & 1 deletion apps/aecore/src/txs/aec_coinbase_tx.erl
Expand Up @@ -3,7 +3,10 @@
%% API
-export([new/2,
check/3,
process/3]).
process/3,
serialize/1,
deserialize/1,
type/0]).

-behavior(aec_tx).

Expand Down Expand Up @@ -43,3 +46,14 @@ process(#coinbase_tx{account = AccountPubkey}, Trees0, Height) ->
{error, notfound} ->
{error, account_not_found}
end.

serialize(#coinbase_tx{account = Account, nonce = Nonce}) ->
#{<<"pubkey">> => base64:encode(Account),
<<"nonce">> => Nonce}.

deserialize(#{<<"pubkey">> := Account, <<"nonce">> := Nonce}) ->
#coinbase_tx{account = base64:decode(Account), nonce = Nonce}.

type() ->
<<"coinbase">>.

25 changes: 14 additions & 11 deletions apps/aecore/src/txs/aec_tx.erl
Expand Up @@ -21,6 +21,12 @@
-callback process(Tx :: term(), Trees :: trees(), Height :: non_neg_integer()) ->
{ok, NewTrees :: trees()} | {error, Reason :: term()}.

-callback serialize(Tx :: term()) -> map().

-callback deserialize(map()) -> Tx :: term().

-callback type() -> binary().

%%%%=============================================================================
%% API
%%%=============================================================================
Expand All @@ -42,9 +48,7 @@ apply_signed([SignedTx | Rest], Trees0, Height) ->
apply_signed(Rest, Trees2, Height);
{error, _Reason} = Error ->
Error
end;
{error, _Reason} = Error ->
Error
end
end;
{error, _Reason} = Error ->
Error
Expand All @@ -59,16 +63,15 @@ apply_signed([SignedTx | Rest], Trees0, Height) ->
%% Check transaction. Prepare state tree: e.g., create newly referenced account
%%------------------------------------------------------------------------------
-spec check_single(tx(), trees(), non_neg_integer()) -> {ok, trees()} | {error, term()}.
check_single(#coinbase_tx{} = Tx, Trees, Height) ->
aec_coinbase_tx:check(Tx, Trees, Height);
check_single(_Other, _Trees_, _Height) ->
{error, not_implemented}.
check_single(Tx, Trees, Height) ->
Mod = tx_dispatcher:handler(Tx),
Mod:check(Tx, Trees, Height).

%%------------------------------------------------------------------------------
%% Process the transaction. Accounts must already be present in the state tree
%%------------------------------------------------------------------------------
-spec process_single(tx(), trees(), non_neg_integer()) -> {ok, trees()} | {error, term()}.
process_single(#coinbase_tx{} = Tx, Trees, Height) ->
aec_coinbase_tx:process(Tx, Trees, Height);
process_single(_Other, _Trees_, _Height) ->
{error, not_implemented}.
process_single(Tx, Trees, Height) ->
Mod = tx_dispatcher:handler(Tx),
Mod:process(Tx, Trees, Height).

9 changes: 9 additions & 0 deletions apps/aecore/src/txs/tx_dispatcher.erl
@@ -0,0 +1,9 @@
-module(tx_dispatcher).
-export([handler/1,handler_by_type/1]).
-type pubkey() :: binary().
-record(coinbase_tx,{account = <<>> :: pubkey(),
nonce = 0 :: non_neg_integer()}).
handler(#coinbase_tx{}) ->
aec_coinbase_tx.
handler_by_type(<<"coinbase">>) ->
aec_coinbase_tx.
27 changes: 20 additions & 7 deletions apps/aecore/test/aec_mining_tests.erl
Expand Up @@ -58,8 +58,10 @@ mine_block_test_() ->
meck:expect(aec_pow, pick_nonce, 0, 1),
meck:expect(aec_tx, apply_signed, 3, {ok, Trees}),
meck:expect(aec_keys, pubkey, 0, {ok, ?TEST_PUB}),
meck:expect(aec_keys, sign, 1, {ok, #signed_tx{data = {<<"123">>}, signatures = [sig1]}}),

meck:expect(aec_keys, sign, 1,
{ok, #signed_tx{data = #coinbase_tx{account = <<"pubkey">>,
nonce = 1},
signatures = [<<"sig1">>]}}),
{ok, Block} = ?TEST_MODULE:mine(400),

?assertEqual(1, Block#block.height),
Expand All @@ -75,8 +77,10 @@ mine_block_test_() ->
meck:expect(aec_pow, pick_nonce, 0, 1),
meck:expect(aec_tx, apply_signed, 3, {ok, Trees}),
meck:expect(aec_keys, pubkey, 0, {ok, ?TEST_PUB}),
meck:expect(aec_keys, sign, 1, {ok, #signed_tx{data = {<<"123">>}, signatures = [sig1]}}),

meck:expect(aec_keys, sign, 1,
{ok, #signed_tx{data = #coinbase_tx{account = <<"pubkey">>,
nonce = 1},
signatures = [<<"sig1">>]}}),
?assertEqual({error, generation_count_exhausted}, ?TEST_MODULE:mine())
end}},
{"Cannot apply signed tx (PoW module " ++ atom_to_list(PoWMod) ++ ")",
Expand All @@ -85,7 +89,10 @@ mine_block_test_() ->
meck:expect(aec_chain, top, 0, {ok, #block{}}),
meck:expect(aec_tx, apply_signed, 3, {error, tx_failed}),
meck:expect(aec_keys, pubkey, 0, {ok, ?TEST_PUB}),
meck:expect(aec_keys, sign, 1, {ok, #signed_tx{data = {<<"123">>}, signatures = [sig1]}}),
meck:expect(aec_keys, sign, 1,
{ok, #signed_tx{data = #coinbase_tx{account = <<"pubkey">>,
nonce = 1},
signatures = [<<"sig1">>]}}),
?assertEqual({error, tx_failed}, ?TEST_MODULE:mine())
end},
{timeout, 60,
Expand All @@ -108,7 +115,10 @@ mine_block_test_() ->
meck:expect(aec_governance, recalculate_difficulty_frequency, 0, 10),
meck:expect(aec_governance, expected_block_mine_rate, 0, 5),
meck:expect(aec_keys, pubkey, 0, {ok, ?TEST_PUB}),
meck:expect(aec_keys, sign, 1, {ok, #signed_tx{data = {<<"123">>}, signatures = [sig1]}}),
meck:expect(aec_keys, sign, 1,
{ok, #signed_tx{data = #coinbase_tx{account = <<"pubkey">>,
nonce = 1},
signatures = [<<"sig1">>]}}),

{ok, Block} = ?TEST_MODULE:mine(400),

Expand Down Expand Up @@ -153,7 +163,10 @@ mine_block_test_() ->
meck:expect(aec_governance, recalculate_difficulty_frequency, 0, 10),
meck:expect(aec_governance, expected_block_mine_rate, 0, 100000),
meck:expect(aec_keys, pubkey, 0, {ok, ?TEST_PUB}),
meck:expect(aec_keys, sign, 1, {ok, #signed_tx{data = {<<"123">>}, signatures = [sig1]}}),
meck:expect(aec_keys, sign, 1,
{ok, #signed_tx{data = #coinbase_tx{account = <<"pubkey">>,
nonce = 1},
signatures = [<<"sig1">>]}}),

{ok, Block} = ?TEST_MODULE:mine(400),

Expand Down
31 changes: 30 additions & 1 deletion apps/aehttp/priv/swagger.json
Expand Up @@ -227,7 +227,7 @@
"transactions" : {
"type" : "array",
"items" : {
"type" : "string"
"$ref" : "#/definitions/SignedTx"
}
}
}
Expand Down Expand Up @@ -315,6 +315,35 @@
"format" : "int64"
}
}
},
"CoinbaseTx" : {
"type" : "object",
"properties" : {
"pubkey" : {
"type" : "string"
},
"nonce" : {
"type" : "integer"
}
}
},
"SignedTx" : {
"type" : "object",
"properties" : {
"type" : {
"type" : "string"
},
"data" : {
"type" : "object",
"properties" : { }
},
"signatures" : {
"type" : "array",
"items" : {
"type" : "string"
}
}
}
}
},
"externalDocs" : {
Expand Down
23 changes: 22 additions & 1 deletion config/swagger.yaml
Expand Up @@ -186,7 +186,7 @@ definitions:
transactions:
type: array
items:
type: string
$ref: '#/definitions/SignedTx'
Top:
type: object
properties:
Expand Down Expand Up @@ -244,6 +244,27 @@ definitions:
balance:
type: integer
format: int64
CoinbaseTx:
type: object
properties:
pubkey:
type: string
nonce:
type: integer
SignedTx:
type: object
properties:
type:
type: string
data:
type: object
schema:
oneOf:
- $ref: '#/definitions/CoinbaseTx'
signatures:
type: array
items:
type: string
externalDocs:
description: Find out more about Aeternity
url: 'http://www.aeternity.com'
23 changes: 14 additions & 9 deletions py/tests/test_api_ext.py
Expand Up @@ -23,12 +23,21 @@
from swagger_client.apis.external_api import ExternalApi
from swagger_client.api_client import ApiClient
from swagger_client.models.block import Block
from swagger_client.models.signed_tx import SignedTx
from swagger_client.models.coinbase_tx import CoinbaseTx

def utc_now():
d = datetime.datetime.utcnow()
epoch = datetime.datetime(1970,1,1)
return int((d - epoch).total_seconds())

def signed_coinbase_tx(height):
account = "BAAggMEhrC3ODBqlYeQ6dk00F87AKMkV6kkyhgfJ/luOzGUC+4APxFkVgAYPai3TjSyLRObv0GeDACg1ZxwnfHY="
coinbase = CoinbaseTx(pubkey = account, nonce = height - 1)
return SignedTx(data = coinbase, type = "coinbase",
signatures =
["Some signature"])


class TestExternalApi(unittest.TestCase):
EXT_API = {
Expand Down Expand Up @@ -96,16 +105,17 @@ def test_post_block(self):
api = self.EXT_API['dev1']
top = api.get_top()
top_block = api.get_block_by_hash(top.hash)
block = Block(height = top_block.height + 1,
block_height = top_block.height + 1
block = Block(height = block_height,
prev_hash = top.hash,
## temporary
state_hash = "6CN+HP79yKQYgD/GD3zfDb7Jcc9qp2MrHdzqxgoCxuQ=",
## temporary
txs_hash = "hVwaPrDaxgos7LbLqmAmNgVRx7hyZKT0oUV61t2RiwI=",
txs_hash = "VqLNlzhbj4qyzy/z9EweR+TtZyaJqWJYHnjckoBuYTM=",
## temporary
target = 553713663,
## temporary
nonce = 1191330979,
nonce = block_height,
time = utc_now(),
version = 1,
## temporary
Expand All @@ -118,11 +128,7 @@ def test_post_block(self):
103724145,110979886,116332888,117754872,128960259,133685357
],
## temporary
transactions=["g2gDZAAJc2lnbmVkX3R4aANkAAtjb2luYmFzZV90eG0AA"
+ "ABBBAAggMEhrC3ODBqlYeQ6dk00F87AKMkV6kkyhgfJ/luOzGUC+4"
+ "APxFkVgAYPai3TjSyLRObv0GeDACg1ZxwnfHZhAGwAAAABbQAAAEc"
+ "wRQIgayzfIlgGTevxOmL/ucn0qG8WgQ49Rvg1ETmr9wkjJXECIQDN"
+ "rCsjv23qPirn7jNVJ1XWqzumrH/WdxUr2byP+dVwQWo="]
transactions=[signed_coinbase_tx(block_height)]
)
api.post_block(block)
print("Posted block " + str(block.height))
Expand All @@ -142,7 +148,6 @@ def test_download_chain(self):
if block.height == 0:
print("Downloaded genesis block")


if __name__ == '__main__':
unittest.main()

0 comments on commit 386608e

Please sign in to comment.