Skip to content
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

Simplify storage of contract source code #61

Open
wants to merge 7 commits into
base: contract-source-code
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ tx_id string tx hash
creator string creators address
blockno uint64 block number
ts timestamp last updated timestamp (unixnano)
payload string compiled bytecode
abi string contract abi
byte_code string compiled bytecode
source_code string contract source code
deploy_args string constructor function arguments
verified_status string verified status
verified_token string verified token address
code string verified contract code
verified_token string verified token symbol
```

event
Expand Down
2 changes: 1 addition & 1 deletion indexer/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (ns *Cache) refreshVariables(info BlockInfo, blockDoc *doc.EsBlock, minerGR
mapBalance := make(map[string]string)
ns.addrsBalance.Range(func(k, v interface{}) bool {
if addr, ok := k.(string); ok {
if isExiststake := ns.idxer.MinerBalance(blockDoc, addr, minerGRPC); isExiststake == false {
if stakeExists := ns.idxer.MinerBalance(blockDoc, addr, minerGRPC); stakeExists == false {
mapBalance[addr] = ""
}
}
Expand Down
45 changes: 32 additions & 13 deletions indexer/documents/conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,27 @@ func ConvTx(txIdx uint64, tx *types.Tx, receipt *types.Receipt, blockDoc *EsBloc
}
}

// ConvContractCreateTx creates document for token creation
func ConvContract(txDoc *EsTx, contractAddress []byte) *EsContract {

// ConvContractFromTx creates document for contract creation
func ConvContractFromTx(txDoc *EsTx, contractAddress []byte) *EsContract {
byteCode, sourceCode, abi, deployArgs := extractContractCode(txDoc.Payload)
return ConvContract(txDoc, contractAddress, txDoc.Account, byteCode, abi, sourceCode, deployArgs)
}

func ConvContractFromCall(txDoc *EsTx, contractAddress []byte, creator, sourceCode, deployArgs string) *EsContract {
byteCode, abi, err := CompileSourceCode(sourceCode)
if err != nil {
panic(err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to invoke panic here? Does any caller catches the panic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. The idea is to make the app to close
What would be the alternative? Maybe retry?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither do I. You know much more than me about indexer. If panic is propagated to top level of call stack, the process will be terminated. I don't know this is expected behavior or not of indexer.

}
return ConvContract(txDoc, contractAddress, creator, byteCode, abi, sourceCode, deployArgs)
}

func ConvContract(txDoc *EsTx, contractAddress []byte, creator string, byteCode []byte, abi, sourceCode, deployArgs string) *EsContract {
return &EsContract{
BaseEsType: &BaseEsType{Id: transaction.EncodeAndResolveAccount(contractAddress, txDoc.BlockNo)},
Creator: txDoc.Account,
TxId: txDoc.GetID(),
BlockNo: txDoc.BlockNo,
Timestamp: txDoc.Timestamp,
TxId: txDoc.GetID(),
Creator: creator,
ABI: abi,
ByteCode: byteCode,
SourceCode: sourceCode,
Expand All @@ -116,13 +126,18 @@ func ConvInternalContract(txDoc *EsTx, contractAddress []byte) *EsContract {
}
}

func ConvContractUp(contractAddress string, status, token, codeUrl, code string) *EsContractUp {
return &EsContractUp{
func ConvContractSource(contractAddress string, sourceCode string) *EsContractSource {
return &EsContractSource{
BaseEsType: &BaseEsType{Id: contractAddress},
SourceCode: sourceCode,
}
}

func ConvContractToken(contractAddress string, status, token string) *EsContractToken {
return &EsContractToken{
BaseEsType: &BaseEsType{Id: contractAddress},
VerifiedToken: token,
VerifiedStatus: status,
CodeUrl: codeUrl,
SourceCode: code,
}
}

Expand All @@ -139,7 +154,10 @@ func extractContractCode(payload []byte) ([]byte, string, string, string) {
}
// on hardfork 4, the deploy contains the contract source code and deploy args
sourceCode, deployArgs := extractSourceCode(payload)
bytecode, abi := compileSourceCode(sourceCode)
bytecode, abi, err := CompileSourceCode(sourceCode)
if err != nil {
panic(err)
}
return bytecode, sourceCode, abi, deployArgs
}

Expand Down Expand Up @@ -170,17 +188,18 @@ func extractSourceCode(payload []byte) (string, string) {
return string(sourceCode), string(deployArgs)
}

func compileSourceCode(sourceCode string) ([]byte, string) {
// CompileSourceCode compiles the source code and returns the bytecode and abi
func CompileSourceCode(sourceCode string) ([]byte, string, error) {
bytecodeABI, err := lua_compiler.CompileCode(sourceCode)
if err != nil {
panic(err)
return nil, "", err
}
// read the bytecode length
bytecodeLength := binary.BigEndian.Uint32(bytecodeABI[:4])
// extract the bytecode and abi
bytecode := bytecodeABI[4:bytecodeLength]
abi := bytecodeABI[4+bytecodeLength:]
return bytecode, string(abi)
return bytecode, string(abi), nil
}

// ConvEvent converts Event from RPC into Elasticsearch type
Expand Down
2 changes: 1 addition & 1 deletion indexer/documents/conv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func TestConvTx(t *testing.T) {

func TestConvContract(t *testing.T) {
fn_test := func(esTx *EsTx, contractAddress []byte, esContractExpect *EsContract) {
esContractConv := ConvContract(esTx, contractAddress)
esContractConv := ConvContractFromTx(esTx, contractAddress)
require.Equal(t, esContractExpect, esContractConv)
}

Expand Down
10 changes: 6 additions & 4 deletions indexer/documents/documents.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,17 @@ type EsContract struct {

VerifiedStatus string `json:"verified_status" db:"verified_status"`
VerifiedToken string `json:"verified_token" db:"verified_token"`
CodeUrl string `json:"code_url" db:"code_url"`
}

type EsContractUp struct {
type EsContractSource struct {
*BaseEsType
SourceCode string `json:"source_code" db:"source_code"`
}

type EsContractToken struct {
*BaseEsType
VerifiedStatus string `json:"verified_status" db:"verified_status"`
VerifiedToken string `json:"verified_token" db:"verified_token"`
CodeUrl string `json:"code_url" db:"code_url"`
SourceCode string `json:"source_code" db:"source_code"`
}

// EsEvent is a contract-event mapping stored in the database
Expand Down
11 changes: 9 additions & 2 deletions indexer/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,17 @@ func (ns *Indexer) updateTokenVerified(tokenDoc *doc.EsTokenUpVerified) {
}
}

func (ns *Indexer) updateContract(contractDoc *doc.EsContractUp) {
func (ns *Indexer) updateContractSource(contractDoc *doc.EsContractSource) {
err := ns.db.Update(contractDoc, ns.indexNamePrefix+"contract", contractDoc.Id)
if err != nil {
ns.log.Error().Str("Id", contractDoc.Id).Err(err).Str("method", "updateContract").Msg("error while update")
ns.log.Error().Str("Id", contractDoc.Id).Err(err).Str("method", "updateContractSource").Msg("error while update")
}
}

func (ns *Indexer) updateContractToken(contractDoc *doc.EsContractToken) {
err := ns.db.Update(contractDoc, ns.indexNamePrefix+"contract", contractDoc.Id)
if err != nil {
ns.log.Error().Str("Id", contractDoc.Id).Err(err).Str("method", "updateContractToken").Msg("error while update")
}
}

Expand Down
78 changes: 55 additions & 23 deletions indexer/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/aergoio/aergo-indexer-2.0/indexer/client"
doc "github.com/aergoio/aergo-indexer-2.0/indexer/documents"
"github.com/aergoio/aergo-indexer-2.0/indexer/transaction"
"github.com/aergoio/aergo-indexer-2.0/lua_compiler"
"github.com/aergoio/aergo-indexer-2.0/types"
)

Expand Down Expand Up @@ -90,7 +89,7 @@ func (ns *Indexer) MinerTx(txIdx uint64, info BlockInfo, blockDoc *doc.EsBlock,

// Process Contract Deploy
if txDoc.Category == transaction.TxDeploy {
contractDoc := doc.ConvContract(txDoc, receipt.ContractAddress)
contractDoc := doc.ConvContractFromTx(txDoc, receipt.ContractAddress)
ns.addContract(info.Type, contractDoc)
}

Expand Down Expand Up @@ -301,7 +300,7 @@ func (ns *Indexer) MinerTokenVerified(tokenAddr, contractAddr, metadata string,
if contractAddr != "" && updateContractAddr != contractAddr {
tokenDoc, err := ns.getToken(contractAddr)
if err != nil || tokenDoc == nil {
ns.log.Error().Err(err).Str("addr", contractAddr).Msg("tokenDoc is not exist. wait until tokenDoc added")
ns.log.Error().Err(err).Str("addr", contractAddr).Msg("tokenDoc does not exist. wait until tokenDoc added")
return contractAddr
}

Expand All @@ -318,7 +317,7 @@ func (ns *Indexer) MinerTokenVerified(tokenAddr, contractAddr, metadata string,
if updateContractAddr != "" {
tokenDoc, err := ns.getToken(updateContractAddr)
if err != nil || tokenDoc == nil {
ns.log.Error().Err(err).Str("addr", updateContractAddr).Msg("tokenDoc is not exist. wait until tokenDoc added")
ns.log.Error().Err(err).Str("addr", updateContractAddr).Msg("tokenDoc does not exist. wait until tokenDoc added")
return contractAddr // 기존 contract address 반환
}

Expand All @@ -333,42 +332,47 @@ func (ns *Indexer) MinerTokenVerified(tokenAddr, contractAddr, metadata string,
return updateContractAddr
}

func (ns *Indexer) MinerContractVerified(tokenAddr, contractAddr, metadata string, MinerGRPC *client.AergoClientController) (updateContractAddr string) {
updateContractAddr, _, codeUrl := transaction.UnmarshalMetadataVerifyContract(metadata)
// it appears that this function is used for 2 different cases:
// 1. verifying and updating the contract source code
// 2. updating the verified token status
// TODO: separate the logic into two different functions
func (ns *Indexer) MinerContractVerified(tokenSymbol, contractAddr, metadata string, MinerGRPC *client.AergoClientController) (updateContractAddr string) {
updateContractAddr, _, _ = transaction.UnmarshalMetadataVerifyContract(metadata)

// remove exist contract info
// remove existing contract info (verified token)
if contractAddr != "" && contractAddr != updateContractAddr {
contractDoc, err := ns.getContract(contractAddr)
if err != nil || contractDoc == nil {
ns.log.Error().Err(err).Str("addr", contractAddr).Msg("contractDoc is not exist. wait until contractDoc added")
ns.log.Error().Err(err).Str("addr", contractAddr).Msg("contractDoc does not exist. wait until contractDoc is added")
return contractAddr
}
contractUpDoc := doc.ConvContractUp(contractDoc.Id, string(NotVerified), "", "", "")
ns.updateContract(contractUpDoc)
ns.log.Info().Str("contract", contractAddr).Str("token", tokenAddr).Msg("verified contract removed")
contractUpDoc := doc.ConvContractToken(contractDoc.Id, string(NotVerified), "")
ns.updateContractToken(contractUpDoc)
ns.log.Info().Str("contract", contractAddr).Str("token", tokenSymbol).Msg("verified contract removed")
}

// update contract info
if updateContractAddr != "" {
contractDoc, err := ns.getContract(updateContractAddr)
if err != nil || contractDoc == nil {
ns.log.Error().Err(err).Msg("contractDoc is not exist. wait until contractDoc added")
ns.log.Error().Err(err).Msg("contractDoc does not exist. wait until contractDoc is added")
return contractAddr // 기존 contract address 반환
}

/*
// skip if codeUrl not changed
var code string
var status string = string(NotVerified)
if codeUrl != "" && contractDoc.CodeUrl == codeUrl {
ns.log.Debug().Str("method", "verifyContract").Str("tokenAddr", tokenAddr).Msg("codeUrl is not changed, skip")
ns.log.Debug().Str("method", "verifyContract").Str("token", tokenSymbol).Msg("codeUrl is not changed, skip")
return updateContractAddr
}
code, err = lua_compiler.GetCode(codeUrl)
if err != nil {
ns.log.Error().Err(err).Str("method", "verifyContract").Msg("Failed to get code")
} else if len(code) > 0 {
status = string(Verified)
...
}
*/

// TODO : valid bytecode
/*
Expand All @@ -378,23 +382,51 @@ func (ns *Indexer) MinerContractVerified(tokenAddr, contractAddr, metadata strin
}

// compare bytecode and payload
var status string
if bytes.Contains([]byte(contractDoc.Payload), bytecode) == true {
status = string(Verified)
if bytes.Equal([]byte(contractDoc.ByteCode), bytecode) == true {
...
} else {
ns.log.Error().Str("method", "verifyContract").Str("tokenAddr", tokenAddr).Msg("Failed to verify contract")
ns.log.Error().Str("method", "verifyContract").Str("token", tokenSymbol).Msg("Failed to verify contract")
fmt.Println([]byte(contractDoc.Payload))
var i interface{}
json.Unmarshal([]byte(contractDoc.Payload), i)
fmt.Println(i)
fmt.Println(bytecode)
status = string(NotVerified)
}
*/

contractUpDoc := doc.ConvContractUp(updateContractAddr, status, tokenAddr, codeUrl, code)
ns.updateContract(contractUpDoc)
ns.log.Info().Str("contract", updateContractAddr).Str("token", tokenAddr).Msg("verified contract updated")
contractUpDoc := doc.ConvContractToken(updateContractAddr, string(Verified), tokenSymbol)
ns.updateContractToken(contractUpDoc)
ns.log.Info().Str("contract", updateContractAddr).Str("token", tokenSymbol).Msg("verified contract updated")
}
return updateContractAddr
}

// TODO: use this function in the backend
func (ns *Indexer) checkContractSourceCode(contractAddress, sourceCode string) (status string) {
contractDoc, err := ns.getContract(contractAddress)
if err != nil || contractDoc == nil {
ns.log.Error().Err(err).Str("addr", contractAddress).Msg("not found")
return "this contract is not yet added to the index. wait until it is indexed or check the contract address"
}

// compile the source code
bytecode, _, err := doc.CompileSourceCode(sourceCode)
if err != nil {
ns.log.Error().Err(err).Str("addr", contractAddress).Msg("failed to compile source code")
return "compile error"
}

// compare the generated bytecode with the contract bytecode
isCorrect := bytes.Equal(bytecode, []byte(contractDoc.ByteCode))

if isCorrect {
// store the source code in the contract doc
contractUpDoc := doc.ConvContractSource(contractDoc.Id, sourceCode)
ns.updateContractSource(contractUpDoc)
ns.log.Info().Str("contract", contractAddress).Msg("contract source code updated")
return "OK"
} else {
ns.log.Error().Str("contract", contractAddress).Msg("invalid source code")
return "invalid source code"
}
}