From 02e6e13e1caf36dae5b3fd4782346d297502f87c Mon Sep 17 00:00:00 2001 From: p4u Date: Wed, 4 Sep 2024 17:21:54 +0200 Subject: [PATCH] extend faucet package support for other transactions Faucet package can now be used on NewProcess, SetAccount, SetProcess and SetSik related transactions. Signed-off-by: p4u --- cmd/end2endtest/account.go | 12 +- go.mod | 2 +- go.sum | 4 +- test/api_test.go | 10 +- vochain/account_test.go | 198 ++++++++++++++---- vochain/state/account.go | 5 +- vochain/transaction/account_tx.go | 56 +---- vochain/transaction/transaction.go | 231 +++++++++++++++++---- vochain/transaction/vochaintx/vochaintx.go | 20 ++ 9 files changed, 392 insertions(+), 146 deletions(-) diff --git a/cmd/end2endtest/account.go b/cmd/end2endtest/account.go index af0ab6c86..08de5b7e1 100644 --- a/cmd/end2endtest/account.go +++ b/cmd/end2endtest/account.go @@ -122,10 +122,16 @@ func testCreateAndSetAccount(api *apiclient.HTTPclient, fp *models.FaucetPackage return err } + // get the create account transaction cost + txCost, err := api.TransactionCost(models.TxType_CREATE_ACCOUNT) + if err != nil { + return fmt.Errorf("error fetching transaction cost: %w", err) + } + // create account for bob with faucet package from alice afp, err := vochain.GenerateFaucetPackage(alice, bob.Address(), aliceAcc.Balance/2) if err != nil { - return fmt.Errorf("error in GenerateFaucetPackage %v", err) + return fmt.Errorf("error in GenerateFaucetPackage %w", err) } bobAcc, err := ensureAccountExists(api.Clone(hex.EncodeToString(bob.PrivateKey())), afp) @@ -134,9 +140,9 @@ func testCreateAndSetAccount(api *apiclient.HTTPclient, fp *models.FaucetPackage } // check balance added from payload - if bobAcc.Balance != aliceAcc.Balance/2 { + if bobAcc.Balance+txCost != aliceAcc.Balance/2 { return fmt.Errorf("expected balance for bob (%s) is %d but got %d", - bob.Address(), aliceAcc.Balance/2, bobAcc.Balance) + bob.Address(), (aliceAcc.Balance/2)-txCost, bobAcc.Balance) } log.Infow("account for bob successfully created with payload signed by alice", "alice", alice.Address(), "bob", bobAcc) diff --git a/go.mod b/go.mod index ea64ca31e..89c3db364 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a github.com/vocdoni/storage-proofs-eth-go v0.1.6 go.mongodb.org/mongo-driver v1.12.1 - go.vocdoni.io/proto v1.15.8 + go.vocdoni.io/proto v1.15.10-0.20240903073233-86144b1e2165 golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa golang.org/x/net v0.28.0 diff --git a/go.sum b/go.sum index 66d7a2bba..6191ad6e3 100644 --- a/go.sum +++ b/go.sum @@ -1598,8 +1598,8 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.vocdoni.io/proto v1.15.8 h1:I5HVHffQwXyp0jootnCVj83+PoJ8L703RJ2CFSPTa2Q= -go.vocdoni.io/proto v1.15.8/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo= +go.vocdoni.io/proto v1.15.10-0.20240903073233-86144b1e2165 h1:7r0RaKfYyGCVh1qeS2795jOk7WnpP9CI0IJOB/lxFVk= +go.vocdoni.io/proto v1.15.10-0.20240903073233-86144b1e2165/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= diff --git a/test/api_test.go b/test/api_test.go index 267895636..072e217d0 100644 --- a/test/api_test.go +++ b/test/api_test.go @@ -702,7 +702,15 @@ func createAccount(t testing.TB, c *testutil.TestHTTPclient, metaData, err := json.Marshal(meta) qt.Assert(t, err, qt.IsNil) - fp, err := vochain.GenerateFaucetPackage(server.Account, signer.Address(), initialBalance) + // get the txCost for the create account transacction + data, statusCode := c.Request("GET", nil, "chain", "transactions", "cost") + qt.Assert(t, statusCode, qt.Equals, 200) + var txCosts api.Transaction + qt.Assert(t, json.Unmarshal(data, &txCosts), qt.IsNil) + txCost := txCosts.Costs[genesis.TxTypeToCostName(models.TxType_CREATE_ACCOUNT)] + + // add the tx cost to the initialBalance for the faucet package + fp, err := vochain.GenerateFaucetPackage(server.Account, signer.Address(), initialBalance+txCost) qt.Assert(t, err, qt.IsNil) // transaction diff --git a/vochain/account_test.go b/vochain/account_test.go index a491ea940..3cb155959 100644 --- a/vochain/account_test.go +++ b/vochain/account_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/rand" + "regexp" "testing" cometabcitypes "github.com/cometbft/cometbft/abci/types" @@ -73,7 +74,7 @@ func setupTestBaseApplicationAndSigners(t *testing.T, } func TestSetAccountTx(t *testing.T) { - app, signers, err := setupTestBaseApplicationAndSigners(t, 10) + app, signers, err := setupTestBaseApplicationAndSigners(t, 11) qt.Assert(t, err, qt.IsNil) // set account 0 qt.Assert(t, @@ -84,6 +85,9 @@ func TestSetAccountTx(t *testing.T) { ) testCommitState(t, app) + // set create account tx cost to 200 + qt.Assert(t, app.State.SetTxBaseCost(models.TxType_CREATE_ACCOUNT, 200), qt.IsNil) + // CREATE ACCOUNT // should work is a valid faucet payload and tx.Account is provided and no infoURI is set @@ -97,13 +101,13 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err := app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(8900)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(9000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer1Account, err := app.State.GetAccount(signers[1].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer1Account, qt.IsNotNil) - qt.Assert(t, signer1Account.Balance, qt.DeepEquals, uint64(1000)) + qt.Assert(t, signer1Account.Balance, qt.DeepEquals, uint64(800)) qt.Assert(t, signer1Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer1Account.InfoURI, qt.CmpEquals(), "") @@ -117,13 +121,13 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(7800)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(8000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer2Account, err := app.State.GetAccount(signers[2].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer2Account, qt.IsNotNil) - qt.Assert(t, signer2Account.Balance, qt.DeepEquals, uint64(1000)) + qt.Assert(t, signer2Account.Balance, qt.DeepEquals, uint64(800)) qt.Assert(t, signer2Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer2Account.InfoURI, qt.CmpEquals(), "") @@ -137,13 +141,13 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6700)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(7000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer3Account, err := app.State.GetAccount(signers[3].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer3Account, qt.IsNotNil) - qt.Assert(t, signer3Account.Balance, qt.DeepEquals, uint64(1000)) + qt.Assert(t, signer3Account.Balance, qt.DeepEquals, uint64(800)) qt.Assert(t, signer3Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer3Account.InfoURI, qt.CmpEquals(), infoURI) @@ -158,13 +162,13 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer4Account, err := app.State.GetAccount(signers[4].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer4Account, qt.IsNotNil) - qt.Assert(t, signer4Account.Balance, qt.DeepEquals, uint64(1000)) + qt.Assert(t, signer4Account.Balance, qt.DeepEquals, uint64(800)) qt.Assert(t, signer4Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer4Account.InfoURI, qt.CmpEquals(), infoURI) @@ -178,7 +182,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err := app.State.GetAccount(signers[5].Address(), false) @@ -193,7 +197,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) @@ -208,7 +212,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) @@ -223,7 +227,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) @@ -240,7 +244,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) @@ -271,7 +275,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) @@ -288,7 +292,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(6000)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) @@ -296,7 +300,7 @@ func TestSetAccountTx(t *testing.T) { qt.Assert(t, signer5Account, qt.IsNil) // should work if random nonce provided - faucetPkg, err = GenerateFaucetPackage(signers[0], signers[5].Address(), 10) + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[5].Address(), 200) qt.Assert(t, err, qt.IsNil) qt.Assert(t, testSetAccountTx(t, signers[5], common.Address{}, faucetPkg, app, infoURI, rand.Uint32(), true), @@ -305,19 +309,19 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5490)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5800)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer5Account, err = app.State.GetAccount(signers[5].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer5Account, qt.IsNotNil) - qt.Assert(t, signer5Account.Balance, qt.DeepEquals, uint64(10)) + qt.Assert(t, signer5Account.Balance, qt.DeepEquals, uint64(0)) qt.Assert(t, signer5Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer5Account.InfoURI, qt.CmpEquals(), infoURI) qt.Assert(t, signer5Account.DelegateAddrs, qt.DeepEquals, [][]byte(nil)) // should work if random account provided, the account created is the signer one - faucetPkg, err = GenerateFaucetPackage(signers[0], signers[6].Address(), 10) + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[6].Address(), 200) qt.Assert(t, err, qt.IsNil) qt.Assert(t, testSetAccountTx(t, signers[6], signers[7].Address(), faucetPkg, app, infoURI, rand.Uint32(), true), @@ -326,13 +330,13 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5380)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5600)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer6Account, err = app.State.GetAccount(signers[6].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer6Account, qt.IsNotNil) - qt.Assert(t, signer6Account.Balance, qt.DeepEquals, uint64(10)) + qt.Assert(t, signer6Account.Balance, qt.DeepEquals, uint64(0)) qt.Assert(t, signer6Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer6Account.InfoURI, qt.CmpEquals(), infoURI) qt.Assert(t, signer6Account.DelegateAddrs, qt.DeepEquals, [][]byte(nil)) @@ -343,7 +347,7 @@ func TestSetAccountTx(t *testing.T) { // should ignore tx cost if tx cost is set to 0 qt.Assert(t, app.State.SetTxBaseCost(models.TxType_CREATE_ACCOUNT, 0), qt.IsNil) testCommitState(t, app) - faucetPkg, err = GenerateFaucetPackage(signers[0], signers[7].Address(), 10) + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[7].Address(), 200) qt.Assert(t, err, qt.IsNil) qt.Assert(t, testSetAccountTx(t, signers[7], common.Address{}, faucetPkg, app, infoURI, 0, true), @@ -352,13 +356,13 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer7Account, err = app.State.GetAccount(signers[7].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer7Account, qt.IsNotNil) - qt.Assert(t, signer7Account.Balance, qt.DeepEquals, uint64(10)) + qt.Assert(t, signer7Account.Balance, qt.DeepEquals, uint64(200)) qt.Assert(t, signer7Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer7Account.InfoURI, qt.CmpEquals(), infoURI) qt.Assert(t, signer7Account.DelegateAddrs, qt.DeepEquals, [][]byte(nil)) @@ -371,7 +375,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer8Account, err := app.State.GetAccount(signers[8].Address(), false) @@ -383,7 +387,7 @@ func TestSetAccountTx(t *testing.T) { qt.Assert(t, signer8Account.DelegateAddrs, qt.DeepEquals, [][]byte(nil)) // should not work if tx cost is not 0 and no faucet package is provided - qt.Assert(t, app.State.SetTxBaseCost(models.TxType_CREATE_ACCOUNT, 1), qt.IsNil) + qt.Assert(t, app.State.SetTxBaseCost(models.TxType_CREATE_ACCOUNT, 200), qt.IsNil) testCommitState(t, app) qt.Assert(t, testSetAccountTx(t, signers[9], common.Address{}, nil, app, infoURI, 0, true), @@ -392,7 +396,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer9Account, err := app.State.GetAccount(signers[9].Address(), false) @@ -401,7 +405,8 @@ func TestSetAccountTx(t *testing.T) { // SET ACCOUNT INFO URI - // should not work for itself with faucet package (the faucet package is ignored) and invalid InfoURI + // should not work (the faucet package is ignored) and invalid InfoURI + qt.Assert(t, app.State.SetTxBaseCost(models.TxType_SET_ACCOUNT_INFO_URI, 200), qt.IsNil) faucetPkg, err = GenerateFaucetPackage(signers[0], signers[0].Address(), 1000) qt.Assert(t, err, qt.IsNil) qt.Assert(t, testSetAccountTx(t, @@ -411,7 +416,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) @@ -423,7 +428,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) @@ -433,7 +438,7 @@ func TestSetAccountTx(t *testing.T) { qt.IsNotNil, ) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) @@ -443,7 +448,7 @@ func TestSetAccountTx(t *testing.T) { qt.IsNotNil, ) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5370)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5400)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) @@ -455,7 +460,7 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5270)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5200)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(1)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI2) @@ -469,15 +474,16 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5270)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5200)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(1)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI2) signer1Account, err = app.State.GetAccount(signers[1].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer1Account, qt.IsNotNil) - qt.Assert(t, signer1Account.Balance, qt.DeepEquals, uint64(1000)) + qt.Assert(t, signer1Account.Balance, qt.DeepEquals, uint64(800)) qt.Assert(t, signer1Account.Nonce, qt.DeepEquals, uint32(0)) qt.Assert(t, signer1Account.InfoURI, qt.CmpEquals(), "") + // should work if delegate qt.Assert(t, app.State.SetAccountDelegate( signers[0].Address(), @@ -491,15 +497,125 @@ func TestSetAccountTx(t *testing.T) { signer0Account, err = app.State.GetAccount(signers[0].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer0Account, qt.IsNotNil) - qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5270)) + qt.Assert(t, signer0Account.Balance, qt.DeepEquals, uint64(5200)) qt.Assert(t, signer0Account.Nonce, qt.DeepEquals, uint32(1)) qt.Assert(t, signer0Account.InfoURI, qt.CmpEquals(), infoURI) signer1Account, err = app.State.GetAccount(signers[1].Address(), false) qt.Assert(t, err, qt.IsNil) qt.Assert(t, signer1Account, qt.IsNotNil) - qt.Assert(t, signer1Account.Balance, qt.DeepEquals, uint64(900)) + qt.Assert(t, signer1Account.Balance, qt.DeepEquals, uint64(600)) qt.Assert(t, signer1Account.Nonce, qt.DeepEquals, uint32(1)) qt.Assert(t, signer1Account.InfoURI, qt.CmpEquals(), "") + + // should work, try using a faucet package for a set account info tx + // first create the account (and check balance is 0, because tx cost 200) + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[9].Address(), 200) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, testSetAccountTx(t, + signers[9], signers[9].Address(), faucetPkg, app, "", uint32(0), true), + qt.IsNil, + ) + + signer9Account, err = app.State.GetAccount(signers[9].Address(), false) + qt.Assert(t, err, qt.IsNil) + + qt.Assert(t, signer9Account, qt.IsNotNil) + qt.Assert(t, signer9Account.Balance, qt.DeepEquals, uint64(0)) + qt.Assert(t, signer9Account.Nonce, qt.DeepEquals, uint32(0)) + qt.Assert(t, signer9Account.InfoURI, qt.CmpEquals(), "") + + // second send the set account info uri tx, check the remaining balance is 1 + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[9].Address(), 201) + qt.Assert(t, err, qt.IsNil) + infoURI3 := "ipfs://newInfoUri" + qt.Assert(t, testSetAccountTx(t, + signers[9], signers[9].Address(), faucetPkg, app, infoURI3, uint32(0), false), + qt.IsNil, + ) + + signer9Account, err = app.State.GetAccount(signers[9].Address(), false) + qt.Assert(t, err, qt.IsNil) + + qt.Assert(t, signer9Account, qt.IsNotNil) + qt.Assert(t, signer9Account.Balance, qt.DeepEquals, uint64(1)) + qt.Assert(t, signer9Account.Nonce, qt.DeepEquals, uint32(1)) + qt.Assert(t, signer9Account.InfoURI, qt.CmpEquals(), infoURI3) + + // third should not work: try using an insufficiente faucet package (100) for a set account info tx + // because tx cost 200 + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[9].Address(), 100) + qt.Assert(t, err, qt.IsNil) + infoURI4 := "ipfs://newInfoUri4" + qt.Assert(t, testSetAccountTx(t, + signers[9], signers[9].Address(), faucetPkg, app, infoURI4, uint32(1), false), + qt.ErrorMatches, regexp.MustCompile(".*"+state.ErrNotEnoughBalance.Error()), + ) + + signer9Account, err = app.State.GetAccount(signers[9].Address(), false) + qt.Assert(t, err, qt.IsNil) + + // the result should be the 100 tokens are now added to the balance, but the infoURI remains unchanged. + qt.Assert(t, signer9Account, qt.IsNotNil) + qt.Assert(t, signer9Account.Balance, qt.DeepEquals, uint64(101)) + qt.Assert(t, signer9Account.Nonce, qt.DeepEquals, uint32(1)) + qt.Assert(t, signer9Account.InfoURI, qt.CmpEquals(), infoURI3) + + // now this should work: try again using a small faucet package (120) for a set account info tx + // but adding faucet (120) + balance (101) is enough to cover the tx cost of 200 + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[9].Address(), 120) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, testSetAccountTx(t, + signers[9], signers[9].Address(), faucetPkg, app, infoURI4, uint32(1), false), + qt.IsNil, + ) + + signer9Account, err = app.State.GetAccount(signers[9].Address(), false) + qt.Assert(t, err, qt.IsNil) + + // the resulting balance should be 21 (and the infoURI is updated) + // i.e. the tokens from balance + faucet (120+101=221) minus the consumed txcost (200), + qt.Assert(t, signer9Account, qt.IsNotNil) + qt.Assert(t, signer9Account.Balance, qt.DeepEquals, uint64(21)) + qt.Assert(t, signer9Account.Nonce, qt.DeepEquals, uint32(2)) + qt.Assert(t, signer9Account.InfoURI, qt.CmpEquals(), infoURI4) + + // now we init signers[10] with 1000 tokens from faucet (signers[0]) + // resulting balance should be 800 (because of the 200 txcost of createAccount) + // then "transfer" 700 of those to signers[9] via a faucet package issued by signers[10] + // used to setAccount + faucetPkg, err = GenerateFaucetPackage(signers[0], signers[10].Address(), 1000) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, testSetAccountTx(t, + signers[10], signers[10].Address(), faucetPkg, app, "", uint32(0), true), + qt.IsNil, + ) + signer10Account, err := app.State.GetAccount(signers[10].Address(), false) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, signer10Account.Balance, qt.DeepEquals, uint64(1000-200)) + + signer9Account, err = app.State.GetAccount(signers[9].Address(), false) + qt.Assert(t, err, qt.IsNil) + oldBalance := signer9Account.Balance + + faucetPkg, err = GenerateFaucetPackage(signers[10], signers[9].Address(), 1000-200+1) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, testSetAccountTx(t, + signers[9], signers[9].Address(), faucetPkg, app, "ipfs://test", uint32(2), false), + qt.ErrorMatches, regexp.MustCompile(".*issuer address does not have enough balance.*"), + ) + + faucetPkg, err = GenerateFaucetPackage(signers[10], signers[9].Address(), 1000-200-50) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, testSetAccountTx(t, + signers[9], signers[9].Address(), faucetPkg, app, "ipfs://test", uint32(2), false), + qt.IsNil, + ) + signer10Account, err = app.State.GetAccount(signers[10].Address(), false) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, signer10Account.Balance, qt.DeepEquals, uint64(50)) + signer9Account, err = app.State.GetAccount(signers[9].Address(), false) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, signer9Account.Balance, qt.DeepEquals, uint64(oldBalance+1000-200-50-200)) } func testSetAccountTx(t *testing.T, @@ -604,7 +720,7 @@ func TestSendTokensTxToTheSameAccount(t *testing.T) { err := signer.Generate() qt.Assert(t, err, qt.IsNil) - app.State.SetAccount(state.BurnAddress, &state.Account{}) + qt.Assert(t, app.State.SetAccount(state.BurnAddress, &state.Account{}), qt.IsNil) err = app.State.SetTxBaseCost(models.TxType_SEND_TOKENS, 10) qt.Assert(t, err, qt.IsNil) @@ -664,7 +780,7 @@ func TestSetAccountDelegateTx(t *testing.T) { err = signer2.Generate() qt.Assert(t, err, qt.IsNil) - app.State.SetAccount(state.BurnAddress, &state.Account{}) + qt.Assert(t, app.State.SetAccount(state.BurnAddress, &state.Account{}), qt.IsNil) err = app.State.SetTxBaseCost(models.TxType_ADD_DELEGATE_FOR_ACCOUNT, 10) qt.Assert(t, err, qt.IsNil) @@ -770,7 +886,7 @@ func TestCollectFaucetTx(t *testing.T) { err = toSigner.Generate() qt.Assert(t, err, qt.IsNil) - app.State.SetAccount(state.BurnAddress, &state.Account{}) + qt.Assert(t, app.State.SetAccount(state.BurnAddress, &state.Account{}), qt.IsNil) err = app.State.SetTxBaseCost(models.TxType_COLLECT_FAUCET, 10) qt.Assert(t, err, qt.IsNil) diff --git a/vochain/state/account.go b/vochain/state/account.go index 8bbe4264a..e0c35c41a 100644 --- a/vochain/state/account.go +++ b/vochain/state/account.go @@ -2,6 +2,7 @@ package state import ( "bytes" + "encoding/hex" "errors" "fmt" @@ -310,16 +311,16 @@ func (v *State) SetAccountDelegate(accountAddr common.Address, } switch txType { case models.TxType_ADD_DELEGATE_FOR_ACCOUNT: - log.Debugf("adding delegates %+v for account %s", delegateAddrs, accountAddr) for _, delegate := range delegateAddrs { + log.Debugw("adding delegate", "account", accountAddr.Hex(), "delegate", hex.EncodeToString(delegate)) if err := acc.AddDelegate(common.BytesToAddress(delegate)); err != nil { return fmt.Errorf("cannot add delegate, AddDelegate: %w", err) } } return v.SetAccount(accountAddr, acc) case models.TxType_DEL_DELEGATE_FOR_ACCOUNT: - log.Debugf("deleting delegates %+v for account %s", delegateAddrs, accountAddr) for _, delegate := range delegateAddrs { + log.Debugw("deleting delegate", "account", accountAddr.Hex(), "delegate", hex.EncodeToString(delegate)) if err := acc.DelDelegate(common.BytesToAddress(delegate)); err != nil { return fmt.Errorf("cannot delete delegate, DelDelegate: %w", err) } diff --git a/vochain/transaction/account_tx.go b/vochain/transaction/account_tx.go index f46abaf57..6c1e6b39c 100644 --- a/vochain/transaction/account_tx.go +++ b/vochain/transaction/account_tx.go @@ -2,7 +2,6 @@ package transaction import ( "bytes" - "encoding/binary" "errors" "fmt" @@ -13,7 +12,6 @@ import ( vstate "go.vocdoni.io/dvote/vochain/state" "go.vocdoni.io/dvote/vochain/transaction/vochaintx" "go.vocdoni.io/proto/build/go/models" - "google.golang.org/protobuf/proto" ) // CreateAccountTxCheck checks if an account creation tx is valid @@ -56,56 +54,13 @@ func (t *TransactionHandler) CreateAccountTxCheck(vtx *vochaintx.Tx) error { if err != nil { return fmt.Errorf("cannot get tx cost: %w", err) } - if txCost == 0 { - return nil - } - if tx.FaucetPackage == nil { - return fmt.Errorf("invalid faucet package provided") - } - if tx.FaucetPackage.Payload == nil { - return fmt.Errorf("invalid faucet package payload") - } - faucetPayload := &models.FaucetPayload{} - if err := proto.Unmarshal(tx.FaucetPackage.Payload, faucetPayload); err != nil { - return fmt.Errorf("could not unmarshal faucet package: %w", err) - } - if faucetPayload.Amount == 0 { - return fmt.Errorf("invalid faucet payload amount provided") - } - if faucetPayload.To == nil { - return fmt.Errorf("invalid to address provided") - } - if !bytes.Equal(faucetPayload.To, txSenderAddress.Bytes()) { - return fmt.Errorf("payload to and tx sender missmatch (%x != %x)", - faucetPayload.To, txSenderAddress.Bytes()) - } - issuerAddress, err := ethereum.AddrFromSignature(tx.FaucetPackage.Payload, tx.FaucetPackage.Signature) + // Check the faucet package + canPayForTx, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], false) if err != nil { - return fmt.Errorf("cannot extract issuer address from faucet package vtx.Signature: %w", err) - } - issuerAcc, err := t.state.GetAccount(issuerAddress, false) - if err != nil { - return fmt.Errorf("cannot get faucet issuer address account: %w", err) - } - if issuerAcc == nil { - return fmt.Errorf("the account signing the faucet payload does not exist (%s)", issuerAddress) - } - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b, faucetPayload.Identifier) - keyHash := ethereum.HashRaw(append(issuerAddress.Bytes(), b...)) - used, err := t.state.FaucetNonce(keyHash, false) - if err != nil { - return fmt.Errorf("cannot check if faucet payload already used: %w", err) - } - if used { - return fmt.Errorf("faucet payload %x already used", keyHash) + return err } - if issuerAcc.Balance < faucetPayload.Amount+txCost { - return fmt.Errorf( - "issuer address does not have enough balance %d, required %d", - issuerAcc.Balance, - faucetPayload.Amount+txCost, - ) + if !canPayForTx { + return fmt.Errorf("faucet package is not enough for paying for the transaction cost") } return nil } @@ -126,6 +81,7 @@ func (t *TransactionHandler) SetAccountDelegateTxCheck(vtx *vochaintx.Tx) error if len(tx.Delegates) == 0 { return fmt.Errorf("invalid delegates") } + txSenderAccount, txSenderAddr, err := t.checkAccountCanPayCost(tx.Txtype, vtx) if err != nil { return err diff --git a/vochain/transaction/transaction.go b/vochain/transaction/transaction.go index 474dac6c8..f8884ef0c 100644 --- a/vochain/transaction/transaction.go +++ b/vochain/transaction/transaction.go @@ -1,6 +1,8 @@ package transaction import ( + "bytes" + "encoding/binary" "encoding/hex" "fmt" @@ -131,13 +133,23 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa }); err != nil { return nil, fmt.Errorf("newProcessTx: cannot schedule end process: %w", err) } + + cost := t.txElectionCostFromProcess(p) + + // check for a faucet package and transfer amount to sender account + sender := common.Address(txSender) + if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, cost, sender, vtx.TxID[:], true); err != nil { + return nil, err + } + return response, t.state.BurnTxCostIncrementNonce( - common.Address(txSender), + sender, models.TxType_NEW_PROCESS, - t.txElectionCostFromProcess(p), + cost, hex.EncodeToString(p.GetProcessId()), ) } + case *models.Tx_SetProcess: cost := uint64(0) txSender, err := t.SetProcessTxCheck(vtx) @@ -203,7 +215,13 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa return nil, fmt.Errorf("unknown set process tx type") } - return response, t.state.BurnTxCostIncrementNonce(common.Address(txSender), tx.Txtype, cost, hex.EncodeToString(tx.ProcessId)) + // check for a faucet package and transfer amount to sender account + sender := common.Address(txSender) + if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, cost, sender, vtx.TxID[:], true); err != nil { + return nil, err + } + + return response, t.state.BurnTxCostIncrementNonce(sender, tx.Txtype, cost, hex.EncodeToString(tx.ProcessId)) } case *models.Tx_SetAccount: @@ -253,41 +271,24 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa return nil, fmt.Errorf("setAccountTx: SetAddressSIK %w", err) } } - if tx.FaucetPackage != nil { - faucetIssuerAddress, err := ethereum.AddrFromSignature(tx.FaucetPackage.Payload, tx.FaucetPackage.Signature) - if err != nil { - return nil, fmt.Errorf("createAccountTx: faucetIssuerAddress %w", err) - } - txCost, err := t.state.TxBaseCost(models.TxType_CREATE_ACCOUNT, false) - if err != nil { - return nil, fmt.Errorf("createAccountTx: txCost %w", err) - } - if txCost != 0 { - if err := t.state.BurnTxCost(faucetIssuerAddress, txCost); err != nil { - return nil, fmt.Errorf("setAccountTx: burnTxCost %w", err) - } - } - faucetPayload := &models.FaucetPayload{} - if err := proto.Unmarshal(tx.FaucetPackage.Payload, faucetPayload); err != nil { - return nil, fmt.Errorf("createAccountTx: cannot unmarshal faucetPayload %w", err) - } - if err := t.state.ConsumeFaucetPayload( - faucetIssuerAddress, - faucetPayload, - ); err != nil { - return nil, fmt.Errorf("setAccountTx: consumeFaucet %w", err) - } - if err := t.state.TransferBalance(&vochaintx.TokenTransfer{ - FromAddress: faucetIssuerAddress, - ToAddress: txSenderAddress, - Amount: faucetPayload.Amount, - TxHash: vtx.TxID[:], - }, false); err != nil { - return nil, fmt.Errorf("setAccountTx: transferBalance %w", err) - } - // transfer balance from faucet package issuer to created account - return response, nil + txCost, err := t.state.TxBaseCost(models.TxType_CREATE_ACCOUNT, false) + if err != nil { + return nil, fmt.Errorf("createAccountTx: txCost %w", err) + } + + // transfer balance from faucet package issuer to created account + canPayWithFaucet, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true) + if err != nil { + return nil, fmt.Errorf("could not verify the faucet package: %w", err) } + if !canPayWithFaucet { + return nil, fmt.Errorf("faucet package is not enough for paying for the transaction cost") + } + // burn the cost of the transaction from the txSender account + if err := t.state.BurnTxCost(txSenderAddress, txCost); err != nil { + return nil, err + } + return response, nil case models.TxType_SET_ACCOUNT_INFO_URI: @@ -295,11 +296,19 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if err != nil { return nil, fmt.Errorf("setAccountInfo: txSenderAddress %w", err) } + txCost, err := t.state.TxBaseCost(models.TxType_SET_ACCOUNT_INFO_URI, false) + if err != nil { + return nil, fmt.Errorf("setAccountInfoUriTx: txCost: %w", err) + } + // check for a faucet package and if exist, transfer the amount to the tx sender + if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil { + return nil, err + } // consume cost for setAccount if err := t.state.BurnTxCostIncrementNonce( txSenderAddress, models.TxType_SET_ACCOUNT_INFO_URI, - 0, + txCost, tx.GetInfoURI(), ); err != nil { return nil, fmt.Errorf("setAccountInfo: burnCostIncrementNonce %w", err) @@ -321,10 +330,18 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if err != nil { return nil, fmt.Errorf("addDelegate: txSenderAddress %w", err) } + txCost, err := t.state.TxBaseCost(models.TxType_ADD_DELEGATE_FOR_ACCOUNT, false) + if err != nil { + return nil, fmt.Errorf("addDelegate: txCost: %w", err) + } + // check for a faucet package and if exist, transfer the amount to the tx sender + if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil { + return nil, err + } if err := t.state.BurnTxCostIncrementNonce( txSenderAddress, models.TxType_ADD_DELEGATE_FOR_ACCOUNT, - 0, + txCost, "", ); err != nil { return nil, fmt.Errorf("addDelegate: burnTxCostIncrementNonce %w", err) @@ -341,10 +358,18 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if err != nil { return nil, fmt.Errorf("delDelegate: txSenderAddress %w", err) } + txCost, err := t.state.TxBaseCost(models.TxType_DEL_DELEGATE_FOR_ACCOUNT, false) + if err != nil { + return nil, fmt.Errorf("delDelegate: txCost: %w", err) + } + // check for a faucet package and if exist, transfer the amount to the tx sender + if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil { + return nil, err + } if err := t.state.BurnTxCostIncrementNonce( txSenderAddress, models.TxType_DEL_DELEGATE_FOR_ACCOUNT, - 0, + txCost, "", ); err != nil { return nil, fmt.Errorf("delDelegate: burnTxCostIncrementNonce %w", err) @@ -361,6 +386,14 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if err != nil { return nil, fmt.Errorf("setValidator: txSenderAddress %w", err) } + txCost, err := t.state.TxBaseCost(models.TxType_SET_ACCOUNT_VALIDATOR, false) + if err != nil { + return nil, fmt.Errorf("setValidator: txCost: %w", err) + } + // check for a faucet package and if exist, transfer the amount to the tx sender + if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil { + return nil, err + } validatorAddr, err := ethereum.AddrFromPublicKey(tx.GetPublicKey()) if err != nil { return nil, fmt.Errorf("setValidator: %w", err) @@ -368,7 +401,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if err := t.state.BurnTxCostIncrementNonce( txSenderAddress, models.TxType_SET_ACCOUNT_VALIDATOR, - 0, + txCost, validatorAddr.Hex(), ); err != nil { return nil, fmt.Errorf("setValidator: burnTxCostIncrementNonce %w", err) @@ -456,10 +489,18 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa return nil, fmt.Errorf("setSIKTx: %w", err) } if forCommit { + txCost, err := t.state.TxBaseCost(models.TxType_SET_ACCOUNT_SIK, false) + if err != nil { + return nil, fmt.Errorf("setAccountInfoUriTx: txCost: %w", err) + } + // check for a faucet package and if exist, transfer the amount to the tx sender + if _, err := t.checkFaucetPackageAndTransfer(vtx.Tx.GetSetSIK().FaucetPackage, txCost, txAddress, vtx.TxID[:], true); err != nil { + return nil, err + } if err := t.state.BurnTxCostIncrementNonce( txAddress, models.TxType_SET_ACCOUNT_SIK, - 0, + txCost, newSIK.String(), ); err != nil { return nil, fmt.Errorf("setSIKTx: burnTxCostIncrementNonce %w", err) @@ -476,10 +517,18 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa return nil, fmt.Errorf("delSIKTx: %w", err) } if forCommit { + txCost, err := t.state.TxBaseCost(models.TxType_DEL_ACCOUNT_SIK, false) + if err != nil { + return nil, fmt.Errorf("delSIKTx: txCost: %w", err) + } + // check for a faucet package and if exist, transfer the amount to the tx sender + if _, err := t.checkFaucetPackageAndTransfer(vtx.Tx.GetSetSIK().FaucetPackage, txCost, txAddress, vtx.TxID[:], true); err != nil { + return nil, err + } if err := t.state.BurnTxCostIncrementNonce( txAddress, models.TxType_DEL_ACCOUNT_SIK, - 0, + txCost, "", ); err != nil { return nil, fmt.Errorf("delSIKTx: burnTxCostIncrementNonce %w", err) @@ -522,6 +571,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa // checkAccountCanPayCost checks if the account can pay the cost of the transaction. // It returns the account and the address of the sender. +// It also checks if a faucet package is available in the transaction and can pay for it. func (t *TransactionHandler) checkAccountCanPayCost(txType models.TxType, vtx *vochaintx.Tx) (*vstate.Account, *common.Address, error) { // extract sender address from signature pubKey, err := ethereum.PubKeyFromSignature(vtx.SignedBody, vtx.Signature) @@ -544,9 +594,98 @@ func (t *TransactionHandler) checkAccountCanPayCost(txType models.TxType, vtx *v if err != nil { return nil, nil, fmt.Errorf("cannot get tx cost for %s: %w", txType.String(), err) } - // check tx sender balance - if txSenderAcc.Balance < cost { - return nil, nil, fmt.Errorf("unauthorized: %s", vstate.ErrNotEnoughBalance) + + if cost > 0 { + // check if faucet package can pay for the transaction + canFaucetPackagePay, err := t.checkFaucetPackageAndTransfer(vtx.GetFaucetPackage(), cost, txSenderAddress, vtx.TxID[:], false) + if err != nil { + return nil, nil, err + } + + // if faucet cannot pay for it, check tx sender balance + if !canFaucetPackagePay { + if txSenderAcc.Balance < cost { + return nil, nil, fmt.Errorf("unauthorized: %s", vstate.ErrNotEnoughBalance) + } + } } return txSenderAcc, &txSenderAddress, nil } + +// checkFaucetPackageAndTransfer checks if the txFaucetPackage is a valid Faucet package issued for txSenderAddress and the account signing the faucet package has +// enough funds for paying for the txCost. +// If forCommit is true, the faucet package balance is transferred to the txSender account. +// Returns false and no error if the faucet package does not exist. If it exists but there is an issue, returns false and the error. +func (t *TransactionHandler) checkFaucetPackageAndTransfer(txFaucetPackage *models.FaucetPackage, txCost uint64, txSenderAddress common.Address, txHash []byte, forCommit bool) (bool, error) { + if txFaucetPackage == nil { + if txCost == 0 { + return true, nil + } + return false, nil + } + if txFaucetPackage.Payload == nil { + return false, fmt.Errorf("invalid faucet package payload") + } + faucetPayload := &models.FaucetPayload{} + if err := proto.Unmarshal(txFaucetPackage.Payload, faucetPayload); err != nil { + return false, fmt.Errorf("could not unmarshal faucet package: %w", err) + } + if faucetPayload.Amount == 0 { + return false, fmt.Errorf("invalid faucet payload amount provided") + } + if faucetPayload.To == nil { + return false, fmt.Errorf("invalid to address provided") + } + if !bytes.Equal(faucetPayload.To, txSenderAddress.Bytes()) { + return false, fmt.Errorf("payload to and tx sender missmatch (%x != %x)", + faucetPayload.To, txSenderAddress.Bytes()) + } + issuerAddress, err := ethereum.AddrFromSignature(txFaucetPackage.Payload, txFaucetPackage.Signature) + if err != nil { + return false, fmt.Errorf("cannot extract issuer address from faucet package vtx.Signature: %w", err) + } + issuerAcc, err := t.state.GetAccount(issuerAddress, false) + if err != nil { + return false, fmt.Errorf("cannot get faucet issuer address account: %w", err) + } + if issuerAcc == nil { + return false, fmt.Errorf("the account signing the faucet payload does not exist (%s)", issuerAddress) + } + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, faucetPayload.Identifier) + keyHash := ethereum.HashRaw(append(issuerAddress.Bytes(), b...)) + used, err := t.state.FaucetNonce(keyHash, false) + if err != nil { + return false, fmt.Errorf("cannot check if faucet payload already used: %w", err) + } + if used { + return false, fmt.Errorf("faucet payload %x already used", keyHash) + } + if issuerAcc.Balance < faucetPayload.Amount { + return false, fmt.Errorf( + "issuer address does not have enough balance %d, required %d", + issuerAcc.Balance, + faucetPayload.Amount, + ) + } + + // if forCommit, then the cost of the transaction is consumed from the faucet + if forCommit { + if err := t.state.ConsumeFaucetPayload( + issuerAddress, + faucetPayload, + ); err != nil { + return false, fmt.Errorf("consumeFaucet: %w", err) + } + if err := t.state.TransferBalance(&vochaintx.TokenTransfer{ + FromAddress: issuerAddress, + ToAddress: txSenderAddress, + Amount: faucetPayload.Amount, + TxHash: txHash, + }, false); err != nil { + return false, fmt.Errorf("consumeFaucet: %w", err) + } + } + + return true, nil +} diff --git a/vochain/transaction/vochaintx/vochaintx.go b/vochain/transaction/vochaintx/vochaintx.go index 66bc950fe..5781135a7 100644 --- a/vochain/transaction/vochaintx/vochaintx.go +++ b/vochain/transaction/vochaintx/vochaintx.go @@ -88,3 +88,23 @@ type TokenTransfer struct { Amount uint64 TxHash []byte } + +// GetFaucetPackage returns the FaucetPackage found inside the tx.Tx.Payload, or nil if not found. +func (tx *Tx) GetFaucetPackage() *models.FaucetPackage { + switch tx.Tx.Payload.(type) { + case *models.Tx_NewProcess: + return tx.Tx.GetNewProcess().GetFaucetPackage() + case *models.Tx_SetProcess: + return tx.Tx.GetSetProcess().GetFaucetPackage() + case *models.Tx_SetAccount: + return tx.Tx.GetSetAccount().GetFaucetPackage() + case *models.Tx_CollectFaucet: + return tx.Tx.GetCollectFaucet().GetFaucetPackage() + case *models.Tx_SetSIK: + return tx.Tx.GetSetSIK().GetFaucetPackage() + case *models.Tx_DelSIK: + return tx.Tx.GetDelSIK().GetFaucetPackage() + default: + return nil + } +}