-
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] historical eth_getbalance & eth_call #4339
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 |
---|---|---|
|
@@ -17,6 +17,7 @@ import ( | |
|
||
"github.com/ethereum/go-ethereum/core/vm" | ||
"github.com/ethereum/go-ethereum/eth/tracers" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
|
||
// Force-load the tracer engines to trigger registration | ||
_ "github.com/ethereum/go-ethereum/eth/tracers/js" | ||
|
@@ -76,9 +77,15 @@ const ( | |
defaultTraceTimeout = 5 * time.Second | ||
) | ||
|
||
var ( | ||
errHistoricalNotSupported = errors.New("historical data is not supported") | ||
) | ||
|
||
type ( | ||
// CoreService provides api interface for user to interact with blockchain data | ||
CoreService interface { | ||
// BalanceOf returns the balance of an address | ||
BalanceOf(addr address.Address, height rpc.BlockNumber) (*big.Int, error) | ||
// Account returns the metadata of an account | ||
Account(addr address.Address) (*iotextypes.AccountMeta, *iotextypes.BlockIdentifier, error) | ||
// ChainMeta returns blockchain metadata | ||
|
@@ -88,7 +95,7 @@ type ( | |
// SendAction is the API to send an action to blockchain. | ||
SendAction(ctx context.Context, in *iotextypes.Action) (string, error) | ||
// ReadContract reads the state in a contract address specified by the slot | ||
ReadContract(ctx context.Context, callerAddr address.Address, sc *action.Execution) (string, *iotextypes.Receipt, error) | ||
ReadContract(ctx context.Context, height rpc.BlockNumber, callerAddr address.Address, sc *action.Execution) (string, *iotextypes.Receipt, error) | ||
// ReadState reads state on blockchain | ||
ReadState(protocolID string, height string, methodName []byte, arguments [][]byte) (*iotexapi.ReadStateResponse, error) | ||
// SuggestGasPrice suggests gas price | ||
|
@@ -202,6 +209,7 @@ type ( | |
apiStats *nodestats.APILocalStats | ||
sgdIndexer blockindex.SGDRegistry | ||
getBlockTime evm.GetBlockTime | ||
isArchiveSupport bool | ||
} | ||
|
||
// jobDesc provides a struct to get and store logs in core.LogsInRange | ||
|
@@ -245,6 +253,13 @@ func WithSGDIndexer(sgdIndexer blockindex.SGDRegistry) Option { | |
} | ||
} | ||
|
||
// WithArchiveSupport is the option to enable archive support | ||
func WithArchiveSupport(enabled bool) Option { | ||
return func(svr *coreService) { | ||
svr.isArchiveSupport = enabled | ||
} | ||
} | ||
|
||
type intrinsicGasCalculator interface { | ||
IntrinsicGas() (uint64, error) | ||
} | ||
|
@@ -306,6 +321,45 @@ func newCoreService( | |
return &core, nil | ||
} | ||
|
||
func (core *coreService) BalanceOf(addr address.Address, height rpc.BlockNumber) (*big.Int, error) { | ||
ctx, span := tracer.NewSpan(context.Background(), "coreService.BalanceOf") | ||
defer span.End() | ||
addrStr := addr.String() | ||
stateHeight, err := core.blocknumToStateHeight(height) | ||
if err != nil { | ||
return nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
if !core.isArchiveSupport && stateHeight != core.TipHeight() { | ||
return nil, status.Error(codes.NotFound, errHistoricalNotSupported.Error()) | ||
} | ||
if addrStr == address.RewardingPoolAddr || addrStr == address.StakingBucketPoolAddr { | ||
// TODO: get protocol account at height | ||
if height != rpc.BlockNumber(core.TipHeight()) { | ||
return nil, status.Error(codes.NotFound, "system account state at height is not supported") | ||
} | ||
acc, _, err := core.getProtocolAccount(ctx, addrStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
balance, ok := new(big.Int).SetString(acc.Balance, 10) | ||
if !ok { | ||
return nil, status.Errorf(codes.Internal, "failed to parse balance %s", acc.Balance) | ||
} | ||
return balance, nil | ||
} | ||
span.AddEvent("accountutil.AccountState") | ||
ctx = genesis.WithGenesisContext(ctx, core.bc.Genesis()) | ||
stateReader := protocol.StateReader(core.sf) | ||
if core.isArchiveSupport { | ||
stateReader = newStateReaderWithHeight(core.sf, stateHeight) | ||
} | ||
state, err := accountutil.AccountState(ctx, stateReader, addr) | ||
if err != nil { | ||
return nil, status.Error(codes.NotFound, err.Error()) | ||
} | ||
return state.Balance, nil | ||
} | ||
|
||
// 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") | ||
|
@@ -314,9 +368,9 @@ func (core *coreService) Account(addr address.Address) (*iotextypes.AccountMeta, | |
if addrStr == address.RewardingPoolAddr || addrStr == address.StakingBucketPoolAddr { | ||
return core.getProtocolAccount(ctx, addrStr) | ||
} | ||
span.AddEvent("accountutil.AccountStateWithHeight") | ||
span.AddEvent("accountutil.AccountState") | ||
ctx = genesis.WithGenesisContext(ctx, core.bc.Genesis()) | ||
state, tipHeight, err := accountutil.AccountStateWithHeight(ctx, core.sf, addr) | ||
state, err := accountutil.AccountState(ctx, core.sf, addr) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.NotFound, err.Error()) | ||
} | ||
|
@@ -350,6 +404,7 @@ func (core *coreService) Account(addr address.Address) (*iotextypes.AccountMeta, | |
accountMeta.ContractByteCode = code | ||
} | ||
span.AddEvent("bc.BlockHeaderByHeight") | ||
tipHeight := core.bc.TipHeight() | ||
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. the |
||
header, err := core.bc.BlockHeaderByHeight(tipHeight) | ||
if err != nil { | ||
return nil, nil, status.Error(codes.NotFound, err.Error()) | ||
|
@@ -527,48 +582,106 @@ func (core *coreService) validateChainID(chainID uint32) error { | |
return nil | ||
} | ||
|
||
// TODO: a polymorphism impl of coreservice might be more suitable for historical data retrieval | ||
// ReadContract reads the state in a contract address specified by the slot | ||
func (core *coreService) ReadContract(ctx context.Context, callerAddr address.Address, sc *action.Execution) (string, *iotextypes.Receipt, error) { | ||
log.Logger("api").Debug("receive read smart contract request") | ||
key := hash.Hash160b(append([]byte(sc.Contract()), sc.Data()...)) | ||
func (core *coreService) ReadContract(ctx context.Context, height rpc.BlockNumber, callerAddr address.Address, sc *action.Execution, | ||
) (string, *iotextypes.Receipt, error) { | ||
// TODO: either moving readcache into the upper layer or change the storage format | ||
heightBytes, _ := height.MarshalText() | ||
key := hash.Hash160b(bytes.Join([][]byte{heightBytes, []byte(sc.Contract()), sc.Data()}, nil)) | ||
if d, ok := core.readCache.Get(key); ok { | ||
res := iotexapi.ReadContractResponse{} | ||
if err := proto.Unmarshal(d, &res); err == nil { | ||
return res.Data, res.Receipt, nil | ||
} | ||
} | ||
ctx = genesis.WithGenesisContext(ctx, core.bc.Genesis()) | ||
state, err := accountutil.AccountState(ctx, core.sf, callerAddr) | ||
if err != nil { | ||
return "", nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
if ctx, err = core.bc.Context(ctx); err != nil { | ||
return "", nil, err | ||
} | ||
ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ | ||
BlockHeight: core.bc.TipHeight(), | ||
})) | ||
var pendingNonce uint64 | ||
if protocol.MustGetFeatureCtx(ctx).RefactorFreshAccountConversion { | ||
pendingNonce = state.PendingNonceConsideringFreshAccount() | ||
} else { | ||
pendingNonce = state.PendingNonce() | ||
} | ||
sc.SetNonce(pendingNonce) | ||
|
||
var ( | ||
g = core.bc.Genesis() | ||
blockGasLimit = g.BlockGasLimitByHeight(core.bc.TipHeight()) | ||
retval []byte | ||
receipt = &action.Receipt{} | ||
) | ||
if sc.GasLimit() == 0 || blockGasLimit < sc.GasLimit() { | ||
sc.SetGasLimit(blockGasLimit) | ||
} | ||
sc.SetGasPrice(big.NewInt(0)) // ReadContract() is read-only, use 0 to prevent insufficient gas | ||
if !core.isArchiveSupport { | ||
ctx, err := core.bc.Context(ctx) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(ctx, protocol.BlockCtx{ | ||
BlockHeight: core.bc.TipHeight(), | ||
})) | ||
acct, err := accountutil.AccountState(ctx, core.sf, callerAddr) | ||
if err != nil { | ||
return "", nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
var pendingNonce uint64 | ||
if protocol.MustGetFeatureCtx(ctx).RefactorFreshAccountConversion { | ||
pendingNonce = acct.PendingNonceConsideringFreshAccount() | ||
} else { | ||
pendingNonce = acct.PendingNonce() | ||
} | ||
sc.SetNonce(pendingNonce) | ||
var ( | ||
g = core.bc.Genesis() | ||
blockGasLimit = g.BlockGasLimitByHeight(core.bc.TipHeight()) | ||
) | ||
if sc.GasLimit() == 0 || blockGasLimit < sc.GasLimit() { | ||
sc.SetGasLimit(blockGasLimit) | ||
} | ||
|
||
retval, receipt, err := core.simulateExecution(ctx, callerAddr, sc, core.dao.GetBlockHash, core.getBlockTime) | ||
if err != nil { | ||
return "", nil, status.Error(codes.Internal, err.Error()) | ||
retval, receipt, err = core.simulateExecution(ctx, callerAddr, sc, core.dao.GetBlockHash, core.getBlockTime) | ||
if err != nil { | ||
return "", nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
} else { | ||
stateHeight, err := core.blocknumToStateHeight(height) | ||
if err != nil { | ||
return "", nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
ctx, err := core.bc.ContextAtHeight(ctx, stateHeight) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
ctx = protocol.WithFeatureCtx( | ||
protocol.WithBlockCtx( | ||
ctx, | ||
protocol.BlockCtx{ | ||
BlockHeight: stateHeight, | ||
}, | ||
), | ||
) | ||
|
||
acct, err := accountutil.AccountStateAtHeight(ctx, stateHeight, core.sf, callerAddr) | ||
if err != nil { | ||
return "", nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
|
||
var pendingNonce uint64 | ||
if protocol.MustGetFeatureCtx(ctx).RefactorFreshAccountConversion { | ||
pendingNonce = acct.PendingNonceConsideringFreshAccount() | ||
} else { | ||
pendingNonce = acct.PendingNonce() | ||
} | ||
sc.SetNonce(pendingNonce) | ||
|
||
var ( | ||
g = core.bc.Genesis() | ||
blockGasLimit = g.BlockGasLimitByHeight(stateHeight) | ||
) | ||
if sc.GasLimit() == 0 || blockGasLimit < sc.GasLimit() { | ||
sc.SetGasLimit(blockGasLimit) | ||
} | ||
|
||
ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ | ||
GetBlockHash: core.dao.GetBlockHash, | ||
GetBlockTime: core.getBlockTime, | ||
DepositGasFunc: rewarding.DepositGasWithSGD, | ||
Sgd: core.sgdIndexer, | ||
}) | ||
retval, receipt, err = core.sf.SimulateExecutionAtHeight(ctx, stateHeight, callerAddr, sc) | ||
if err != nil { | ||
return "", nil, status.Error(codes.Internal, err.Error()) | ||
} | ||
} | ||
|
||
// ReadContract() is read-only, if no error returned, we consider it a success | ||
receipt.Status = uint64(iotextypes.ReceiptStatus_Success) | ||
res := iotexapi.ReadContractResponse{ | ||
|
@@ -1740,6 +1853,7 @@ func (core *coreService) ReceiveBlock(blk *block.Block) error { | |
return core.chainListener.ReceiveBlock(blk) | ||
} | ||
|
||
// TODO: merge this func into EstimateGasForAction | ||
func (core *coreService) SimulateExecution(ctx context.Context, addr address.Address, exec *action.Execution) ([]byte, *action.Receipt, error) { | ||
ctx = genesis.WithGenesisContext(ctx, core.bc.Genesis()) | ||
state, err := accountutil.AccountState(ctx, core.sf, addr) | ||
|
@@ -1775,6 +1889,7 @@ func (core *coreService) SyncingProgress() (uint64, uint64, uint64) { | |
return startingHeight, currentHeight, targetHeight | ||
} | ||
|
||
// TODO: support this by height | ||
// TraceTransaction returns the trace result of transaction | ||
func (core *coreService) TraceTransaction(ctx context.Context, actHash string, config *tracers.TraceConfig) ([]byte, *action.Receipt, any, error) { | ||
actInfo, err := core.Action(util.Remove0xPrefix(actHash), false) | ||
|
@@ -1797,6 +1912,7 @@ func (core *coreService) TraceTransaction(ctx context.Context, actHash string, c | |
return retval, receipt, tracer, err | ||
} | ||
|
||
// TODO: support this by height | ||
// TraceCall returns the trace result of call | ||
func (core *coreService) TraceCall(ctx context.Context, | ||
callerAddr address.Address, | ||
|
@@ -1926,3 +2042,18 @@ func filterReceipts(receipts []*action.Receipt, actHash hash.Hash256) *action.Re | |
} | ||
return nil | ||
} | ||
|
||
func (core *coreService) blocknumToStateHeight(blockNum rpc.BlockNumber) (uint64, error) { | ||
var stateHeight uint64 | ||
switch blockNum { | ||
case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber, rpc.LatestBlockNumber: | ||
stateHeight = core.bc.TipHeight() | ||
case rpc.EarliestBlockNumber: | ||
stateHeight = 1 | ||
case rpc.PendingBlockNumber: | ||
return 0, errors.New("pending block number is not supported") | ||
default: | ||
stateHeight = uint64(blockNum.Int64()) | ||
} | ||
return stateHeight, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ import ( | |
|
||
"github.com/ethereum/go-ethereum/eth/tracers" | ||
"github.com/ethereum/go-ethereum/eth/tracers/logger" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" | ||
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" | ||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" | ||
|
@@ -367,8 +368,8 @@ func (svr *gRPCHandler) ReadContract(ctx context.Context, in *iotexapi.ReadContr | |
return nil, status.Error(codes.InvalidArgument, err.Error()) | ||
} | ||
sc.SetGasLimit(in.GetGasLimit()) | ||
|
||
data, receipt, err := svr.coreService.ReadContract(ctx, callerAddr, sc) | ||
sc.SetGasPrice(big.NewInt(0)) // ReadContract() is read-only, use 0 to prevent insufficient gas | ||
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. can this be omitted? I mean, by default it should be 0? |
||
data, receipt, err := svr.coreService.ReadContract(ctx, rpc.LatestBlockNumber, callerAddr, sc) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
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.
format