Skip to content

Commit

Permalink
feat: add support for 'finalized' revision in accounts (vechain#689)
Browse files Browse the repository at this point in the history
* feat: add support for 'finalized' revision in accounts

* fix: golanglint error

* test: add a finalized account test

* chore: update thor.yaml

* blocks: test for finalized id

* fix: accounts test

* feat: add finalized and best support to other endpoints

* fix: thor.yaml

* fix: e2e tests

* revert solo finalized block and fix tests

* revert transactions endpoints and move finalizer interface

* revert transactions endpoints and move finalizer interface

* update thor.yaml

* remove whitespace

* add tests

* add coverage

* Update api/debug/debug.go

Co-authored-by: libotony <[email protected]>

* Update api/debug/debug.go

Co-authored-by: libotony <[email protected]>

---------

Co-authored-by: libotony <[email protected]>
  • Loading branch information
darrenvechain and libotony authored May 14, 2024
1 parent 95e06e8 commit de6037f
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 179 deletions.
87 changes: 45 additions & 42 deletions api/accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
"fmt"
"math/big"
"net/http"
"strconv"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/vechain/thor/v2/api/utils"
"github.com/vechain/thor/v2/bft"
"github.com/vechain/thor/v2/chain"
"github.com/vechain/thor/v2/runtime"
"github.com/vechain/thor/v2/state"
Expand All @@ -30,19 +30,22 @@ type Accounts struct {
stater *state.Stater
callGasLimit uint64
forkConfig thor.ForkConfig
bft bft.Finalizer
}

func New(
repo *chain.Repository,
stater *state.Stater,
callGasLimit uint64,
forkConfig thor.ForkConfig,
bft bft.Finalizer,
) *Accounts {
return &Accounts{
repo,
stater,
callGasLimit,
forkConfig,
bft,
}
}

Expand All @@ -62,8 +65,15 @@ func (a *Accounts) handleGetCode(w http.ResponseWriter, req *http.Request) error
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "address"))
}
summary, err := a.handleRevision(req.URL.Query().Get("revision"))
revision, err := utils.ParseRevision(req.URL.Query().Get("revision"))
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := utils.GetSummary(revision, a.repo, a.bft)
if err != nil {
if a.repo.IsNotFound(err) {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
return err
}
code, err := a.getCode(addr, summary)
Expand Down Expand Up @@ -111,8 +121,15 @@ func (a *Accounts) handleGetAccount(w http.ResponseWriter, req *http.Request) er
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "address"))
}
summary, err := a.handleRevision(req.URL.Query().Get("revision"))
revision, err := utils.ParseRevision(req.URL.Query().Get("revision"))
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := utils.GetSummary(revision, a.repo, a.bft)
if err != nil {
if a.repo.IsNotFound(err) {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
return err
}
acc, err := a.getAccount(addr, summary)
Expand All @@ -131,8 +148,15 @@ func (a *Accounts) handleGetStorage(w http.ResponseWriter, req *http.Request) er
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "key"))
}
summary, err := a.handleRevision(req.URL.Query().Get("revision"))
revision, err := utils.ParseRevision(req.URL.Query().Get("revision"))
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := utils.GetSummary(revision, a.repo, a.bft)
if err != nil {
if a.repo.IsNotFound(err) {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
return err
}
storage, err := a.getStorage(addr, key, summary)
Expand All @@ -147,8 +171,15 @@ 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"))
}
summary, err := a.handleRevision(req.URL.Query().Get("revision"))
revision, err := utils.ParseRevision(req.URL.Query().Get("revision"))
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := utils.GetSummary(revision, a.repo, a.bft)
if err != nil {
if a.repo.IsNotFound(err) {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
return err
}
var addr *thor.Address
Expand Down Expand Up @@ -183,11 +214,18 @@ 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"))
}
h, err := a.handleRevision(req.URL.Query().Get("revision"))
revision, err := utils.ParseRevision(req.URL.Query().Get("revision"))
if err != nil {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := utils.GetSummary(revision, a.repo, a.bft)
if err != nil {
if a.repo.IsNotFound(err) {
return utils.BadRequest(errors.WithMessage(err, "revision"))
}
return err
}
results, err := a.batchCall(req.Context(), batchCallData, h)
results, err := a.batchCall(req.Context(), batchCallData, summary)
if err != nil {
return err
}
Expand Down Expand Up @@ -311,41 +349,6 @@ func (a *Accounts) handleBatchCallData(batchCallData *BatchCallData) (txCtx *xen
return
}

func (a *Accounts) handleRevision(revision string) (*chain.BlockSummary, error) {
if revision == "" || revision == "best" {
return a.repo.BestBlockSummary(), nil
}
if len(revision) == 66 || len(revision) == 64 {
blockID, err := thor.ParseBytes32(revision)
if err != nil {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
summary, err := a.repo.GetBlockSummary(blockID)
if err != nil {
if a.repo.IsNotFound(err) {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
return nil, err
}
return summary, nil
}
n, err := strconv.ParseUint(revision, 0, 0)
if err != nil {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
if n > math.MaxUint32 {
return nil, utils.BadRequest(errors.WithMessage(errors.New("block number out of max uint32"), "revision"))
}
summary, err := a.repo.NewBestChain().GetBlockSummary(uint32(n))
if err != nil {
if a.repo.IsNotFound(err) {
return nil, utils.BadRequest(errors.WithMessage(err, "revision"))
}
return nil, err
}
return summary, nil
}

func (a *Accounts) Mount(root *mux.Router, pathPrefix string) {
sub := root.PathPrefix(pathPrefix).Subrouter()

Expand Down
77 changes: 72 additions & 5 deletions api/accounts/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"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"
Expand Down Expand Up @@ -110,11 +111,16 @@ func TestAccount(t *testing.T) {
getAccount(t)
getAccountWithNonExisitingRevision(t)
getAccountWithGenesisRevision(t)
getAccountWithFinalizedRevision(t)
getCode(t)
getCodeWithNonExisitingRevision(t)
getStorage(t)
getStorageWithNonExisitingRevision(t)
deployContractWithCall(t)
callContract(t)
callContractWithNonExisitingRevision(t)
batchCall(t)
batchCallWithNonExisitingRevision(t)
}

func getAccount(t *testing.T) {
Expand All @@ -135,11 +141,12 @@ func getAccount(t *testing.T) {
}

func getAccountWithNonExisitingRevision(t *testing.T) {
revision64Len := "0123456789012345678901234567890123456789012345678901234567890123"
revision64Len := "0x00000000851caf3cfdb6e899cf5958bfb1ac3413d346d43539627e6be7ec1b4a"

_, statusCode := httpGet(t, ts.URL+"/accounts/"+addr.String()+"?revision="+revision64Len)
res, statusCode := httpGet(t, ts.URL+"/accounts/"+addr.String()+"?revision="+revision64Len)

assert.Equal(t, http.StatusBadRequest, statusCode, "bad revision")
assert.Equal(t, "revision: leveldb: not found\n", string(res), "revision not found")
}

func getAccountWithGenesisRevision(t *testing.T) {
Expand All @@ -162,6 +169,18 @@ func getAccountWithGenesisRevision(t *testing.T) {
assert.Equal(t, false, acc.HasCode, "hasCode should be false")
}

func getAccountWithFinalizedRevision(t *testing.T) {
soloAddress := "0xf077b491b355E64048cE21E3A6Fc4751eEeA77fa"

genesisAccount := httpGetAccount(t, soloAddress+"?revision="+genesisBlock.Header().ID().String())
finalizedAccount := httpGetAccount(t, soloAddress+"?revision=finalized")

genesisEnergy := (*big.Int)(&genesisAccount.Energy)
finalizedEnergy := (*big.Int)(&finalizedAccount.Energy)

assert.Equal(t, genesisEnergy, finalizedEnergy, "finalized energy should equal genesis energy")
}

func getCode(t *testing.T) {
_, statusCode := httpGet(t, ts.URL+"/accounts/"+invalidAddr+"/code")
assert.Equal(t, http.StatusBadRequest, statusCode, "bad address")
Expand All @@ -183,6 +202,15 @@ func getCode(t *testing.T) {
assert.Equal(t, http.StatusOK, statusCode, "OK")
}

func getCodeWithNonExisitingRevision(t *testing.T) {
revision64Len := "0x00000000851caf3cfdb6e899cf5958bfb1ac3413d346d43539627e6be7ec1b4a"

res, statusCode := httpGet(t, ts.URL+"/accounts/"+contractAddr.String()+"/code?revision="+revision64Len)

assert.Equal(t, http.StatusBadRequest, statusCode, "bad revision")
assert.Equal(t, "revision: leveldb: not found\n", string(res), "revision not found")
}

func getStorage(t *testing.T) {
_, statusCode := httpGet(t, ts.URL+"/accounts/"+invalidAddr+"/storage/"+storageKey.String())
assert.Equal(t, http.StatusBadRequest, statusCode, "bad address")
Expand All @@ -207,6 +235,15 @@ func getStorage(t *testing.T) {
assert.Equal(t, http.StatusOK, statusCode, "OK")
}

func getStorageWithNonExisitingRevision(t *testing.T) {
revision64Len := "0x00000000851caf3cfdb6e899cf5958bfb1ac3413d346d43539627e6be7ec1b4a"

res, statusCode := httpGet(t, ts.URL+"/accounts/"+contractAddr.String()+"/storage/"+storageKey.String()+"?revision="+revision64Len)

assert.Equal(t, http.StatusBadRequest, statusCode, "bad revision")
assert.Equal(t, "revision: leveldb: not found\n", string(res), "revision not found")
}

func initAccountServer(t *testing.T) {
db := muxdb.NewMem()
stater := state.NewStater(db)
Expand Down Expand Up @@ -237,7 +274,7 @@ func initAccountServer(t *testing.T) {

router := mux.NewRouter()
gasLimit = math.MaxUint32
acc = accounts.New(repo, stater, gasLimit, thor.NoFork)
acc = accounts.New(repo, stater, gasLimit, thor.NoFork, solo.NewBFTEngine(repo))
acc.Mount(router, "/accounts")
ts = httptest.NewServer(router)
}
Expand Down Expand Up @@ -356,6 +393,15 @@ func callContract(t *testing.T) {
assert.Equal(t, a+b, ret)
}

func callContractWithNonExisitingRevision(t *testing.T) {
revision64Len := "0x00000000851caf3cfdb6e899cf5958bfb1ac3413d346d43539627e6be7ec1b4a"

res, statusCode := httpPost(t, ts.URL+"/accounts/"+contractAddr.String()+"?revision="+revision64Len, nil)

assert.Equal(t, http.StatusBadRequest, statusCode, "bad revision")
assert.Equal(t, "revision: leveldb: not found\n", string(res), "revision not found")
}

func batchCall(t *testing.T) {
// Request body is not a valid JSON
malformedBody := 123
Expand Down Expand Up @@ -387,8 +433,8 @@ func batchCall(t *testing.T) {
assert.Equal(t, http.StatusInternalServerError, statusCode, "invalid blockRef")

// Request body has an invalid malformed revision
_, statusCode = httpPost(t, fmt.Sprintf("%s/accounts/*?revision=%d", ts.URL, malformedBody), badBody)
assert.Equal(t, http.StatusBadRequest, statusCode, "invalid revision")
_, statusCode = httpPost(t, fmt.Sprintf("%s/accounts/*?revision=%s", ts.URL, "0xZZZ"), badBody)
assert.Equal(t, http.StatusBadRequest, statusCode, "revision")

// Request body has an invalid revision number
_, statusCode = httpPost(t, ts.URL+"/accounts/*?revision="+invalidNumberRevision, badBody)
Expand Down Expand Up @@ -467,6 +513,15 @@ func batchCall(t *testing.T) {
assert.Equal(t, http.StatusForbidden, statusCode)
}

func batchCallWithNonExisitingRevision(t *testing.T) {
revision64Len := "0x00000000851caf3cfdb6e899cf5958bfb1ac3413d346d43539627e6be7ec1b4a"

res, statusCode := httpPost(t, ts.URL+"/accounts/*?revision="+revision64Len, nil)

assert.Equal(t, http.StatusBadRequest, statusCode, "bad revision")
assert.Equal(t, "revision: leveldb: not found\n", string(res), "revision not found")
}

func httpPost(t *testing.T, url string, body interface{}) ([]byte, int) {
data, err := json.Marshal(body)
if err != nil {
Expand Down Expand Up @@ -496,3 +551,15 @@ func httpGet(t *testing.T, url string) ([]byte, int) {
}
return r, res.StatusCode
}

func httpGetAccount(t *testing.T, path string) *accounts.Account {
res, statusCode := httpGet(t, ts.URL+"/accounts/"+path)
var acc accounts.Account
if err := json.Unmarshal(res, &acc); err != nil {
t.Fatal(err)
}

assert.Equal(t, http.StatusOK, statusCode, "get account failed")

return &acc
}
7 changes: 4 additions & 3 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/vechain/thor/v2/api/subscriptions"
"github.com/vechain/thor/v2/api/transactions"
"github.com/vechain/thor/v2/api/transfers"
"github.com/vechain/thor/v2/bft"
"github.com/vechain/thor/v2/chain"
"github.com/vechain/thor/v2/logdb"
"github.com/vechain/thor/v2/state"
Expand All @@ -34,7 +35,7 @@ func New(
stater *state.Stater,
txPool *txpool.TxPool,
logDB *logdb.LogDB,
bft blocks.BFTEngine,
bft bft.Finalizer,
nw node.Network,
allowedOrigins string,
backtraceLimit uint32,
Expand Down Expand Up @@ -62,7 +63,7 @@ func New(
http.Redirect(w, req, "doc/stoplight-ui/", http.StatusTemporaryRedirect)
})

accounts.New(repo, stater, callGasLimit, forkConfig).
accounts.New(repo, stater, callGasLimit, forkConfig, bft).
Mount(router, "/accounts")

if !skipLogs {
Expand All @@ -75,7 +76,7 @@ func New(
Mount(router, "/blocks")
transactions.New(repo, txPool).
Mount(router, "/transactions")
debug.New(repo, stater, forkConfig, callGasLimit, allowCustomTracer).
debug.New(repo, stater, forkConfig, callGasLimit, allowCustomTracer, bft).
Mount(router, "/debug")
node.New(nw).
Mount(router, "/node")
Expand Down
Loading

0 comments on commit de6037f

Please sign in to comment.