diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index e126960b2..9294723eb 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -12,7 +12,6 @@ import ( "net/http" "net/http/httptest" "testing" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -20,19 +19,16 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - ABI "github.com/vechain/thor/v2/abi" "github.com/vechain/thor/v2/api/accounts" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/packer" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/thorclient" - tccommon "github.com/vechain/thor/v2/thorclient/common" "github.com/vechain/thor/v2/tx" + + ABI "github.com/vechain/thor/v2/abi" + tccommon "github.com/vechain/thor/v2/thorclient/common" ) // pragma solidity ^0.4.18; @@ -94,7 +90,7 @@ const ( ) var ( - gasLimit uint64 + gasLimit = math.MaxUint32 addr = thor.BytesToAddress([]byte("to")) value = big.NewInt(10000) storageKey = thor.Bytes32{} @@ -102,7 +98,6 @@ var ( contractAddr thor.Address bytecode = common.Hex2Bytes("608060405234801561001057600080fd5b50610125806100206000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806324b8ba5f14604e578063bb4e3f4d14607b575b600080fd5b348015605957600080fd5b506079600480360381019080803560ff16906020019092919050505060cf565b005b348015608657600080fd5b5060b3600480360381019080803560ff169060200190929190803560ff16906020019092919050505060ec565b604051808260ff1660ff16815260200191505060405180910390f35b806000806101000a81548160ff021916908360ff16021790555050565b60008183019050929150505600a165627a7a723058201584add23e31d36c569b468097fe01033525686b59bbb263fb3ab82e9553dae50029") runtimeBytecode = common.Hex2Bytes("6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806324b8ba5f14604e578063bb4e3f4d14607b575b600080fd5b348015605957600080fd5b506079600480360381019080803560ff16906020019092919050505060cf565b005b348015608657600080fd5b5060b3600480360381019080803560ff169060200190929190803560ff16906020019092919050505060ec565b604051808260ff1660ff16815260200191505060405180910390f35b806000806101000a81548160ff021916908360ff16021790555050565b60008183019050929150505600a165627a7a723058201584add23e31d36c569b468097fe01033525686b59bbb263fb3ab82e9553dae50029") - acc *accounts.Accounts ts *httptest.Server tclient *thorclient.Client ) @@ -270,22 +265,14 @@ func getStorageWithNonExistingRevision(t *testing.T) { } func initAccountServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - genesisBlock = b - repo, _ := chain.NewRepository(db, b) + genesisBlock = thorChain.GenesisBlock() claTransfer := tx.NewClause(&addr).WithValue(value) claDeploy := tx.NewClause(nil).WithData(bytecode) - transaction := buildTxWithClauses(repo.ChainTag(), claTransfer, claDeploy) + transaction := buildTxWithClauses(thorChain.Repo().ChainTag(), claTransfer, claDeploy) contractAddr = thor.CreateContractAddress(transaction.ID(), 1, 0) - packTx(repo, stater, transaction, t) - method := "set" abi, _ := ABI.New([]byte(abiJSON)) m, _ := abi.MethodByName(method) @@ -294,13 +281,19 @@ func initAccountServer(t *testing.T) { t.Fatal(err) } claCall := tx.NewClause(&contractAddr).WithData(input) - transactionCall := buildTxWithClauses(repo.ChainTag(), claCall) - packTx(repo, stater, transactionCall, t) + transactionCall := buildTxWithClauses(thorChain.Repo().ChainTag(), claCall) + require.NoError(t, + thorChain.MintTransactions( + genesis.DevAccounts()[0], + transaction, + transactionCall, + ), + ) router := mux.NewRouter() - gasLimit = math.MaxUint32 - acc = accounts.New(repo, stater, gasLimit, thor.NoFork, solo.NewBFTEngine(repo)) - acc.Mount(router, "/accounts") + accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, thorChain.Engine()). + Mount(router, "/accounts") + ts = httptest.NewServer(router) } @@ -318,31 +311,6 @@ func buildTxWithClauses(chaiTag byte, clauses ...*tx.Clause) *tx.Transaction { return tx.MustSign(trx, genesis.DevAccounts()[0].PrivateKey) } -func packTx(repo *chain.Repository, stater *state.Stater, transaction *tx.Transaction, t *testing.T) { - packer := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - flow, err := packer.Schedule(repo.BestBlockSummary(), uint64(time.Now().Unix())) - if err != nil { - t.Fatal(err) - } - err = flow.Adopt(transaction) - if err != nil { - t.Fatal(err) - } - b, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - t.Fatal(err) - } - if _, err := stage.Commit(); err != nil { - t.Fatal(err) - } - if err := repo.AddBlock(b, receipts, 0); err != nil { - t.Fatal(err) - } - if err := repo.SetBestBlockID(b.Header().ID()); err != nil { - t.Fatal(err) - } -} - func deployContractWithCall(t *testing.T) { badBody := &accounts.CallData{ Gas: 10000000, diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index a203cadd6..dcb6c4e94 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -14,19 +14,14 @@ import ( "strconv" "strings" "testing" - "time" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/blocks" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/packer" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/thorclient" "github.com/vechain/thor/v2/tx" @@ -175,22 +170,14 @@ func testGetBlockWithRevisionNumberTooHigh(t *testing.T) { } func initBlockServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - genesisBlock = b + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) - repo, _ := chain.NewRepository(db, b) addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) trx := tx.MustSign( new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). Gas(21000). @@ -201,34 +188,17 @@ func initBlockServer(t *testing.T) { genesis.DevAccounts()[0].PrivateKey, ) - packer := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - sum, _ := repo.GetBlockSummary(b.Header().ID()) - flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) - if err != nil { - t.Fatal(err) - } - err = flow.Adopt(trx) - if err != nil { - t.Fatal(err) - } - block, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - t.Fatal(err) - } - if _, err := stage.Commit(); err != nil { - t.Fatal(err) - } - if err := repo.AddBlock(block, receipts, 0); err != nil { - t.Fatal(err) - } - if err := repo.SetBestBlockID(block.Header().ID()); err != nil { - t.Fatal(err) - } + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], trx)) + + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + + genesisBlock = allBlocks[0] + blk = allBlocks[1] + router := mux.NewRouter() - bftEngine := solo.NewBFTEngine(repo) - blocks.New(repo, bftEngine).Mount(router, "/blocks") + blocks.New(thorChain.Repo(), thorChain.Engine()).Mount(router, "/blocks") ts = httptest.NewServer(router) - blk = block } func checkCollapsedBlock(t *testing.T, expBl *block.Block, actBl *blocks.JSONCollapsedBlock) { diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 36f26c075..1275a9030 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -13,7 +13,6 @@ import ( "net/http/httptest" "strings" "testing" - "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -22,13 +21,11 @@ import ( "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/builtin" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/packer" "github.com/vechain/thor/v2/state" "github.com/vechain/thor/v2/test/datagen" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/thorclient" "github.com/vechain/thor/v2/tracers/logger" @@ -515,22 +512,15 @@ func testStorageRangeDefaultOption(t *testing.T) { } func initDebugServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) addr := thor.BytesToAddress([]byte("to")) // Adding an empty clause transaction to the block to cover the case of // scanning multiple txs before getting the right one noClausesTx := new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(thorChain.Repo().ChainTag()). Expiration(10). Gas(21000). Build() @@ -539,7 +529,7 @@ func initDebugServer(t *testing.T) { cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) cla2 := tx.NewClause(&addr).WithValue(big.NewInt(10000)) transaction = new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). Gas(37000). @@ -550,38 +540,16 @@ func initDebugServer(t *testing.T) { Build() transaction = tx.MustSign(transaction, genesis.DevAccounts()[0].PrivateKey) - packer := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - sum, _ := repo.GetBlockSummary(b.Header().ID()) - flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) - if err != nil { - t.Fatal(err) - } - err = flow.Adopt(noClausesTx) - if err != nil { - t.Fatal(err) - } - err = flow.Adopt(transaction) - if err != nil { - t.Fatal(err) - } - b, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - blk = b - if err != nil { - t.Fatal(err) - } - if _, err := stage.Commit(); err != nil { - t.Fatal(err) - } - if err := repo.AddBlock(b, receipts, 0); err != nil { - t.Fatal(err) - } - if err := repo.SetBestBlockID(b.Header().ID()); err != nil { - t.Fatal(err) - } + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction, noClausesTx)) + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0])) + + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + blk = allBlocks[1] - forkConfig := thor.GetForkConfig(b.Header().ID()) + forkConfig := thor.GetForkConfig(blk.Header().ID()) router := mux.NewRouter() - debug = New(repo, stater, forkConfig, 21000, true, solo.NewBFTEngine(repo), []string{"all"}, false) + debug = New(thorChain.Repo(), thorChain.Stater(), forkConfig, 21000, true, thorChain.Engine(), []string{"all"}, false) debug.Mount(router, "/debug") ts = httptest.NewServer(router) } diff --git a/api/events/events_test.go b/api/events/events_test.go index 3d28e41dc..b1268d378 100644 --- a/api/events/events_test.go +++ b/api/events/events_test.go @@ -17,11 +17,8 @@ import ( "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/events" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/logdb" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/thorclient" "github.com/vechain/thor/v2/tx" @@ -37,8 +34,7 @@ var ( ) func TestEmptyEvents(t *testing.T) { - db := createDb(t) - initEventServer(t, db, defaultLogLimit) + initEventServer(t, defaultLogLimit) defer ts.Close() tclient = thorclient.New(ts.URL) @@ -51,21 +47,19 @@ func TestEmptyEvents(t *testing.T) { } func TestEvents(t *testing.T) { - db := createDb(t) - initEventServer(t, db, defaultLogLimit) + thorChain := initEventServer(t, defaultLogLimit) defer ts.Close() blocksToInsert := 5 tclient = thorclient.New(ts.URL) - insertBlocks(t, db, blocksToInsert) + insertBlocks(t, thorChain.LogDB(), blocksToInsert) testEventWithBlocks(t, blocksToInsert) } func TestOption(t *testing.T) { - db := createDb(t) - initEventServer(t, db, 5) + thorChain := initEventServer(t, 5) defer ts.Close() - insertBlocks(t, db, 5) + insertBlocks(t, thorChain.LogDB(), 5) tclient = thorclient.New(ts.URL) filter := events.EventFilter{ @@ -75,20 +69,20 @@ func TestOption(t *testing.T) { Order: logdb.DESC, } - res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/events", filter) + res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/event", filter) require.NoError(t, err) assert.Equal(t, "options.limit exceeds the maximum allowed value of 5", strings.Trim(string(res), "\n")) assert.Equal(t, http.StatusForbidden, statusCode) filter.Options.Limit = 5 - _, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/events", filter) + _, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/event", filter) require.NoError(t, err) assert.Equal(t, http.StatusOK, statusCode) // with nil options, should use default limit, when the filtered lower // or equal to the limit, should return the filtered events filter.Options = nil - res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/events", filter) + res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/event", filter) require.NoError(t, err) assert.Equal(t, http.StatusOK, statusCode) var tLogs []*events.FilteredEvent @@ -99,8 +93,8 @@ func TestOption(t *testing.T) { assert.Equal(t, 5, len(tLogs)) // when the filtered events exceed the limit, should return the forbidden - insertBlocks(t, db, 6) - res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/events", filter) + insertBlocks(t, thorChain.LogDB(), 6) + res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/event", filter) require.NoError(t, err) assert.Equal(t, http.StatusForbidden, statusCode) assert.Equal(t, "the number of filtered logs exceeds the maximum allowed value of 5, please use pagination", strings.Trim(string(res), "\n")) @@ -110,7 +104,7 @@ func TestOption(t *testing.T) { func testEventsBadRequest(t *testing.T) { badBody := []byte{0x00, 0x01, 0x02} - _, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/events", badBody) + _, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/event", badBody) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, statusCode) } @@ -123,7 +117,7 @@ func testEventWithEmptyDb(t *testing.T) { Order: logdb.DESC, } - res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/events", emptyFilter) + res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/event", emptyFilter) require.NoError(t, err) var tLogs []*events.FilteredEvent if err := json.Unmarshal(res, &tLogs); err != nil { @@ -142,7 +136,7 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { Order: logdb.DESC, } - res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/events", emptyFilter) + res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/event", emptyFilter) require.NoError(t, err) var tLogs []*events.FilteredEvent if err := json.Unmarshal(res, &tLogs); err != nil { @@ -169,7 +163,7 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { }}, } - res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/events", matchingFilter) + res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/event", matchingFilter) require.NoError(t, err) if err := json.Unmarshal(res, &tLogs); err != nil { t.Fatal(err) @@ -183,30 +177,15 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { } // Init functions -func initEventServer(t *testing.T, logDb *logdb.LogDB, limit uint64) { - router := mux.NewRouter() - - muxDb := muxdb.NewMem() - stater := state.NewStater(muxDb) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - - repo, _ := chain.NewRepository(muxDb, b) +func initEventServer(t *testing.T, limit uint64) *testchain.Chain { + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) - events.New(repo, logDb, limit).Mount(router, "/events") + router := mux.NewRouter() + events.New(thorChain.Repo(), thorChain.LogDB(), limit).Mount(router, "/logs/event") ts = httptest.NewServer(router) -} -func createDb(t *testing.T) *logdb.LogDB { - logDb, err := logdb.NewMem() - if err != nil { - t.Fatal(err) - } - return logDb + return thorChain } // Utilities functions diff --git a/api/metrics_test.go b/api/metrics_test.go index 59e86c9c8..7cb1794e4 100644 --- a/api/metrics_test.go +++ b/api/metrics_test.go @@ -20,14 +20,11 @@ import ( "github.com/gorilla/websocket" "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/accounts" "github.com/vechain/thor/v2/api/subscriptions" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/cmd/thor/solo" - "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/metrics" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/txpool" ) @@ -37,28 +34,21 @@ func init() { } func TestMetricsMiddleware(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) // inject some invalid data to db - data := db.NewStore("chain.data") + data := thorChain.Database().NewStore("chain.data") var blkID thor.Bytes32 rand.Read(blkID[:]) data.Put(blkID[:], []byte("invalid data")) // get summary should fail since the block data is not rlp encoded - _, err = repo.GetBlockSummary(blkID) + _, err = thorChain.Repo().GetBlockSummary(blkID) assert.NotNil(t, err) router := mux.NewRouter() - acc := accounts.New(repo, stater, math.MaxUint64, thor.NoFork, solo.NewBFTEngine(repo)) + acc := accounts.New(thorChain.Repo(), thorChain.Stater(), math.MaxUint64, thor.NoFork, thorChain.Engine()) acc.Mount(router, "/accounts") router.PathPrefix("/metrics").Handler(metrics.HTTPHandler()) router.Use(metricsMiddleware) @@ -109,18 +99,11 @@ func TestMetricsMiddleware(t *testing.T) { } func TestWebsocketMetrics(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) router := mux.NewRouter() - sub := subscriptions.New(repo, []string{"*"}, 10, txpool.New(repo, stater, txpool.Options{})) + sub := subscriptions.New(thorChain.Repo(), []string{"*"}, 10, txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{})) sub.Mount(router, "/subscriptions") router.PathPrefix("/metrics").Handler(metrics.HTTPHandler()) router.Use(metricsMiddleware) diff --git a/api/node/node_test.go b/api/node/node_test.go index e9bbe95fe..3dd2e96ee 100644 --- a/api/node/node_test.go +++ b/api/node/node_test.go @@ -13,11 +13,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/node" - "github.com/vechain/thor/v2/chain" "github.com/vechain/thor/v2/comm" - "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thorclient" "github.com/vechain/thor/v2/txpool" ) @@ -34,21 +31,19 @@ func TestNode(t *testing.T) { } func initCommServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) - comm := comm.New(repo, txpool.New(repo, stater, txpool.Options{ - Limit: 10000, - LimitPerAccount: 16, - MaxLifetime: 10 * time.Minute, - })) + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) + + communicator := comm.New( + thorChain.Repo(), + txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 10000, + LimitPerAccount: 16, + MaxLifetime: 10 * time.Minute, + })) + router := mux.NewRouter() - node.New(comm).Mount(router, "/node") + node.New(communicator).Mount(router, "/node") + ts = httptest.NewServer(router) } diff --git a/api/subscriptions/beat2_reader_test.go b/api/subscriptions/beat2_reader_test.go index 4a292f485..a31884a0b 100644 --- a/api/subscriptions/beat2_reader_test.go +++ b/api/subscriptions/beat2_reader_test.go @@ -9,17 +9,21 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/thor" ) func TestBeat2Reader_Read(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] // Act - beatReader := newBeat2Reader(repo, genesisBlk.Header().ID(), newMessageCache[Beat2Message](10)) + beatReader := newBeat2Reader(thorChain.Repo(), genesisBlk.Header().ID(), newMessageCache[Beat2Message](10)) res, ok, err := beatReader.Read() // Assert @@ -40,11 +44,13 @@ func TestBeat2Reader_Read(t *testing.T) { func TestBeat2Reader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - newBlock := generatedBlocks[1] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + newBlock := allBlocks[1] // Act - beatReader := newBeat2Reader(repo, newBlock.Header().ID(), newMessageCache[Beat2Message](10)) + beatReader := newBeat2Reader(thorChain.Repo(), newBlock.Header().ID(), newMessageCache[Beat2Message](10)) res, ok, err := beatReader.Read() // Assert @@ -55,10 +61,10 @@ func TestBeat2Reader_Read_NoNewBlocksToRead(t *testing.T) { func TestBeat2Reader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - repo, _, _ := initChain(t) + thorChain := initChain(t) // Act - beatReader := newBeat2Reader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), newMessageCache[Beat2Message](10)) + beatReader := newBeat2Reader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), newMessageCache[Beat2Message](10)) res, ok, err := beatReader.Read() // Assert diff --git a/api/subscriptions/beat_reader_test.go b/api/subscriptions/beat_reader_test.go index 07d020a7b..508913eea 100644 --- a/api/subscriptions/beat_reader_test.go +++ b/api/subscriptions/beat_reader_test.go @@ -9,17 +9,20 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/thor" ) func TestBeatReader_Read(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] // Act - beatReader := newBeatReader(repo, genesisBlk.Header().ID(), newMessageCache[BeatMessage](10)) + beatReader := newBeatReader(thorChain.Repo(), genesisBlk.Header().ID(), newMessageCache[BeatMessage](10)) res, ok, err := beatReader.Read() // Assert @@ -38,11 +41,13 @@ func TestBeatReader_Read(t *testing.T) { func TestBeatReader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - newBlock := generatedBlocks[1] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + newBlock := allBlocks[1] // Act - beatReader := newBeatReader(repo, newBlock.Header().ID(), newMessageCache[BeatMessage](10)) + beatReader := newBeatReader(thorChain.Repo(), newBlock.Header().ID(), newMessageCache[BeatMessage](10)) res, ok, err := beatReader.Read() // Assert @@ -53,10 +58,10 @@ func TestBeatReader_Read_NoNewBlocksToRead(t *testing.T) { func TestBeatReader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - repo, _, _ := initChain(t) + thorChain := initChain(t) // Act - beatReader := newBeatReader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), newMessageCache[BeatMessage](10)) + beatReader := newBeatReader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), newMessageCache[BeatMessage](10)) res, ok, err := beatReader.Read() // Assert diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index f772ba389..29a866963 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -8,27 +8,28 @@ package subscriptions import ( "math/big" "testing" - "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" - "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/packer" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/eventcontract" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" - "github.com/vechain/thor/v2/txpool" ) func TestBlockReader_Read(t *testing.T) { - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + // Arrange + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] // Test case 1: Successful read next blocks - br := newBlockReader(repo, genesisBlk.Header().ID()) + br := newBlockReader(thorChain.Repo(), genesisBlk.Header().ID()) res, ok, err := br.Read() assert.NoError(t, err) @@ -41,7 +42,7 @@ func TestBlockReader_Read(t *testing.T) { } // Test case 2: There is no new block - br = newBlockReader(repo, newBlock.Header().ID()) + br = newBlockReader(thorChain.Repo(), newBlock.Header().ID()) res, ok, err = br.Read() assert.NoError(t, err) @@ -49,7 +50,7 @@ func TestBlockReader_Read(t *testing.T) { assert.Empty(t, res) // Test case 3: Error when reading blocks - br = newBlockReader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + br = newBlockReader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err = br.Read() assert.Error(t, err) @@ -57,27 +58,14 @@ func TestBlockReader_Read(t *testing.T) { assert.Empty(t, res) } -func initChain(t *testing.T) (*chain.Repository, []*block.Block, *txpool.TxPool) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) - - txPool := txpool.New(repo, stater, txpool.Options{ - Limit: 100, - LimitPerAccount: 16, - MaxLifetime: time.Hour, - }) +func initChain(t *testing.T) *testchain.Chain { + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) tr := new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). Gas(21000). @@ -87,52 +75,20 @@ func initChain(t *testing.T) (*chain.Repository, []*block.Block, *txpool.TxPool) Build() tr = tx.MustSign(tr, genesis.DevAccounts()[0].PrivateKey) - packer := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - sum, _ := repo.GetBlockSummary(b.Header().ID()) - flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) - if err != nil { - t.Fatal(err) - } - err = flow.Adopt(tr) - if err != nil { - t.Fatal(err) - } - blk, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - t.Fatal(err) - } - if _, err := stage.Commit(); err != nil { - t.Fatal(err) - } - insertMockOutputEvent(receipts) - if err := repo.AddBlock(blk, receipts, 0); err != nil { - t.Fatal(err) - } - if err := repo.SetBestBlockID(blk.Header().ID()); err != nil { - t.Fatal(err) - } - return repo, []*block.Block{b, blk}, txPool -} + txDeploy := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(100). + Gas(1_000_000). + Nonce(3). + Clause(tx.NewClause(nil).WithData(common.Hex2Bytes(eventcontract.HexBytecode))). + BlockRef(tx.NewBlockRef(0)). + Build() + sigTxDeploy, err := crypto.Sign(txDeploy.SigningHash().Bytes(), genesis.DevAccounts()[1].PrivateKey) + require.NoError(t, err) + txDeploy = txDeploy.WithSignature(sigTxDeploy) -// This is a helper function to forcly insert an event into the output receipts -func insertMockOutputEvent(receipts tx.Receipts) { - oldReceipt := receipts[0] - events := make(tx.Events, 0) - events = append(events, &tx.Event{ - Address: thor.BytesToAddress([]byte("to")), - Topics: []thor.Bytes32{thor.BytesToBytes32([]byte("topic"))}, - Data: []byte("data"), - }) - outputs := &tx.Output{ - Transfers: oldReceipt.Outputs[0].Transfers, - Events: events, - } - receipts[0] = &tx.Receipt{ - Reverted: oldReceipt.Reverted, - GasUsed: oldReceipt.GasUsed, - Outputs: []*tx.Output{outputs}, - GasPayer: oldReceipt.GasPayer, - Paid: oldReceipt.Paid, - Reward: oldReceipt.Reward, - } + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], tr, txDeploy)) + + return thorChain } diff --git a/api/subscriptions/event_reader_test.go b/api/subscriptions/event_reader_test.go index b1fe280ba..71122f40f 100644 --- a/api/subscriptions/event_reader_test.go +++ b/api/subscriptions/event_reader_test.go @@ -8,17 +8,22 @@ package subscriptions import ( "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" "github.com/vechain/thor/v2/chain" ) func TestEventReader_Read(t *testing.T) { - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + // Arrange + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] er := &eventReader{ - repo: repo, + repo: thorChain.Repo(), filter: &EventFilter{}, blockReader: &mockBlockReaderWithError{}, } @@ -30,7 +35,7 @@ func TestEventReader_Read(t *testing.T) { assert.False(t, ok) // Test case 2: Events are available to read - er = newEventReader(repo, genesisBlk.Header().ID(), &EventFilter{}) + er = newEventReader(thorChain.Repo(), genesisBlk.Header().ID(), &EventFilter{}) events, ok, err = er.Read() @@ -44,7 +49,7 @@ func TestEventReader_Read(t *testing.T) { t.Fatal("unexpected type") } } - assert.Equal(t, 1, len(eventMessages)) + assert.Equal(t, 2, len(eventMessages)) eventMsg := eventMessages[0] assert.Equal(t, newBlock.Header().ID(), eventMsg.Meta.BlockID) assert.Equal(t, newBlock.Header().Number(), eventMsg.Meta.BlockNumber) diff --git a/api/subscriptions/message_cache_test.go b/api/subscriptions/message_cache_test.go index 6bf65c600..29f40de89 100644 --- a/api/subscriptions/message_cache_test.go +++ b/api/subscriptions/message_cache_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/block" ) @@ -29,10 +30,13 @@ func handler(blk *block.Block) func() (message, error) { } func TestMessageCache_GetOrAdd(t *testing.T) { - _, generatedBlocks, _ := initChain(t) + thorChain := initChain(t) - blk0 := generatedBlocks[0] - blk1 := generatedBlocks[1] + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + + blk0 := allBlocks[0] + blk1 := allBlocks[1] cache := newMessageCache[message](10) diff --git a/api/subscriptions/pending_tx_test.go b/api/subscriptions/pending_tx_test.go index a4deb2c89..00e6a0140 100644 --- a/api/subscriptions/pending_tx_test.go +++ b/api/subscriptions/pending_tx_test.go @@ -23,7 +23,14 @@ import ( ) func TestPendingTx_Subscribe(t *testing.T) { - _, _, txPool := initChain(t) + // Arrange + thorChain := initChain(t) + txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 100, + LimitPerAccount: 16, + MaxLifetime: time.Hour, + }) + p := newPendingTx(txPool) // When initialized, there should be no listeners @@ -36,7 +43,13 @@ func TestPendingTx_Subscribe(t *testing.T) { } func TestPendingTx_Unsubscribe(t *testing.T) { - _, _, txPool := initChain(t) + // Arrange + thorChain := initChain(t) + txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 100, + LimitPerAccount: 16, + MaxLifetime: time.Hour, + }) p := newPendingTx(txPool) ch := make(chan *tx.Transaction) diff --git a/api/subscriptions/subscriptions_test.go b/api/subscriptions/subscriptions_test.go index cce2fd8f2..0c0bffe3a 100644 --- a/api/subscriptions/subscriptions_test.go +++ b/api/subscriptions/subscriptions_test.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "io" + "math/big" "net/http" "net/http/httptest" "net/url" @@ -16,23 +17,22 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/packer" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/eventcontract" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/tx" "github.com/vechain/thor/v2/txpool" ) var ts *httptest.Server -var sub *Subscriptions -var txPool *txpool.TxPool -var repo *chain.Repository var blocks []*block.Block func TestSubscriptions(t *testing.T) { @@ -217,25 +217,111 @@ func TestParseAddress(t *testing.T) { } func initSubscriptionsServer(t *testing.T) { - r, generatedBlocks, pool := initChain(t) - repo = r - txPool = pool - blocks = generatedBlocks + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) + + txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 100, + LimitPerAccount: 16, + MaxLifetime: time.Hour, + }) + + addr := thor.BytesToAddress([]byte("to")) + cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + tr := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(10). + Gas(21000). + Nonce(1). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + + sig, err := crypto.Sign(tr.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) + if err != nil { + t.Fatal(err) + } + tr = tr.WithSignature(sig) + + txDeploy := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(100). + Gas(1_000_000). + Nonce(3). + Clause(tx.NewClause(nil).WithData(common.Hex2Bytes(eventcontract.HexBytecode))). + BlockRef(tx.NewBlockRef(0)). + Build() + sigTxDeploy, err := crypto.Sign(txDeploy.SigningHash().Bytes(), genesis.DevAccounts()[1].PrivateKey) + require.NoError(t, err) + txDeploy = txDeploy.WithSignature(sigTxDeploy) + + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], tr, txDeploy)) + + blocks, err = thorChain.GetAllBlocks() + require.NoError(t, err) + router := mux.NewRouter() - sub = New(repo, []string{}, 5, txPool) - sub.Mount(router, "/subscriptions") + New(thorChain.Repo(), []string{}, 5, txPool). + Mount(router, "/subscriptions") ts = httptest.NewServer(router) } func TestSubscriptionsBacktrace(t *testing.T) { - r, generatedBlocks, pool := initChainMultipleBlocks(t, 10) - repo = r - txPool = pool - blocks = generatedBlocks + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) + + txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 100, + LimitPerAccount: 16, + MaxLifetime: time.Hour, + }) + + addr := thor.BytesToAddress([]byte("to")) + cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) + tr := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(10). + Gas(21000). + Nonce(1). + Clause(cla). + BlockRef(tx.NewBlockRef(0)). + Build() + + sig, err := crypto.Sign(tr.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) + if err != nil { + t.Fatal(err) + } + tr = tr.WithSignature(sig) + + txDeploy := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(100). + Gas(1_000_000). + Nonce(3). + Clause(tx.NewClause(nil).WithData(common.Hex2Bytes(eventcontract.HexBytecode))). + BlockRef(tx.NewBlockRef(0)). + Build() + sigTxDeploy, err := crypto.Sign(txDeploy.SigningHash().Bytes(), genesis.DevAccounts()[1].PrivateKey) + require.NoError(t, err) + txDeploy = txDeploy.WithSignature(sigTxDeploy) + + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], tr, txDeploy)) + + for i := 0; i < 10; i++ { + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0])) + } + + blocks, err = thorChain.GetAllBlocks() + require.NoError(t, err) + router := mux.NewRouter() - sub = New(repo, []string{}, 5, txPool) - sub.Mount(router, "/subscriptions") + New(thorChain.Repo(), []string{}, 5, txPool).Mount(router, "/subscriptions") ts = httptest.NewServer(router) + defer ts.Close() t.Run("testHandleSubjectWithTransferBacktraceLimit", testHandleSubjectWithTransferBacktraceLimit) @@ -258,50 +344,3 @@ func testHandleSubjectWithTransferBacktraceLimit(t *testing.T) { assert.Equal(t, body, []byte("pos: backtrace limit exceeded\n")) assert.Nil(t, conn) } - -func initChainMultipleBlocks(t *testing.T, blockCount int) (*chain.Repository, []*block.Block, *txpool.TxPool) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) - - txPool := txpool.New(repo, stater, txpool.Options{ - Limit: 100, - LimitPerAccount: 16, - MaxLifetime: time.Hour, - }) - - packer := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - - tmpBlock := b - createdBlocks := []*block.Block{b} - for i := 0; i < blockCount; i++ { - sum, _ := repo.GetBlockSummary(tmpBlock.Header().ID()) - flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) - if err != nil { - t.Fatal(err) - } - blk, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - t.Fatal(err) - } - if _, err := stage.Commit(); err != nil { - t.Fatal(err) - } - if err := repo.AddBlock(blk, receipts, 0); err != nil { - t.Fatal(err) - } - if err := repo.SetBestBlockID(blk.Header().ID()); err != nil { - t.Fatal(err) - } - createdBlocks = append(createdBlocks, blk) - tmpBlock = blk - } - - return repo, createdBlocks, txPool -} diff --git a/api/subscriptions/transfer_reader_test.go b/api/subscriptions/transfer_reader_test.go index 8bef2175c..de7d0bf66 100644 --- a/api/subscriptions/transfer_reader_test.go +++ b/api/subscriptions/transfer_reader_test.go @@ -8,19 +8,23 @@ package subscriptions import ( "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" "github.com/vechain/thor/v2/thor" ) func TestTransferReader_Read(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] filter := &TransferFilter{} // Act - br := newTransferReader(repo, genesisBlk.Header().ID(), filter) + br := newTransferReader(thorChain.Repo(), genesisBlk.Header().ID(), filter) res, ok, err := br.Read() // Assert @@ -38,12 +42,14 @@ func TestTransferReader_Read(t *testing.T) { func TestTransferReader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - newBlock := generatedBlocks[1] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + newBlock := allBlocks[1] filter := &TransferFilter{} // Act - br := newTransferReader(repo, newBlock.Header().ID(), filter) + br := newTransferReader(thorChain.Repo(), newBlock.Header().ID(), filter) res, ok, err := br.Read() // Assert @@ -54,11 +60,11 @@ func TestTransferReader_Read_NoNewBlocksToRead(t *testing.T) { func TestTransferReader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - repo, _, _ := initChain(t) + thorChain := initChain(t) filter := &TransferFilter{} // Act - br := newTransferReader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), filter) + br := newTransferReader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), filter) res, ok, err := br.Read() // Assert @@ -69,8 +75,10 @@ func TestTransferReader_Read_ErrorWhenReadingBlocks(t *testing.T) { func TestTransferReader_Read_NoTransferMatchingTheFilter(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] nonExistingAddress := thor.MustParseAddress("0xffffffffffffffffffffffffffffffffffffffff") badFilter := &TransferFilter{ @@ -78,7 +86,7 @@ func TestTransferReader_Read_NoTransferMatchingTheFilter(t *testing.T) { } // Act - br := newTransferReader(repo, genesisBlk.Header().ID(), badFilter) + br := newTransferReader(thorChain.Repo(), genesisBlk.Header().ID(), badFilter) res, ok, err := br.Read() // Assert diff --git a/api/transactions/transactions_test.go b/api/transactions/transactions_test.go index cef22e191..68c9d535d 100644 --- a/api/transactions/transactions_test.go +++ b/api/transactions/transactions_test.go @@ -20,11 +20,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/transactions" - "github.com/vechain/thor/v2/chain" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/packer" - "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/thorclient" "github.com/vechain/thor/v2/tx" @@ -32,11 +29,11 @@ import ( ) var ( - repo *chain.Repository ts *httptest.Server transaction *tx.Transaction mempoolTx *tx.Transaction tclient *thorclient.Client + chainTag byte ) func TestTransaction(t *testing.T) { @@ -110,7 +107,6 @@ func getTxReceipt(t *testing.T) { func sendTx(t *testing.T) { var blockRef = tx.NewBlockRef(0) - var chainTag = repo.ChainTag() var expiration = uint32(10) var gas = uint64(21000) @@ -270,19 +266,15 @@ func httpPostAndCheckResponseStatus(t *testing.T, url string, obj interface{}, r } func initTransactionServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) + + chainTag = thorChain.Repo().ChainTag() - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ = chain.NewRepository(db, b) addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) transaction = new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(chainTag). GasPriceCoef(1). Expiration(10). Gas(21000). @@ -292,47 +284,26 @@ func initTransactionServer(t *testing.T) { Build() transaction = tx.MustSign(transaction, genesis.DevAccounts()[0].PrivateKey) + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction)) + + mempool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{Limit: 10000, LimitPerAccount: 16, MaxLifetime: 10 * time.Minute}) + mempoolTx = new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(chainTag). Expiration(10). Gas(21000). Nonce(1). Build() mempoolTx = tx.MustSign(mempoolTx, genesis.DevAccounts()[0].PrivateKey) - packer := packer.New(repo, stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - sum, _ := repo.GetBlockSummary(b.Header().ID()) - flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) - if err != nil { - t.Fatal(err) - } - err = flow.Adopt(transaction) - if err != nil { - t.Fatal(err) - } - b, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - t.Fatal(err) - } - if _, err := stage.Commit(); err != nil { - t.Fatal(err) - } - if err := repo.AddBlock(b, receipts, 0); err != nil { - t.Fatal(err) - } - if err := repo.SetBestBlockID(b.Header().ID()); err != nil { - t.Fatal(err) - } - router := mux.NewRouter() - // Add a tx to the mempool to have both pending and non-pending transactions - mempool := txpool.New(repo, stater, txpool.Options{Limit: 10000, LimitPerAccount: 16, MaxLifetime: 10 * time.Minute}) e := mempool.Add(mempoolTx) if e != nil { t.Fatal(e) } - transactions.New(repo, mempool).Mount(router, "/transactions") + router := mux.NewRouter() + transactions.New(thorChain.Repo(), mempool).Mount(router, "/transactions") ts = httptest.NewServer(router) } diff --git a/api/transfers/transfers_test.go b/api/transfers/transfers_test.go index b0764c85f..04a8c7b42 100644 --- a/api/transfers/transfers_test.go +++ b/api/transfers/transfers_test.go @@ -19,12 +19,9 @@ import ( "github.com/vechain/thor/v2/api/events" "github.com/vechain/thor/v2/api/transfers" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/logdb" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/state" "github.com/vechain/thor/v2/test/datagen" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thorclient" "github.com/vechain/thor/v2/tx" ) @@ -72,20 +69,20 @@ func TestOption(t *testing.T) { Order: logdb.DESC, } - res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/transfers", filter) + res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", filter) require.NoError(t, err) assert.Equal(t, "options.limit exceeds the maximum allowed value of 5", strings.Trim(string(res), "\n")) assert.Equal(t, http.StatusForbidden, statusCode) filter.Options.Limit = 5 - _, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/transfers", filter) + _, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", filter) require.NoError(t, err) assert.Equal(t, http.StatusOK, statusCode) // with nil options, should use default limit, when the filtered lower // or equal to the limit, should return the filtered transfers filter.Options = nil - res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/transfers", filter) + res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", filter) require.NoError(t, err) assert.Equal(t, http.StatusOK, statusCode) var tLogs []*events.FilteredEvent @@ -97,7 +94,7 @@ func TestOption(t *testing.T) { // when the filtered transfers exceed the limit, should return the forbidden insertBlocks(t, db, 6) - res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/transfers", filter) + res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", filter) require.NoError(t, err) assert.Equal(t, http.StatusForbidden, statusCode) assert.Equal(t, "the number of filtered logs exceeds the maximum allowed value of 5, please use pagination", strings.Trim(string(res), "\n")) @@ -107,7 +104,7 @@ func TestOption(t *testing.T) { func testTransferBadRequest(t *testing.T) { badBody := []byte{0x00, 0x01, 0x02} - _, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/transfers", badBody) + _, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", badBody) require.NoError(t, err) assert.Equal(t, http.StatusBadRequest, statusCode) } @@ -120,7 +117,7 @@ func testTransferWithEmptyDb(t *testing.T) { Order: logdb.DESC, } - res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/transfers", emptyFilter) + res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", emptyFilter) require.NoError(t, err) var tLogs []*transfers.FilteredTransfer if err := json.Unmarshal(res, &tLogs); err != nil { @@ -139,7 +136,7 @@ func testTransferWithBlocks(t *testing.T, expectedBlocks int) { Order: logdb.DESC, } - res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/transfers", emptyFilter) + res, statusCode, err := tclient.RawHTTPClient().RawHTTPPost("/logs/transfers", emptyFilter) require.NoError(t, err) var tLogs []*transfers.FilteredTransfer if err := json.Unmarshal(res, &tLogs); err != nil { @@ -174,20 +171,12 @@ func insertBlocks(t *testing.T, db *logdb.LogDB, n int) { } func initTransferServer(t *testing.T, logDb *logdb.LogDB, limit uint64) { - router := mux.NewRouter() - - muxDb := muxdb.NewMem() - stater := state.NewStater(muxDb) - gene := genesis.NewDevnet() - - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) - repo, _ := chain.NewRepository(muxDb, b) + router := mux.NewRouter() + transfers.New(thorChain.Repo(), logDb, limit).Mount(router, "/logs/transfers") - transfers.New(repo, logDb, limit).Mount(router, "/transfers") ts = httptest.NewServer(router) } diff --git a/api/utils/revisions_test.go b/api/utils/revisions_test.go index 76a4bbfdc..12926eaa1 100644 --- a/api/utils/revisions_test.go +++ b/api/utils/revisions_test.go @@ -12,11 +12,8 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/cmd/thor/solo" - "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/state" + "github.com/stretchr/testify/require" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" ) @@ -101,15 +98,8 @@ func TestAllowNext(t *testing.T) { } func TestGetSummary(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) - bft := solo.NewBFTEngine(repo) + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) // Test cases testCases := []struct { @@ -151,7 +141,7 @@ func TestGetSummary(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - summary, err := GetSummary(tc.revision, repo, bft) + summary, err := GetSummary(tc.revision, thorChain.Repo(), thorChain.Engine()) if tc.err != nil { assert.Equal(t, tc.err.Error(), err.Error()) } else { @@ -163,22 +153,17 @@ func TestGetSummary(t *testing.T) { } func TestGetSummaryAndState(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - repo, _ := chain.NewRepository(db, b) - bft := solo.NewBFTEngine(repo) + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) + + b := thorChain.GenesisBlock() - summary, _, err := GetSummaryAndState(&Revision{revBest}, repo, bft, stater) + summary, _, err := GetSummaryAndState(&Revision{revBest}, thorChain.Repo(), thorChain.Engine(), thorChain.Stater()) assert.Nil(t, err) assert.Equal(t, summary.Header.Number(), b.Header().Number()) assert.Equal(t, summary.Header.Timestamp(), b.Header().Timestamp()) - summary, _, err = GetSummaryAndState(&Revision{revNext}, repo, bft, stater) + summary, _, err = GetSummaryAndState(&Revision{revNext}, thorChain.Repo(), thorChain.Engine(), thorChain.Stater()) assert.Nil(t, err) assert.Equal(t, summary.Header.Number(), b.Header().Number()+1) assert.Equal(t, summary.Header.Timestamp(), b.Header().Timestamp()+thor.BlockInterval) diff --git a/cmd/thor/solo/types.go b/cmd/thor/solo/types.go index c431ece0d..b436419a8 100644 --- a/cmd/thor/solo/types.go +++ b/cmd/thor/solo/types.go @@ -6,6 +6,7 @@ package solo import ( + "github.com/vechain/thor/v2/bft" "github.com/vechain/thor/v2/chain" "github.com/vechain/thor/v2/comm" "github.com/vechain/thor/v2/thor" @@ -34,7 +35,7 @@ func (engine *BFTEngine) Justified() (thor.Bytes32, error) { return engine.justified, nil } -func NewBFTEngine(repo *chain.Repository) *BFTEngine { +func NewBFTEngine(repo *chain.Repository) bft.Committer { return &BFTEngine{ finalized: repo.GenesisBlock().Header().ID(), justified: repo.GenesisBlock().Header().ID(), diff --git a/test/eventcontract/event_contract.go b/test/eventcontract/event_contract.go new file mode 100644 index 000000000..cb239c919 --- /dev/null +++ b/test/eventcontract/event_contract.go @@ -0,0 +1,77 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package eventcontract + +const Code = `// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract EventContract { + + // Event that is triggered on deployment + event Deployed(string message); + + // Event that is triggered when triggerEvent method is called + event Triggered(string message); + + // Constructor is executed upon contract deployment + constructor() { + emit Deployed("it's deployed"); + } + + // Function that triggers the Triggered event + function triggerEvent(string memory message) public { + emit Triggered(message); + } +}` + +const ABI = `[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "Triggered", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "triggerEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]` + +const HexBytecode = `608060405234801561001057600080fd5b507f90e3596779ac8b6be4c38e80c57aadc0ee3d1b6c8c20a9633d1c860890855f8860405161003e906100a8565b60405180910390a16100c8565b600082825260208201905092915050565b7f69742773206465706c6f79656400000000000000000000000000000000000000600082015250565b6000610092600d8361004b565b915061009d8261005c565b602082019050919050565b600060208201905081810360008301526100c181610085565b9050919050565b610309806100d76000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063e6c75c6b14610030575b600080fd5b61004a600480360381019061004591906101e0565b61004c565b005b7f2b22cb97612862145333bc8f03d3ae7b4ea194fc038c00d4994dcd794bbf6f558160405161007b91906102b1565b60405180910390a150565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100ed826100a4565b810181811067ffffffffffffffff8211171561010c5761010b6100b5565b5b80604052505050565b600061011f610086565b905061012b82826100e4565b919050565b600067ffffffffffffffff82111561014b5761014a6100b5565b5b610154826100a4565b9050602081019050919050565b82818337600083830152505050565b600061018361017e84610130565b610115565b90508281526020810184848401111561019f5761019e61009f565b5b6101aa848285610161565b509392505050565b600082601f8301126101c7576101c661009a565b5b81356101d7848260208601610170565b91505092915050565b6000602082840312156101f6576101f5610090565b5b600082013567ffffffffffffffff81111561021457610213610095565b5b610220848285016101b2565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610263578082015181840152602081019050610248565b83811115610272576000848401525b50505050565b600061028382610229565b61028d8185610234565b935061029d818560208601610245565b6102a6816100a4565b840191505092915050565b600060208201905081810360008301526102cb8184610278565b90509291505056fea2646970667358221220c8faa509c384033da1201cb3e75b6fe60090e6722ae8c11af2a827446254d80664736f6c634300080a0033` diff --git a/test/testchain/chain.go b/test/testchain/chain.go new file mode 100644 index 000000000..b35687d14 --- /dev/null +++ b/test/testchain/chain.go @@ -0,0 +1,222 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package testchain + +import ( + "errors" + "fmt" + "slices" + "time" + + "github.com/vechain/thor/v2/bft" + "github.com/vechain/thor/v2/block" + "github.com/vechain/thor/v2/chain" + "github.com/vechain/thor/v2/cmd/thor/solo" + "github.com/vechain/thor/v2/genesis" + "github.com/vechain/thor/v2/logdb" + "github.com/vechain/thor/v2/muxdb" + "github.com/vechain/thor/v2/packer" + "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/tx" +) + +// Chain represents the blockchain structure. +// It includes database (db), genesis information (genesis), consensus engine (engine), +// repository for blocks and state (repo), state manager (stater), and the genesis block (genesisBlock). +type Chain struct { + db *muxdb.MuxDB + genesis *genesis.Genesis + engine bft.Committer + repo *chain.Repository + stater *state.Stater + genesisBlock *block.Block + logDB *logdb.LogDB + forkConfig thor.ForkConfig +} + +func New( + db *muxdb.MuxDB, + genesis *genesis.Genesis, + engine bft.Committer, + repo *chain.Repository, + stater *state.Stater, + genesisBlock *block.Block, + logDB *logdb.LogDB, +) *Chain { + return &Chain{ + db: db, + genesis: genesis, + engine: engine, + repo: repo, + stater: stater, + genesisBlock: genesisBlock, + logDB: logDB, + forkConfig: thor.GetForkConfig(genesisBlock.Header().ID()), + } +} + +// NewIntegrationTestChain is a convenience function that creates a Chain for testing. +// It uses an in-memory database, development network genesis, and a solo BFT engine. +func NewIntegrationTestChain() (*Chain, error) { + // Initialize the database + db := muxdb.NewMem() + + // Create the state manager (Stater) with the initialized database. + stater := state.NewStater(db) + + // Initialize the genesis and retrieve the genesis block + gene := genesis.NewDevnet() + geneBlk, _, _, err := gene.Build(stater) + if err != nil { + return nil, err + } + + // Create the repository which manages chain data, using the database and genesis block. + repo, err := chain.NewRepository(db, geneBlk) + if err != nil { + return nil, err + } + + // Create an inMemory logdb + logDb, err := logdb.NewMem() + if err != nil { + return nil, err + } + + return New( + db, + gene, + solo.NewBFTEngine(repo), + repo, + stater, + geneBlk, + logDb, + ), nil +} + +// Repo returns the blockchain's repository, which stores blocks and other data. +func (c *Chain) Repo() *chain.Repository { + return c.repo +} + +// Stater returns the current state manager of the chain, which is responsible for managing the state of accounts and other elements. +func (c *Chain) Stater() *state.Stater { + return c.stater +} + +// Engine returns the consensus engine responsible for the blockchain's consensus mechanism. +func (c *Chain) Engine() bft.Committer { + return c.engine +} + +// GenesisBlock returns the genesis block of the chain, which is the first block in the blockchain. +func (c *Chain) GenesisBlock() *block.Block { + return c.genesisBlock +} + +// MintTransactions creates a block with the provided transactions and adds it to the blockchain. +// It wraps the transactions with receipts and passes them to MintTransactionsWithReceiptFunc. +func (c *Chain) MintTransactions(account genesis.DevAccount, transactions ...*tx.Transaction) error { + return c.MintBlock(account, transactions...) +} + +// MintBlock creates and finalizes a new block with the given transactions. +// It schedules a new block, adopts transactions, packs them into a block, and commits it to the chain. +func (c *Chain) MintBlock(account genesis.DevAccount, transactions ...*tx.Transaction) error { + // Create a new block packer with the current chain state and account information. + blkPacker := packer.New(c.Repo(), c.Stater(), account.Address, &genesis.DevAccounts()[0].Address, c.forkConfig) + + // Create a new block + blkFlow, err := blkPacker.Mock( + c.Repo().BestBlockSummary(), + c.Repo().BestBlockSummary().Header.Timestamp()+thor.BlockInterval, + c.Repo().BestBlockSummary().Header.GasLimit(), + ) + if err != nil { + return fmt.Errorf("unable to mock a new block: %w", err) + } + + // Adopt the provided transactions into the block. + for _, trx := range transactions { + if err = blkFlow.Adopt(trx); err != nil { + return fmt.Errorf("unable to adopt tx into block: %w", err) + } + } + + // Pack the adopted transactions into a block. + newBlk, stage, receipts, err := blkFlow.Pack(account.PrivateKey, 0, false) + if err != nil { + return fmt.Errorf("unable to pack tx: %w", err) + } + + // Commit the new block to the chain's state. + if _, err := stage.Commit(); err != nil { + return fmt.Errorf("unable to commit tx: %w", err) + } + + // Add the block to the repository. + if err := c.Repo().AddBlock(newBlk, receipts, 0); err != nil { + return fmt.Errorf("unable to add tx to repo: %w", err) + } + + // Set the new block as the best (latest) block in the repository. + if err := c.Repo().SetBestBlockID(newBlk.Header().ID()); err != nil { + return fmt.Errorf("unable to set best block: %w", err) + } + + return nil +} + +// GetAllBlocks retrieves all blocks from the blockchain, starting from the best block and moving backward to the genesis block. +// It limits the retrieval time to 5 seconds to avoid excessive delays. +func (c *Chain) GetAllBlocks() ([]*block.Block, error) { + bestBlkSummary := c.Repo().BestBlockSummary() + var blks []*block.Block + currBlockID := bestBlkSummary.Header.ID() + startTime := time.Now() + + // Traverse the chain backwards until the genesis block is reached or timeout occurs. + for { + blk, err := c.repo.GetBlock(currBlockID) + if err != nil { + return nil, err + } + blks = append(blks, blk) + + // Stop when the genesis block is reached and reverse the slice to have genesis at position 0. + if blk.Header().Number() == c.genesisBlock.Header().Number() { + slices.Reverse(blks) // make sure genesis is at position 0 + return blks, err + } + currBlockID = blk.Header().ParentID() + + // Check if the retrieval process is taking too long (more than 5 seconds). + if time.Since(startTime) > 5*time.Second { + return nil, errors.New("taking more than 5 seconds to retrieve all blocks") + } + } +} + +// BestBlock returns the current best (latest) block in the chain. +func (c *Chain) BestBlock() (*block.Block, error) { + return c.Repo().GetBlock(c.Repo().BestBlockSummary().Header.ID()) +} + +// GetForkConfig returns the current fork configuration based on the ID of the genesis block. +func (c *Chain) GetForkConfig() thor.ForkConfig { + return c.forkConfig +} + +// Database returns the current database. +func (c *Chain) Database() *muxdb.MuxDB { + return c.db +} + +// LogDB returns the current logdb. +func (c *Chain) LogDB() *logdb.LogDB { + return c.logDB +} diff --git a/thorclient/api_test.go b/thorclient/api_test.go new file mode 100644 index 000000000..e6a0e43be --- /dev/null +++ b/thorclient/api_test.go @@ -0,0 +1,381 @@ +// Copyright (c) 2024 The VeChainThor developers + +// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying +// file LICENSE or + +package thorclient + +import ( + "math/big" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/gorilla/mux" + "github.com/stretchr/testify/require" + "github.com/vechain/thor/v2/api/accounts" + "github.com/vechain/thor/v2/api/blocks" + "github.com/vechain/thor/v2/api/debug" + "github.com/vechain/thor/v2/api/events" + "github.com/vechain/thor/v2/api/node" + "github.com/vechain/thor/v2/comm" + "github.com/vechain/thor/v2/genesis" + "github.com/vechain/thor/v2/logdb" + "github.com/vechain/thor/v2/test/datagen" + "github.com/vechain/thor/v2/test/testchain" + "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/tx" + "github.com/vechain/thor/v2/txpool" + + // Force-load the tracer native engines to trigger registration + _ "github.com/vechain/thor/v2/tracers/js" + _ "github.com/vechain/thor/v2/tracers/logger" +) + +const ( + gasLimit = 30_000_000 + logDBLimit = 1_000 +) + +func initAPIServer(t *testing.T) (*testchain.Chain, *httptest.Server) { + thorChain, err := testchain.NewIntegrationTestChain() + require.NoError(t, err) + + // mint some transactions to be used in the endpoints + mintTransactions(t, thorChain) + + router := mux.NewRouter() + + accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, thorChain.Engine()). + Mount(router, "/accounts") + + blocks.New(thorChain.Repo(), thorChain.Engine()).Mount(router, "/blocks") + + debug.New(thorChain.Repo(), thorChain.Stater(), thorChain.GetForkConfig(), gasLimit, true, thorChain.Engine(), []string{"all"}, false). + Mount(router, "/debug") + + logDb, err := logdb.NewMem() + require.NoError(t, err) + events.New(thorChain.Repo(), logDb, logDBLimit).Mount(router, "/logs/event") + + communicator := comm.New( + thorChain.Repo(), + txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 10000, + LimitPerAccount: 16, + MaxLifetime: 10 * time.Minute, + }), + ) + node.New(communicator).Mount(router, "/node") + + return thorChain, httptest.NewServer(router) +} + +func mintTransactions(t *testing.T, thorChain *testchain.Chain) { + toAddr := datagen.RandAddress() + + noClausesTx := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + Expiration(10). + Gas(21000). + Build() + sig, err := crypto.Sign(noClausesTx.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) + if err != nil { + t.Fatal(err) + } + noClausesTx = noClausesTx.WithSignature(sig) + + cla := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) + cla2 := tx.NewClause(&toAddr).WithValue(big.NewInt(10000)) + transaction := new(tx.Builder). + ChainTag(thorChain.Repo().ChainTag()). + GasPriceCoef(1). + Expiration(10). + Gas(37000). + Nonce(1). + Clause(cla). + Clause(cla2). + BlockRef(tx.NewBlockRef(0)). + Build() + + sig, err = crypto.Sign(transaction.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) + if err != nil { + t.Fatal(err) + } + transaction = transaction.WithSignature(sig) + + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction, noClausesTx)) +} + +func TestAPIs(t *testing.T) { + thorChain, ts := initAPIServer(t) + defer ts.Close() + + for name, tt := range map[string]func(*testing.T, *testchain.Chain, *httptest.Server){ + "testAccountEndpoint": testAccountEndpoint, + "testBlocksEndpoint": testBlocksEndpoint, + "testDebugEndpoint": testDebugEndpoint, + "testEventsEndpoint": testEventsEndpoint, + "testNodeEndpoint": testNodeEndpoint, + } { + t.Run(name, func(t *testing.T) { + tt(t, thorChain, ts) + }) + } +} + +func testAccountEndpoint(t *testing.T, _ *testchain.Chain, ts *httptest.Server) { + // Example storage key + storageKey := "0x0000000000000000000000000000000000000000000000000000000000000000" + + // Example addresses + address1 := "0x0123456789abcdef0123456789abcdef01234567" + address2 := "0xabcdef0123456789abcdef0123456789abcdef01" + + // 1. Test GET /accounts/{address} + t.Run("GetAccount", func(t *testing.T) { + resp, err := ts.Client().Get(ts.URL + "/accounts/" + address1) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 2. Test GET /accounts/{address}/code + t.Run("GetCode", func(t *testing.T) { + resp, err := ts.Client().Get(ts.URL + "/accounts/" + address1 + "/code") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 3. Test GET /accounts/{address}/storage/{key} + t.Run("GetStorage", func(t *testing.T) { + resp, err := ts.Client().Get(ts.URL + "/accounts/" + address1 + "/storage/" + storageKey) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 4. Test POST /accounts/* + t.Run("InspectClauses", func(t *testing.T) { + // Define the payload for the batch call + payload := `{ + "clauses": [ + { + "to": "` + address1 + `", + "value": "0x0", + "data": "0x" + }, + { + "to": "` + address2 + `", + "value": "0x1", + "data": "0x" + } + ], + "gas": 1000000, + "gasPrice": "0x0", + "caller": "` + address1 + `" + }` + req, err := http.NewRequest("POST", ts.URL+"/accounts/*", strings.NewReader(payload)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + // Simulate sending request with revision query parameter + query := req.URL.Query() + query.Add("revision", "best") // Add any revision parameter as expected + req.URL.RawQuery = query.Encode() + + // Perform the request + resp, err := ts.Client().Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + }) +} + +func testBlocksEndpoint(t *testing.T, _ *testchain.Chain, ts *httptest.Server) { + // Example revision (this could be a block number or block ID) + revision := "best" // You can adjust this to a real block number or ID for integration testing + + // 1. Test GET /blocks/{revision} + t.Run("GetBlock", func(t *testing.T) { + // Send request to get block information by revision + resp, err := ts.Client().Get(ts.URL + "/blocks/" + revision) + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 2. Test GET /blocks/{revision}?expanded=true + t.Run("GetBlockExpanded", func(t *testing.T) { + // Send request to get expanded block information (includes transactions and receipts) + resp, err := ts.Client().Get(ts.URL + "/blocks/" + revision + "?expanded=true") + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 3. Test GET /blocks/{revision}?expanded=invalid (should return bad request) + t.Run("GetBlockInvalidExpanded", func(t *testing.T) { + // Send request with an invalid 'expanded' parameter + resp, err := ts.Client().Get(ts.URL + "/blocks/" + revision + "?expanded=invalid") + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 400 Bad Request + require.Equal(t, 400, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) +} + +func testDebugEndpoint(t *testing.T, thorChain *testchain.Chain, ts *httptest.Server) { + // Example block ID, transaction index, and clause index + bestBlock, _ := thorChain.BestBlock() + blockID := bestBlock.Header().ID().String() + txIndex := uint64(0) + clauseIndex := uint32(0) + + // Example contract address + contractAddress := "0xabcdef0123456789abcdef0123456789abcdef01" + + // 1. Test POST /debug/tracers (Trace an existing clause) + t.Run("TraceClause", func(t *testing.T) { + payload := `{ + "name": "structLoggerTracer", + "target": "` + blockID + `/` + strconv.FormatUint(txIndex, 10) + `/` + strconv.FormatUint(uint64(clauseIndex), 10) + `" + }` + + req, err := http.NewRequest("POST", ts.URL+"/debug/tracers", strings.NewReader(payload)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + // Perform the request + resp, err := ts.Client().Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 2. Test POST /debug/tracers/call (Trace a contract call) + t.Run("TraceCall", func(t *testing.T) { + payload := `{ + "name": "structLoggerTracer", + "to": "` + contractAddress + `", + "value": "0x0", + "data": "0x", + "gas": 1000000, + "gasPrice": "0x0", + "caller": "` + contractAddress + `" + }` + + req, err := http.NewRequest("POST", ts.URL+"/debug/tracers/call", strings.NewReader(payload)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + // Perform the request + resp, err := ts.Client().Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) + + // 3. Test POST /debug/storage-range (Debug storage for a contract) + t.Run("DebugStorage", func(t *testing.T) { + payload := `{ + "address": "` + contractAddress + `", + "target": "` + blockID + `/` + strconv.FormatUint(txIndex, 10) + `/` + strconv.FormatUint(uint64(clauseIndex), 10) + `", + "keyStart": "", + "maxResult": 100 + }` + + req, err := http.NewRequest("POST", ts.URL+"/debug/storage-range", strings.NewReader(payload)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + // Perform the request + resp, err := ts.Client().Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + }) +} + +func testEventsEndpoint(t *testing.T, _ *testchain.Chain, ts *httptest.Server) { + // Example address and topic for filtering events + address := "0x0123456789abcdef0123456789abcdef01234567" + topic := thor.BytesToBytes32([]byte("topic")).String() + + // 1. Test POST /events (Filter events) + t.Run("FilterEvents", func(t *testing.T) { + // Define the payload for filtering events + payload := `{ + "criteriaSet": [ + { + "address": "` + address + `", + "topic0": "` + topic + `" + } + ], + "options": { + "limit": 10, + "offset": 0 + } + }` + + req, err := http.NewRequest("POST", ts.URL+"/logs/event", strings.NewReader(payload)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + // Perform the request + resp, err := ts.Client().Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + // body, err := ioutil.ReadAll(resp.Body) + // require.NoError(t, err) + // fmt.Println(string(body)) + }) +} + +func testNodeEndpoint(t *testing.T, _ *testchain.Chain, ts *httptest.Server) { + // 1. Test GET /node/network/peers + t.Run("GetPeersStats", func(t *testing.T) { + // Send request to get peers statistics + resp, err := ts.Client().Get(ts.URL + "/node/network/peers") + require.NoError(t, err) + defer resp.Body.Close() + + // Ensure the response code is 200 OK + require.Equal(t, 200, resp.StatusCode) + // Optionally, you can unmarshal and validate the response body here + // body, err := ioutil.ReadAll(resp.Body) + // require.NoError(t, err) + // fmt.Println(string(body)) + }) +}