diff --git a/.github/workflows/test-e2e.yaml b/.github/workflows/test-e2e.yaml index e891479be..999075b10 100644 --- a/.github/workflows/test-e2e.yaml +++ b/.github/workflows/test-e2e.yaml @@ -44,7 +44,7 @@ jobs: with: repository: vechain/thor-e2e-tests # https://github.com/vechain/thor-e2e-tests/tree/00bd3f1b949b05da94e82686e0089a11a136c34c - ref: 00bd3f1b949b05da94e82686e0089a11a136c34c + ref: 48aa71d61ee6438cc270f541a6a369731717ac06 - name: Download artifact uses: actions/download-artifact@v4 diff --git a/api/accounts/accounts.go b/api/accounts/accounts.go index 20e09b755..e8cd6cf90 100644 --- a/api/accounts/accounts.go +++ b/api/accounts/accounts.go @@ -17,6 +17,7 @@ import ( "github.com/pkg/errors" "github.com/vechain/thor/v2/api/utils" "github.com/vechain/thor/v2/bft" + "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/chain" "github.com/vechain/thor/v2/runtime" "github.com/vechain/thor/v2/state" @@ -49,10 +50,8 @@ func New( } } -func (a *Accounts) getCode(addr thor.Address, summary *chain.BlockSummary) ([]byte, error) { - code, err := a.stater. - NewState(summary.Header.StateRoot(), summary.Header.Number(), summary.Conflicts, summary.SteadyNum). - GetCode(addr) +func (a *Accounts) getCode(addr thor.Address, state *state.State) ([]byte, error) { + code, err := state.GetCode(addr) if err != nil { return nil, err } @@ -65,26 +64,27 @@ func (a *Accounts) handleGetCode(w http.ResponseWriter, req *http.Request) error if err != nil { return utils.BadRequest(errors.WithMessage(err, "address")) } - revision, err := utils.ParseRevision(req.URL.Query().Get("revision")) + revision, err := utils.ParseRevision(req.URL.Query().Get("revision"), false) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } - summary, err := utils.GetSummary(revision, a.repo, a.bft) + + _, st, err := utils.GetSummaryAndState(revision, a.repo, a.bft, a.stater) if err != nil { if a.repo.IsNotFound(err) { return utils.BadRequest(errors.WithMessage(err, "revision")) } return err } - code, err := a.getCode(addr, summary) + code, err := a.getCode(addr, st) if err != nil { return err } + return utils.WriteJSON(w, map[string]string{"code": hexutil.Encode(code)}) } -func (a *Accounts) getAccount(addr thor.Address, summary *chain.BlockSummary) (*Account, error) { - state := a.stater.NewState(summary.Header.StateRoot(), summary.Header.Number(), summary.Conflicts, summary.SteadyNum) +func (a *Accounts) getAccount(addr thor.Address, header *block.Header, state *state.State) (*Account, error) { b, err := state.GetBalance(addr) if err != nil { return nil, err @@ -93,7 +93,7 @@ func (a *Accounts) getAccount(addr thor.Address, summary *chain.BlockSummary) (* if err != nil { return nil, err } - energy, err := state.GetEnergy(addr, summary.Header.Timestamp()) + energy, err := state.GetEnergy(addr, header.Timestamp()) if err != nil { return nil, err } @@ -105,11 +105,8 @@ func (a *Accounts) getAccount(addr thor.Address, summary *chain.BlockSummary) (* }, nil } -func (a *Accounts) getStorage(addr thor.Address, key thor.Bytes32, summary *chain.BlockSummary) (thor.Bytes32, error) { - storage, err := a.stater. - NewState(summary.Header.StateRoot(), summary.Header.Number(), summary.Conflicts, summary.SteadyNum). - GetStorage(addr, key) - +func (a *Accounts) getStorage(addr thor.Address, key thor.Bytes32, state *state.State) (thor.Bytes32, error) { + storage, err := state.GetStorage(addr, key) if err != nil { return thor.Bytes32{}, err } @@ -121,18 +118,20 @@ func (a *Accounts) handleGetAccount(w http.ResponseWriter, req *http.Request) er if err != nil { return utils.BadRequest(errors.WithMessage(err, "address")) } - revision, err := utils.ParseRevision(req.URL.Query().Get("revision")) + revision, err := utils.ParseRevision(req.URL.Query().Get("revision"), false) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } - summary, err := utils.GetSummary(revision, a.repo, a.bft) + + summary, st, err := utils.GetSummaryAndState(revision, a.repo, a.bft, a.stater) if err != nil { if a.repo.IsNotFound(err) { return utils.BadRequest(errors.WithMessage(err, "revision")) } return err } - acc, err := a.getAccount(addr, summary) + + acc, err := a.getAccount(addr, summary.Header, st) if err != nil { return err } @@ -148,18 +147,20 @@ func (a *Accounts) handleGetStorage(w http.ResponseWriter, req *http.Request) er if err != nil { return utils.BadRequest(errors.WithMessage(err, "key")) } - revision, err := utils.ParseRevision(req.URL.Query().Get("revision")) + revision, err := utils.ParseRevision(req.URL.Query().Get("revision"), false) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } - summary, err := utils.GetSummary(revision, a.repo, a.bft) + + _, st, err := utils.GetSummaryAndState(revision, a.repo, a.bft, a.stater) if err != nil { if a.repo.IsNotFound(err) { return utils.BadRequest(errors.WithMessage(err, "revision")) } return err } - storage, err := a.getStorage(addr, key, summary) + + storage, err := a.getStorage(addr, key, st) if err != nil { return err } @@ -171,11 +172,11 @@ func (a *Accounts) handleCallContract(w http.ResponseWriter, req *http.Request) if err := utils.ParseJSON(req.Body, &callData); err != nil { return utils.BadRequest(errors.WithMessage(err, "body")) } - revision, err := utils.ParseRevision(req.URL.Query().Get("revision")) + revision, err := utils.ParseRevision(req.URL.Query().Get("revision"), true) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } - summary, err := utils.GetSummary(revision, a.repo, a.bft) + summary, st, err := utils.GetSummaryAndState(revision, a.repo, a.bft, a.stater) if err != nil { if a.repo.IsNotFound(err) { return utils.BadRequest(errors.WithMessage(err, "revision")) @@ -202,7 +203,7 @@ func (a *Accounts) handleCallContract(w http.ResponseWriter, req *http.Request) GasPrice: callData.GasPrice, Caller: callData.Caller, } - results, err := a.batchCall(req.Context(), batchCallData, summary) + results, err := a.batchCall(req.Context(), batchCallData, summary.Header, st) if err != nil { return err } @@ -214,34 +215,37 @@ func (a *Accounts) handleCallBatchCode(w http.ResponseWriter, req *http.Request) if err := utils.ParseJSON(req.Body, &batchCallData); err != nil { return utils.BadRequest(errors.WithMessage(err, "body")) } - revision, err := utils.ParseRevision(req.URL.Query().Get("revision")) + revision, err := utils.ParseRevision(req.URL.Query().Get("revision"), true) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } - summary, err := utils.GetSummary(revision, a.repo, a.bft) + summary, st, err := utils.GetSummaryAndState(revision, a.repo, a.bft, a.stater) if err != nil { if a.repo.IsNotFound(err) { return utils.BadRequest(errors.WithMessage(err, "revision")) } return err } - results, err := a.batchCall(req.Context(), batchCallData, summary) + results, err := a.batchCall(req.Context(), batchCallData, summary.Header, st) if err != nil { return err } return utils.WriteJSON(w, results) } -func (a *Accounts) batchCall(ctx context.Context, batchCallData *BatchCallData, summary *chain.BlockSummary) (results BatchCallResults, err error) { +func (a *Accounts) batchCall( + ctx context.Context, + batchCallData *BatchCallData, + header *block.Header, + st *state.State, +) (results BatchCallResults, err error) { txCtx, gas, clauses, err := a.handleBatchCallData(batchCallData) if err != nil { return nil, err } - header := summary.Header - state := a.stater.NewState(header.StateRoot(), header.Number(), summary.Conflicts, summary.SteadyNum) signer, _ := header.Signer() - rt := runtime.New(a.repo.NewChain(header.ParentID()), state, + rt := runtime.New(a.repo.NewChain(header.ParentID()), st, &xenv.BlockContext{ Beneficiary: header.Beneficiary(), Signer: signer, diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go index 67ce4cd84..9e4c622eb 100644 --- a/api/accounts/accounts_test.go +++ b/api/accounts/accounts_test.go @@ -375,6 +375,13 @@ func callContract(t *testing.T) { reqBody := &accounts.CallData{ Data: hexutil.Encode(input), } + + // next revisoun should be valid + _, statusCode = httpPost(t, ts.URL+"/accounts/"+contractAddr.String()+"?revision=next", reqBody) + assert.Equal(t, http.StatusOK, statusCode, "next revision should be okay") + _, statusCode = httpPost(t, ts.URL+"/accounts?revision=next", reqBody) + assert.Equal(t, http.StatusOK, statusCode, "next revision should be okay") + res, statusCode := httpPost(t, ts.URL+"/accounts/"+contractAddr.String(), reqBody) var output *accounts.CallResult if err = json.Unmarshal(res, &output); err != nil { @@ -464,6 +471,10 @@ func batchCall(t *testing.T) { }}, } + // 'next' revisoun should be valid + _, statusCode = httpPost(t, ts.URL+"/accounts/*?revision=next", reqBody) + assert.Equal(t, http.StatusOK, statusCode, "next revision should be okay") + res, statusCode := httpPost(t, ts.URL+"/accounts/*", reqBody) var results accounts.BatchCallResults if err = json.Unmarshal(res, &results); err != nil { diff --git a/api/blocks/blocks.go b/api/blocks/blocks.go index 2a1c35fff..3062138c7 100644 --- a/api/blocks/blocks.go +++ b/api/blocks/blocks.go @@ -30,7 +30,7 @@ func New(repo *chain.Repository, bft bft.Finalizer) *Blocks { } func (b *Blocks) handleGetBlock(w http.ResponseWriter, req *http.Request) error { - revision, err := utils.ParseRevision(mux.Vars(req)["revision"]) + revision, err := utils.ParseRevision(mux.Vars(req)["revision"], false) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } diff --git a/api/blocks/blocks_test.go b/api/blocks/blocks_test.go index c2b676422..f6d8feffe 100644 --- a/api/blocks/blocks_test.go +++ b/api/blocks/blocks_test.go @@ -42,16 +42,25 @@ func TestBlock(t *testing.T) { initBlockServer(t) defer ts.Close() - testBadQueryParams(t) - testInvalidBlockId(t) - testInvalidBlockNumber(t) - testGetBlockById(t) - testGetBlockNotFound(t) - testGetExpandedBlockById(t) - testGetBlockByHeight(t) - testGetBestBlock(t) - testGetFinalizedBlock(t) - testGetBlockWithRevisionNumberTooHigh(t) + tests := []struct { + name string + run func(*testing.T) + }{ + {"testBadQueryParams", testBadQueryParams}, + {"testInvalidBlockId", testInvalidBlockId}, + {"testInvalidBlockNumber", testInvalidBlockNumber}, + {"testGetBlockById", testGetBlockById}, + {"testGetBlockNotFound", testGetBlockNotFound}, + {"testGetExpandedBlockById", testGetExpandedBlockById}, + {"testGetBlockByHeight", testGetBlockByHeight}, + {"testGetBestBlock", testGetBestBlock}, + {"testGetFinalizedBlock", testGetFinalizedBlock}, + {"testGetBlockWithRevisionNumberTooHigh", testGetBlockWithRevisionNumberTooHigh}, + } + + for _, tt := range tests { + t.Run(tt.name, tt.run) + } } func testBadQueryParams(t *testing.T) { diff --git a/api/debug/debug.go b/api/debug/debug.go index 6ca200fc7..646657c6f 100644 --- a/api/debug/debug.go +++ b/api/debug/debug.go @@ -20,6 +20,7 @@ import ( "github.com/pkg/errors" "github.com/vechain/thor/v2/api/utils" "github.com/vechain/thor/v2/bft" + "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/chain" "github.com/vechain/thor/v2/consensus" "github.com/vechain/thor/v2/genesis" @@ -177,11 +178,11 @@ func (d *Debug) handleTraceCall(w http.ResponseWriter, req *http.Request) error if err := utils.ParseJSON(req.Body, &opt); err != nil { return utils.BadRequest(errors.WithMessage(err, "body")) } - revision, err := utils.ParseRevision(req.URL.Query().Get("revision")) + revision, err := utils.ParseRevision(req.URL.Query().Get("revision"), true) if err != nil { return utils.BadRequest(errors.WithMessage(err, "revision")) } - summary, err := utils.GetSummary(revision, d.repo, d.bft) + summary, st, err := utils.GetSummaryAndState(revision, d.repo, d.bft, d.stater) if err != nil { if d.repo.IsNotFound(err) { return utils.BadRequest(errors.WithMessage(err, "revision")) @@ -199,7 +200,7 @@ func (d *Debug) handleTraceCall(w http.ResponseWriter, req *http.Request) error return err } - res, err := d.traceCall(req.Context(), tracer, summary, txCtx, gas, clause) + res, err := d.traceCall(req.Context(), tracer, summary.Header, st, txCtx, gas, clause) if err != nil { return err } @@ -214,13 +215,12 @@ func (d *Debug) createTracer(name string, config json.RawMessage) (tracers.Trace return tracers.DefaultDirectory.New(name, config, d.allowCustomTracer) } -func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, summary *chain.BlockSummary, txCtx *xenv.TransactionContext, gas uint64, clause *tx.Clause) (interface{}, error) { - header := summary.Header - state := d.stater.NewState(header.StateRoot(), header.Number(), summary.Conflicts, summary.SteadyNum) +func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, header *block.Header, st *state.State, txCtx *xenv.TransactionContext, gas uint64, clause *tx.Clause) (interface{}, error) { signer, _ := header.Signer() + rt := runtime.New( - d.repo.NewChain(header.ID()), - state, + d.repo.NewChain(header.ParentID()), + st, &xenv.BlockContext{ Beneficiary: header.Beneficiary(), Signer: signer, @@ -232,9 +232,9 @@ func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, summary *c d.forkConfig) tracer.SetContext(&tracers.Context{ - BlockID: summary.Header.ID(), - BlockTime: summary.Header.Timestamp(), - State: state, + BlockID: header.ID(), + BlockTime: header.Timestamp(), + State: st, }) rt.SetVMConfig(vm.Config{Tracer: tracer}) diff --git a/api/debug/debug_test.go b/api/debug/debug_test.go index b7534bf67..8cc6204ca 100644 --- a/api/debug/debug_test.go +++ b/api/debug/debug_test.go @@ -65,6 +65,7 @@ func TestDebug(t *testing.T) { testHandleTraceCallWithMalformedBodyRequest(t) testHandleTraceCallWithEmptyTraceCallOption(t) testHandleTraceCall(t) + testTraceCallNextBlock(t) testHandleTraceCallWithValidRevisions(t) testHandleTraceCallWithRevisionAsNonExistingHeight(t) testHandleTraceCallWithRevisionAsNonExistingId(t) @@ -102,7 +103,6 @@ func TestStorageRangeFunc(t *testing.T) { } storageRangeRes, err := storageRangeAt(trie, start, 1) - assert.NoError(t, err) assert.NotNil(t, storageRangeRes.NextKey) storage := storageRangeRes.Storage @@ -250,6 +250,11 @@ func testHandleTraceCallWithEmptyTraceCallOption(t *testing.T) { assert.Equal(t, expectedExecutionResult, parsedExecutionRes) } +func testTraceCallNextBlock(t *testing.T) { + traceCallOption := &TraceCallOption{} + httpPostAndCheckResponseStatus(t, ts.URL+"/debug/tracers/call?revision=next", traceCallOption, 200) +} + func testHandleTraceCall(t *testing.T) { addr := randAddress() provedWork := math.HexOrDecimal256(*big.NewInt(1000)) diff --git a/api/doc/thor.yaml b/api/doc/thor.yaml index 5c31b84ca..2c6d03d47 100644 --- a/api/doc/thor.yaml +++ b/api/doc/thor.yaml @@ -12,7 +12,7 @@ info: license: name: LGPL 3.0 url: https://www.gnu.org/licenses/lgpl-3.0.en.html - version: 2.1.2 + version: 2.1.3 servers: - url: / description: Current Node @@ -70,7 +70,7 @@ paths: /accounts/*: post: parameters: - - $ref: '#/components/parameters/RevisionInQuery' + - $ref: '#/components/parameters/CallCodeRevisionInQuery' tags: - Accounts summary: Inspect clauses @@ -83,6 +83,8 @@ paths: - Estimate the gas consumption of a transaction. Note: The `caller` field should be provided for higher accuracy. The fields `gasPrice`, `gasPayer`, `provedWork`, `blockRef` and `expiration` are for exposing themselves in EVM. Transaction meta features won't be reflected in the result, for example, no error is returned if the transaction is technically expired. For more information, please refer to the vechain documentation. + + It is recommended to set the `revision` query parameter to `next` when estimating gas for a transaction. To access historical details, you can specify a `revision` as a query parameter. requestBody: @@ -90,7 +92,7 @@ paths: content: application/json: schema: - $ref : '#/components/schemas/ExecuteCodesRequest' + $ref: '#/components/schemas/ExecuteCodesRequest' responses: '200': description: OK @@ -692,8 +694,9 @@ paths: This endpoint enables clients to create a tracer for a specific vechain function call. You can customize the tracer using various options to suit your debugging requirements. + parameters: - - $ref: '#/components/parameters/RevisionInQuery' + - $ref: '#/components/parameters/CallCodeRevisionInQuery' requestBody: required: true content: @@ -817,13 +820,13 @@ components: - "0x000000000000000000000000435933c8064b4ae76be665428e0307ef2ccfbd68" - "0x0000000000000000000000000f872421dc479f3c11edd89512731814d0598db5" data: "0x0000000000000000000000000000000000000000000000013f306a2409fc0000" - transfers: [] + transfers: [ ] gasUsed: 13326 reverted: false vmError: "" # VET Transfer - data: "0x" - events: [] + events: [ ] transfers: - sender: "0x435933c8064b4ae76be665428e0307ef2ccfbd68" recipient: "0xf077b491b355e64048ce21e3a6fc4751eeea77fa" @@ -838,7 +841,7 @@ components: topics: - "0xb35bf4274d4295009f1ec66ed3f579db287889444366c03d3a695539372e8951" data: "0x000000000000000000000000435933c8064b4ae76be665428e0307ef2ccfbd68" - transfers: [] + transfers: [ ] gasUsed: 31881 reverted: false vmError: "" @@ -1184,7 +1187,7 @@ components: example: target: '0x010709463c1f0c9aa66a31182fb36d1977d99bfb6526bae0564a0eac4006c31a/0/0' name: "prestate" - config: {} + config: { } PostDebugTracerCallRequest: title: PostDebugTracerCallRequest @@ -2132,11 +2135,11 @@ components: type: object description: | The configuration of the tracer. It is specific to the `name` - example: {} + example: { } nullable: true example: name: "prestate" - config: {} + config: { } StorageRangeOption: type: object @@ -2253,6 +2256,20 @@ components: schema: type: string + CallCodeRevisionInQuery: + name: revision + in: query + description: | + Specify either `best`,`next`, `finalized`, a block number or block ID. If omitted, the `best` block is assumed. + + If the `next` block is specified, the call code will be executed on the next block, with the following: + - The block number is the `best` block number plus one. + - The timestamp is the `best` block timestamp plus the block interval. + - Total score, gas limit and beneficiary are the same as the `best` block. + - The signer will be the zero address + schema: + type: string + RevisionInPath: name: revision in: path diff --git a/api/utils/revisions.go b/api/utils/revisions.go index 3632c4d35..c4cf668cb 100644 --- a/api/utils/revisions.go +++ b/api/utils/revisions.go @@ -6,53 +6,54 @@ package utils import ( + "errors" "math" "strconv" - "github.com/pkg/errors" "github.com/vechain/thor/v2/bft" + "github.com/vechain/thor/v2/block" "github.com/vechain/thor/v2/chain" + "github.com/vechain/thor/v2/state" "github.com/vechain/thor/v2/thor" ) -type Revision = interface{} +const ( + revBest int64 = -1 + revFinalized int64 = -2 + revNext int64 = -3 +) -func GetSummary(revision Revision, repo *chain.Repository, bft bft.Finalizer) (s *chain.BlockSummary, err error) { - var id thor.Bytes32 - switch revision := revision.(type) { - case thor.Bytes32: - id = revision - case uint32: - id, err = repo.NewBestChain().GetBlockID(revision) - if err != nil { - return - } - case string: - id = bft.Finalized() - default: - id = repo.BestBlockSummary().Header.ID() - } - summary, err := repo.GetBlockSummary(id) - if err != nil { - return nil, err - } - return summary, nil +type Revision struct { + val interface{} +} + +func (rev *Revision) IsNext() bool { + return rev.val == revNext } // ParseRevision parses a query parameter into a block number or block ID. -func ParseRevision(revision string) (Revision, error) { +func ParseRevision(revision string, allowNext bool) (*Revision, error) { if revision == "" || revision == "best" { - return nil, nil + return &Revision{revBest}, nil } + if revision == "finalized" { - return revision, nil + return &Revision{revFinalized}, nil + } + + if revision == "next" { + if !allowNext { + return nil, errors.New("invalid revision: next is not allowed") + } + return &Revision{revNext}, nil } + if len(revision) == 66 || len(revision) == 64 { blockID, err := thor.ParseBytes32(revision) if err != nil { return nil, err } - return blockID, nil + return &Revision{blockID}, nil } n, err := strconv.ParseUint(revision, 0, 0) if err != nil { @@ -61,5 +62,86 @@ func ParseRevision(revision string) (Revision, error) { if n > math.MaxUint32 { return nil, errors.New("block number out of max uint32") } - return uint32(n), err + return &Revision{uint32(n)}, err +} + +// GetSummary returns the block summary for the given revision, +// revision required to be a deterministic block other than "next". +func GetSummary(rev *Revision, repo *chain.Repository, bft bft.Finalizer) (sum *chain.BlockSummary, err error) { + var id thor.Bytes32 + switch rev := rev.val.(type) { + case thor.Bytes32: + id = rev + case uint32: + id, err = repo.NewBestChain().GetBlockID(rev) + if err != nil { + return + } + case int64: + switch rev { + case revBest: + id = repo.BestBlockSummary().Header.ID() + case revFinalized: + id = bft.Finalized() + } + } + if id.IsZero() { + return nil, errors.New("invalid revision") + } + summary, err := repo.GetBlockSummary(id) + if err != nil { + return nil, err + } + return summary, nil +} + +// GetSummaryAndState returns the block summary and state for the given revision, +// this function supports the "next" revision. +func GetSummaryAndState(rev *Revision, repo *chain.Repository, bft bft.Finalizer, stater *state.Stater) (*chain.BlockSummary, *state.State, error) { + if rev.IsNext() { + best := repo.BestBlockSummary() + + // here we create a fake(no signature) "next" block header which reused most part of the parent block + // but set the timestamp and number to the next block. The following parameters will be used in the evm + // number, timestamp, total score, gas limit, beneficiary and "signer" + // since the fake block is not signed, the signer is the zero address, it is important that the subsequent + // call to header.Signer(), the error should be ignored. + builder := new(block.Builder). + ParentID(best.Header.ID()). + Timestamp(best.Header.Timestamp() + thor.BlockInterval). + TotalScore(best.Header.TotalScore()). + GasLimit(best.Header.GasLimit()). + GasUsed(best.Header.GasUsed()). + Beneficiary(best.Header.Beneficiary()). + StateRoot(best.Header.StateRoot()). + ReceiptsRoot(best.Header.ReceiptsRoot()). + TransactionFeatures(best.Header.TxsFeatures()). + Alpha(best.Header.Alpha()) + + // here we skipped the block's tx list thus header.txRoot will be an empty root + // since txRoot won't be supplied into the evm, it's safe to skip it. + if best.Header.COM() { + builder.COM() + } + mocked := builder.Build() + + // state is also reused from the parent block + st := stater.NewState(best.Header.StateRoot(), best.Header.Number(), best.Conflicts, best.SteadyNum) + + // rebuild the block summary with the next header (mocked) AND the best block status + return &chain.BlockSummary{ + Header: mocked.Header(), + Txs: best.Txs, + Size: uint64(mocked.Size()), + Conflicts: best.Conflicts, + SteadyNum: best.SteadyNum, + }, st, nil + } + sum, err := GetSummary(rev, repo, bft) + if err != nil { + return nil, nil, err + } + + st := stater.NewState(sum.Header.StateRoot(), sum.Header.Number(), sum.Conflicts, sum.SteadyNum) + return sum, st, nil } diff --git a/api/utils/revisions_test.go b/api/utils/revisions_test.go index 00d721bc9..3af996693 100644 --- a/api/utils/revisions_test.go +++ b/api/utils/revisions_test.go @@ -24,32 +24,36 @@ func TestParseRevision(t *testing.T) { testCases := []struct { revision string err error - expected Revision + expected *Revision }{ { revision: "", err: nil, - expected: nil, + expected: &Revision{revBest}, }, { revision: "1234", err: nil, - expected: uint32(1234), + expected: &Revision{uint32(1234)}, }, { revision: "best", err: nil, - expected: nil, + expected: &Revision{revBest}, }, { revision: "finalized", err: nil, - expected: "finalized", + expected: &Revision{revFinalized}, + }, { + revision: "next", + err: nil, + expected: &Revision{revNext}, }, { revision: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", err: nil, - expected: thor.MustParseBytes32("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + expected: &Revision{thor.MustParseBytes32("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")}, }, { revision: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdzz", @@ -70,7 +74,7 @@ func TestParseRevision(t *testing.T) { for _, tc := range testCases { t.Run(tc.revision, func(t *testing.T) { - result, err := ParseRevision(tc.revision) + result, err := ParseRevision(tc.revision, true) if tc.err != nil { assert.Equal(t, tc.err.Error(), err.Error()) } else { @@ -81,6 +85,16 @@ func TestParseRevision(t *testing.T) { } } +func TestAllowNext(t *testing.T) { + _, err := ParseRevision("next", false) + assert.Error(t, err, "invalid revision: next is not allowed") + + _, err = ParseRevision("next", true) + assert.Nil(t, err) + _, err = ParseRevision("finalized", false) + assert.Nil(t, err) +} + func TestGetSummary(t *testing.T) { db := muxdb.NewMem() stater := state.NewStater(db) @@ -94,29 +108,39 @@ func TestGetSummary(t *testing.T) { // Test cases testCases := []struct { - revision Revision + name string + revision *Revision err error }{ { - revision: nil, + name: "best", + revision: &Revision{revBest}, err: nil, }, { - revision: uint32(1234), + name: "1234", + revision: &Revision{uint32(1234)}, err: errors.New("not found"), }, { - revision: "finalized", + name: "finalized", + revision: &Revision{revFinalized}, err: nil, }, { - revision: thor.MustParseBytes32("0x00000000c05a20fbca2bf6ae3affba6af4a74b800b585bf7a4988aba7aea69f6"), + name: "0x00000000c05a20fbca2bf6ae3affba6af4a74b800b585bf7a4988aba7aea69f6", + revision: &Revision{thor.MustParseBytes32("0x00000000c05a20fbca2bf6ae3affba6af4a74b800b585bf7a4988aba7aea69f6")}, err: nil, }, + { + name: "next", + revision: &Revision{revNext}, + err: errors.New("invalid revision"), + }, } for _, tc := range testCases { - t.Run(fmt.Sprintf("%v", tc.revision), func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { summary, err := GetSummary(tc.revision, repo, bft) if tc.err != nil { assert.Equal(t, tc.err.Error(), err.Error()) @@ -127,3 +151,29 @@ 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) + + summary, _, err := GetSummaryAndState(&Revision{revBest}, repo, bft, 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) + 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) + + signer, err := summary.Header.Signer() + assert.NotNil(t, err) + assert.True(t, signer.IsZero()) +} diff --git a/cmd/thor/VERSION b/cmd/thor/VERSION index eca07e4c1..ac2cdeba0 100644 --- a/cmd/thor/VERSION +++ b/cmd/thor/VERSION @@ -1 +1 @@ -2.1.2 +2.1.3