diff --git a/api/service/explorer/schema.go b/api/service/explorer/schema.go index dc9ac2162..c0d9f5583 100644 --- a/api/service/explorer/schema.go +++ b/api/service/explorer/schema.go @@ -37,21 +37,21 @@ func writeCheckpoint(db databaseWriter, bn uint64) error { return db.Put(blockCheckpoint, []byte{}) } -func getTraceResultKey(hash common.Hash) []byte { - return []byte(fmt.Sprintf("%s_%x", TracePrefix, hash)) +func getTraceResultKey(key []byte) []byte { + return append([]byte(TracePrefix), key...) } -func isTraceResultInDB(db databaseReader, hash common.Hash) (bool, error) { - key := getTraceResultKey(hash) +func isTraceResultInDB(db databaseReader, key []byte) (bool, error) { + key = getTraceResultKey(key) return db.Has(key) } -func writeTraceResult(db databaseWriter, hash common.Hash, data []byte) error { - key := getTraceResultKey(hash) +func writeTraceResult(db databaseWriter, key []byte, data []byte) error { + key = getTraceResultKey(key) return db.Put(key, data) } -func getTraceResult(db databaseReader, hash common.Hash) ([]byte, error) { - key := getTraceResultKey(hash) +func getTraceResult(db databaseReader, key []byte) ([]byte, error) { + key = getTraceResultKey(key) return db.Get(key) } diff --git a/api/service/explorer/service.go b/api/service/explorer/service.go index 30d587c93..d681d2a2f 100644 --- a/api/service/explorer/service.go +++ b/api/service/explorer/service.go @@ -20,6 +20,7 @@ import ( msg_pb "github.com/harmony-one/harmony/api/proto/message" "github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/hmy" + "github.com/harmony-one/harmony/hmy/tracers" "github.com/harmony-one/harmony/internal/chain" "github.com/harmony-one/harmony/internal/common" nodeconfig "github.com/harmony-one/harmony/internal/configs/node" @@ -192,8 +193,8 @@ func (s *Service) GetTraceResultByHash(hash ethCommon.Hash) (json.RawMessage, er } // DumpTraceResult instruct the explorer storage to trace data in explorer DB -func (s *Service) DumpTraceResult(hash ethCommon.Hash, data []byte) { - s.storage.DumpTraceResult(hash, data) +func (s *Service) DumpTraceResult(data *tracers.TraceBlockStorage) { + s.storage.DumpTraceResult(data) } // DumpNewBlock instruct the explorer storage to dump block data in explorer DB diff --git a/api/service/explorer/storage.go b/api/service/explorer/storage.go index b567f265f..fa13b97d8 100644 --- a/api/service/explorer/storage.go +++ b/api/service/explorer/storage.go @@ -14,6 +14,7 @@ import ( "github.com/harmony-one/harmony/core" core2 "github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/hmy/tracers" common2 "github.com/harmony-one/harmony/internal/common" "github.com/harmony-one/harmony/internal/utils" staking "github.com/harmony-one/harmony/staking/types" @@ -50,8 +51,7 @@ type ( traceResult struct { btc batch - hash common.Hash - data []byte + data *tracers.TraceBlockStorage } ) @@ -82,8 +82,8 @@ func (s *storage) Close() { close(s.closeC) } -func (s *storage) DumpTraceResult(hash common.Hash, data []byte) { - s.tm.AddNewTraceTask(hash, data) +func (s *storage) DumpTraceResult(data *tracers.TraceBlockStorage) { + s.tm.AddNewTraceTask(data) } func (s *storage) DumpNewBlock(b *types.Block) { @@ -130,7 +130,16 @@ func (s *storage) GetTraceResultByHash(hash common.Hash) (json.RawMessage, error if !s.available.IsSet() { return nil, ErrExplorerNotReady } - return getTraceResult(s.db, hash) + traceStorage := &tracers.TraceBlockStorage{ + Hash: hash, + } + err := traceStorage.FromDB(func(key []byte) ([]byte, error) { + return getTraceResult(s.db, key) + }) + if err != nil { + return nil, err + } + return traceStorage.ToJson() } func (s *storage) run() { @@ -157,7 +166,7 @@ func (s *storage) loop() { s.log.Error().Err(err).Msg("explorer db failed to write") } case res := <-s.resultT: - s.log.Info().Str("block hash", res.hash.Hex()).Msg("writing trace into explorer DB") + s.log.Info().Str("block hash", res.data.Hash.Hex()).Msg("writing trace into explorer DB") if err := res.btc.Write(); err != nil { s.log.Error().Err(err).Msg("explorer db failed to write trace data") } @@ -195,9 +204,8 @@ func (tm *taskManager) AddNewTask(b *types.Block) { } } -func (tm *taskManager) AddNewTraceTask(hash common.Hash, data []byte) { +func (tm *taskManager) AddNewTraceTask(data *tracers.TraceBlockStorage) { tm.T <- &traceResult{ - hash: hash, data: data, } } @@ -292,11 +300,13 @@ LOOP: } } case traceResult := <-bc.tm.T: - if exist, err := isTraceResultInDB(bc.db, traceResult.hash); exist || err != nil { + if exist, err := isTraceResultInDB(bc.db, traceResult.data.KeyDB()); exist || err != nil { continue } traceResult.btc = bc.db.NewBatch() - _ = writeTraceResult(traceResult.btc, traceResult.hash, traceResult.data) + traceResult.data.ToDB(func(key, value []byte) { + _ = writeTraceResult(traceResult.btc, key, value) + }) select { case bc.resultT <- traceResult: case <-bc.closeC: diff --git a/hmy/tracers/block_tracer_storage.go b/hmy/tracers/block_tracer_storage.go index d0e666e6b..95f7e53cb 100644 --- a/hmy/tracers/block_tracer_storage.go +++ b/hmy/tracers/block_tracer_storage.go @@ -18,12 +18,16 @@ package tracers import ( "encoding/json" + "errors" "fmt" "math/big" + "strconv" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" + "github.com/harmony-one/harmony/core/vm" "github.com/harmony-one/harmony/crypto/hash" + "github.com/harmony-one/harmony/internal/utils" ) type ActionStorage struct { @@ -109,27 +113,46 @@ func (ts *TraceBlockStorage) indexAddress(address common.Address) int { return index } -func (ts *TraceBlockStorage) toDB(write func([]byte, []byte)) { +func (ts *TraceBlockStorage) KeyDB() []byte { + return ts.Hash[:] +} + +func (ts *TraceBlockStorage) ToDB(write func([]byte, []byte)) { for index, key := range ts.DataKeyTable { write(key[:], ts.dataValueTable[index]) } bytes, _ := rlp.EncodeToBytes(ts) - write(ts.Hash[:], bytes) + write(ts.KeyDB(), bytes) } -func (ts *TraceBlockStorage) fromDB(read func([]byte) []byte, hash common.Hash) { - bytes := read(hash[:]) - rlp.DecodeBytes(bytes, ts) +func (ts *TraceBlockStorage) FromDB(read func([]byte) ([]byte, error)) error { + bytes, err := read(ts.KeyDB()) + if err != nil { + return err + } + err = rlp.DecodeBytes(bytes, ts) + if err != nil { + return err + } for _, key := range ts.DataKeyTable { - ts.dataValueTable = append(ts.dataValueTable, read(key[:])) + data, err := read(key[:]) + if err != nil { + return err + } + ts.dataValueTable = append(ts.dataValueTable, data) } + return nil } -func (ts *TraceBlockStorage) txJson(index int) []json.RawMessage { +func (ts *TraceBlockStorage) TxJson(index int) ([]json.RawMessage, error) { var results []json.RawMessage var txStorage TxStorage + var err error b := ts.TraceStorages[index] - rlp.DecodeBytes(b, &txStorage) + err = rlp.DecodeBytes(b, &txStorage) + if err != nil { + return nil, err + } headPiece := fmt.Sprintf( `"blockNumber":%d,"blockHash":"%s","transactionHash":"%s","transactionPosition":%d`, @@ -142,8 +165,8 @@ func (ts *TraceBlockStorage) txJson(index int) []json.RawMessage { typStr, acStr, outStr := ac.toJsonStr() if acStr == nil { - //err = errors.New("tracer internal failure") - return nil + err = errors.New("tracer internal failure") + return nil, err } traceStr, _ := json.Marshal(acStorage.TraceAddress) bodyPiece := fmt.Sprintf( @@ -161,13 +184,167 @@ func (ts *TraceBlockStorage) txJson(index int) []json.RawMessage { jstr := "{" + headPiece + bodyPiece + resultPiece + "}" results = append(results, json.RawMessage(jstr)) } - return results + return results, nil } -func (ts *TraceBlockStorage) toJson() []json.RawMessage { +func (ts *TraceBlockStorage) ToJson() (json.RawMessage, error) { var results []json.RawMessage for i := range ts.TraceStorages { - results = append(results, ts.txJson(i)...) + tx, err := ts.TxJson(i) + if err != nil { + return nil, err + } + results = append(results, tx...) + } + return json.Marshal(results) +} + +type JsonCallAction struct { + CallType string `json:"callType"` + Value string `json:"value"` + From common.Address `json:"from"` + To common.Address `json:"to"` + Gas string `json:"gas"` + Input string `json:"input"` +} + +type JsonCreateAction struct { + From common.Address `json:"from"` + Value string `json:"value"` + Gas string `json:"gas"` + Init string `json:"init"` +} + +type JsonSuicideAction struct { + RefundAddress common.Address `json:"refundAddress"` + Balance string `json:"balance"` + Address common.Address `json:"address"` +} + +type JsonCallOutput struct { + Output string `json:"output"` + GasUsed string `json:"gasUsed"` +} + +type JsonCreateOutput struct { + Address common.Address `json:"address"` + Code string `json:"code"` + GasUsed string `json:"gasUsed"` +} + +type JsonTrace struct { + BlockNumber uint64 `json:"blockNumber"` + BlockHash common.Hash `json:"blockHash"` + TransactionHash common.Hash `json:"transactionHash"` + TransactionPosition uint `json:"transactionPosition"` + Subtraces uint `json:"subtraces"` + TraceAddress []uint `json:"traceAddress"` + Typ string `json:"type"` + Action json.RawMessage `json:"action"` + Result json.RawMessage `json:"result"` + Err string `json:"error"` + Revert string `json:"revert"` +} + +func (ts *TraceBlockStorage) fromJson(bytes []byte) { + var actionObjs []JsonTrace + var txs []*TxStorage + var tx *TxStorage + json.Unmarshal(bytes, &actionObjs) + for _, obj := range actionObjs { + ac := action{} + if len(obj.Err) > 0 { + ac.err = errors.New(obj.Err) + ac.revert = utils.FromHex(obj.Revert) + } + if obj.Typ == "call" { + callAc := &JsonCallAction{} + err := json.Unmarshal(obj.Action, callAc) + if err != nil { + panic(err) + } + switch callAc.CallType { + case "staticcall": + ac.op = vm.STATICCALL + case "call": + ac.op = vm.CALL + case "callcode": + ac.op = vm.CALLCODE + case "delegatecall": + ac.op = vm.DELEGATECALL + } + ac.from = callAc.From + ac.to = callAc.To + ac.value, _ = new(big.Int).SetString(callAc.Value[2:], 16) + ac.gas, _ = strconv.ParseUint(callAc.Gas, 0, 64) + ac.input = utils.FromHex(callAc.Input) + + if ac.err == nil { + callOutput := &JsonCallOutput{} + err = json.Unmarshal(obj.Result, callOutput) + if err != nil { + panic(err) + } + ac.output = utils.FromHex(callOutput.Output) + ac.gasUsed, err = strconv.ParseUint(callOutput.GasUsed, 0, 64) + if err != nil { + panic(err) + } + } + } + if obj.Typ == "create" { + ac.op = vm.CREATE + callAc := &JsonCreateAction{} + err := json.Unmarshal(obj.Action, callAc) + if err != nil { + panic(err) + } + callOutput := &JsonCreateOutput{} + err = json.Unmarshal(obj.Result, callOutput) + if err != nil { + panic(err) + } + ac.from = callAc.From + ac.value, _ = new(big.Int).SetString(callAc.Value[2:], 16) + ac.gas, _ = strconv.ParseUint(callAc.Gas, 0, 64) + ac.input = utils.FromHex(callAc.Init) + + if ac.err == nil { + ac.to = callOutput.Address + ac.output = utils.FromHex(callOutput.Code) + ac.gasUsed, _ = strconv.ParseUint(callOutput.GasUsed, 0, 64) + } + } + if obj.Typ == "suicide" { + ac.op = vm.SELFDESTRUCT + callAc := &JsonSuicideAction{} + err := json.Unmarshal(obj.Action, callAc) + if err != nil { + panic(err) + } + + ac.from = callAc.Address + ac.to = callAc.RefundAddress + ac.value, _ = new(big.Int).SetString(callAc.Balance[2:], 16) + } + ts.Hash = obj.BlockHash + ts.Number = obj.BlockNumber + if tx == nil || tx.Hash != obj.TransactionHash { + tx = &TxStorage{ + Hash: obj.TransactionHash, + } + txs = append(txs, tx) + } + acStorage := ac.toStorage(ts) + acStorage.Subtraces = obj.Subtraces + acStorage.TraceAddress = obj.TraceAddress + tx.Storages = append(tx.Storages, acStorage) + } + for _, tx := range txs { + b, err := rlp.EncodeToBytes(tx) + if err != nil { + panic(err) + } + ts.TraceStorages = append(ts.TraceStorages, b) } - return results } diff --git a/node/node_explorer.go b/node/node_explorer.go index de56a7c93..ba5993516 100644 --- a/node/node_explorer.go +++ b/node/node_explorer.go @@ -131,12 +131,9 @@ func (node *Node) TraceLoopForExplorer() { loop: select { case ev := <-ch: - if traceResults, err := ev.Tracer.GetResult(); err == nil { - if exp, err := node.getExplorerService(); err == nil { - if raw, err := json.Marshal(traceResults); err == nil { - exp.DumpTraceResult(ev.Block.Hash(), raw) - } - } + if exp, err := node.getExplorerService(); err == nil { + storage := ev.Tracer.GetStorage() + exp.DumpTraceResult(storage) } goto loop case <-subscribe.Err():