"sync"
"time"
+ "github.com/bytom/bytom/contract"
"github.com/kr/secureheader"
log "github.com/sirupsen/logrus"
cmn "github.com/tendermint/tmlibs/common"
Data interface{} `json:"data,omitempty"`
}
-//NewSuccessResponse success response
+// NewSuccessResponse success response
func NewSuccessResponse(data interface{}) Response {
return Response{Status: SUCCESS, Data: data}
}
-//FormatErrResp format error response
+// FormatErrResp format error response
func FormatErrResp(err error) (response Response) {
response = Response{Status: FAIL}
root := errors.Root(err)
return response
}
-//NewErrorResponse error response
+// NewErrorResponse error response
func NewErrorResponse(err error) Response {
response := FormatErrResp(err)
return response
wallet *wallet.Wallet
accessTokens *accesstoken.CredentialStore
chain *protocol.Chain
+ contractTracer *contract.TraceService
server *http.Server
handler http.Handler
blockProposer *blockproposer.BlockProposer
}
// NewAPI create and initialize the API
-func NewAPI(sync NetSync, wallet *wallet.Wallet, blockProposer *blockproposer.BlockProposer, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore, dispatcher *event.Dispatcher, notificationMgr *websocket.WSNotificationManager) *API {
+func NewAPI(sync NetSync, wallet *wallet.Wallet, blockProposer *blockproposer.BlockProposer, chain *protocol.Chain, traceService *contract.TraceService, config *cfg.Config, token *accesstoken.CredentialStore, dispatcher *event.Dispatcher, notificationMgr *websocket.WSNotificationManager) *API {
api := &API{
sync: sync,
wallet: wallet,
chain: chain,
+ contractTracer: traceService,
accessTokens: token,
blockProposer: blockProposer,
eventDispatcher: dispatcher,
m.Handle("/get-merkle-proof", jsonHandler(a.getMerkleProof))
m.Handle("/get-vote-result", jsonHandler(a.getVoteResult))
+ m.Handle("/get-contract-instance", jsonHandler(a.getContractInstance))
+ m.Handle("/create-contract-instance", jsonHandler(a.createContractInstance))
+ m.Handle("/remove-contract-instance", jsonHandler(a.removeContractInstance))
+
m.HandleFunc("/websocket-subscribe", a.websocketHandler)
handler := walletHandler(m, walletEnable)
"github.com/bytom/bytom/crypto/sha3pool"
chainjson "github.com/bytom/bytom/encoding/json"
"github.com/bytom/bytom/errors"
+ "github.com/bytom/bytom/protocol/bc"
"github.com/bytom/bytom/protocol/vm/vmutil"
)
return NewSuccessResponse(cs)
}
+
+type ContractInstance struct {
+ UTXOs []*contract.UTXO `json:"utxos"`
+ TxHash *bc.Hash `json:"tx_hash"`
+ Status contract.Status `json:"status"`
+ Unconfirmed []*contract.TreeNode `json:"unconfirmed"`
+}
+
+func (a *API) getContractInstance(_ context.Context, ins struct {
+ TraceID string `json:"trace_id"`
+}) Response {
+ instance, err := a.contractTracer.GetInstance(ins.TraceID)
+ if err != nil {
+ return NewErrorResponse(err)
+ }
+
+ return NewSuccessResponse(&ContractInstance{
+ UTXOs: instance.UTXOs,
+ TxHash: instance.TxHash,
+ Status: instance.Status,
+ Unconfirmed: instance.Unconfirmed,
+ })
+}
+
+func (a *API) createContractInstance(_ context.Context, ins struct {
+ TxHash chainjson.HexBytes `json:"tx_hash"`
+ BlockHash chainjson.HexBytes `json:"block_hash"`
+}) Response {
+ var txHash, blockHash [32]byte
+ copy(txHash[:], ins.TxHash)
+ copy(blockHash[:], ins.BlockHash)
+
+ traceIDs, err := a.contractTracer.CreateInstance(bc.NewHash(txHash), bc.NewHash(blockHash))
+ if err != nil {
+ return NewErrorResponse(err)
+ }
+
+ return NewSuccessResponse(traceIDs)
+}
+
+func (a *API) removeContractInstance(_ context.Context, ins struct {
+ TraceID string `json:"trace_id"`
+}) Response {
+ if err := a.contractTracer.RemoveInstance(ins.TraceID); err != nil {
+ return NewErrorResponse(err)
+ }
+
+ return NewSuccessResponse(nil)
+}
OffChain
)
-type treeNode struct {
- TxHash bc.Hash
- UTXOs []*UTXO
- Children []*treeNode
+type TreeNode struct {
+ TxHash bc.Hash `json:"tx_hash"`
+ UTXOs []*UTXO `json:"utxos"`
+ Children []*TreeNode `json:"children"`
}
type Instance struct {
- TraceID string
- UTXOs []*UTXO
- Unconfirmed []*treeNode
- Status Status
- ScannedHash bc.Hash
- ScannedHeight uint64
+ TraceID string `json:"trace_id"`
+ UTXOs []*UTXO `json:"utxos"`
+ TxHash *bc.Hash `json:"tx_hash"`
+ Status Status `json:"status"`
+ ScannedHash bc.Hash `json:"scanned_hash"`
+ ScannedHeight uint64 `json:"scanned_height"`
+ Unconfirmed []*TreeNode
}
-func newInstance(inUTXOs, outUTXOs []*UTXO) *Instance {
+func newInstance(inUTXOs, outUTXOs []*UTXO, txHash bc.Hash, block *types.Block) *Instance {
inst := &Instance{
- TraceID: uuid.New().String(),
- UTXOs: outUTXOs,
+ TraceID: uuid.New().String(),
+ TxHash: &txHash,
+ UTXOs: outUTXOs,
+ ScannedHeight: block.Height,
+ ScannedHash: block.Hash(),
}
inst.Status = Lagging
if len(outUTXOs) == 0 {
return inst
}
-func (i *Instance) transferTo(newUTXOs []*UTXO) *Instance {
+func (i *Instance) transferTo(newUTXOs []*UTXO, txHash bc.Hash) *Instance {
inst := &Instance{
TraceID: i.TraceID,
Status: i.Status,
Unconfirmed: i.Unconfirmed,
UTXOs: newUTXOs,
+ TxHash: &txHash,
}
if len(newUTXOs) == 0 {
inst.Status = Finalized
inst.UTXOs = i.UTXOs
}
+ inst.confirmTx(txHash)
return inst
}
+func (i *Instance) rollbackTo(newUTXOs []*UTXO) *Instance {
+ return &Instance{
+ TraceID: i.TraceID,
+ Status: InSync,
+ UTXOs: newUTXOs,
+ TxHash: nil,
+ Unconfirmed: nil,
+ }
+}
+
func (i *Instance) confirmTx(txHash bc.Hash) {
for _, node := range i.Unconfirmed {
if node.TxHash == txHash {
i.Unconfirmed = nil
}
-type instanceTable struct {
+type instanceIndex struct {
traceIdToInst map[string]*Instance
utxoHashToInst map[bc.Hash]*Instance
}
-func newInstanceTable() *instanceTable {
- return &instanceTable{
+func newInstanceIndex() *instanceIndex {
+ return &instanceIndex{
traceIdToInst: make(map[string]*Instance),
utxoHashToInst: make(map[bc.Hash]*Instance),
}
}
-func (i *instanceTable) getByID(id string) *Instance {
+func (i *instanceIndex) getAll() []*Instance {
+ var instances []*Instance
+ for _, inst := range i.traceIdToInst {
+ instances = append(instances, inst)
+ }
+ return instances
+}
+
+func (i *instanceIndex) getByID(id string) *Instance {
return i.traceIdToInst[id]
}
-func (i *instanceTable) getByUTXO(utxoHash bc.Hash) *Instance {
+func (i *instanceIndex) getByUTXO(utxoHash bc.Hash) *Instance {
return i.utxoHashToInst[utxoHash]
}
-func (i *instanceTable) save(newInst *Instance) {
+func (i *instanceIndex) add(instance *Instance) {
+ i.traceIdToInst[instance.TraceID] = instance
+ for _, utxo := range instance.UTXOs {
+ i.utxoHashToInst[utxo.Hash] = instance
+ }
+}
+
+func (i *instanceIndex) save(newInst *Instance) {
if old, ok := i.traceIdToInst[newInst.TraceID]; ok {
for _, utxo := range old.UTXOs {
- delete(i.utxoHashToInst, utxo.hash)
+ delete(i.utxoHashToInst, utxo.Hash)
}
}
i.add(newInst)
}
-func (i *instanceTable) remove(id string) {
+func (i *instanceIndex) remove(id string) {
if inst, ok := i.traceIdToInst[id]; ok {
delete(i.traceIdToInst, id)
for _, utxo := range inst.UTXOs {
- delete(i.utxoHashToInst, utxo.hash)
+ delete(i.utxoHashToInst, utxo.Hash)
}
}
}
-func (i *instanceTable) add(instance *Instance) {
- i.traceIdToInst[instance.TraceID] = instance
- for _, utxo := range instance.UTXOs {
- i.utxoHashToInst[utxo.hash] = instance
- }
-}
-
type UTXO struct {
- hash bc.Hash
- assetID bc.AssetID
- amount uint64
- program []byte
- sourceID bc.Hash
- sourcePos uint64
- stateData [][]byte
+ Hash bc.Hash `json:"hash"`
+ AssetID bc.AssetID `json:"asset_id"`
+ Amount uint64 `json:"amount"`
+ Program []byte `json:"program"`
+ SourceID bc.Hash `json:"source_id"`
+ SourcePos uint64 `json:"source_pos"`
+ StateData [][]byte `json:"state_data"`
}
func inputToUTXO(tx *types.Tx, index int) *UTXO {
input := tx.Inputs[index]
+ outputID, _ := input.SpentOutputID()
spendInput := input.TypedInput.(*types.SpendInput)
return &UTXO{
- hash: tx.InputIDs[index],
- assetID: input.AssetID(),
- amount: input.Amount(),
- program: input.ControlProgram(),
- sourceID: spendInput.SourceID,
- sourcePos: spendInput.SourcePosition,
- stateData: spendInput.StateData,
+ Hash: outputID,
+ AssetID: input.AssetID(),
+ Amount: input.Amount(),
+ Program: input.ControlProgram(),
+ SourceID: spendInput.SourceID,
+ SourcePos: spendInput.SourcePosition,
+ StateData: spendInput.StateData,
}
}
outputID := tx.OutputID(index)
originalOutput, _ := tx.OriginalOutput(*outputID)
return &UTXO{
- hash: *outputID,
- assetID: *output.AssetId,
- amount: output.Amount,
- program: output.ControlProgram,
- sourceID: *originalOutput.Source.Ref,
- sourcePos: uint64(index),
- stateData: originalOutput.StateData,
+ Hash: *outputID,
+ AssetID: *output.AssetId,
+ Amount: output.Amount,
+ Program: output.ControlProgram,
+ SourceID: *originalOutput.Source.Ref,
+ SourcePos: uint64(index),
+ StateData: originalOutput.StateData,
}
}
defer ticker.Stop()
for range ticker.C {
- jobs, beginHeight := t.prepareJobs()
+ jobs, beginHeight, beginHash := t.prepareJobs()
if len(jobs) == 0 {
continue
}
- t.tracer = newTracer(nil)
+ t.tracer = newTracer(jobs[beginHash])
- var prevHash *bc.Hash
- catchedJobs := make(map[bc.Hash][]*Instance)
- for height := beginHeight + 1; ; height++ {
- if ok, err := t.tryAttach(height, prevHash, jobs, catchedJobs); err != nil {
+ for height, blockHash := beginHeight, beginHash; ; height++ {
+ if bestHeight := t.tracerService.BestHeight(); height == bestHeight {
+ if err := t.finishJobs(jobs, blockHash); err != nil {
+ log.WithField("err", err).Error("finish jobs")
+ break
+ }
+ }
+
+ if ok, err := t.tryAttach(height+1, &blockHash, jobs); err != nil {
log.WithField("err", err).Error("try attach on trace scheduler")
break
} else if !ok {
- if err := t.detach(prevHash, catchedJobs); err != nil {
+ if err := t.detach(&blockHash, jobs); err != nil {
log.WithField("err", err).Error("detach on trace scheduler")
break
}
height -= 2
}
- if bestHeight := t.tracerService.BestHeight(); height == bestHeight {
- if err := t.finishJobs(jobs, catchedJobs, *prevHash); err != nil {
- log.WithField("err", err).Error("finish jobs")
- break
- }
- }
}
}
}
-func (t *traceScheduler) prepareJobs() (map[bc.Hash][]*Instance, uint64) {
- var beginHeight uint64 = math.MaxUint64
+func (t *traceScheduler) prepareJobs() (map[bc.Hash][]*Instance, uint64, bc.Hash) {
+ beginHeight, beginHash := uint64(math.MaxUint64), bc.Hash{}
hashToJobs := make(map[bc.Hash][]*Instance)
t.instances.Range(func(_, value interface{}) bool {
inst := value.(*Instance)
hashToJobs[inst.ScannedHash] = append(hashToJobs[inst.ScannedHash], inst)
if inst.ScannedHeight < beginHeight {
beginHeight = inst.ScannedHeight
+ beginHash = inst.ScannedHash
}
return true
})
- return hashToJobs, beginHeight
+ return hashToJobs, beginHeight, beginHash
}
-func (t *traceScheduler) tryAttach(height uint64, prevHash *bc.Hash, jobs, catchedJobs map[bc.Hash][]*Instance) (bool, error) {
+func (t *traceScheduler) tryAttach(height uint64, blockHash *bc.Hash, jobs map[bc.Hash][]*Instance) (bool, error) {
block, err := t.infra.Chain.GetBlockByHeight(height)
if err != nil {
return false, err
}
- if prevHash != nil && block.PreviousBlockHash != *prevHash {
+ if block.PreviousBlockHash != *blockHash {
return false, nil
}
- if instances, ok := jobs[block.PreviousBlockHash]; ok {
+ t.tracer.applyBlock(block)
+ *blockHash = block.Hash()
+
+ if instances, ok := jobs[block.Hash()]; ok {
t.tracer.addInstances(instances)
- catchedJobs[block.PreviousBlockHash] = instances
}
-
- t.tracer.applyBlock(block)
- *prevHash = block.Hash()
return true, nil
}
-func (t *traceScheduler) detach(prevHash *bc.Hash, catchedJobs map[bc.Hash][]*Instance) error {
- prevBlock, err := t.infra.Chain.GetBlockByHash(prevHash)
+func (t *traceScheduler) detach(blockHash *bc.Hash, jobs map[bc.Hash][]*Instance) error {
+ block, err := t.infra.Chain.GetBlockByHash(blockHash)
if err != nil {
return err
}
- if instances, ok := catchedJobs[prevBlock.Hash()]; ok {
+ if instances, ok := jobs[block.Hash()]; ok {
for _, inst := range instances {
t.tracer.removeInstance(inst.TraceID)
}
- delete(catchedJobs, prevBlock.Hash())
}
- t.tracer.detachBlock(prevBlock)
- *prevHash = prevBlock.PreviousBlockHash
+ t.tracer.detachBlock(block)
+ *blockHash = block.PreviousBlockHash
return nil
}
-func (t *traceScheduler) finishJobs(jobs, catchedJobs map[bc.Hash][]*Instance, scannedHash bc.Hash) error {
- var inSyncInstances, offChainInstances []*Instance
- for hash, instances := range jobs {
- if _, ok := catchedJobs[hash]; !ok {
- offChainInstances = append(offChainInstances, instances...)
- for _, inst := range instances {
+func (t *traceScheduler) finishJobs(jobs map[bc.Hash][]*Instance, scannedHash bc.Hash) error {
+ inSyncInstances := t.tracer.allInstances()
+ inSyncMap := make(map[string]bool)
+ for _, inst := range inSyncInstances {
+ inSyncMap[inst.TraceID] = true
+ }
+
+ var offChainInstances []*Instance
+ for _, instances := range jobs {
+ for _, inst := range instances {
+ if _, ok := inSyncMap[inst.TraceID]; !ok {
inst.Status = OffChain
+ offChainInstances = append(offChainInstances, inst)
}
- } else {
- inSyncInstances = append(inSyncInstances, instances...)
}
}
tracer *tracer
infra *Infrastructure
scheduler *traceScheduler
- unconfirmedIndex map[bc.Hash]*treeNode
+ unconfirmedIndex map[bc.Hash]*TreeNode
bestHeight uint64
bestHash bc.Hash
}
chainStatus := infra.Repository.GetChainStatus()
if chainStatus == nil {
chainStatus.BlockHeight, chainStatus.BlockHash = infra.Chain.BestChain()
- if err := infra.Repository.SaveChainStatus(chainStatus); err != nil {
+ if err := infra.Repository.SaveChainStatus(chainStatus); err != nil {
logrus.WithField("err", err).Fatal("init chain status for trace service")
}
}
infra: infra,
tracer: newTracer(inSyncInstances),
scheduler: scheduler,
- unconfirmedIndex: make(map[bc.Hash]*treeNode),
+ unconfirmedIndex: make(map[bc.Hash]*TreeNode),
bestHeight: chainStatus.BlockHeight,
bestHash: chainStatus.BlockHash,
}
return
}
- treeNode := &treeNode{TxHash: tx.ID, UTXOs: outUTXOs}
- if inst := t.tracer.table.getByUTXO(inUTXOs[0].hash); inst != nil {
+ treeNode := &TreeNode{TxHash: tx.ID, UTXOs: outUTXOs}
+ if inst := t.tracer.table.getByUTXO(inUTXOs[0].Hash); inst != nil {
inst.Unconfirmed = append(inst.Unconfirmed, treeNode)
t.addToUnconfirmedIndex(treeNode, outUTXOs)
return
}
- if parent, ok := t.unconfirmedIndex[inUTXOs[0].hash]; ok {
+ if parent, ok := t.unconfirmedIndex[inUTXOs[0].Hash]; ok {
parent.Children = append(parent.Children, treeNode)
t.addToUnconfirmedIndex(treeNode, outUTXOs)
}
var traceIDs []string
for _, transfer := range transfers {
- inst := newInstance(transfer.inUTXOs, transfer.outUTXOs)
+ inst := newInstance(transfer.inUTXOs, transfer.outUTXOs, txHash, block)
traceIDs = append(traceIDs, inst.TraceID)
if err := t.addNewTraceJob(inst, block); err != nil {
return nil, err
return false
}
+ for _, inst := range instances {
+ inst.Status = InSync
+ }
+
if err := t.infra.Repository.SaveInstances(instances); err != nil {
logrus.WithField("err", err).Error("save instances when take over instances")
return false
}
if inst.Status != Finalized {
- inst.ScannedHash = block.Hash()
- inst.ScannedHeight = block.Height
if err := t.scheduler.addNewJob(inst); err != nil {
return err
}
return nil
}
-func (t *TraceService) addToUnconfirmedIndex(treeNode *treeNode, utxos []*UTXO) {
+func (t *TraceService) addToUnconfirmedIndex(treeNode *TreeNode, utxos []*UTXO) {
for _, utxo := range utxos {
- t.unconfirmedIndex[utxo.hash] = treeNode
+ t.unconfirmedIndex[utxo.Hash] = treeNode
}
}
// LoadInstances used to load all instances in db
func (t *TraceStore) LoadInstances() ([]*Instance, error) {
- iter := t.db.Iterator()
+ iter := t.db.IteratorPrefix(instancePrefixKey)
defer iter.Release()
var instances []*Instance
)
type tracer struct {
- table *instanceTable
+ table *instanceIndex
}
func newTracer(instances []*Instance) *tracer {
- table := newInstanceTable()
+ table := newInstanceIndex()
for _, inst := range instances {
table.save(inst)
}
return &tracer{table: table}
}
+func (t *tracer) allInstances() []*Instance {
+ return t.table.getAll()
+}
+
func (t *tracer) addInstances(instances []*Instance) {
for _, inst := range instances {
t.table.save(inst)
continue
}
- if inst := t.table.getByUTXO(transfer.inUTXOs[0].hash); inst != nil {
- newInst := inst.transferTo(transfer.outUTXOs)
- newInst.confirmTx(tx.ID)
+ if inst := t.table.getByUTXO(transfer.inUTXOs[0].Hash); inst != nil {
+ newInst := inst.transferTo(transfer.outUTXOs, tx.ID)
newInstances = append(newInstances, newInst)
}
}
func (t *tracer) detachBlock(block *types.Block) []*Instance {
var newInstances []*Instance
- for i := len(block.Transactions); i >= 0; i-- {
+ for i := len(block.Transactions) - 1; i >= 0; i-- {
tx := block.Transactions[i]
transfers := parseTransfers(tx)
for _, transfer := range transfers {
utxos := append(transfer.outUTXOs, transfer.inUTXOs...)
- if inst := t.table.getByUTXO(utxos[0].hash); inst != nil {
- newInst := inst.transferTo(transfer.inUTXOs)
- newInst.Unconfirmed = nil
+ if inst := t.table.getByUTXO(utxos[0].Hash); inst != nil {
+ newInst := inst.rollbackTo(transfer.inUTXOs)
newInstances = append(newInstances, newInst)
}
}
func groupUTXOs(utxos []*UTXO) map[string][]*UTXO {
groupUTXOs := make(map[string][]*UTXO)
for _, utxo := range utxos {
- program := hex.EncodeToString(utxo.program)
+ program := hex.EncodeToString(utxo.Program)
groupUTXOs[program] = append(groupUTXOs[program], utxo)
}
return groupUTXOs
func parseContractUTXOs(tx *types.Tx) ([]*UTXO, []*UTXO) {
var inUTXOs, outUTXOs []*UTXO
for i, input := range tx.Inputs {
- if segwit.IsP2WSHScript(input.ControlProgram()) {
+ if isContract(input.ControlProgram()) {
inUTXOs = append(inUTXOs, inputToUTXO(tx, i))
}
}
for i, output := range tx.Outputs {
- if segwit.IsP2WSHScript(output.ControlProgram) {
+ if isContract(output.ControlProgram) {
outUTXOs = append(outUTXOs, outputToUTXO(tx, i))
}
}
return inUTXOs, outUTXOs
}
+func isContract(program []byte) bool {
+ return !(segwit.IsP2WPKHScript(program) || segwit.IsP2WSHScript(program) || segwit.IsStraightforward(program))
+}
+
func (t *tracer) saveInstances(instances []*Instance) {
for _, inst := range instances {
t.table.save(inst)
notificationMgr *websocket.WSNotificationManager
api *api.API
chain *protocol.Chain
+ traceService *contract.TraceService
blockProposer *blockproposer.BlockProposer
miningEnable bool
}
cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))
}
- startTraceUpdater(chain, config)
+ traceService := startTraceUpdater(chain, config)
var accounts *account.Manager
var assets *asset.Registry
accessTokens: accessTokens,
wallet: wallet,
chain: chain,
+ traceService: traceService,
miningEnable: config.Mining,
notificationMgr: notificationMgr,
}
return node
}
-func startTraceUpdater(chain *protocol.Chain, cfg *cfg.Config) {
+func startTraceUpdater(chain *protocol.Chain, cfg *cfg.Config) *contract.TraceService {
db := dbm.NewDB("trace", cfg.DBBackend, cfg.DBDir())
store := contract.NewTraceStore(db)
tracerService := contract.NewTraceService(contract.NewInfrastructure(chain, store))
traceUpdater := contract.NewTraceUpdater(tracerService, chain)
go traceUpdater.Sync()
+ return tracerService
}
func initNodeConfig(config *cfg.Config) error {
}
func (n *Node) initAndstartAPIServer() {
- n.api = api.NewAPI(n.syncManager, n.wallet, n.blockProposer, n.chain, n.config, n.accessTokens, n.eventDispatcher, n.notificationMgr)
+ n.api = api.NewAPI(n.syncManager, n.wallet, n.blockProposer, n.chain, n.traceService, n.config, n.accessTokens, n.eventDispatcher, n.notificationMgr)
listenAddr := env.String("LISTEN", n.config.ApiAddress)
env.Parse()