From c41489d7f747db52ea6124224d70fcb2cc17c1bb Mon Sep 17 00:00:00 2001 From: otherview Date: Tue, 10 Sep 2024 10:54:14 +0100 Subject: [PATCH 01/11] Refactor thor node --- api/accounts/accounts.go | 8 +- api/accounts/accounts_test.go | 76 +++------- api/api.go | 5 +- api/blocks/blocks.go | 8 +- api/blocks/blocks_test.go | 65 +++------ api/debug/debug.go | 14 +- api/debug/debug_test.go | 71 +++------- api/events/events.go | 8 +- api/events/events_test.go | 43 +++--- api/node/node.go | 8 +- api/node/node_test.go | 31 ++-- api/subscriptions/beat2_reader_test.go | 25 ++-- api/subscriptions/beat_reader_test.go | 24 ++-- api/subscriptions/block_reader_test.go | 79 ++++------- api/subscriptions/event_reader_test.go | 15 +- api/subscriptions/pending_tx_test.go | 17 ++- api/subscriptions/subscriptions.go | 11 +- api/subscriptions/subscriptions_test.go | 163 ++++++++++++++++++++-- api/subscriptions/transfer_reader_test.go | 32 +++-- api/transactions/transactions.go | 8 +- api/transactions/transactions_test.go | 78 ++++------- api/transfers/transfers.go | 8 +- api/transfers/transfers_test.go | 43 +++--- api/utils/http.go | 7 + cmd/thor/solo/types.go | 3 +- node/builder.go | 74 ++++++++++ node/chain.go | 101 ++++++++++++++ node/chain_builder.go | 63 +++++++++ node/node.go | 76 ++++++++++ 29 files changed, 775 insertions(+), 389 deletions(-) create mode 100644 node/builder.go create mode 100644 node/chain.go create mode 100644 node/chain_builder.go create mode 100644 node/node.go diff --git a/api/accounts/accounts.go b/api/accounts/accounts.go index a6d1df3ee..c79a5cade 100644 --- a/api/accounts/accounts.go +++ b/api/accounts/accounts.go @@ -26,6 +26,8 @@ import ( "github.com/vechain/thor/v2/xenv" ) +const MountPath = "/accounts" + type Accounts struct { repo *chain.Repository stater *state.Stater @@ -40,7 +42,7 @@ func New( callGasLimit uint64, forkConfig thor.ForkConfig, bft bft.Committer, -) *Accounts { +) utils.APIServer { return &Accounts{ repo, stater, @@ -382,3 +384,7 @@ func (a *Accounts) Mount(root *mux.Router, pathPrefix string) { Name("accounts_call_contract_address"). HandlerFunc(utils.WrapHandlerFunc(a.handleCallContract)) } + +func (a *Accounts) MountDefaultPath(root *mux.Router) { + a.Mount(root, MountPath) +} diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index 16f08e252..e23fd88d4 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -14,23 +14,19 @@ import ( "net/http" "net/http/httptest" "testing" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "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/node" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) @@ -89,7 +85,7 @@ var addr = thor.BytesToAddress([]byte("to")) var value = big.NewInt(10000) var storageKey = thor.Bytes32{} var storageValue = byte(1) -var gasLimit uint64 +var gasLimit = math.MaxUint32 var genesisBlock *block.Block var contractAddr thor.Address @@ -101,8 +97,6 @@ var runtimeBytecode = common.Hex2Bytes("6080604052600436106049576000357c01000000 var invalidAddr = "abc" //invlaid address var invalidBytes32 = "0x000000000000000000000000000000000000000000000000000000000000000g" //invlaid bytes32 var invalidNumberRevision = "4294967296" //invalid block number - -var acc *accounts.Accounts var ts *httptest.Server func TestAccount(t *testing.T) { @@ -250,22 +244,14 @@ func getStorageWithNonExisitingRevision(t *testing.T) { } func initAccountServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() + thorChain, err := node.NewIntegrationTestChain() + require.NoError(t, err) - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - genesisBlock = b - repo, _ := chain.NewRepository(db, b) claTransfer := tx.NewClause(&addr).WithValue(value) claDeploy := tx.NewClause(nil).WithData(bytecode) - transaction := buildTxWithClauses(t, repo.ChainTag(), claTransfer, claDeploy) - contractAddr = thor.CreateContractAddress(transaction.ID(), 1, 0) - packTx(repo, stater, transaction, t) + transaction := buildTxWithClauses(t, thorChain.Repo().ChainTag(), claTransfer, claDeploy) + contractAddr = thor.CreateContractAddress(transaction.ID(), 1, 0) method := "set" abi, _ := ABI.New([]byte(abiJSON)) m, _ := abi.MethodByName(method) @@ -274,14 +260,21 @@ func initAccountServer(t *testing.T) { t.Fatal(err) } claCall := tx.NewClause(&contractAddr).WithData(input) - transactionCall := buildTxWithClauses(t, repo.ChainTag(), claCall) - packTx(repo, stater, transactionCall, t) - - router := mux.NewRouter() - gasLimit = math.MaxUint32 - acc = accounts.New(repo, stater, gasLimit, thor.NoFork, solo.NewBFTEngine(repo)) - acc.Mount(router, "/accounts") - ts = httptest.NewServer(router) + transactionCall := buildTxWithClauses(t, thorChain.Repo().ChainTag(), claCall) + + require.NoError(t, thorChain.MintTransactions(transaction, transactionCall)) + + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs( + accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, solo.NewBFTEngine(thorChain.Repo())), + ). + Build() + require.NoError(t, err) + + genesisBlock = thorChain.GenesisBlock() + + ts = httptest.NewServer(thorNode.Router()) } func buildTxWithClauses(t *testing.T, chaiTag byte, clauses ...*tx.Clause) *tx.Transaction { @@ -301,31 +294,6 @@ func buildTxWithClauses(t *testing.T, chaiTag byte, clauses ...*tx.Clause) *tx.T return transaction.WithSignature(sig) } -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/api.go b/api/api.go index 38b412a97..d13c94056 100644 --- a/api/api.go +++ b/api/api.go @@ -72,7 +72,7 @@ func New( }) accounts.New(repo, stater, callGasLimit, forkConfig, bft). - Mount(router, "/accounts") + Mount(router, accounts.MountPath) if !skipLogs { events.New(repo, logDB, logsLimit). @@ -114,5 +114,6 @@ func New( handler = RequestLoggerHandler(handler, logger) } - return handler.ServeHTTP, subs.Close // subscriptions handles hijacked conns, which need to be closed + castedSub := subs.(*subscriptions.Subscriptions) + return handler.ServeHTTP, castedSub.Close // subscriptions handles hijacked conns, which need to be closed } diff --git a/api/blocks/blocks.go b/api/blocks/blocks.go index bddb3ac12..634d47d9c 100644 --- a/api/blocks/blocks.go +++ b/api/blocks/blocks.go @@ -17,12 +17,14 @@ import ( "github.com/vechain/thor/v2/thor" ) +const MountPath = "/blocks" + type Blocks struct { repo *chain.Repository bft bft.Committer } -func New(repo *chain.Repository, bft bft.Committer) *Blocks { +func New(repo *chain.Repository, bft bft.Committer) utils.APIServer { return &Blocks{ repo, bft, @@ -98,3 +100,7 @@ func (b *Blocks) Mount(root *mux.Router, pathPrefix string) { Name("blocks_get_block"). HandlerFunc(utils.WrapHandlerFunc(b.handleGetBlock)) } + +func (b *Blocks) MountDefaultPath(router *mux.Router) { + b.Mount(router, MountPath) +} diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index d8ef60771..7460083a3 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -15,20 +15,15 @@ import ( "strconv" "strings" "testing" - "time" "github.com/ethereum/go-ethereum/crypto" - "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/node" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) @@ -158,21 +153,13 @@ func testGetBlockWithRevisionNumberTooHigh(t *testing.T) { } func initBlockServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() + thorChain, err := node.NewIntegrationTestChain() + require.NoError(t, err) - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - genesisBlock = b - - repo, _ := chain.NewRepository(db, b) addr := thor.BytesToAddress([]byte("to")) cla := tx.NewClause(&addr).WithValue(big.NewInt(10000)) tx := new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(thorChain.Repo().ChainTag()). GasPriceCoef(1). Expiration(10). Gas(21000). @@ -186,34 +173,22 @@ func initBlockServer(t *testing.T) { t.Fatal(err) } tx = tx.WithSignature(sig) - 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(tx) - 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) - } - router := mux.NewRouter() - bftEngine := solo.NewBFTEngine(repo) - blocks.New(repo, bftEngine).Mount(router, "/blocks") - ts = httptest.NewServer(router) - blk = block + require.NoError(t, thorChain.MintTransactions(tx)) + + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs( + blocks.New(thorChain.Repo(), solo.NewBFTEngine(thorChain.Repo())), + ). + Build() + require.NoError(t, err) + + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + + genesisBlock = allBlocks[0] + blk = allBlocks[1] + ts = httptest.NewServer(thorNode.Router()) } func checkCollapsedBlock(t *testing.T, expBl *block.Block, actBl *blocks.JSONCollapsedBlock) { diff --git a/api/debug/debug.go b/api/debug/debug.go index 81543b109..357bac416 100644 --- a/api/debug/debug.go +++ b/api/debug/debug.go @@ -35,7 +35,11 @@ import ( "github.com/vechain/thor/v2/xenv" ) -const defaultMaxStorageResult = 1000 +const ( + defaultMaxStorageResult = 1000 + + MountPath = "/debug" +) type Debug struct { repo *chain.Repository @@ -56,8 +60,8 @@ func New( allowCustomTracer bool, bft bft.Committer, allowedTracers []string, - soloMode bool) *Debug { - + soloMode bool, +) utils.APIServer { allowedMap := make(map[string]struct{}) for _, t := range allowedTracers { allowedMap[t] = struct{}{} @@ -477,3 +481,7 @@ func (d *Debug) Mount(root *mux.Router, pathPrefix string) { Name("debug_trace_storage"). HandlerFunc(utils.WrapHandlerFunc(d.handleDebugStorage)) } + +func (d *Debug) MountDefaultPath(router *mux.Router) { + d.Mount(router, MountPath) +} diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 60a2821a0..077e604db 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -16,20 +16,18 @@ import ( "net/http/httptest" "strings" "testing" - "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/gorilla/mux" "github.com/stretchr/testify/assert" + "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/node" "github.com/vechain/thor/v2/state" "github.com/vechain/thor/v2/test/datagen" "github.com/vechain/thor/v2/thor" @@ -513,22 +511,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 := node.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() @@ -541,7 +532,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). @@ -556,40 +547,24 @@ func initDebugServer(t *testing.T) { t.Fatal(err) } transaction = transaction.WithSignature(sig) - 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) - } - forkConfig := thor.GetForkConfig(b.Header().ID()) - router := mux.NewRouter() - debug = New(repo, stater, forkConfig, 21000, true, solo.NewBFTEngine(repo), []string{"all"}, false) - debug.Mount(router, "/debug") - ts = httptest.NewServer(router) + require.NoError(t, thorChain.MintTransactions(transaction, noClausesTx)) + + debug = New(thorChain.Repo(), thorChain.Stater(), thor.NoFork, 21000, true, solo.NewBFTEngine(thorChain.Repo()), []string{"all"}, false).(*Debug) + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs( + debug, + ). + Build() + require.NoError(t, err) + + require.NoError(t, thorNode.GenerateNewBlock()) + + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + blk = allBlocks[1] + ts = httptest.NewServer(thorNode.Router()) } func httpPostAndCheckResponseStatus(t *testing.T, url string, obj interface{}, responseStatusCode int) string { diff --git a/api/events/events.go b/api/events/events.go index 40dff7b09..8e78bdf24 100644 --- a/api/events/events.go +++ b/api/events/events.go @@ -17,13 +17,15 @@ import ( "github.com/vechain/thor/v2/logdb" ) +const MountPath = "/logs/event" + type Events struct { repo *chain.Repository db *logdb.LogDB limit uint64 } -func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) *Events { +func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) utils.APIServer { return &Events{ repo, db, @@ -87,3 +89,7 @@ func (e *Events) Mount(root *mux.Router, pathPrefix string) { Name("logs_filter_event"). HandlerFunc(utils.WrapHandlerFunc(e.handleFilter)) } + +func (e *Events) MountDefaultPath(router *mux.Router) { + e.Mount(router, MountPath) +} diff --git a/api/events/events_test.go b/api/events/events_test.go index c8494019f..ae2400f4b 100644 --- a/api/events/events_test.go +++ b/api/events/events_test.go @@ -14,15 +14,12 @@ import ( "strings" "testing" - "github.com/gorilla/mux" "github.com/stretchr/testify/assert" + "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/node" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) @@ -72,18 +69,18 @@ func TestOption(t *testing.T) { Order: logdb.DESC, } - res, statusCode := httpPost(t, ts.URL+"/events", filter) + res, statusCode := httpPost(t, ts.URL+"/logs/event", filter) 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 = httpPost(t, ts.URL+"/events", filter) + _, statusCode = httpPost(t, ts.URL+"/logs/event", filter) 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 = httpPost(t, ts.URL+"/events", filter) + res, statusCode = httpPost(t, ts.URL+"/logs/event", filter) assert.Equal(t, http.StatusOK, statusCode) var tLogs []*events.FilteredEvent if err := json.Unmarshal(res, &tLogs); err != nil { @@ -94,7 +91,7 @@ func TestOption(t *testing.T) { // when the filtered events exceed the limit, should return the forbidden insertBlocks(t, db, 6) - res, statusCode = httpPost(t, ts.URL+"/events", filter) + res, statusCode = httpPost(t, ts.URL+"/logs/event", filter) 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")) } @@ -103,7 +100,7 @@ func TestOption(t *testing.T) { func testEventsBadRequest(t *testing.T) { badBody := []byte{0x00, 0x01, 0x02} - res, err := http.Post(ts.URL+"/events", "application/x-www-form-urlencoded", bytes.NewReader(badBody)) + res, err := http.Post(ts.URL+"/logs/event", "application/x-www-form-urlencoded", bytes.NewReader(badBody)) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, res.StatusCode) @@ -117,7 +114,7 @@ func testEventWithEmptyDb(t *testing.T) { Order: logdb.DESC, } - res, statusCode := httpPost(t, ts.URL+"/events", emptyFilter) + res, statusCode := httpPost(t, ts.URL+"/logs/event", emptyFilter) var tLogs []*events.FilteredEvent if err := json.Unmarshal(res, &tLogs); err != nil { t.Fatal(err) @@ -135,7 +132,7 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { Order: logdb.DESC, } - res, statusCode := httpPost(t, ts.URL+"/events", emptyFilter) + res, statusCode := httpPost(t, ts.URL+"/logs/event", emptyFilter) var tLogs []*events.FilteredEvent if err := json.Unmarshal(res, &tLogs); err != nil { t.Fatal(err) @@ -161,7 +158,7 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { }}, } - res, statusCode = httpPost(t, ts.URL+"/events", matchingFilter) + res, statusCode = httpPost(t, ts.URL+"/logs/event", matchingFilter) if err := json.Unmarshal(res, &tLogs); err != nil { t.Fatal(err) } @@ -175,21 +172,15 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { // Init functions func initEventServer(t *testing.T, logDb *logdb.LogDB, limit uint64) { - router := mux.NewRouter() + thorChain, err := node.NewIntegrationTestChain() + require.NoError(t, err) - muxDb := muxdb.NewMem() - stater := state.NewStater(muxDb) - gene := genesis.NewDevnet() + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs(events.New(thorChain.Repo(), logDb, limit)).Build() + require.NoError(t, err) - b, _, _, err := gene.Build(stater) - if err != nil { - t.Fatal(err) - } - - repo, _ := chain.NewRepository(muxDb, b) - - events.New(repo, logDb, limit).Mount(router, "/events") - ts = httptest.NewServer(router) + ts = httptest.NewServer(thorNode.Router()) } func createDb(t *testing.T) *logdb.LogDB { diff --git a/api/node/node.go b/api/node/node.go index fa72fbedc..423a13c0d 100644 --- a/api/node/node.go +++ b/api/node/node.go @@ -12,11 +12,13 @@ import ( "github.com/vechain/thor/v2/api/utils" ) +const MountPath = "/node" + type Node struct { nw Network } -func New(nw Network) *Node { +func New(nw Network) utils.APIServer { return &Node{ nw, } @@ -38,3 +40,7 @@ func (n *Node) Mount(root *mux.Router, pathPrefix string) { Name("node_get_peers"). HandlerFunc(utils.WrapHandlerFunc(n.handleNetwork)) } + +func (n *Node) MountDefaultPath(router *mux.Router) { + n.Mount(router, MountPath) +} diff --git a/api/node/node_test.go b/api/node/node_test.go index 90857d0d0..8b38de1eb 100644 --- a/api/node/node_test.go +++ b/api/node/node_test.go @@ -12,15 +12,13 @@ import ( "testing" "time" - "github.com/gorilla/mux" "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/txpool" + + thornode "github.com/vechain/thor/v2/node" ) var ts *httptest.Server @@ -36,23 +34,22 @@ func TestNode(t *testing.T) { } func initCommServer(t *testing.T) { - db := muxdb.NewMem() - stater := state.NewStater(db) - gene := genesis.NewDevnet() + thorChain, err := thornode.NewIntegrationTestChain() + require.NoError(t, err) - 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{ + comm := 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") - ts = httptest.NewServer(router) + + thorNode, err := new(thornode.Builder). + WithChain(thorChain). + WithAPIs(node.New(comm)). + Build() + require.NoError(t, err) + + ts = httptest.NewServer(thorNode.Router()) } func httpGet(t *testing.T, url string) []byte { diff --git a/api/subscriptions/beat2_reader_test.go b/api/subscriptions/beat2_reader_test.go index dbeecbd7f..71d39658a 100644 --- a/api/subscriptions/beat2_reader_test.go +++ b/api/subscriptions/beat2_reader_test.go @@ -8,18 +8,23 @@ package subscriptions import ( "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" "github.com/vechain/thor/v2/thor" ) func TestBeat2Reader_Read(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] // Act - beatReader := newBeat2Reader(repo, genesisBlk.Header().ID()) + beatReader := newBeat2Reader(thorNode.Chain().Repo(), genesisBlk.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -38,11 +43,13 @@ func TestBeat2Reader_Read(t *testing.T) { func TestBeat2Reader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - newBlock := generatedBlocks[1] + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + newBlock := allBlocks[1] // Act - beatReader := newBeat2Reader(repo, newBlock.Header().ID()) + beatReader := newBeat2Reader(thorNode.Chain().Repo(), newBlock.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -53,10 +60,10 @@ func TestBeat2Reader_Read_NoNewBlocksToRead(t *testing.T) { func TestBeat2Reader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - repo, _, _ := initChain(t) + thorNode := initChain(t) // Act - beatReader := newBeat2Reader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + beatReader := newBeat2Reader(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err := beatReader.Read() // Assert diff --git a/api/subscriptions/beat_reader_test.go b/api/subscriptions/beat_reader_test.go index 6e6974af9..edab573eb 100644 --- a/api/subscriptions/beat_reader_test.go +++ b/api/subscriptions/beat_reader_test.go @@ -8,18 +8,22 @@ package subscriptions import ( "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" "github.com/vechain/thor/v2/thor" ) func TestBeatReader_Read(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - genesisBlk := generatedBlocks[0] - newBlock := generatedBlocks[1] + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] // Act - beatReader := newBeatReader(repo, genesisBlk.Header().ID()) + beatReader := newBeatReader(thorNode.Chain().Repo(), genesisBlk.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -38,11 +42,13 @@ func TestBeatReader_Read(t *testing.T) { func TestBeatReader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - repo, generatedBlocks, _ := initChain(t) - newBlock := generatedBlocks[1] + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + newBlock := allBlocks[1] // Act - beatReader := newBeatReader(repo, newBlock.Header().ID()) + beatReader := newBeatReader(thorNode.Chain().Repo(), newBlock.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -53,10 +59,10 @@ func TestBeatReader_Read_NoNewBlocksToRead(t *testing.T) { func TestBeatReader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - repo, _, _ := initChain(t) + thorNode := initChain(t) // Act - beatReader := newBeatReader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + beatReader := newBeatReader(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err := beatReader.Read() // Assert diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index f6db221e5..6c707457c 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -8,28 +8,26 @@ package subscriptions import ( "math/big" "testing" - "time" "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/node" "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 + thorNode := initChain(t) + allBlocks, err := thorNode.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(thorNode.Chain().Repo(), genesisBlk.Header().ID()) res, ok, err := br.Read() assert.NoError(t, err) @@ -42,7 +40,7 @@ func TestBlockReader_Read(t *testing.T) { } // Test case 2: There is no new block - br = newBlockReader(repo, newBlock.Header().ID()) + br = newBlockReader(thorNode.Chain().Repo(), newBlock.Header().ID()) res, ok, err = br.Read() assert.NoError(t, err) @@ -50,7 +48,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(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err = br.Read() assert.Error(t, err) @@ -58,27 +56,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) *node.Node { + thorChain, err := node.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). @@ -92,31 +77,15 @@ func initChain(t *testing.T) (*chain.Repository, []*block.Block, *txpool.TxPool) t.Fatal(err) } tr = tr.WithSignature(sig) - 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 + + require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(&node.TxAndRcpt{Transaction: tr, ReceiptFunc: insertMockOutputEvent})) + + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs(). + Build() + require.NoError(t, err) + return thorNode } // This is a helper function to forcly insert an event into the output receipts diff --git a/api/subscriptions/event_reader_test.go b/api/subscriptions/event_reader_test.go index b1fe280ba..d069b2cc1 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 + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] er := &eventReader{ - repo: repo, + repo: thorNode.Chain().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(thorNode.Chain().Repo(), genesisBlk.Header().ID(), &EventFilter{}) events, ok, err = er.Read() diff --git a/api/subscriptions/pending_tx_test.go b/api/subscriptions/pending_tx_test.go index 629b96e0c..0db3b5f20 100644 --- a/api/subscriptions/pending_tx_test.go +++ b/api/subscriptions/pending_tx_test.go @@ -24,7 +24,14 @@ import ( ) func TestPendingTx_Subscribe(t *testing.T) { - _, _, txPool := initChain(t) + // Arrange + thorNode := initChain(t) + txPool := txpool.New(thorNode.Chain().Repo(), thorNode.Chain().Stater(), txpool.Options{ + Limit: 100, + LimitPerAccount: 16, + MaxLifetime: time.Hour, + }) + p := newPendingTx(txPool) // When initialized, there should be no listeners @@ -37,7 +44,13 @@ func TestPendingTx_Subscribe(t *testing.T) { } func TestPendingTx_Unsubscribe(t *testing.T) { - _, _, txPool := initChain(t) + // Arrange + thorNode := initChain(t) + txPool := txpool.New(thorNode.Chain().Repo(), thorNode.Chain().Stater(), txpool.Options{ + Limit: 100, + LimitPerAccount: 16, + MaxLifetime: time.Hour, + }) p := newPendingTx(txPool) ch := make(chan *tx.Transaction) diff --git a/api/subscriptions/subscriptions.go b/api/subscriptions/subscriptions.go index 0df6cd95b..2e5d9da70 100644 --- a/api/subscriptions/subscriptions.go +++ b/api/subscriptions/subscriptions.go @@ -22,7 +22,10 @@ import ( "github.com/vechain/thor/v2/txpool" ) -const txQueueSize = 20 +const ( + txQueueSize = 20 + MountPath = "/subscriptions" +) type Subscriptions struct { backtraceLimit uint32 @@ -33,6 +36,10 @@ type Subscriptions struct { wg sync.WaitGroup } +func (s *Subscriptions) MountDefaultPath(root *mux.Router) { + s.Mount(root, MountPath) +} + type msgReader interface { Read() (msgs []interface{}, hasMore bool, err error) } @@ -48,7 +55,7 @@ const ( pingPeriod = (pongWait * 7) / 10 ) -func New(repo *chain.Repository, allowedOrigins []string, backtraceLimit uint32, txpool *txpool.TxPool) *Subscriptions { +func New(repo *chain.Repository, allowedOrigins []string, backtraceLimit uint32, txpool *txpool.TxPool) utils.APIServer { sub := &Subscriptions{ backtraceLimit: backtraceLimit, repo: repo, diff --git a/api/subscriptions/subscriptions_test.go b/api/subscriptions/subscriptions_test.go index fb308d3bc..0c46d8ce3 100644 --- a/api/subscriptions/subscriptions_test.go +++ b/api/subscriptions/subscriptions_test.go @@ -8,26 +8,29 @@ package subscriptions import ( "encoding/json" "fmt" + "io" + "math/big" "net/http" "net/http/httptest" "net/url" "strings" "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "github.com/vechain/thor/v2/node" + "github.com/vechain/thor/v2/tx" - "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/chain" + "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/txpool" ) var ts *httptest.Server -var client *http.Client -var sub *Subscriptions -var txPool *txpool.TxPool -var repo *chain.Repository var blocks []*block.Block func TestSubscriptions(t *testing.T) { @@ -211,14 +214,144 @@ func TestParseAddress(t *testing.T) { assert.Equal(t, expectedAddr, *result) } +// This is a helper function to forcly insert an event into the output receipts +func insertMockOutputEventRcpt(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, + } +} + func initSubscriptionsServer(t *testing.T) { - r, generatedBlocks, pool := initChain(t) - repo = r - txPool = pool - blocks = generatedBlocks - router := mux.NewRouter() - sub = New(repo, []string{}, 5, txPool) - sub.Mount(router, "/subscriptions") - ts = httptest.NewServer(router) - client = &http.Client{} + thorChain, err := node.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) + + require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(&node.TxAndRcpt{ + Transaction: tr, + ReceiptFunc: insertMockOutputEventRcpt, // todo review this + })) + + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs( + New(thorChain.Repo(), []string{}, 5, txPool), + ). + Build() + require.NoError(t, err) + + blocks, err = thorNode.GetAllBlocks() + require.NoError(t, err) + + ts = httptest.NewServer(thorNode.Router()) +} + +func TestSubscriptionsBacktrace(t *testing.T) { + thorChain, err := node.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) + + require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(&node.TxAndRcpt{ + Transaction: tr, + ReceiptFunc: insertMockOutputEventRcpt, // todo review this + })) + + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs( + New(thorChain.Repo(), []string{}, 5, txPool), + ). + Build() + require.NoError(t, err) + + for i := 0; i < 10; i++ { + require.NoError(t, thorNode.GenerateNewBlock()) + } + + blocks, err = thorNode.GetAllBlocks() + require.NoError(t, err) + ts = httptest.NewServer(thorNode.Router()) + + defer ts.Close() + + t.Run("testHandleSubjectWithTransferBacktraceLimit", testHandleSubjectWithTransferBacktraceLimit) +} +func testHandleSubjectWithTransferBacktraceLimit(t *testing.T) { + genesisBlock := blocks[0] + queryArg := fmt.Sprintf("pos=%s", genesisBlock.Header().ID().String()) + u := url.URL{Scheme: "ws", Host: strings.TrimPrefix(ts.URL, "http://"), Path: "/subscriptions/transfer", RawQuery: queryArg} + + conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil) + assert.Error(t, err) + assert.Equal(t, "websocket: bad handshake", err.Error()) + defer resp.Body.Close() // Ensure body is closed after reading + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("failed to read response body: %v", err) + } + assert.Equal(t, body, []byte("pos: backtrace limit exceeded\n")) + assert.Nil(t, conn) } diff --git a/api/subscriptions/transfer_reader_test.go b/api/subscriptions/transfer_reader_test.go index 8bef2175c..82d2808a6 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] + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + genesisBlk := allBlocks[0] + newBlock := allBlocks[1] filter := &TransferFilter{} // Act - br := newTransferReader(repo, genesisBlk.Header().ID(), filter) + br := newTransferReader(thorNode.Chain().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] + thorNode := initChain(t) + allBlocks, err := thorNode.GetAllBlocks() + require.NoError(t, err) + newBlock := allBlocks[1] filter := &TransferFilter{} // Act - br := newTransferReader(repo, newBlock.Header().ID(), filter) + br := newTransferReader(thorNode.Chain().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) + thorNode := initChain(t) filter := &TransferFilter{} // Act - br := newTransferReader(repo, thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), filter) + br := newTransferReader(thorNode.Chain().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] + thorNode := initChain(t) + allBlocks, err := thorNode.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(thorNode.Chain().Repo(), genesisBlk.Header().ID(), badFilter) res, ok, err := br.Read() // Assert diff --git a/api/transactions/transactions.go b/api/transactions/transactions.go index 85404cf21..85de5335a 100644 --- a/api/transactions/transactions.go +++ b/api/transactions/transactions.go @@ -18,12 +18,14 @@ import ( "github.com/vechain/thor/v2/txpool" ) +const MountPath = "/transactions" + type Transactions struct { repo *chain.Repository pool *txpool.TxPool } -func New(repo *chain.Repository, pool *txpool.TxPool) *Transactions { +func New(repo *chain.Repository, pool *txpool.TxPool) utils.APIServer { return &Transactions{ repo, pool, @@ -230,3 +232,7 @@ func (t *Transactions) Mount(root *mux.Router, pathPrefix string) { Name("transactions_get_receipt"). HandlerFunc(utils.WrapHandlerFunc(t.handleGetTransactionReceiptByID)) } + +func (t *Transactions) MountDefaultPath(router *mux.Router) { + t.Mount(router, MountPath) +} diff --git a/api/transactions/transactions_test.go b/api/transactions/transactions_test.go index 52b127a56..42ef88a52 100644 --- a/api/transactions/transactions_test.go +++ b/api/transactions/transactions_test.go @@ -17,26 +17,24 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/vechain/thor/v2/node" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "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/thor" "github.com/vechain/thor/v2/tx" "github.com/vechain/thor/v2/txpool" ) -var repo *chain.Repository var ts *httptest.Server var transaction *tx.Transaction var mempoolTx *tx.Transaction +var chainTag byte func TestTransaction(t *testing.T) { initTransactionServer(t) @@ -108,7 +106,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) @@ -276,19 +273,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 := node.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). @@ -296,62 +289,41 @@ func initTransactionServer(t *testing.T) { Clause(cla). 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(transaction)) + + mempool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{Limit: 10000, LimitPerAccount: 16, MaxLifetime: 10 * time.Minute}) + + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs(transactions.New(thorChain.Repo(), mempool)). + Build() + require.NoError(t, err) mempoolTx = new(tx.Builder). - ChainTag(repo.ChainTag()). + ChainTag(chainTag). Expiration(10). Gas(21000). Nonce(1). Build() - sig, err := crypto.Sign(transaction.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) - if err != nil { - t.Fatal(err) - } - sig2, err := crypto.Sign(mempoolTx.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) if err != nil { t.Fatal(err) } - - transaction = transaction.WithSignature(sig) mempoolTx = mempoolTx.WithSignature(sig2) - 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") - - ts = httptest.NewServer(router) + ts = httptest.NewServer(thorNode.Router()) } func checkMatchingTx(t *testing.T, expectedTx *tx.Transaction, actualTx *transactions.Transaction) { diff --git a/api/transfers/transfers.go b/api/transfers/transfers.go index cad4ee6b3..d13b82415 100644 --- a/api/transfers/transfers.go +++ b/api/transfers/transfers.go @@ -18,13 +18,15 @@ import ( "github.com/vechain/thor/v2/logdb" ) +const MountPath = "/logs/transfers" + type Transfers struct { repo *chain.Repository db *logdb.LogDB limit uint64 } -func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) *Transfers { +func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) utils.APIServer { return &Transfers{ repo, db, @@ -93,3 +95,7 @@ func (t *Transfers) Mount(root *mux.Router, pathPrefix string) { Name("logs_filter_transfer"). HandlerFunc(utils.WrapHandlerFunc(t.handleFilterTransferLogs)) } + +func (t *Transfers) MountDefaultPath(router *mux.Router) { + t.Mount(router, MountPath) +} diff --git a/api/transfers/transfers_test.go b/api/transfers/transfers_test.go index 0c7814993..948732b45 100644 --- a/api/transfers/transfers_test.go +++ b/api/transfers/transfers_test.go @@ -16,16 +16,14 @@ import ( "strings" "testing" - "github.com/gorilla/mux" + "github.com/stretchr/testify/require" + "github.com/vechain/thor/v2/node" + "github.com/stretchr/testify/assert" "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/thor" "github.com/vechain/thor/v2/tx" ) @@ -67,18 +65,18 @@ func TestOption(t *testing.T) { Order: logdb.DESC, } - res, statusCode := httpPost(t, ts.URL+"/transfers", filter) + res, statusCode := httpPost(t, ts.URL+"/logs/transfers", filter) 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 = httpPost(t, ts.URL+"/transfers", filter) + _, statusCode = httpPost(t, ts.URL+"/logs/transfers", filter) 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 = httpPost(t, ts.URL+"/transfers", filter) + res, statusCode = httpPost(t, ts.URL+"/logs/transfers", filter) assert.Equal(t, http.StatusOK, statusCode) var tLogs []*events.FilteredEvent if err := json.Unmarshal(res, &tLogs); err != nil { @@ -89,7 +87,7 @@ func TestOption(t *testing.T) { // when the filtered transfers exceed the limit, should return the forbidden insertBlocks(t, db, 6) - res, statusCode = httpPost(t, ts.URL+"/transfers", filter) + res, statusCode = httpPost(t, ts.URL+"/logs/transfers", filter) 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")) } @@ -98,7 +96,7 @@ func TestOption(t *testing.T) { func testTransferBadRequest(t *testing.T) { badBody := []byte{0x00, 0x01, 0x02} - res, err := http.Post(ts.URL+"/transfers", "application/x-www-form-urlencoded", bytes.NewReader(badBody)) + res, err := http.Post(ts.URL+"/logs/transfers", "application/x-www-form-urlencoded", bytes.NewReader(badBody)) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, res.StatusCode) @@ -112,7 +110,7 @@ func testTransferWithEmptyDb(t *testing.T) { Order: logdb.DESC, } - res, statusCode := httpPost(t, ts.URL+"/transfers", emptyFilter) + res, statusCode := httpPost(t, ts.URL+"/logs/transfers", emptyFilter) var tLogs []*transfers.FilteredTransfer if err := json.Unmarshal(res, &tLogs); err != nil { t.Fatal(err) @@ -130,7 +128,7 @@ func testTransferWithBlocks(t *testing.T, expectedBlocks int) { Order: logdb.DESC, } - res, statusCode := httpPost(t, ts.URL+"/transfers", emptyFilter) + res, statusCode := httpPost(t, ts.URL+"/logs/transfers", emptyFilter) var tLogs []*transfers.FilteredTransfer if err := json.Unmarshal(res, &tLogs); err != nil { t.Fatal(err) @@ -164,21 +162,16 @@ 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 := node.NewIntegrationTestChain() + require.NoError(t, err) - repo, _ := chain.NewRepository(muxDb, b) + thorNode, err := new(node.Builder). + WithChain(thorChain). + WithAPIs(transfers.New(thorChain.Repo(), logDb, limit)). + Build() + require.NoError(t, err) - transfers.New(repo, logDb, limit).Mount(router, "/transfers") - ts = httptest.NewServer(router) + ts = httptest.NewServer(thorNode.Router()) } func createDb(t *testing.T) *logdb.LogDB { diff --git a/api/utils/http.go b/api/utils/http.go index 652c3e408..b991205fe 100644 --- a/api/utils/http.go +++ b/api/utils/http.go @@ -9,6 +9,8 @@ import ( "encoding/json" "io" "net/http" + + "github.com/gorilla/mux" ) type httpError struct { @@ -87,3 +89,8 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { // M shortcut for type map[string]interface{}. type M map[string]interface{} + +type APIServer interface { + Mount(root *mux.Router, pathPrefix string) + MountDefaultPath(root *mux.Router) +} 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/node/builder.go b/node/builder.go new file mode 100644 index 000000000..9aad89dec --- /dev/null +++ b/node/builder.go @@ -0,0 +1,74 @@ +package node + +import ( + "github.com/gorilla/mux" + "github.com/vechain/thor/v2/api/accounts" + "github.com/vechain/thor/v2/api/utils" + "github.com/vechain/thor/v2/bft" + "github.com/vechain/thor/v2/chain" + "github.com/vechain/thor/v2/genesis" + "github.com/vechain/thor/v2/muxdb" + "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/tx" +) + +type TxAndRcpt struct { + Transaction *tx.Transaction + ReceiptFunc func(tx.Receipts) +} + +func RegisterAccountsAPI( + gasLimit uint64, + forkConfig thor.ForkConfig, + bftFunc func(repo *chain.Repository) bft.Committer, +) func(repo *chain.Repository, stater *state.Stater, router *mux.Router) { + return func(repo *chain.Repository, stater *state.Stater, router *mux.Router) { + accounts.New(repo, stater, gasLimit, forkConfig, bftFunc(repo)).Mount(router, "/accounts") + } +} + +type Builder struct { + dbFunc func() *muxdb.MuxDB + genesisFunc func() *genesis.Genesis + engineFunc func(repo *chain.Repository) bft.Committer + router *mux.Router + chain *Chain +} + +func (b *Builder) WithDB(memFunc func() *muxdb.MuxDB) *Builder { + b.dbFunc = memFunc + return b +} + +func (b *Builder) WithGenesis(genesisFunc func() *genesis.Genesis) *Builder { + b.genesisFunc = genesisFunc + return b +} + +func (b *Builder) WithBFTEngine(engineFunc func(repo *chain.Repository) bft.Committer) *Builder { + b.engineFunc = engineFunc + return b +} + +func (b *Builder) WithAPIs(apis ...utils.APIServer) *Builder { + b.router = mux.NewRouter() + + for _, api := range apis { + api.MountDefaultPath(b.router) + } + + return b +} + +func (b *Builder) Build() (*Node, error) { + return &Node{ + chain: b.chain, + router: b.router, + }, nil +} + +func (b *Builder) WithChain(chain *Chain) *Builder { + b.chain = chain + return b +} diff --git a/node/chain.go b/node/chain.go new file mode 100644 index 000000000..f1f00ea67 --- /dev/null +++ b/node/chain.go @@ -0,0 +1,101 @@ +package node + +import ( + "fmt" + "time" + + "github.com/vechain/thor/v2/bft" + "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/thor" + "github.com/vechain/thor/v2/tx" +) + +type Chain struct { + db *muxdb.MuxDB + genesis *genesis.Genesis + engine bft.Committer + repo *chain.Repository + stater *state.Stater + genesisBlock *block.Block +} + +func (c *Chain) Repo() *chain.Repository { + return c.repo +} + +func (c *Chain) Stater() *state.Stater { + return c.stater +} + +func (c *Chain) GenesisBlock() *block.Block { + return c.genesisBlock +} + +func (c *Chain) MintTransactions(transactions ...*tx.Transaction) error { + for i, transaction := range transactions { + err := c.commitTxOnChain(transaction, nil) + if err != nil { + return fmt.Errorf("unable to mint tx: %d : %w", i, err) + } + } + return nil +} + +func (c *Chain) MintTransactionsWithReceiptFunc(txAndRcpts ...*TxAndRcpt) error { + for i, txAndRcpt := range txAndRcpts { + err := c.commitTxOnChain(txAndRcpt.Transaction, txAndRcpt.ReceiptFunc) + if err != nil { + return fmt.Errorf("unable to mint tx: %d : %w", i, err) + } + } + return nil +} + +func (c *Chain) commitTxOnChain(transaction *tx.Transaction, receiptFunc func(tx.Receipts)) error { + newBlk, stage, rcpts, err := c.packTxIntoBlock(transaction) + if err != nil { + return err + } + + if receiptFunc != nil { + receiptFunc(rcpts) + } + + return c.commitTxToChain(newBlk, stage, rcpts) +} +func (c *Chain) packTxIntoBlock(transaction *tx.Transaction) (*block.Block, *state.Stage, tx.Receipts, error) { + // TODO Fix the hardcoded accounts + blkPacker := packer.New(c.Repo(), c.Stater(), genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) + flow, err := blkPacker.Schedule(c.Repo().BestBlockSummary(), uint64(time.Now().Unix())) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to schedule tx: %w", err) + } + err = flow.Adopt(transaction) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to adopt tx: %w", err) + } + newBlk, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to pack tx: %w", err) + } + + return newBlk, stage, receipts, nil +} + +func (c *Chain) commitTxToChain(newBlk *block.Block, stage *state.Stage, receipts tx.Receipts) error { + if _, err := stage.Commit(); err != nil { + return fmt.Errorf("unable to commit tx: %w", err) + } + if err := c.Repo().AddBlock(newBlk, receipts, 0); err != nil { + return fmt.Errorf("unable to add tx to repo: %w", err) + } + if err := c.Repo().SetBestBlockID(newBlk.Header().ID()); err != nil { + return fmt.Errorf("unable to set best block: %w", err) + } + return nil +} diff --git a/node/chain_builder.go b/node/chain_builder.go new file mode 100644 index 000000000..f6582fd70 --- /dev/null +++ b/node/chain_builder.go @@ -0,0 +1,63 @@ +package node + +import ( + "github.com/vechain/thor/v2/bft" + "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" +) + +type ChainBuilder struct { + dbFunc func() *muxdb.MuxDB + genesisFunc func() *genesis.Genesis + engineFunc func(repo *chain.Repository) bft.Committer +} + +func (b *ChainBuilder) WithDB(db func() *muxdb.MuxDB) *ChainBuilder { + b.dbFunc = db + return b +} + +func (b *ChainBuilder) WithGenesis(genesisFunc func() *genesis.Genesis) *ChainBuilder { + b.genesisFunc = genesisFunc + return b +} + +func (b *ChainBuilder) WithBFTEngine(engineFunc func(repo *chain.Repository) bft.Committer) *ChainBuilder { + b.engineFunc = engineFunc + return b +} + +func (b *ChainBuilder) Build() (*Chain, error) { + db := b.dbFunc() + stater := state.NewStater(db) + gene := b.genesisFunc() + geneBlk, _, _, err := gene.Build(stater) + if err != nil { + return nil, err + } + + repo, err := chain.NewRepository(db, geneBlk) + if err != nil { + return nil, err + } + + return &Chain{ + db: db, + genesis: gene, + genesisBlock: geneBlk, + repo: repo, + stater: stater, + engine: b.engineFunc(repo), + }, nil +} + +func NewIntegrationTestChain() (*Chain, error) { + return new(ChainBuilder). + WithDB(muxdb.NewMem). + WithGenesis(genesis.NewDevnet). + WithBFTEngine(solo.NewBFTEngine). + Build() +} diff --git a/node/node.go b/node/node.go new file mode 100644 index 000000000..f03a797ef --- /dev/null +++ b/node/node.go @@ -0,0 +1,76 @@ +package node + +import ( + "fmt" + "net/http" + "slices" + "time" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/vechain/thor/v2/block" + "github.com/vechain/thor/v2/genesis" + "github.com/vechain/thor/v2/packer" + "github.com/vechain/thor/v2/thor" +) + +type Node struct { + chain *Chain + router *mux.Router +} + +func (n *Node) Router() http.Handler { + return n.router +} + +func (n *Node) GenerateNewBlock() error { + packer := packer.New(n.chain.Repo(), n.chain.stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) // todo this should pack with other accoutns + + sum := n.chain.repo.BestBlockSummary() + flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) + if err != nil { + return fmt.Errorf("unable to schedule new block") + } + blk, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) + if err != nil { + return fmt.Errorf("unable to pack new block") + } + if _, err := stage.Commit(); err != nil { + return fmt.Errorf("unable to commit new block") + } + if err := n.chain.Repo().AddBlock(blk, receipts, 0); err != nil { + return fmt.Errorf("unable to add new block to chain") + } + if err := n.chain.Repo().SetBestBlockID(blk.Header().ID()); err != nil { + return fmt.Errorf("unable to set best block in chain") + } + return nil +} + +func (n *Node) GetAllBlocks() ([]*block.Block, error) { + bestBlkSummary := n.chain.Repo().BestBlockSummary() + var blks []*block.Block + currBlockID := bestBlkSummary.Header.ID() + startTime := time.Now() + for { + blk, err := n.chain.Repo().GetBlock(currBlockID) + if err != nil { + return nil, err + } + blks = append(blks, blk) + + if blk.Header().Number() == n.chain.genesisBlock.Header().Number() { + slices.Reverse(blks) // make sure genesis is at position 0 + return blks, err + } + currBlockID = blk.Header().ParentID() + + if time.Since(startTime) > 5*time.Second { + return nil, errors.New("taking more than 5 seconds to retrieve all blocks") + } + } +} + +func (n *Node) Chain() *Chain { + return n.chain +} From 11f9440f05b2c007c3659a47133caf21a81ce16e Mon Sep 17 00:00:00 2001 From: otherview Date: Thu, 12 Sep 2024 10:59:09 +0100 Subject: [PATCH 02/11] thorchain allows insertion of blocks --- api/accounts/accounts_test.go | 8 ++- api/blocks/blocks_test.go | 2 +- api/debug/debug_test.go | 2 +- api/subscriptions/block_reader_test.go | 5 +- api/subscriptions/subscriptions_test.go | 18 ++++--- api/transactions/transactions_test.go | 2 +- node/chain.go | 65 ++++++++++--------------- 7 files changed, 51 insertions(+), 51 deletions(-) diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index e23fd88d4..31562a974 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -262,7 +262,13 @@ func initAccountServer(t *testing.T) { claCall := tx.NewClause(&contractAddr).WithData(input) transactionCall := buildTxWithClauses(t, thorChain.Repo().ChainTag(), claCall) - require.NoError(t, thorChain.MintTransactions(transaction, transactionCall)) + require.NoError(t, + thorChain.MintTransactions( + genesis.DevAccounts()[0], + transaction, + transactionCall, + ), + ) thorNode, err := new(node.Builder). WithChain(thorChain). diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index 7460083a3..b43184da1 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -173,7 +173,7 @@ func initBlockServer(t *testing.T) { t.Fatal(err) } tx = tx.WithSignature(sig) - require.NoError(t, thorChain.MintTransactions(tx)) + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], tx)) thorNode, err := new(node.Builder). WithChain(thorChain). diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 077e604db..8bd238aec 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -548,7 +548,7 @@ func initDebugServer(t *testing.T) { } transaction = transaction.WithSignature(sig) - require.NoError(t, thorChain.MintTransactions(transaction, noClausesTx)) + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction, noClausesTx)) debug = New(thorChain.Repo(), thorChain.Stater(), thor.NoFork, 21000, true, solo.NewBFTEngine(thorChain.Repo()), []string{"all"}, false).(*Debug) thorNode, err := new(node.Builder). diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index 6c707457c..fb7427993 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -78,7 +78,10 @@ func initChain(t *testing.T) *node.Node { } tr = tr.WithSignature(sig) - require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(&node.TxAndRcpt{Transaction: tr, ReceiptFunc: insertMockOutputEvent})) + require.NoError(t, thorChain.MintTransactionsWithReceiptFunc( + genesis.DevAccounts()[0], + &node.TxAndRcpt{Transaction: tr, ReceiptFunc: insertMockOutputEvent}), + ) thorNode, err := new(node.Builder). WithChain(thorChain). diff --git a/api/subscriptions/subscriptions_test.go b/api/subscriptions/subscriptions_test.go index 0c46d8ce3..b779e4417 100644 --- a/api/subscriptions/subscriptions_test.go +++ b/api/subscriptions/subscriptions_test.go @@ -265,10 +265,11 @@ func initSubscriptionsServer(t *testing.T) { } tr = tr.WithSignature(sig) - require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(&node.TxAndRcpt{ - Transaction: tr, - ReceiptFunc: insertMockOutputEventRcpt, // todo review this - })) + require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(genesis.DevAccounts()[0], + &node.TxAndRcpt{ + Transaction: tr, + ReceiptFunc: insertMockOutputEventRcpt, // todo review this + })) thorNode, err := new(node.Builder). WithChain(thorChain). @@ -312,10 +313,11 @@ func TestSubscriptionsBacktrace(t *testing.T) { } tr = tr.WithSignature(sig) - require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(&node.TxAndRcpt{ - Transaction: tr, - ReceiptFunc: insertMockOutputEventRcpt, // todo review this - })) + require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(genesis.DevAccounts()[0], + &node.TxAndRcpt{ + Transaction: tr, + ReceiptFunc: insertMockOutputEventRcpt, // todo review this + })) thorNode, err := new(node.Builder). WithChain(thorChain). diff --git a/api/transactions/transactions_test.go b/api/transactions/transactions_test.go index 42ef88a52..7df2184d2 100644 --- a/api/transactions/transactions_test.go +++ b/api/transactions/transactions_test.go @@ -294,7 +294,7 @@ func initTransactionServer(t *testing.T) { t.Fatal(err) } transaction = transaction.WithSignature(sig) - require.NoError(t, thorChain.MintTransactions(transaction)) + 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}) diff --git a/node/chain.go b/node/chain.go index f1f00ea67..875f67b49 100644 --- a/node/chain.go +++ b/node/chain.go @@ -36,58 +36,47 @@ func (c *Chain) GenesisBlock() *block.Block { return c.genesisBlock } -func (c *Chain) MintTransactions(transactions ...*tx.Transaction) error { - for i, transaction := range transactions { - err := c.commitTxOnChain(transaction, nil) - if err != nil { - return fmt.Errorf("unable to mint tx: %d : %w", i, err) - } +func (c *Chain) MintTransactions(account genesis.DevAccount, transactions ...*tx.Transaction) error { + var txAndRcpts []*TxAndRcpt + for _, transaction := range transactions { + txAndRcpts = append(txAndRcpts, &TxAndRcpt{Transaction: transaction}) } - return nil + return c.MintTransactionsWithReceiptFunc(account, txAndRcpts...) } -func (c *Chain) MintTransactionsWithReceiptFunc(txAndRcpts ...*TxAndRcpt) error { - for i, txAndRcpt := range txAndRcpts { - err := c.commitTxOnChain(txAndRcpt.Transaction, txAndRcpt.ReceiptFunc) - if err != nil { - return fmt.Errorf("unable to mint tx: %d : %w", i, err) - } - } - return nil +func (c *Chain) MintTransactionsWithReceiptFunc(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) error { + return c.MintBlock(account, txAndRcpts...) } -func (c *Chain) commitTxOnChain(transaction *tx.Transaction, receiptFunc func(tx.Receipts)) error { - newBlk, stage, rcpts, err := c.packTxIntoBlock(transaction) +func (c *Chain) MintBlock(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) error { + // create a new block + blkPacker := packer.New(c.Repo(), c.Stater(), account.Address, &genesis.DevAccounts()[0].Address, thor.NoFork) + blkFlow, err := blkPacker.Schedule(c.Repo().BestBlockSummary(), uint64(time.Now().Unix())) if err != nil { - return err + return fmt.Errorf("unable to schedule a new block: %w", err) } - if receiptFunc != nil { - receiptFunc(rcpts) + // adopt transactions in the new block + for _, txAndRcpt := range txAndRcpts { + if err = blkFlow.Adopt(txAndRcpt.Transaction); err != nil { + return fmt.Errorf("unable to adopt tx into block: %w", err) + } } - return c.commitTxToChain(newBlk, stage, rcpts) -} -func (c *Chain) packTxIntoBlock(transaction *tx.Transaction) (*block.Block, *state.Stage, tx.Receipts, error) { - // TODO Fix the hardcoded accounts - blkPacker := packer.New(c.Repo(), c.Stater(), genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) - flow, err := blkPacker.Schedule(c.Repo().BestBlockSummary(), uint64(time.Now().Unix())) + // pack the transactions into the block + newBlk, stage, receipts, err := blkFlow.Pack(account.PrivateKey, 0, false) if err != nil { - return nil, nil, nil, fmt.Errorf("unable to schedule tx: %w", err) - } - err = flow.Adopt(transaction) - if err != nil { - return nil, nil, nil, fmt.Errorf("unable to adopt tx: %w", err) - } - newBlk, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - return nil, nil, nil, fmt.Errorf("unable to pack tx: %w", err) + return fmt.Errorf("unable to pack tx: %w", err) } - return newBlk, stage, receipts, nil -} + // modify any receipts in the new block + for _, txAndRcpt := range txAndRcpts { + if txAndRcpt.ReceiptFunc != nil { + txAndRcpt.ReceiptFunc(receipts) + } + } -func (c *Chain) commitTxToChain(newBlk *block.Block, stage *state.Stage, receipts tx.Receipts) error { + // commit block to chain state if _, err := stage.Commit(); err != nil { return fmt.Errorf("unable to commit tx: %w", err) } From 0d65b5cee3e8c8631656e5903784d487de9329a2 Mon Sep 17 00:00:00 2001 From: otherview Date: Thu, 12 Sep 2024 15:17:12 +0100 Subject: [PATCH 03/11] remove thorNode, added testchain --- api/accounts/accounts.go | 8 +-- api/accounts/accounts_test.go | 22 +++---- api/api.go | 5 +- api/blocks/blocks.go | 8 +-- api/blocks/blocks_test.go | 21 +++---- api/debug/debug.go | 12 +--- api/debug/debug_test.go | 26 ++++---- api/events/events.go | 8 +-- api/events/events_test.go | 14 ++--- api/node/node.go | 8 +-- api/node/node_test.go | 27 ++++---- api/subscriptions/beat2_reader_test.go | 16 ++--- api/subscriptions/beat_reader_test.go | 16 ++--- api/subscriptions/block_reader_test.go | 25 +++----- api/subscriptions/event_reader_test.go | 8 +-- api/subscriptions/pending_tx_test.go | 8 +-- api/subscriptions/subscriptions.go | 11 +--- api/subscriptions/subscriptions_test.go | 43 +++++-------- api/subscriptions/transfer_reader_test.go | 22 +++---- api/transactions/transactions.go | 8 +-- api/transactions/transactions_test.go | 19 +++--- api/transfers/transfers.go | 8 +-- api/transfers/transfers_test.go | 17 +++-- api/utils/http.go | 7 --- node/builder.go | 74 ---------------------- node/node.go | 76 ----------------------- {node => test/testchain}/chain.go | 28 ++++++++- {node => test/testchain}/chain_builder.go | 2 +- test/testchain/types.go | 8 +++ 29 files changed, 174 insertions(+), 381 deletions(-) delete mode 100644 node/builder.go delete mode 100644 node/node.go rename {node => test/testchain}/chain.go (79%) rename {node => test/testchain}/chain_builder.go (98%) create mode 100644 test/testchain/types.go diff --git a/api/accounts/accounts.go b/api/accounts/accounts.go index c79a5cade..a6d1df3ee 100644 --- a/api/accounts/accounts.go +++ b/api/accounts/accounts.go @@ -26,8 +26,6 @@ import ( "github.com/vechain/thor/v2/xenv" ) -const MountPath = "/accounts" - type Accounts struct { repo *chain.Repository stater *state.Stater @@ -42,7 +40,7 @@ func New( callGasLimit uint64, forkConfig thor.ForkConfig, bft bft.Committer, -) utils.APIServer { +) *Accounts { return &Accounts{ repo, stater, @@ -384,7 +382,3 @@ func (a *Accounts) Mount(root *mux.Router, pathPrefix string) { Name("accounts_call_contract_address"). HandlerFunc(utils.WrapHandlerFunc(a.handleCallContract)) } - -func (a *Accounts) MountDefaultPath(root *mux.Router) { - a.Mount(root, MountPath) -} diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index 31562a974..b2facf688 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -19,16 +19,18 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "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/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/node" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" + + ABI "github.com/vechain/thor/v2/abi" ) // pragma solidity ^0.4.18; @@ -244,7 +246,7 @@ func getStorageWithNonExisitingRevision(t *testing.T) { } func initAccountServer(t *testing.T) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) claTransfer := tx.NewClause(&addr).WithValue(value) @@ -270,17 +272,13 @@ func initAccountServer(t *testing.T) { ), ) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs( - accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, solo.NewBFTEngine(thorChain.Repo())), - ). - Build() - require.NoError(t, err) - genesisBlock = thorChain.GenesisBlock() - ts = httptest.NewServer(thorNode.Router()) + router := mux.NewRouter() + accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, solo.NewBFTEngine(thorChain.Repo())). + Mount(router, "/accounts") + + ts = httptest.NewServer(router) } func buildTxWithClauses(t *testing.T, chaiTag byte, clauses ...*tx.Clause) *tx.Transaction { diff --git a/api/api.go b/api/api.go index d13c94056..38b412a97 100644 --- a/api/api.go +++ b/api/api.go @@ -72,7 +72,7 @@ func New( }) accounts.New(repo, stater, callGasLimit, forkConfig, bft). - Mount(router, accounts.MountPath) + Mount(router, "/accounts") if !skipLogs { events.New(repo, logDB, logsLimit). @@ -114,6 +114,5 @@ func New( handler = RequestLoggerHandler(handler, logger) } - castedSub := subs.(*subscriptions.Subscriptions) - return handler.ServeHTTP, castedSub.Close // subscriptions handles hijacked conns, which need to be closed + return handler.ServeHTTP, subs.Close // subscriptions handles hijacked conns, which need to be closed } diff --git a/api/blocks/blocks.go b/api/blocks/blocks.go index 634d47d9c..bddb3ac12 100644 --- a/api/blocks/blocks.go +++ b/api/blocks/blocks.go @@ -17,14 +17,12 @@ import ( "github.com/vechain/thor/v2/thor" ) -const MountPath = "/blocks" - type Blocks struct { repo *chain.Repository bft bft.Committer } -func New(repo *chain.Repository, bft bft.Committer) utils.APIServer { +func New(repo *chain.Repository, bft bft.Committer) *Blocks { return &Blocks{ repo, bft, @@ -100,7 +98,3 @@ func (b *Blocks) Mount(root *mux.Router, pathPrefix string) { Name("blocks_get_block"). HandlerFunc(utils.WrapHandlerFunc(b.handleGetBlock)) } - -func (b *Blocks) MountDefaultPath(router *mux.Router) { - b.Mount(router, MountPath) -} diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index 3ff8f8673..41ed05daa 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -17,13 +17,14 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" + "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/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/node" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) @@ -153,7 +154,7 @@ func testGetBlockWithRevisionNumberTooHigh(t *testing.T) { } func initBlockServer(t *testing.T) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) addr := thor.BytesToAddress([]byte("to")) @@ -175,20 +176,16 @@ func initBlockServer(t *testing.T) { tx = tx.WithSignature(sig) require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], tx)) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs( - blocks.New(thorChain.Repo(), solo.NewBFTEngine(thorChain.Repo())), - ). - Build() - require.NoError(t, err) - - allBlocks, err := thorNode.GetAllBlocks() + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) genesisBlock = allBlocks[0] blk = allBlocks[1] - ts = httptest.NewServer(thorNode.Router()) + + router := mux.NewRouter() + bftEngine := solo.NewBFTEngine(thorChain.Repo()) + blocks.New(thorChain.Repo(), bftEngine).Mount(router, "/blocks") + ts = httptest.NewServer(router) } func checkCollapsedBlock(t *testing.T, expBl *block.Block, actBl *blocks.JSONCollapsedBlock) { diff --git a/api/debug/debug.go b/api/debug/debug.go index 357bac416..e84a88d57 100644 --- a/api/debug/debug.go +++ b/api/debug/debug.go @@ -35,11 +35,7 @@ import ( "github.com/vechain/thor/v2/xenv" ) -const ( - defaultMaxStorageResult = 1000 - - MountPath = "/debug" -) +const defaultMaxStorageResult = 1000 type Debug struct { repo *chain.Repository @@ -61,7 +57,7 @@ func New( bft bft.Committer, allowedTracers []string, soloMode bool, -) utils.APIServer { +) *Debug { allowedMap := make(map[string]struct{}) for _, t := range allowedTracers { allowedMap[t] = struct{}{} @@ -481,7 +477,3 @@ func (d *Debug) Mount(root *mux.Router, pathPrefix string) { Name("debug_trace_storage"). HandlerFunc(utils.WrapHandlerFunc(d.handleDebugStorage)) } - -func (d *Debug) MountDefaultPath(router *mux.Router) { - d.Mount(router, MountPath) -} diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index ab269deea..4668ff0ac 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/block" @@ -27,9 +28,9 @@ import ( "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/node" "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/tracers/logger" "github.com/vechain/thor/v2/tx" @@ -511,7 +512,7 @@ func testStorageRangeDefaultOption(t *testing.T) { } func initDebugServer(t *testing.T) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) addr := thor.BytesToAddress([]byte("to")) @@ -549,22 +550,17 @@ func initDebugServer(t *testing.T) { transaction = transaction.WithSignature(sig) require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0], transaction, noClausesTx)) + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0])) - debug = New(thorChain.Repo(), thorChain.Stater(), thor.NoFork, 21000, true, solo.NewBFTEngine(thorChain.Repo()), []string{"all"}, false).(*Debug) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs( - debug, - ). - Build() - require.NoError(t, err) - - require.NoError(t, thorNode.GenerateNewBlock()) - - allBlocks, err := thorNode.GetAllBlocks() + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) blk = allBlocks[1] - ts = httptest.NewServer(thorNode.Router()) + + forkConfig := thor.GetForkConfig(blk.Header().ID()) + router := mux.NewRouter() + debug = New(thorChain.Repo(), thorChain.Stater(), forkConfig, 21000, true, solo.NewBFTEngine(thorChain.Repo()), []string{"all"}, false) + debug.Mount(router, "/debug") + ts = httptest.NewServer(router) } func httpPostAndCheckResponseStatus(t *testing.T, url string, obj interface{}, responseStatusCode int) string { diff --git a/api/events/events.go b/api/events/events.go index 8e78bdf24..40dff7b09 100644 --- a/api/events/events.go +++ b/api/events/events.go @@ -17,15 +17,13 @@ import ( "github.com/vechain/thor/v2/logdb" ) -const MountPath = "/logs/event" - type Events struct { repo *chain.Repository db *logdb.LogDB limit uint64 } -func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) utils.APIServer { +func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) *Events { return &Events{ repo, db, @@ -89,7 +87,3 @@ func (e *Events) Mount(root *mux.Router, pathPrefix string) { Name("logs_filter_event"). HandlerFunc(utils.WrapHandlerFunc(e.handleFilter)) } - -func (e *Events) MountDefaultPath(router *mux.Router) { - e.Mount(router, MountPath) -} diff --git a/api/events/events_test.go b/api/events/events_test.go index ae2400f4b..0010e69ad 100644 --- a/api/events/events_test.go +++ b/api/events/events_test.go @@ -14,12 +14,13 @@ import ( "strings" "testing" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/events" "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/logdb" - "github.com/vechain/thor/v2/node" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) @@ -172,15 +173,12 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { // Init functions func initEventServer(t *testing.T, logDb *logdb.LogDB, limit uint64) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs(events.New(thorChain.Repo(), logDb, limit)).Build() - require.NoError(t, err) - - ts = httptest.NewServer(thorNode.Router()) + router := mux.NewRouter() + events.New(thorChain.Repo(), logDb, limit).Mount(router, "/logs/event") + ts = httptest.NewServer(router) } func createDb(t *testing.T) *logdb.LogDB { diff --git a/api/node/node.go b/api/node/node.go index 51cbe8f78..11c1ce7e1 100644 --- a/api/node/node.go +++ b/api/node/node.go @@ -12,13 +12,11 @@ import ( "github.com/vechain/thor/v2/api/utils" ) -const MountPath = "/node" - type Node struct { nw Network } -func New(nw Network) utils.APIServer { +func New(nw Network) *Node { return &Node{ nw, } @@ -40,7 +38,3 @@ func (n *Node) Mount(root *mux.Router, pathPrefix string) { Name("node_get_peers"). HandlerFunc(utils.WrapHandlerFunc(n.handleNetwork)) } - -func (n *Node) MountDefaultPath(router *mux.Router) { - n.Mount(router, MountPath) -} diff --git a/api/node/node_test.go b/api/node/node_test.go index 8b38de1eb..d6eb58ee1 100644 --- a/api/node/node_test.go +++ b/api/node/node_test.go @@ -12,13 +12,13 @@ import ( "testing" "time" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/node" "github.com/vechain/thor/v2/comm" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/txpool" - - thornode "github.com/vechain/thor/v2/node" ) var ts *httptest.Server @@ -34,22 +34,21 @@ func TestNode(t *testing.T) { } func initCommServer(t *testing.T) { - thorChain, err := thornode.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) - comm := comm.New(thorChain.Repo(), txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ - Limit: 10000, - LimitPerAccount: 16, - MaxLifetime: 10 * time.Minute, - })) + communicator := comm.New( + thorChain.Repo(), + txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ + Limit: 10000, + LimitPerAccount: 16, + MaxLifetime: 10 * time.Minute, + })) - thorNode, err := new(thornode.Builder). - WithChain(thorChain). - WithAPIs(node.New(comm)). - Build() - require.NoError(t, err) + router := mux.NewRouter() + node.New(communicator).Mount(router, "/node") - ts = httptest.NewServer(thorNode.Router()) + ts = httptest.NewServer(router) } func httpGet(t *testing.T, url string) []byte { diff --git a/api/subscriptions/beat2_reader_test.go b/api/subscriptions/beat2_reader_test.go index 71d39658a..8335866e9 100644 --- a/api/subscriptions/beat2_reader_test.go +++ b/api/subscriptions/beat2_reader_test.go @@ -16,15 +16,15 @@ import ( func TestBeat2Reader_Read(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) genesisBlk := allBlocks[0] newBlock := allBlocks[1] // Act - beatReader := newBeat2Reader(thorNode.Chain().Repo(), genesisBlk.Header().ID()) + beatReader := newBeat2Reader(thorChain.Repo(), genesisBlk.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -43,13 +43,13 @@ func TestBeat2Reader_Read(t *testing.T) { func TestBeat2Reader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) newBlock := allBlocks[1] // Act - beatReader := newBeat2Reader(thorNode.Chain().Repo(), newBlock.Header().ID()) + beatReader := newBeat2Reader(thorChain.Repo(), newBlock.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -60,10 +60,10 @@ func TestBeat2Reader_Read_NoNewBlocksToRead(t *testing.T) { func TestBeat2Reader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - thorNode := initChain(t) + thorChain := initChain(t) // Act - beatReader := newBeat2Reader(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + beatReader := newBeat2Reader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err := beatReader.Read() // Assert diff --git a/api/subscriptions/beat_reader_test.go b/api/subscriptions/beat_reader_test.go index edab573eb..131c7a942 100644 --- a/api/subscriptions/beat_reader_test.go +++ b/api/subscriptions/beat_reader_test.go @@ -16,14 +16,14 @@ import ( func TestBeatReader_Read(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) genesisBlk := allBlocks[0] newBlock := allBlocks[1] // Act - beatReader := newBeatReader(thorNode.Chain().Repo(), genesisBlk.Header().ID()) + beatReader := newBeatReader(thorChain.Repo(), genesisBlk.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -42,13 +42,13 @@ func TestBeatReader_Read(t *testing.T) { func TestBeatReader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) newBlock := allBlocks[1] // Act - beatReader := newBeatReader(thorNode.Chain().Repo(), newBlock.Header().ID()) + beatReader := newBeatReader(thorChain.Repo(), newBlock.Header().ID()) res, ok, err := beatReader.Read() // Assert @@ -59,10 +59,10 @@ func TestBeatReader_Read_NoNewBlocksToRead(t *testing.T) { func TestBeatReader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - thorNode := initChain(t) + thorChain := initChain(t) // Act - beatReader := newBeatReader(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + beatReader := newBeatReader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err := beatReader.Read() // Assert diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index fb7427993..f276641c0 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -13,21 +13,21 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/node" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) func TestBlockReader_Read(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + 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(thorNode.Chain().Repo(), genesisBlk.Header().ID()) + br := newBlockReader(thorChain.Repo(), genesisBlk.Header().ID()) res, ok, err := br.Read() assert.NoError(t, err) @@ -40,7 +40,7 @@ func TestBlockReader_Read(t *testing.T) { } // Test case 2: There is no new block - br = newBlockReader(thorNode.Chain().Repo(), newBlock.Header().ID()) + br = newBlockReader(thorChain.Repo(), newBlock.Header().ID()) res, ok, err = br.Read() assert.NoError(t, err) @@ -48,7 +48,7 @@ func TestBlockReader_Read(t *testing.T) { assert.Empty(t, res) // Test case 3: Error when reading blocks - br = newBlockReader(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + br = newBlockReader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) res, ok, err = br.Read() assert.Error(t, err) @@ -56,8 +56,8 @@ func TestBlockReader_Read(t *testing.T) { assert.Empty(t, res) } -func initChain(t *testing.T) *node.Node { - thorChain, err := node.NewIntegrationTestChain() +func initChain(t *testing.T) *testchain.Chain { + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) addr := thor.BytesToAddress([]byte("to")) @@ -80,15 +80,10 @@ func initChain(t *testing.T) *node.Node { require.NoError(t, thorChain.MintTransactionsWithReceiptFunc( genesis.DevAccounts()[0], - &node.TxAndRcpt{Transaction: tr, ReceiptFunc: insertMockOutputEvent}), + &testchain.TxAndRcpt{Transaction: tr, ReceiptFunc: insertMockOutputEvent}), ) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs(). - Build() - require.NoError(t, err) - return thorNode + return thorChain } // This is a helper function to forcly insert an event into the output receipts diff --git a/api/subscriptions/event_reader_test.go b/api/subscriptions/event_reader_test.go index d069b2cc1..a742a3d1b 100644 --- a/api/subscriptions/event_reader_test.go +++ b/api/subscriptions/event_reader_test.go @@ -16,14 +16,14 @@ import ( func TestEventReader_Read(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) genesisBlk := allBlocks[0] newBlock := allBlocks[1] er := &eventReader{ - repo: thorNode.Chain().Repo(), + repo: thorChain.Repo(), filter: &EventFilter{}, blockReader: &mockBlockReaderWithError{}, } @@ -35,7 +35,7 @@ func TestEventReader_Read(t *testing.T) { assert.False(t, ok) // Test case 2: Events are available to read - er = newEventReader(thorNode.Chain().Repo(), genesisBlk.Header().ID(), &EventFilter{}) + er = newEventReader(thorChain.Repo(), genesisBlk.Header().ID(), &EventFilter{}) events, ok, err = er.Read() diff --git a/api/subscriptions/pending_tx_test.go b/api/subscriptions/pending_tx_test.go index 0db3b5f20..6be36d144 100644 --- a/api/subscriptions/pending_tx_test.go +++ b/api/subscriptions/pending_tx_test.go @@ -25,8 +25,8 @@ import ( func TestPendingTx_Subscribe(t *testing.T) { // Arrange - thorNode := initChain(t) - txPool := txpool.New(thorNode.Chain().Repo(), thorNode.Chain().Stater(), txpool.Options{ + thorChain := initChain(t) + txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ Limit: 100, LimitPerAccount: 16, MaxLifetime: time.Hour, @@ -45,8 +45,8 @@ func TestPendingTx_Subscribe(t *testing.T) { func TestPendingTx_Unsubscribe(t *testing.T) { // Arrange - thorNode := initChain(t) - txPool := txpool.New(thorNode.Chain().Repo(), thorNode.Chain().Stater(), txpool.Options{ + thorChain := initChain(t) + txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ Limit: 100, LimitPerAccount: 16, MaxLifetime: time.Hour, diff --git a/api/subscriptions/subscriptions.go b/api/subscriptions/subscriptions.go index 743c9df9c..73f895bf3 100644 --- a/api/subscriptions/subscriptions.go +++ b/api/subscriptions/subscriptions.go @@ -22,10 +22,7 @@ import ( "github.com/vechain/thor/v2/txpool" ) -const ( - txQueueSize = 20 - MountPath = "/subscriptions" -) +const txQueueSize = 20 type Subscriptions struct { backtraceLimit uint32 @@ -36,10 +33,6 @@ type Subscriptions struct { wg sync.WaitGroup } -func (s *Subscriptions) MountDefaultPath(root *mux.Router) { - s.Mount(root, MountPath) -} - type msgReader interface { Read() (msgs []interface{}, hasMore bool, err error) } @@ -55,7 +48,7 @@ const ( pingPeriod = (pongWait * 7) / 10 ) -func New(repo *chain.Repository, allowedOrigins []string, backtraceLimit uint32, txpool *txpool.TxPool) utils.APIServer { +func New(repo *chain.Repository, allowedOrigins []string, backtraceLimit uint32, txpool *txpool.TxPool) *Subscriptions { sub := &Subscriptions{ backtraceLimit: backtraceLimit, repo: repo, diff --git a/api/subscriptions/subscriptions_test.go b/api/subscriptions/subscriptions_test.go index 455d840d9..4c25105b9 100644 --- a/api/subscriptions/subscriptions_test.go +++ b/api/subscriptions/subscriptions_test.go @@ -18,12 +18,13 @@ import ( "time" "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/genesis" - "github.com/vechain/thor/v2/node" + "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" @@ -237,7 +238,7 @@ func insertMockOutputEventRcpt(receipts tx.Receipts) { } func initSubscriptionsServer(t *testing.T) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ @@ -265,27 +266,22 @@ func initSubscriptionsServer(t *testing.T) { tr = tr.WithSignature(sig) require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(genesis.DevAccounts()[0], - &node.TxAndRcpt{ + &testchain.TxAndRcpt{ Transaction: tr, ReceiptFunc: insertMockOutputEventRcpt, // todo review this })) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs( - New(thorChain.Repo(), []string{}, 5, txPool), - ). - Build() - require.NoError(t, err) - - blocks, err = thorNode.GetAllBlocks() + blocks, err = thorChain.GetAllBlocks() require.NoError(t, err) - ts = httptest.NewServer(thorNode.Router()) + router := mux.NewRouter() + New(thorChain.Repo(), []string{}, 5, txPool). + Mount(router, "/subscriptions") + ts = httptest.NewServer(router) } func TestSubscriptionsBacktrace(t *testing.T) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) txPool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{ @@ -313,26 +309,21 @@ func TestSubscriptionsBacktrace(t *testing.T) { tr = tr.WithSignature(sig) require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(genesis.DevAccounts()[0], - &node.TxAndRcpt{ + &testchain.TxAndRcpt{ Transaction: tr, ReceiptFunc: insertMockOutputEventRcpt, // todo review this })) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs( - New(thorChain.Repo(), []string{}, 5, txPool), - ). - Build() - require.NoError(t, err) - for i := 0; i < 10; i++ { - require.NoError(t, thorNode.GenerateNewBlock()) + require.NoError(t, thorChain.MintTransactions(genesis.DevAccounts()[0])) } - blocks, err = thorNode.GetAllBlocks() + blocks, err = thorChain.GetAllBlocks() require.NoError(t, err) - ts = httptest.NewServer(thorNode.Router()) + + router := mux.NewRouter() + New(thorChain.Repo(), []string{}, 5, txPool).Mount(router, "/subscriptions") + ts = httptest.NewServer(router) defer ts.Close() diff --git a/api/subscriptions/transfer_reader_test.go b/api/subscriptions/transfer_reader_test.go index 82d2808a6..de7d0bf66 100644 --- a/api/subscriptions/transfer_reader_test.go +++ b/api/subscriptions/transfer_reader_test.go @@ -16,15 +16,15 @@ import ( func TestTransferReader_Read(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) genesisBlk := allBlocks[0] newBlock := allBlocks[1] filter := &TransferFilter{} // Act - br := newTransferReader(thorNode.Chain().Repo(), genesisBlk.Header().ID(), filter) + br := newTransferReader(thorChain.Repo(), genesisBlk.Header().ID(), filter) res, ok, err := br.Read() // Assert @@ -42,14 +42,14 @@ func TestTransferReader_Read(t *testing.T) { func TestTransferReader_Read_NoNewBlocksToRead(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) newBlock := allBlocks[1] filter := &TransferFilter{} // Act - br := newTransferReader(thorNode.Chain().Repo(), newBlock.Header().ID(), filter) + br := newTransferReader(thorChain.Repo(), newBlock.Header().ID(), filter) res, ok, err := br.Read() // Assert @@ -60,11 +60,11 @@ func TestTransferReader_Read_NoNewBlocksToRead(t *testing.T) { func TestTransferReader_Read_ErrorWhenReadingBlocks(t *testing.T) { // Arrange - thorNode := initChain(t) + thorChain := initChain(t) filter := &TransferFilter{} // Act - br := newTransferReader(thorNode.Chain().Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), filter) + br := newTransferReader(thorChain.Repo(), thor.MustParseBytes32("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), filter) res, ok, err := br.Read() // Assert @@ -75,8 +75,8 @@ func TestTransferReader_Read_ErrorWhenReadingBlocks(t *testing.T) { func TestTransferReader_Read_NoTransferMatchingTheFilter(t *testing.T) { // Arrange - thorNode := initChain(t) - allBlocks, err := thorNode.GetAllBlocks() + thorChain := initChain(t) + allBlocks, err := thorChain.GetAllBlocks() require.NoError(t, err) genesisBlk := allBlocks[0] @@ -86,7 +86,7 @@ func TestTransferReader_Read_NoTransferMatchingTheFilter(t *testing.T) { } // Act - br := newTransferReader(thorNode.Chain().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.go b/api/transactions/transactions.go index 85de5335a..85404cf21 100644 --- a/api/transactions/transactions.go +++ b/api/transactions/transactions.go @@ -18,14 +18,12 @@ import ( "github.com/vechain/thor/v2/txpool" ) -const MountPath = "/transactions" - type Transactions struct { repo *chain.Repository pool *txpool.TxPool } -func New(repo *chain.Repository, pool *txpool.TxPool) utils.APIServer { +func New(repo *chain.Repository, pool *txpool.TxPool) *Transactions { return &Transactions{ repo, pool, @@ -232,7 +230,3 @@ func (t *Transactions) Mount(root *mux.Router, pathPrefix string) { Name("transactions_get_receipt"). HandlerFunc(utils.WrapHandlerFunc(t.handleGetTransactionReceiptByID)) } - -func (t *Transactions) MountDefaultPath(router *mux.Router) { - t.Mount(router, MountPath) -} diff --git a/api/transactions/transactions_test.go b/api/transactions/transactions_test.go index f1872e061..58a904e47 100644 --- a/api/transactions/transactions_test.go +++ b/api/transactions/transactions_test.go @@ -17,15 +17,15 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" - "github.com/vechain/thor/v2/node" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/transactions" "github.com/vechain/thor/v2/genesis" + "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" @@ -273,7 +273,7 @@ func httpPostAndCheckResponseStatus(t *testing.T, url string, obj interface{}, r } func initTransactionServer(t *testing.T) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) chainTag = thorChain.Repo().ChainTag() @@ -298,12 +298,6 @@ func initTransactionServer(t *testing.T) { mempool := txpool.New(thorChain.Repo(), thorChain.Stater(), txpool.Options{Limit: 10000, LimitPerAccount: 16, MaxLifetime: 10 * time.Minute}) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs(transactions.New(thorChain.Repo(), mempool)). - Build() - require.NoError(t, err) - mempoolTx = new(tx.Builder). ChainTag(chainTag). Expiration(10). @@ -323,7 +317,10 @@ func initTransactionServer(t *testing.T) { t.Fatal(e) } - ts = httptest.NewServer(thorNode.Router()) + router := mux.NewRouter() + transactions.New(thorChain.Repo(), mempool).Mount(router, "/transactions") + + ts = httptest.NewServer(router) } func checkMatchingTx(t *testing.T, expectedTx *tx.Transaction, actualTx *transactions.Transaction) { diff --git a/api/transfers/transfers.go b/api/transfers/transfers.go index d13b82415..cad4ee6b3 100644 --- a/api/transfers/transfers.go +++ b/api/transfers/transfers.go @@ -18,15 +18,13 @@ import ( "github.com/vechain/thor/v2/logdb" ) -const MountPath = "/logs/transfers" - type Transfers struct { repo *chain.Repository db *logdb.LogDB limit uint64 } -func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) utils.APIServer { +func New(repo *chain.Repository, db *logdb.LogDB, logsLimit uint64) *Transfers { return &Transfers{ repo, db, @@ -95,7 +93,3 @@ func (t *Transfers) Mount(root *mux.Router, pathPrefix string) { Name("logs_filter_transfer"). HandlerFunc(utils.WrapHandlerFunc(t.handleFilterTransferLogs)) } - -func (t *Transfers) MountDefaultPath(router *mux.Router) { - t.Mount(router, MountPath) -} diff --git a/api/transfers/transfers_test.go b/api/transfers/transfers_test.go index 948732b45..a519b5a5a 100644 --- a/api/transfers/transfers_test.go +++ b/api/transfers/transfers_test.go @@ -16,14 +16,14 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" - "github.com/vechain/thor/v2/node" - + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "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/logdb" + "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" "github.com/vechain/thor/v2/tx" ) @@ -162,16 +162,13 @@ func insertBlocks(t *testing.T, db *logdb.LogDB, n int) { } func initTransferServer(t *testing.T, logDb *logdb.LogDB, limit uint64) { - thorChain, err := node.NewIntegrationTestChain() + thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) - thorNode, err := new(node.Builder). - WithChain(thorChain). - WithAPIs(transfers.New(thorChain.Repo(), logDb, limit)). - Build() - require.NoError(t, err) + router := mux.NewRouter() + transfers.New(thorChain.Repo(), logDb, limit).Mount(router, "/logs/transfers") - ts = httptest.NewServer(thorNode.Router()) + ts = httptest.NewServer(router) } func createDb(t *testing.T) *logdb.LogDB { diff --git a/api/utils/http.go b/api/utils/http.go index b991205fe..652c3e408 100644 --- a/api/utils/http.go +++ b/api/utils/http.go @@ -9,8 +9,6 @@ import ( "encoding/json" "io" "net/http" - - "github.com/gorilla/mux" ) type httpError struct { @@ -89,8 +87,3 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { // M shortcut for type map[string]interface{}. type M map[string]interface{} - -type APIServer interface { - Mount(root *mux.Router, pathPrefix string) - MountDefaultPath(root *mux.Router) -} diff --git a/node/builder.go b/node/builder.go deleted file mode 100644 index 9aad89dec..000000000 --- a/node/builder.go +++ /dev/null @@ -1,74 +0,0 @@ -package node - -import ( - "github.com/gorilla/mux" - "github.com/vechain/thor/v2/api/accounts" - "github.com/vechain/thor/v2/api/utils" - "github.com/vechain/thor/v2/bft" - "github.com/vechain/thor/v2/chain" - "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/muxdb" - "github.com/vechain/thor/v2/state" - "github.com/vechain/thor/v2/thor" - "github.com/vechain/thor/v2/tx" -) - -type TxAndRcpt struct { - Transaction *tx.Transaction - ReceiptFunc func(tx.Receipts) -} - -func RegisterAccountsAPI( - gasLimit uint64, - forkConfig thor.ForkConfig, - bftFunc func(repo *chain.Repository) bft.Committer, -) func(repo *chain.Repository, stater *state.Stater, router *mux.Router) { - return func(repo *chain.Repository, stater *state.Stater, router *mux.Router) { - accounts.New(repo, stater, gasLimit, forkConfig, bftFunc(repo)).Mount(router, "/accounts") - } -} - -type Builder struct { - dbFunc func() *muxdb.MuxDB - genesisFunc func() *genesis.Genesis - engineFunc func(repo *chain.Repository) bft.Committer - router *mux.Router - chain *Chain -} - -func (b *Builder) WithDB(memFunc func() *muxdb.MuxDB) *Builder { - b.dbFunc = memFunc - return b -} - -func (b *Builder) WithGenesis(genesisFunc func() *genesis.Genesis) *Builder { - b.genesisFunc = genesisFunc - return b -} - -func (b *Builder) WithBFTEngine(engineFunc func(repo *chain.Repository) bft.Committer) *Builder { - b.engineFunc = engineFunc - return b -} - -func (b *Builder) WithAPIs(apis ...utils.APIServer) *Builder { - b.router = mux.NewRouter() - - for _, api := range apis { - api.MountDefaultPath(b.router) - } - - return b -} - -func (b *Builder) Build() (*Node, error) { - return &Node{ - chain: b.chain, - router: b.router, - }, nil -} - -func (b *Builder) WithChain(chain *Chain) *Builder { - b.chain = chain - return b -} diff --git a/node/node.go b/node/node.go deleted file mode 100644 index f03a797ef..000000000 --- a/node/node.go +++ /dev/null @@ -1,76 +0,0 @@ -package node - -import ( - "fmt" - "net/http" - "slices" - "time" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/genesis" - "github.com/vechain/thor/v2/packer" - "github.com/vechain/thor/v2/thor" -) - -type Node struct { - chain *Chain - router *mux.Router -} - -func (n *Node) Router() http.Handler { - return n.router -} - -func (n *Node) GenerateNewBlock() error { - packer := packer.New(n.chain.Repo(), n.chain.stater, genesis.DevAccounts()[0].Address, &genesis.DevAccounts()[0].Address, thor.NoFork) // todo this should pack with other accoutns - - sum := n.chain.repo.BestBlockSummary() - flow, err := packer.Schedule(sum, uint64(time.Now().Unix())) - if err != nil { - return fmt.Errorf("unable to schedule new block") - } - blk, stage, receipts, err := flow.Pack(genesis.DevAccounts()[0].PrivateKey, 0, false) - if err != nil { - return fmt.Errorf("unable to pack new block") - } - if _, err := stage.Commit(); err != nil { - return fmt.Errorf("unable to commit new block") - } - if err := n.chain.Repo().AddBlock(blk, receipts, 0); err != nil { - return fmt.Errorf("unable to add new block to chain") - } - if err := n.chain.Repo().SetBestBlockID(blk.Header().ID()); err != nil { - return fmt.Errorf("unable to set best block in chain") - } - return nil -} - -func (n *Node) GetAllBlocks() ([]*block.Block, error) { - bestBlkSummary := n.chain.Repo().BestBlockSummary() - var blks []*block.Block - currBlockID := bestBlkSummary.Header.ID() - startTime := time.Now() - for { - blk, err := n.chain.Repo().GetBlock(currBlockID) - if err != nil { - return nil, err - } - blks = append(blks, blk) - - if blk.Header().Number() == n.chain.genesisBlock.Header().Number() { - slices.Reverse(blks) // make sure genesis is at position 0 - return blks, err - } - currBlockID = blk.Header().ParentID() - - if time.Since(startTime) > 5*time.Second { - return nil, errors.New("taking more than 5 seconds to retrieve all blocks") - } - } -} - -func (n *Node) Chain() *Chain { - return n.chain -} diff --git a/node/chain.go b/test/testchain/chain.go similarity index 79% rename from node/chain.go rename to test/testchain/chain.go index 875f67b49..1ea5dc046 100644 --- a/node/chain.go +++ b/test/testchain/chain.go @@ -1,7 +1,9 @@ -package node +package testchain import ( + "errors" "fmt" + "slices" "time" "github.com/vechain/thor/v2/bft" @@ -88,3 +90,27 @@ func (c *Chain) MintBlock(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) } return nil } + +func (c *Chain) GetAllBlocks() ([]*block.Block, error) { + bestBlkSummary := c.Repo().BestBlockSummary() + var blks []*block.Block + currBlockID := bestBlkSummary.Header.ID() + startTime := time.Now() + for { + blk, err := c.repo.GetBlock(currBlockID) + if err != nil { + return nil, err + } + blks = append(blks, blk) + + 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() + + if time.Since(startTime) > 5*time.Second { + return nil, errors.New("taking more than 5 seconds to retrieve all blocks") + } + } +} diff --git a/node/chain_builder.go b/test/testchain/chain_builder.go similarity index 98% rename from node/chain_builder.go rename to test/testchain/chain_builder.go index f6582fd70..9f8d60fc2 100644 --- a/node/chain_builder.go +++ b/test/testchain/chain_builder.go @@ -1,4 +1,4 @@ -package node +package testchain import ( "github.com/vechain/thor/v2/bft" diff --git a/test/testchain/types.go b/test/testchain/types.go new file mode 100644 index 000000000..10009f235 --- /dev/null +++ b/test/testchain/types.go @@ -0,0 +1,8 @@ +package testchain + +import "github.com/vechain/thor/v2/tx" + +type TxAndRcpt struct { + Transaction *tx.Transaction + ReceiptFunc func(tx.Receipts) +} From 41a72abdb3d4130734f8509e88dbfb3674f4f077 Mon Sep 17 00:00:00 2001 From: otherview Date: Thu, 12 Sep 2024 17:25:52 +0100 Subject: [PATCH 04/11] clean up + comments --- api/subscriptions/block_reader_test.go | 2 +- test/testchain/chain.go | 50 +++++++++++++++++++++--- test/testchain/chain_builder.go | 53 +++++++++++++++++--------- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index f276641c0..f5ab37a7c 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -86,7 +86,7 @@ func initChain(t *testing.T) *testchain.Chain { return thorChain } -// This is a helper function to forcly insert an event into the output receipts +// This is a helper function to forcibly insert an event into the output receipts func insertMockOutputEvent(receipts tx.Receipts) { oldReceipt := receipts[0] events := make(tx.Events, 0) diff --git a/test/testchain/chain.go b/test/testchain/chain.go index 1ea5dc046..5799e0ef1 100644 --- a/test/testchain/chain.go +++ b/test/testchain/chain.go @@ -17,6 +17,9 @@ import ( "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 @@ -26,18 +29,28 @@ type Chain struct { genesisBlock *block.Block } +// 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 { var txAndRcpts []*TxAndRcpt for _, transaction := range transactions { @@ -46,56 +59,71 @@ func (c *Chain) MintTransactions(account genesis.DevAccount, transactions ...*tx return c.MintTransactionsWithReceiptFunc(account, txAndRcpts...) } +// MintTransactionsWithReceiptFunc mints a block by accepting transactions and their associated receipt functions. +// It calls MintBlock to finalize the process. func (c *Chain) MintTransactionsWithReceiptFunc(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) error { return c.MintBlock(account, txAndRcpts...) } +// MintBlock creates and finalizes a new block with the given transactions and receipts. +// 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, txAndRcpts ...*TxAndRcpt) error { - // create a new block + // 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, thor.NoFork) + + // Schedule a new block to be packed at the current time. blkFlow, err := blkPacker.Schedule(c.Repo().BestBlockSummary(), uint64(time.Now().Unix())) if err != nil { return fmt.Errorf("unable to schedule a new block: %w", err) } - // adopt transactions in the new block + // Adopt the provided transactions into the block. for _, txAndRcpt := range txAndRcpts { if err = blkFlow.Adopt(txAndRcpt.Transaction); err != nil { return fmt.Errorf("unable to adopt tx into block: %w", err) } } - // pack the transactions into the block + // 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) } - // modify any receipts in the new block + // Execute any receipt modification functions if provided. for _, txAndRcpt := range txAndRcpts { if txAndRcpt.ReceiptFunc != nil { txAndRcpt.ReceiptFunc(receipts) } } - // commit block to chain state + // 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 { @@ -103,14 +131,26 @@ func (c *Chain) GetAllBlocks() ([]*block.Block, error) { } 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 thor.GetForkConfig(c.GenesisBlock().Header().ID()) +} diff --git a/test/testchain/chain_builder.go b/test/testchain/chain_builder.go index 9f8d60fc2..6ef5faaaa 100644 --- a/test/testchain/chain_builder.go +++ b/test/testchain/chain_builder.go @@ -9,55 +9,74 @@ import ( "github.com/vechain/thor/v2/state" ) +// ChainBuilder is a builder pattern struct used to construct a Chain instance. +// It allows customization of the database, genesis configuration, and the consensus engine (BFT). type ChainBuilder struct { - dbFunc func() *muxdb.MuxDB - genesisFunc func() *genesis.Genesis - engineFunc func(repo *chain.Repository) bft.Committer + dbFunc func() *muxdb.MuxDB // Function to initialize the database. + genesisFunc func() *genesis.Genesis // Function to initialize the genesis configuration. + engineFunc func(repo *chain.Repository) bft.Committer // Function to initialize the BFT consensus engine. } +// 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) { + return new(ChainBuilder). + WithDB(muxdb.NewMem). // Use in-memory database. + WithGenesis(genesis.NewDevnet). // Use devnet genesis settings. + WithBFTEngine(solo.NewBFTEngine). // Use solo BFT engine. + Build() // Build and return the chain. +} + +// WithDB sets the database initialization function in the builder. +// It returns the updated builder instance to allow method chaining. func (b *ChainBuilder) WithDB(db func() *muxdb.MuxDB) *ChainBuilder { b.dbFunc = db return b } +// WithGenesis sets the genesis initialization function in the builder. +// It allows custom genesis settings and returns the updated builder instance. func (b *ChainBuilder) WithGenesis(genesisFunc func() *genesis.Genesis) *ChainBuilder { b.genesisFunc = genesisFunc return b } +// WithBFTEngine sets the consensus engine initialization function in the builder. +// It allows custom consensus settings and returns the updated builder instance. func (b *ChainBuilder) WithBFTEngine(engineFunc func(repo *chain.Repository) bft.Committer) *ChainBuilder { b.engineFunc = engineFunc return b } +// Build finalizes the chain creation process by calling the configured functions. +// It sets up the database, genesis block, state manager, repository, and consensus engine. func (b *ChainBuilder) Build() (*Chain, error) { + // Initialize the database using the provided function. db := b.dbFunc() + + // Create the state manager (Stater) with the initialized database. stater := state.NewStater(db) + + // Initialize the genesis block using the provided genesis function. gene := b.genesisFunc() 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 } + // Return a new Chain instance with all components initialized. return &Chain{ - db: db, - genesis: gene, - genesisBlock: geneBlk, - repo: repo, - stater: stater, - engine: b.engineFunc(repo), + db: db, // The initialized database. + genesis: gene, // The genesis configuration. + genesisBlock: geneBlk, // The genesis block. + repo: repo, // The chain repository. + stater: stater, // The state manager. + engine: b.engineFunc(repo), // The consensus engine using the repository. }, nil } - -func NewIntegrationTestChain() (*Chain, error) { - return new(ChainBuilder). - WithDB(muxdb.NewMem). - WithGenesis(genesis.NewDevnet). - WithBFTEngine(solo.NewBFTEngine). - Build() -} From f23b273863f4778771e0389c77028d5bdbfed9d2 Mon Sep 17 00:00:00 2001 From: otherview Date: Thu, 12 Sep 2024 17:27:53 +0100 Subject: [PATCH 05/11] adding license headers --- test/testchain/chain.go | 5 +++++ test/testchain/chain_builder.go | 5 +++++ test/testchain/types.go | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/test/testchain/chain.go b/test/testchain/chain.go index 5799e0ef1..26af9e18c 100644 --- a/test/testchain/chain.go +++ b/test/testchain/chain.go @@ -1,3 +1,8 @@ +// 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 ( diff --git a/test/testchain/chain_builder.go b/test/testchain/chain_builder.go index 6ef5faaaa..aa3eb2bdb 100644 --- a/test/testchain/chain_builder.go +++ b/test/testchain/chain_builder.go @@ -1,3 +1,8 @@ +// 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 ( diff --git a/test/testchain/types.go b/test/testchain/types.go index 10009f235..4981eb848 100644 --- a/test/testchain/types.go +++ b/test/testchain/types.go @@ -1,3 +1,8 @@ +// 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 "github.com/vechain/thor/v2/tx" From fa985e347b4d02be9acf0f2119d6eb937d1bd7e9 Mon Sep 17 00:00:00 2001 From: otherview Date: Fri, 13 Sep 2024 09:11:27 +0100 Subject: [PATCH 06/11] adding templating tests for thorclient --- thorclient/api_test.go | 376 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 thorclient/api_test.go diff --git a/thorclient/api_test.go b/thorclient/api_test.go new file mode 100644 index 000000000..4064564ae --- /dev/null +++ b/thorclient/api_test.go @@ -0,0 +1,376 @@ +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.RandomAddress() + + 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)) + }) +} From c5c8423524bdcf33e2c7c6bfb4d6076f354fc6b8 Mon Sep 17 00:00:00 2001 From: otherview Date: Mon, 16 Sep 2024 10:53:17 +0100 Subject: [PATCH 07/11] Remove test event hacks --- api/subscriptions/block_reader_test.go | 49 ++++++---------- api/subscriptions/event_reader_test.go | 2 +- api/subscriptions/subscriptions_test.go | 63 ++++++++++---------- test/eventcontract/event_contract.go | 77 +++++++++++++++++++++++++ test/testchain/chain.go | 27 ++------- thorclient/api_test.go | 5 ++ 6 files changed, 136 insertions(+), 87 deletions(-) create mode 100644 test/eventcontract/event_contract.go diff --git a/api/subscriptions/block_reader_test.go b/api/subscriptions/block_reader_test.go index f5ab37a7c..864978a70 100644 --- a/api/subscriptions/block_reader_test.go +++ b/api/subscriptions/block_reader_test.go @@ -9,10 +9,12 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/genesis" + "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" @@ -72,39 +74,24 @@ func initChain(t *testing.T) *testchain.Chain { BlockRef(tx.NewBlockRef(0)). Build() - sig, err := crypto.Sign(tr.SigningHash().Bytes(), genesis.DevAccounts()[0].PrivateKey) - if err != nil { - t.Fatal(err) - } + sig, err := crypto.Sign(tr.SigningHash().Bytes(), genesis.DevAccounts()[1].PrivateKey) + require.NoError(t, err) tr = tr.WithSignature(sig) - require.NoError(t, thorChain.MintTransactionsWithReceiptFunc( - genesis.DevAccounts()[0], - &testchain.TxAndRcpt{Transaction: tr, ReceiptFunc: insertMockOutputEvent}), - ) + 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)) return thorChain } - -// This is a helper function to forcibly 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, - } -} diff --git a/api/subscriptions/event_reader_test.go b/api/subscriptions/event_reader_test.go index a742a3d1b..71122f40f 100644 --- a/api/subscriptions/event_reader_test.go +++ b/api/subscriptions/event_reader_test.go @@ -49,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/subscriptions_test.go b/api/subscriptions/subscriptions_test.go index 4c25105b9..0c0bffe3a 100644 --- a/api/subscriptions/subscriptions_test.go +++ b/api/subscriptions/subscriptions_test.go @@ -17,6 +17,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/gorilla/mux" "github.com/gorilla/websocket" @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/genesis" + "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" @@ -214,29 +216,6 @@ func TestParseAddress(t *testing.T) { assert.Equal(t, expectedAddr, *result) } -// This is a helper function to forcly insert an event into the output receipts -func insertMockOutputEventRcpt(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, - } -} - func initSubscriptionsServer(t *testing.T) { thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) @@ -265,11 +244,20 @@ func initSubscriptionsServer(t *testing.T) { } tr = tr.WithSignature(sig) - require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(genesis.DevAccounts()[0], - &testchain.TxAndRcpt{ - Transaction: tr, - ReceiptFunc: insertMockOutputEventRcpt, // todo review this - })) + 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) @@ -308,11 +296,20 @@ func TestSubscriptionsBacktrace(t *testing.T) { } tr = tr.WithSignature(sig) - require.NoError(t, thorChain.MintTransactionsWithReceiptFunc(genesis.DevAccounts()[0], - &testchain.TxAndRcpt{ - Transaction: tr, - ReceiptFunc: insertMockOutputEventRcpt, // todo review this - })) + 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])) 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 index 26af9e18c..9e4853598 100644 --- a/test/testchain/chain.go +++ b/test/testchain/chain.go @@ -57,22 +57,12 @@ func (c *Chain) GenesisBlock() *block.Block { // 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 { - var txAndRcpts []*TxAndRcpt - for _, transaction := range transactions { - txAndRcpts = append(txAndRcpts, &TxAndRcpt{Transaction: transaction}) - } - return c.MintTransactionsWithReceiptFunc(account, txAndRcpts...) -} - -// MintTransactionsWithReceiptFunc mints a block by accepting transactions and their associated receipt functions. -// It calls MintBlock to finalize the process. -func (c *Chain) MintTransactionsWithReceiptFunc(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) error { - return c.MintBlock(account, txAndRcpts...) + return c.MintBlock(account, transactions...) } -// MintBlock creates and finalizes a new block with the given transactions and receipts. +// 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, txAndRcpts ...*TxAndRcpt) error { +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, thor.NoFork) @@ -83,8 +73,8 @@ func (c *Chain) MintBlock(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) } // Adopt the provided transactions into the block. - for _, txAndRcpt := range txAndRcpts { - if err = blkFlow.Adopt(txAndRcpt.Transaction); err != nil { + for _, trx := range transactions { + if err = blkFlow.Adopt(trx); err != nil { return fmt.Errorf("unable to adopt tx into block: %w", err) } } @@ -95,13 +85,6 @@ func (c *Chain) MintBlock(account genesis.DevAccount, txAndRcpts ...*TxAndRcpt) return fmt.Errorf("unable to pack tx: %w", err) } - // Execute any receipt modification functions if provided. - for _, txAndRcpt := range txAndRcpts { - if txAndRcpt.ReceiptFunc != nil { - txAndRcpt.ReceiptFunc(receipts) - } - } - // Commit the new block to the chain's state. if _, err := stage.Commit(); err != nil { return fmt.Errorf("unable to commit tx: %w", err) diff --git a/thorclient/api_test.go b/thorclient/api_test.go index 4064564ae..6c7f140c5 100644 --- a/thorclient/api_test.go +++ b/thorclient/api_test.go @@ -1,3 +1,8 @@ +// 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 ( From 0bde008f5bf0d0fc6f191f60ed334a6c810ae010 Mon Sep 17 00:00:00 2001 From: otherview Date: Mon, 16 Sep 2024 11:08:48 +0100 Subject: [PATCH 08/11] remove types --- test/testchain/types.go | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 test/testchain/types.go diff --git a/test/testchain/types.go b/test/testchain/types.go deleted file mode 100644 index 4981eb848..000000000 --- a/test/testchain/types.go +++ /dev/null @@ -1,13 +0,0 @@ -// 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 "github.com/vechain/thor/v2/tx" - -type TxAndRcpt struct { - Transaction *tx.Transaction - ReceiptFunc func(tx.Receipts) -} From 5db83a97d1965ebcef58e2d40420bd3eda823036 Mon Sep 17 00:00:00 2001 From: otherview Date: Tue, 5 Nov 2024 15:00:05 +0000 Subject: [PATCH 09/11] removed chain_builder + added logdb to testchain --- api/accounts/accounts_test.go | 3 +- api/blocks/blocks_test.go | 4 +- api/debug/debug_test.go | 3 +- api/events/events_test.go | 27 ++++------ api/metrics_test.go | 37 ++++---------- api/utils/revisions_test.go | 37 +++++--------- test/testchain/chain.go | 72 +++++++++++++++++++++++++++ test/testchain/chain_builder.go | 87 --------------------------------- 8 files changed, 105 insertions(+), 165 deletions(-) delete mode 100644 test/testchain/chain_builder.go diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index 7ccb6748b..9294723eb 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/accounts" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" @@ -292,7 +291,7 @@ func initAccountServer(t *testing.T) { ) router := mux.NewRouter() - accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, solo.NewBFTEngine(thorChain.Repo())). + accounts.New(thorChain.Repo(), thorChain.Stater(), uint64(gasLimit), thor.NoFork, thorChain.Engine()). Mount(router, "/accounts") ts = httptest.NewServer(router) diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index 5431e9086..dcb6c4e94 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -20,7 +20,6 @@ import ( "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/api/blocks" "github.com/vechain/thor/v2/block" - "github.com/vechain/thor/v2/cmd/thor/solo" "github.com/vechain/thor/v2/genesis" "github.com/vechain/thor/v2/test/testchain" "github.com/vechain/thor/v2/thor" @@ -198,8 +197,7 @@ func initBlockServer(t *testing.T) { blk = allBlocks[1] router := mux.NewRouter() - bftEngine := solo.NewBFTEngine(thorChain.Repo()) - blocks.New(thorChain.Repo(), bftEngine).Mount(router, "/blocks") + blocks.New(thorChain.Repo(), thorChain.Engine()).Mount(router, "/blocks") ts = httptest.NewServer(router) } diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index 9ccc33dcc..1275a9030 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/require" "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/builtin" - "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" @@ -550,7 +549,7 @@ func initDebugServer(t *testing.T) { forkConfig := thor.GetForkConfig(blk.Header().ID()) router := mux.NewRouter() - debug = New(thorChain.Repo(), thorChain.Stater(), forkConfig, 21000, true, solo.NewBFTEngine(thorChain.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 ef7b00ad4..b1268d378 100644 --- a/api/events/events_test.go +++ b/api/events/events_test.go @@ -34,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) @@ -48,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{ @@ -96,7 +93,7 @@ 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) + insertBlocks(t, thorChain.LogDB(), 6) res, statusCode, err = tclient.RawHTTPClient().RawHTTPPost("/logs/event", filter) require.NoError(t, err) assert.Equal(t, http.StatusForbidden, statusCode) @@ -180,21 +177,15 @@ func testEventWithBlocks(t *testing.T, expectedBlocks int) { } // Init functions -func initEventServer(t *testing.T, logDb *logdb.LogDB, limit uint64) { +func initEventServer(t *testing.T, limit uint64) *testchain.Chain { thorChain, err := testchain.NewIntegrationTestChain() require.NoError(t, err) router := mux.NewRouter() - events.New(thorChain.Repo(), logDb, limit).Mount(router, "/logs/event") + 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/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/test/testchain/chain.go b/test/testchain/chain.go index 9e4853598..c5e472e49 100644 --- a/test/testchain/chain.go +++ b/test/testchain/chain.go @@ -14,7 +14,9 @@ import ( "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" @@ -32,6 +34,66 @@ type Chain struct { repo *chain.Repository stater *state.Stater genesisBlock *block.Block + logDB *logdb.LogDB +} + +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, + } +} + +// 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. @@ -142,3 +204,13 @@ func (c *Chain) BestBlock() (*block.Block, error) { func (c *Chain) GetForkConfig() thor.ForkConfig { return thor.GetForkConfig(c.GenesisBlock().Header().ID()) } + +// 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/test/testchain/chain_builder.go b/test/testchain/chain_builder.go deleted file mode 100644 index aa3eb2bdb..000000000 --- a/test/testchain/chain_builder.go +++ /dev/null @@ -1,87 +0,0 @@ -// 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 ( - "github.com/vechain/thor/v2/bft" - "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" -) - -// ChainBuilder is a builder pattern struct used to construct a Chain instance. -// It allows customization of the database, genesis configuration, and the consensus engine (BFT). -type ChainBuilder struct { - dbFunc func() *muxdb.MuxDB // Function to initialize the database. - genesisFunc func() *genesis.Genesis // Function to initialize the genesis configuration. - engineFunc func(repo *chain.Repository) bft.Committer // Function to initialize the BFT consensus engine. -} - -// 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) { - return new(ChainBuilder). - WithDB(muxdb.NewMem). // Use in-memory database. - WithGenesis(genesis.NewDevnet). // Use devnet genesis settings. - WithBFTEngine(solo.NewBFTEngine). // Use solo BFT engine. - Build() // Build and return the chain. -} - -// WithDB sets the database initialization function in the builder. -// It returns the updated builder instance to allow method chaining. -func (b *ChainBuilder) WithDB(db func() *muxdb.MuxDB) *ChainBuilder { - b.dbFunc = db - return b -} - -// WithGenesis sets the genesis initialization function in the builder. -// It allows custom genesis settings and returns the updated builder instance. -func (b *ChainBuilder) WithGenesis(genesisFunc func() *genesis.Genesis) *ChainBuilder { - b.genesisFunc = genesisFunc - return b -} - -// WithBFTEngine sets the consensus engine initialization function in the builder. -// It allows custom consensus settings and returns the updated builder instance. -func (b *ChainBuilder) WithBFTEngine(engineFunc func(repo *chain.Repository) bft.Committer) *ChainBuilder { - b.engineFunc = engineFunc - return b -} - -// Build finalizes the chain creation process by calling the configured functions. -// It sets up the database, genesis block, state manager, repository, and consensus engine. -func (b *ChainBuilder) Build() (*Chain, error) { - // Initialize the database using the provided function. - db := b.dbFunc() - - // Create the state manager (Stater) with the initialized database. - stater := state.NewStater(db) - - // Initialize the genesis block using the provided genesis function. - gene := b.genesisFunc() - 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 - } - - // Return a new Chain instance with all components initialized. - return &Chain{ - db: db, // The initialized database. - genesis: gene, // The genesis configuration. - genesisBlock: geneBlk, // The genesis block. - repo: repo, // The chain repository. - stater: stater, // The state manager. - engine: b.engineFunc(repo), // The consensus engine using the repository. - }, nil -} From 51d640fd050d4b45c62c32a0886398beabe4f661 Mon Sep 17 00:00:00 2001 From: otherview Date: Wed, 6 Nov 2024 11:25:14 +0000 Subject: [PATCH 10/11] pr comments --- test/testchain/chain.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/testchain/chain.go b/test/testchain/chain.go index c5e472e49..adb450e0e 100644 --- a/test/testchain/chain.go +++ b/test/testchain/chain.go @@ -35,6 +35,7 @@ type Chain struct { stater *state.Stater genesisBlock *block.Block logDB *logdb.LogDB + forkConfig thor.ForkConfig } func New( @@ -54,6 +55,7 @@ func New( stater: stater, genesisBlock: genesisBlock, logDB: logDB, + forkConfig: thor.GetForkConfig(genesisBlock.Header().ID()), } } @@ -128,10 +130,14 @@ func (c *Chain) MintBlock(account genesis.DevAccount, transactions ...*tx.Transa // 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, thor.NoFork) - // Schedule a new block to be packed at the current time. - blkFlow, err := blkPacker.Schedule(c.Repo().BestBlockSummary(), uint64(time.Now().Unix())) + // 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 schedule a new block: %w", err) + return fmt.Errorf("unable to mock a new block: %w", err) } // Adopt the provided transactions into the block. @@ -202,7 +208,7 @@ func (c *Chain) BestBlock() (*block.Block, error) { // GetForkConfig returns the current fork configuration based on the ID of the genesis block. func (c *Chain) GetForkConfig() thor.ForkConfig { - return thor.GetForkConfig(c.GenesisBlock().Header().ID()) + return c.forkConfig } // Database returns the current database. From 6ffe126423329f7efc1e80633e3cf0c75e855fa8 Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Thu, 7 Nov 2024 10:15:13 +0000 Subject: [PATCH 11/11] Update test/testchain/chain.go Co-authored-by: libotony --- test/testchain/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testchain/chain.go b/test/testchain/chain.go index adb450e0e..b35687d14 100644 --- a/test/testchain/chain.go +++ b/test/testchain/chain.go @@ -128,7 +128,7 @@ func (c *Chain) MintTransactions(account genesis.DevAccount, transactions ...*tx // 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, thor.NoFork) + blkPacker := packer.New(c.Repo(), c.Stater(), account.Address, &genesis.DevAccounts()[0].Address, c.forkConfig) // Create a new block blkFlow, err := blkPacker.Mock(