-
Notifications
You must be signed in to change notification settings - Fork 328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api] archive-mode for eth_getBalance & eth_call #4529
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,7 @@ import ( | |
"github.com/iotexproject/iotex-core/v2/pkg/log" | ||
"github.com/iotexproject/iotex-core/v2/pkg/tracer" | ||
"github.com/iotexproject/iotex-core/v2/pkg/unit" | ||
"github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" | ||
"github.com/iotexproject/iotex-core/v2/pkg/version" | ||
"github.com/iotexproject/iotex-core/v2/server/itx/nodestats" | ||
"github.com/iotexproject/iotex-core/v2/state" | ||
|
@@ -81,6 +82,7 @@ const ( | |
type ( | ||
// CoreService provides api interface for user to interact with blockchain data | ||
CoreService interface { | ||
WithHeight(uint64) CoreServiceReaderWithHeight | ||
// Account returns the metadata of an account | ||
Account(addr address.Address) (*iotextypes.AccountMeta, *iotextypes.BlockIdentifier, error) | ||
// ChainMeta returns blockchain metadata | ||
|
@@ -202,6 +204,7 @@ type ( | |
gs *gasstation.GasStation | ||
broadcastHandler BroadcastOutbound | ||
cfg Config | ||
archiveSupported bool | ||
registry *protocol.Registry | ||
chainListener apitypes.Listener | ||
electionCommittee committee.Committee | ||
|
@@ -245,13 +248,20 @@ func WithAPIStats(stats *nodestats.APILocalStats) Option { | |
} | ||
} | ||
|
||
// WithArchiveSupport is the option to enable archive support | ||
func WithArchiveSupport() Option { | ||
return func(svr *coreService) { | ||
svr.archiveSupported = true | ||
} | ||
} | ||
|
||
type intrinsicGasCalculator interface { | ||
IntrinsicGas() (uint64, error) | ||
} | ||
|
||
var ( | ||
// ErrNotFound indicates the record isn't found | ||
ErrNotFound = errors.New("not found") | ||
ErrNotFound = errors.New("not found") | ||
ErrArchiveNotSupported = errors.New("archive-mode not supported") | ||
) | ||
|
||
// newcoreService creates a api server that contains major blockchain components | ||
|
@@ -305,6 +315,10 @@ func newCoreService( | |
return &core, nil | ||
} | ||
|
||
func (core *coreService) WithHeight(height uint64) CoreServiceReaderWithHeight { | ||
return newCoreServiceWithHeight(core, height) | ||
} | ||
|
||
// Account returns the metadata of an account | ||
func (core *coreService) Account(addr address.Address) (*iotextypes.AccountMeta, *iotextypes.BlockIdentifier, error) { | ||
ctx, span := tracer.NewSpan(context.Background(), "coreService.Account") | ||
|
@@ -324,17 +338,22 @@ func (core *coreService) Account(addr address.Address) (*iotextypes.AccountMeta, | |
if err != nil { | ||
return nil, nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
return core.acccount(ctx, tipHeight, state, pendingNonce, addr) | ||
} | ||
|
||
func (core *coreService) acccount(ctx context.Context, height uint64, state *state.Account, pendingNonce uint64, addr address.Address) (*iotextypes.AccountMeta, *iotextypes.BlockIdentifier, error) { | ||
if core.indexer == nil { | ||
return nil, nil, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error()) | ||
} | ||
span := tracer.SpanFromContext(ctx) | ||
span.AddEvent("indexer.GetActionCount") | ||
numActions, err := core.indexer.GetActionCountByAddress(hash.BytesToHash160(addr.Bytes())) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.NotFound, err.Error()) | ||
} | ||
// TODO: deprecate nonce field in account meta | ||
accountMeta := &iotextypes.AccountMeta{ | ||
Address: addrStr, | ||
Address: addr.String(), | ||
Balance: state.Balance.String(), | ||
PendingNonce: pendingNonce, | ||
NumActions: numActions, | ||
|
@@ -349,15 +368,15 @@ func (core *coreService) Account(addr address.Address) (*iotextypes.AccountMeta, | |
accountMeta.ContractByteCode = code | ||
} | ||
span.AddEvent("bc.BlockHeaderByHeight") | ||
header, err := core.bc.BlockHeaderByHeight(tipHeight) | ||
header, err := core.bc.BlockHeaderByHeight(height) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.NotFound, err.Error()) | ||
} | ||
hash := header.HashBlock() | ||
span.AddEvent("coreService.Account.End") | ||
return accountMeta, &iotextypes.BlockIdentifier{ | ||
Hash: hex.EncodeToString(hash[:]), | ||
Height: tipHeight, | ||
Height: height, | ||
}, nil | ||
} | ||
|
||
|
@@ -520,7 +539,21 @@ func (core *coreService) ReadContract(ctx context.Context, callerAddr address.Ad | |
if !ok { | ||
return "", nil, status.Error(codes.InvalidArgument, "expecting action.Execution") | ||
} | ||
key := hash.Hash160b(append([]byte(exec.Contract()), exec.Data()...)) | ||
var ( | ||
tipHeight = core.bc.TipHeight() | ||
hdBytes = append(byteutil.Uint64ToBytesBigEndian(tipHeight), []byte(exec.Contract())...) | ||
key = hash.Hash160b(append(hdBytes, exec.Data()...)) | ||
) | ||
return core.readContract(ctx, key, tipHeight, false, callerAddr, elp) | ||
} | ||
|
||
func (core *coreService) readContract( | ||
ctx context.Context, | ||
key hash.Hash160, | ||
height uint64, | ||
archive bool, | ||
callerAddr address.Address, | ||
elp action.Envelope) (string, *iotextypes.Receipt, error) { | ||
// TODO: either moving readcache into the upper layer or change the storage format | ||
if d, ok := core.readCache.Get(key); ok { | ||
res := iotexapi.ReadContractResponse{} | ||
|
@@ -530,12 +563,12 @@ func (core *coreService) ReadContract(ctx context.Context, callerAddr address.Ad | |
} | ||
var ( | ||
g = core.bc.Genesis() | ||
blockGasLimit = g.BlockGasLimitByHeight(core.bc.TipHeight()) | ||
blockGasLimit = g.BlockGasLimitByHeight(height) | ||
) | ||
if elp.Gas() == 0 || blockGasLimit < elp.Gas() { | ||
elp.SetGas(blockGasLimit) | ||
} | ||
retval, receipt, err := core.simulateExecution(ctx, callerAddr, elp) | ||
retval, receipt, err := core.simulateExecution(ctx, height, archive, callerAddr, elp) | ||
if err != nil { | ||
return "", nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
|
@@ -1680,7 +1713,7 @@ func (core *coreService) isGasLimitEnough( | |
) (bool, *action.Receipt, []byte, error) { | ||
ctx, span := tracer.NewSpan(ctx, "Server.isGasLimitEnough") | ||
defer span.End() | ||
ret, receipt, err := core.simulateExecution(ctx, caller, elp, opts...) | ||
ret, receipt, err := core.simulateExecution(ctx, core.bc.TipHeight(), false, caller, elp, opts...) | ||
if err != nil { | ||
return false, nil, nil, err | ||
} | ||
|
@@ -1825,10 +1858,11 @@ func (core *coreService) ReceiveBlock(blk *block.Block) error { | |
func (core *coreService) SimulateExecution(ctx context.Context, addr address.Address, elp action.Envelope) ([]byte, *action.Receipt, error) { | ||
var ( | ||
g = core.bc.Genesis() | ||
blockGasLimit = g.BlockGasLimitByHeight(core.bc.TipHeight()) | ||
tipHeight = core.bc.TipHeight() | ||
blockGasLimit = g.BlockGasLimitByHeight(tipHeight) | ||
) | ||
elp.SetGas(blockGasLimit) | ||
return core.simulateExecution(ctx, addr, elp) | ||
return core.simulateExecution(ctx, tipHeight, false, addr, elp) | ||
} | ||
|
||
// SyncingProgress returns the syncing status of node | ||
|
@@ -1852,7 +1886,7 @@ func (core *coreService) TraceTransaction(ctx context.Context, actHash string, c | |
} | ||
addr, _ := address.FromString(address.ZeroAddress) | ||
return core.traceTx(ctx, new(tracers.Context), config, func(ctx context.Context) ([]byte, *action.Receipt, error) { | ||
return core.simulateExecution(ctx, addr, act.Envelope) | ||
return core.simulateExecution(ctx, core.bc.TipHeight(), false, addr, act.Envelope) | ||
}) | ||
} | ||
|
||
|
@@ -1880,7 +1914,7 @@ func (core *coreService) TraceCall(ctx context.Context, | |
elp := (&action.EnvelopeBuilder{}).SetAction(action.NewExecution(contractAddress, amount, data)). | ||
SetGasLimit(gasLimit).Build() | ||
return core.traceTx(ctx, new(tracers.Context), config, func(ctx context.Context) ([]byte, *action.Receipt, error) { | ||
return core.simulateExecution(ctx, callerAddr, elp) | ||
return core.simulateExecution(ctx, core.bc.TipHeight(), false, callerAddr, elp) | ||
}) | ||
} | ||
|
||
|
@@ -1939,20 +1973,42 @@ func (core *coreService) traceTx(ctx context.Context, txctx *tracers.Context, co | |
return retval, receipt, tracer, err | ||
} | ||
|
||
func (core *coreService) simulateExecution(ctx context.Context, addr address.Address, elp action.Envelope, opts ...protocol.SimulateOption) ([]byte, *action.Receipt, error) { | ||
ctx, err := core.bc.Context(ctx) | ||
func (core *coreService) simulateExecution( | ||
ctx context.Context, | ||
height uint64, | ||
archive bool, | ||
envestcc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
addr address.Address, | ||
elp action.Envelope, | ||
opts ...protocol.SimulateOption) ([]byte, *action.Receipt, error) { | ||
var ( | ||
err error | ||
ws protocol.StateManager | ||
) | ||
if archive { | ||
ctx, err = core.bc.ContextAtHeight(ctx, height) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
ws, err = core.sf.WorkingSetAtHeight(ctx, height) | ||
} else { | ||
ctx, err = core.bc.Context(ctx) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
ws, err = core.sf.WorkingSet(ctx) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these lines would be duplicated multiple times if use |
||
} | ||
if err != nil { | ||
return nil, nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
state, err := accountutil.AccountState(ctx, core.sf, addr) | ||
state, err := accountutil.AccountState(ctx, ws, addr) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
var pendingNonce uint64 | ||
ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ | ||
BlockHeight: core.bc.TipHeight(), | ||
BlockHeight: height, | ||
})) | ||
if protocol.MustGetFeatureCtx(ctx).RefactorFreshAccountConversion { | ||
if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount { | ||
pendingNonce = state.PendingNonceConsideringFreshAccount() | ||
} else { | ||
pendingNonce = state.PendingNonce() | ||
|
@@ -1963,10 +2019,6 @@ func (core *coreService) simulateExecution(ctx context.Context, addr address.Add | |
GetBlockTime: core.getBlockTime, | ||
DepositGasFunc: rewarding.DepositGas, | ||
}) | ||
ws, err := core.sf.WorkingSet(ctx) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
return evm.SimulateExecution(ctx, ws, addr, elp, opts...) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/iotexproject/go-pkgs/hash" | ||
"github.com/iotexproject/iotex-address/address" | ||
"github.com/iotexproject/iotex-proto/golang/iotextypes" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
|
||
"github.com/iotexproject/iotex-core/v2/action" | ||
accountutil "github.com/iotexproject/iotex-core/v2/action/protocol/account/util" | ||
"github.com/iotexproject/iotex-core/v2/blockchain/genesis" | ||
"github.com/iotexproject/iotex-core/v2/pkg/log" | ||
"github.com/iotexproject/iotex-core/v2/pkg/tracer" | ||
"github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" | ||
"github.com/iotexproject/iotex-core/v2/state" | ||
) | ||
|
||
type ( | ||
// CoreServiceReaderWithHeight is an interface for state reader at certain height | ||
CoreServiceReaderWithHeight interface { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you explain why we need to create such an interface? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this interface is created to represent APIs needed for archive-mode query (there will be a total 4-6 of them, 2 enabled in this PR, and more to enable in upcoming PR), so we can keep using existing API without having to add the
|
||
Account(address.Address) (*iotextypes.AccountMeta, *iotextypes.BlockIdentifier, error) | ||
ReadContract(context.Context, address.Address, action.Envelope) (string, *iotextypes.Receipt, error) | ||
} | ||
|
||
coreServiceReaderWithHeight struct { | ||
cs *coreService | ||
height uint64 | ||
} | ||
) | ||
|
||
func newCoreServiceWithHeight(cs *coreService, height uint64) *coreServiceReaderWithHeight { | ||
return &coreServiceReaderWithHeight{ | ||
cs: cs, | ||
height: height, | ||
} | ||
} | ||
|
||
func (core *coreServiceReaderWithHeight) Account(addr address.Address) (*iotextypes.AccountMeta, *iotextypes.BlockIdentifier, error) { | ||
if !core.cs.archiveSupported { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bd7ca1f#diff-699e59a265a0599d1a49698bbf378f57a7aeb60957441a257249102ac8785a8dR50 will return error if archive is not supported There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return nil, nil, ErrArchiveNotSupported | ||
} | ||
ctx, span := tracer.NewSpan(context.Background(), "coreServiceReaderWithHeight.Account") | ||
defer span.End() | ||
addrStr := addr.String() | ||
if addrStr == address.RewardingPoolAddr || addrStr == address.StakingBucketPoolAddr { | ||
return core.cs.getProtocolAccount(ctx, addrStr) | ||
} | ||
state, pendingNonce, err := core.stateAndNonce(addr) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
return core.cs.acccount(ctx, core.height, state, pendingNonce, addr) | ||
} | ||
|
||
func (core *coreServiceReaderWithHeight) stateAndNonce(addr address.Address) (*state.Account, uint64, error) { | ||
var ( | ||
g = core.cs.bc.Genesis() | ||
ctx = genesis.WithGenesisContext(context.Background(), g) | ||
) | ||
ws, err := core.cs.sf.WorkingSetAtHeight(ctx, core.height) | ||
if err != nil { | ||
return nil, 0, status.Error(codes.Internal, err.Error()) | ||
} | ||
state, err := accountutil.AccountState(ctx, ws, addr) | ||
if err != nil { | ||
return nil, 0, status.Error(codes.NotFound, err.Error()) | ||
} | ||
var pendingNonce uint64 | ||
if g.IsSumatra(core.height) { | ||
pendingNonce = state.PendingNonceConsideringFreshAccount() | ||
} else { | ||
pendingNonce = state.PendingNonce() | ||
} | ||
return state, pendingNonce, nil | ||
} | ||
|
||
func (core *coreServiceReaderWithHeight) ReadContract(ctx context.Context, callerAddr address.Address, elp action.Envelope) (string, *iotextypes.Receipt, error) { | ||
if !core.cs.archiveSupported { | ||
return "", nil, ErrArchiveNotSupported | ||
} | ||
log.Logger("api").Debug("receive read smart contract request") | ||
exec, ok := elp.Action().(*action.Execution) | ||
if !ok { | ||
return "", nil, status.Error(codes.InvalidArgument, "expecting action.Execution") | ||
} | ||
var ( | ||
hdBytes = append(byteutil.Uint64ToBytesBigEndian(core.height), []byte(exec.Contract())...) | ||
key = hash.Hash160b(append(hdBytes, exec.Data()...)) | ||
) | ||
return core.cs.readContract(ctx, key, core.height, true, callerAddr, elp) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed L261 comment, the name clears explains what the error is