From 1195115230090fa4cb648a6a6a0ece48aa36a165 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Fri, 23 Sep 2022 09:49:09 -0700 Subject: [PATCH 1/8] [accesscontrol] Add dependency dag --- app/app.go | 32 ++++++++- app/utils.go | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 +- go.sum | 4 +- 4 files changed, 224 insertions(+), 5 deletions(-) diff --git a/app/app.go b/app/app.go index a66f9d353a..48034a5b2a 100644 --- a/app/app.go +++ b/app/app.go @@ -316,7 +316,7 @@ type App struct { optimisticProcessingInfo *OptimisticProcessingInfo // batchVerifier *ante.SR25519BatchVerifier - // txDecoder sdk.TxDecoder + txDecoder sdk.TxDecoder } // New returns a reference to an initialized blockchain app @@ -375,7 +375,7 @@ func New( Tracer: &tr, TracerContext: context.Background(), }, - // txDecoder: encodingConfig.TxConfig.TxDecoder(), + txDecoder: encodingConfig.TxConfig.TxDecoder(), } app.ParamsKeeper = initParamsKeeper(appCodec, cdc, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) @@ -871,6 +871,30 @@ func (app *App) ProcessProposalHandler(ctx sdk.Context, req *abci.RequestProcess }, nil } +func (app *App) BuildDependencyDag(ctx sdk.Context, txs [][]byte) (*Dag, error) { + // contains the latest msg index for a specific Access Operation + dependencyDag := Dag{ + NodeMap: make(map[int]DagNode), + EdgesMap: make(map[int][]DagEdge), + } + for txIndex, txBytes := range txs { + tx, err := app.txDecoder(txBytes) + if err != nil { + return nil, err + } + msgs := tx.GetMsgs() + for _, msg := range msgs { + msgDependencies := app.AccessControlKeeper.GetResourceDepedencyMapping(ctx, msg.GetAccessMappingKey()) + for _, accessOp := range msgDependencies.AccessOps { + // make a new node in the dependency dag + dependencyDag.AddNodeBuildDependency(ctx, txIndex, accessOp) + } + } + + } + return &dependencyDag, nil +} + func (app *App) FinalizeBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { startTime := time.Now() defer func() { @@ -941,6 +965,10 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ // } // app.batchVerifier.VerifyTxs(ctx, typedTxs) + // TODO: build dag here and verify + // for tx in txs, we need to get their message dependencies from access control module + // for each resource type it uses, we need to + txResults := []*abci.ExecTxResult{} for _, tx := range txs { // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) diff --git a/app/utils.go b/app/utils.go index 51ed0501a9..88d1b4e9bf 100644 --- a/app/utils.go +++ b/app/utils.go @@ -3,6 +3,9 @@ package app import ( "time" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + accesscontroltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" abci "github.com/tendermint/tendermint/abci/types" ) @@ -17,6 +20,193 @@ type OptimisticProcessingInfo struct { EndBlockResp abci.ResponseEndBlock } +type DagNode struct { + NodeId int + TxIndex int + AccessOperation accesscontroltypes.AccessOperation +} + +type DagEdge struct { + FromNodeId int + ToNodeId int + AccessOperation *accesscontroltypes.AccessOperation +} + +type Dag struct { + NodeMap map[int]DagNode + EdgesMap map[int][]DagEdge // maps node Id (from node) and contains edge info + AccessOpsMap map[accesscontroltypes.AccessOperation]int // tracks latest node to use a specific access op + TxIndexMap map[int]int // tracks latest node ID for a tx index + NextId int +} + +func (edge *DagEdge) GetCompletionSignal() *CompletionSignal { + // only if there is an access operation + if edge.AccessOperation != nil { + return &CompletionSignal{ + FromNodeId: edge.FromNodeId, + ToNodeId: edge.ToNodeId, + AccessOperation: *edge.AccessOperation, + } + } else { + return nil + } +} + +// Order returns the number of vertices in a graph. +func (dag *Dag) Order() int { + return len(dag.NodeMap) +} + +// Visit calls the do function for each neighbor w of vertex v, used by the graph acyclic validator +func (dag *Dag) Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool) { + for w, _ := range dag.EdgesMap[v] { + // just have cost as zero because we only need for acyclic validation purposes + if do(w, 0) { + return true + } + } + return false +} + +func NewDag() Dag { + return Dag{ + NodeMap: make(map[int]DagNode), + EdgesMap: make(map[int][]DagEdge), + AccessOpsMap: make(map[accesscontroltypes.AccessOperation]int), + TxIndexMap: make(map[int]int), + NextId: 0, + } +} + +func (dag *Dag) AddNode(txIndex int, accessOp types.AccessOperation) DagNode { + dagNode := DagNode{ + NodeId: dag.NextId, + TxIndex: txIndex, + AccessOperation: accessOp, + } + dag.NodeMap[dag.NextId] = dagNode + dag.NextId += 1 + return dagNode +} + +func (dag *Dag) AddEdge(fromIndex int, toIndex int, accessOp *accesscontroltypes.AccessOperation) *DagEdge { + // no-ops if the from or to node doesn't exist + if _, ok := dag.NodeMap[fromIndex]; !ok { + return nil + } + if _, ok := dag.NodeMap[toIndex]; !ok { + return nil + } + newEdge := DagEdge{fromIndex, toIndex, accessOp} + edges, ok := dag.EdgesMap[fromIndex] + if !ok { + edges = []DagEdge{newEdge} + } + dag.EdgesMap[fromIndex] = append(edges, newEdge) + return &newEdge +} + +func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp types.AccessOperation) { + dagNode := dag.AddNode(txIndex, accessOp) + // if in TxIndexMap, make an edge from the previous node index + if lastTxNodeId, ok := dag.TxIndexMap[txIndex]; ok { + // add an edge with no access op + dag.AddEdge(lastTxNodeId, dagNode.NodeId, nil) + // update tx index map + dag.TxIndexMap[txIndex] = dagNode.NodeId + } + // if the blocking access ops are in access ops map, make an edge + switch accessOp.AccessType { + case accesscontroltypes.AccessType_READ: + // if we need to do a read, we need latest write as a dependency + writeAccessOp := accesscontroltypes.AccessOperation{ + AccessType: accesscontroltypes.AccessType_WRITE, + ResourceType: accessOp.GetResourceType(), + IdentifierTemplate: accessOp.GetIdentifierTemplate(), + } + if writeNodeId, ok := dag.AccessOpsMap[writeAccessOp]; ok { + // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) + writeNode := dag.NodeMap[writeNodeId] + // if from a previous transaction, we need to create an edge + if writeNode.TxIndex < dagNode.TxIndex { + dag.AddEdge(writeNode.NodeId, dagNode.NodeId, &writeAccessOp) + } + } + case accesscontroltypes.AccessType_WRITE: + // if we need to do a write, we need read and write as dependencies + writeAccessOp := accesscontroltypes.AccessOperation{ + AccessType: accesscontroltypes.AccessType_WRITE, + ResourceType: accessOp.GetResourceType(), + IdentifierTemplate: accessOp.GetIdentifierTemplate(), + } + if writeNodeId, ok := dag.AccessOpsMap[writeAccessOp]; ok { + // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) + writeNode := dag.NodeMap[writeNodeId] + // if from a previous transaction, we need to create an edge + if writeNode.TxIndex < dagNode.TxIndex { + dag.AddEdge(writeNode.NodeId, dagNode.NodeId, &writeAccessOp) + } + } + readAccessOp := accesscontroltypes.AccessOperation{ + AccessType: accesscontroltypes.AccessType_READ, + ResourceType: accessOp.GetResourceType(), + IdentifierTemplate: accessOp.GetIdentifierTemplate(), + } + if readNodeId, ok := dag.AccessOpsMap[readAccessOp]; ok { + // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) + readNode := dag.NodeMap[readNodeId] + // if from a previous transaction, we need to create an edge + if readNode.TxIndex < dagNode.TxIndex { + dag.AddEdge(readNode.NodeId, dagNode.NodeId, &readAccessOp) + } + } + default: + // unknown or something else, raise error + ctx.Logger().Error("Invalid AccessControlType Received") + } + // update access ops map with the latest node id using a specific access op + dag.AccessOpsMap[accessOp] = dagNode.NodeId +} + +// returns completion signaling map and blocking signals map +func (dag *Dag) BuildCompletionSignalMaps() (completionSignalingMap map[int]map[accesscontroltypes.AccessOperation][]CompletionSignal, blockingSignalsMap map[int]map[accesscontroltypes.AccessOperation][]CompletionSignal) { + // go through every node + for _, node := range dag.NodeMap { + // for each node, assign its completion signaling, and also assign blocking signals for the destination nodes + if outgoingEdges, ok := dag.EdgesMap[node.NodeId]; ok { + var completionSignals []CompletionSignal + for _, edge := range outgoingEdges { + maybeCompletionSignal := edge.GetCompletionSignal() + if maybeCompletionSignal != nil { + completionSignal := *maybeCompletionSignal + completionSignals = append(completionSignals, completionSignal) + // also add it to the right blocking signal in the right txindex + toNode := dag.NodeMap[edge.ToNodeId] + if blockingSignals, ok2 := blockingSignalsMap[toNode.TxIndex][node.AccessOperation]; ok2 { + blockingSignalsMap[toNode.TxIndex][node.AccessOperation] = append(blockingSignals, completionSignal) + } else { + blockingSignalsMap[toNode.TxIndex][node.AccessOperation] = []CompletionSignal{completionSignal} + } + } + } + // assign completion signals for the tx index + if signals, ok3 := completionSignalingMap[node.TxIndex][node.AccessOperation]; ok3 { + completionSignalingMap[node.TxIndex][node.AccessOperation] = append(signals, completionSignals...) + } else { + completionSignalingMap[node.TxIndex][node.AccessOperation] = completionSignals + } + } + } + return +} + +type CompletionSignal struct { + FromNodeId int + ToNodeId int + AccessOperation types.AccessOperation +} + type BlockProcessRequest interface { GetHash() []byte GetTxs() [][]byte diff --git a/go.mod b/go.mod index 980c742f99..a8f779dbca 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/stretchr/testify v1.8.0 github.com/tendermint/tendermint v0.37.0-dev github.com/tendermint/tm-db v0.6.8-0.20220519162814-e24b96538a12 + github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 go.opentelemetry.io/otel v1.9.0 go.opentelemetry.io/otel/exporters/jaeger v1.9.0 go.opentelemetry.io/otel/sdk v1.9.0 @@ -129,7 +130,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.66 + github.com/cosmos/cosmos-sdk => ../sei-cosmos // github.com/sei-protocol/sei-cosmos v0.1.20 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.13 diff --git a/go.sum b/go.sum index deff6c322a..c90ecd48e7 100644 --- a/go.sum +++ b/go.sum @@ -1095,8 +1095,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.66 h1:ow4z4wKm1iN1GoZAaz+kCB6nv+BKSLnhpFvyale785M= -github.com/sei-protocol/sei-cosmos v0.1.66/go.mod h1:3uCdb2FCio9uVQRPdZxZ1GqHqyb1moYH93xtTMa7DL8= github.com/sei-protocol/sei-tendermint v0.1.13 h1:uaMXhi+zpaqcUDlshxjqmPmUI/zSZla2a2ZmOtDK5RM= github.com/sei-protocol/sei-tendermint v0.1.13/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1248,6 +1246,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= From 498ca9427c27530bb381ed17045c07c5a6835511 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Fri, 23 Sep 2022 15:19:45 -0700 Subject: [PATCH 2/8] Add basic test for dependency dag --- app/app.go | 52 +++++++----- app/graph.go | 197 ++++++++++++++++++++++++++++++++++++++++++++++ app/graph_test.go | 90 +++++++++++++++++++++ app/utils.go | 190 -------------------------------------------- 4 files changed, 319 insertions(+), 210 deletions(-) create mode 100644 app/graph.go create mode 100644 app/graph_test.go diff --git a/app/app.go b/app/app.go index 48034a5b2a..da52bb4873 100644 --- a/app/app.go +++ b/app/app.go @@ -12,6 +12,7 @@ import ( "time" storetypes "github.com/cosmos/cosmos-sdk/store/types" + graph "github.com/yourbasic/graph" appparams "github.com/sei-protocol/sei-chain/app/params" "github.com/sei-protocol/sei-chain/utils" @@ -884,7 +885,7 @@ func (app *App) BuildDependencyDag(ctx sdk.Context, txs [][]byte) (*Dag, error) } msgs := tx.GetMsgs() for _, msg := range msgs { - msgDependencies := app.AccessControlKeeper.GetResourceDepedencyMapping(ctx, msg.GetAccessMappingKey()) + msgDependencies := app.AccessControlKeeper.GetResourceDependencyMapping(ctx, acltypes.GenerateMessageKey(msg)) for _, accessOp := range msgDependencies.AccessOps { // make a new node in the dependency dag dependencyDag.AddNodeBuildDependency(ctx, txIndex, accessOp) @@ -892,6 +893,10 @@ func (app *App) BuildDependencyDag(ctx sdk.Context, txs [][]byte) (*Dag, error) } } + + if !graph.Acyclic(&dependencyDag) { + return nil, ErrCycleInDAG + } return &dependencyDag, nil } @@ -965,27 +970,34 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ // } // app.batchVerifier.VerifyTxs(ctx, typedTxs) - // TODO: build dag here and verify - // for tx in txs, we need to get their message dependencies from access control module - // for each resource type it uses, we need to - + dag, err := app.BuildDependencyDag(ctx, txs) txResults := []*abci.ExecTxResult{} - for _, tx := range txs { - // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) - deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ - Tx: tx, - }) - txResults = append(txResults, &abci.ExecTxResult{ - Code: deliverTxResp.Code, - Data: deliverTxResp.Data, - Log: deliverTxResp.Log, - Info: deliverTxResp.Info, - GasWanted: deliverTxResp.GasWanted, - GasUsed: deliverTxResp.GasUsed, - Events: deliverTxResp.Events, - Codespace: deliverTxResp.Codespace, - }) + if err != nil { + if err == ErrCycleInDAG { + // something went wrong in dag, process txs sequentially + for _, tx := range txs { + // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) + deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ + Tx: tx, + }) + txResults = append(txResults, &abci.ExecTxResult{ + Code: deliverTxResp.Code, + Data: deliverTxResp.Data, + Log: deliverTxResp.Log, + Info: deliverTxResp.Info, + GasWanted: deliverTxResp.GasWanted, + GasUsed: deliverTxResp.GasUsed, + Events: deliverTxResp.Events, + Codespace: deliverTxResp.Codespace, + }) + } + } + } else { + // no error, lets process txs concurrently + _, _ = dag.BuildCompletionSignalMaps() + // TODO: create channel map here } + endBlockResp := app.EndBlock(ctx, abci.RequestEndBlock{ Height: req.GetHeight(), }) diff --git a/app/graph.go b/app/graph.go new file mode 100644 index 0000000000..f6d0676b47 --- /dev/null +++ b/app/graph.go @@ -0,0 +1,197 @@ +package app + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" +) + +type DagNode struct { + NodeId int + TxIndex int + AccessOperation acltypes.AccessOperation +} + +type DagEdge struct { + FromNodeId int + ToNodeId int + AccessOperation *acltypes.AccessOperation +} + +type Dag struct { + NodeMap map[int]DagNode + EdgesMap map[int][]DagEdge // maps node Id (from node) and contains edge info + AccessOpsMap map[acltypes.AccessOperation][]int // tracks latest node to use a specific access op + TxIndexMap map[int]int // tracks latest node ID for a tx index + NextId int +} + +func (edge *DagEdge) GetCompletionSignal() *CompletionSignal { + // only if there is an access operation + if edge.AccessOperation != nil { + return &CompletionSignal{ + FromNodeId: edge.FromNodeId, + ToNodeId: edge.ToNodeId, + AccessOperation: *edge.AccessOperation, + } + } else { + return nil + } +} + +// Order returns the number of vertices in a graph. +func (dag Dag) Order() int { + return len(dag.NodeMap) +} + +// Visit calls the do function for each neighbor w of vertex v, used by the graph acyclic validator +func (dag Dag) Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool) { + for _, edge := range dag.EdgesMap[v] { + // just have cost as zero because we only need for acyclic validation purposes + if do(edge.ToNodeId, 0) { + return true + } + } + return false +} + +func NewDag() Dag { + return Dag{ + NodeMap: make(map[int]DagNode), + EdgesMap: make(map[int][]DagEdge), + AccessOpsMap: make(map[acltypes.AccessOperation][]int), + TxIndexMap: make(map[int]int), + NextId: 0, + } +} + +func (dag *Dag) AddNode(txIndex int, accessOp acltypes.AccessOperation) DagNode { + dagNode := DagNode{ + NodeId: dag.NextId, + TxIndex: txIndex, + AccessOperation: accessOp, + } + dag.NodeMap[dag.NextId] = dagNode + dag.NextId += 1 + return dagNode +} + +func (dag *Dag) AddEdge(fromIndex int, toIndex int, accessOp *acltypes.AccessOperation) *DagEdge { + // no-ops if the from or to node doesn't exist + if _, ok := dag.NodeMap[fromIndex]; !ok { + return nil + } + if _, ok := dag.NodeMap[toIndex]; !ok { + return nil + } + newEdge := DagEdge{fromIndex, toIndex, accessOp} + dag.EdgesMap[fromIndex] = append(dag.EdgesMap[fromIndex], newEdge) + return &newEdge +} + +func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp acltypes.AccessOperation) { + dagNode := dag.AddNode(txIndex, accessOp) + // if in TxIndexMap, make an edge from the previous node index + if lastTxNodeId, ok := dag.TxIndexMap[txIndex]; ok { + // add an edge with no access op + dag.AddEdge(lastTxNodeId, dagNode.NodeId, nil) + } + // update tx index map + dag.TxIndexMap[txIndex] = dagNode.NodeId + // if the blocking access ops are in access ops map, make an edge + switch accessOp.AccessType { + case acltypes.AccessType_READ: + // if we need to do a read, we need latest write as a dependency + // TODO: replace hardcoded access op dependencies with helper that generates (and also generates superseding resources too eg. Resource.ALL is blocking for Resource.KV) + writeAccessOp := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_WRITE, + ResourceType: accessOp.GetResourceType(), + IdentifierTemplate: accessOp.GetIdentifierTemplate(), + } + if writeNodeIds, ok := dag.AccessOpsMap[writeAccessOp]; ok { + for _, wn := range writeNodeIds { + writeNode := dag.NodeMap[wn] + // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) + // if from a previous transaction, we need to create an edge + if writeNode.TxIndex < dagNode.TxIndex { + lastTxNode := dag.NodeMap[dag.TxIndexMap[writeNode.TxIndex]] + dag.AddEdge(lastTxNode.NodeId, dagNode.NodeId, &writeAccessOp) + } + } + } + case acltypes.AccessType_WRITE: + // if we need to do a write, we need read and write as dependencies + writeAccessOp := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_WRITE, + ResourceType: accessOp.GetResourceType(), + IdentifierTemplate: accessOp.GetIdentifierTemplate(), + } + if writeNodeIds, ok := dag.AccessOpsMap[writeAccessOp]; ok { + for _, wn := range writeNodeIds { + // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) + writeNode := dag.NodeMap[wn] + // if from a previous transaction, we need to create an edge + if writeNode.TxIndex < dagNode.TxIndex { + // we need to get the last node from that tx + lastTxNode := dag.NodeMap[dag.TxIndexMap[writeNode.TxIndex]] + dag.AddEdge(lastTxNode.NodeId, dagNode.NodeId, &writeAccessOp) + } + } + } + readAccessOp := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_READ, + ResourceType: accessOp.GetResourceType(), + IdentifierTemplate: accessOp.GetIdentifierTemplate(), + } + if readNodeIds, ok := dag.AccessOpsMap[readAccessOp]; ok { + for _, rn := range readNodeIds { + readNode := dag.NodeMap[rn] + // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) + // if from a previous transaction, we need to create an edge + if readNode.TxIndex < dagNode.TxIndex { + dag.AddEdge(readNode.NodeId, dagNode.NodeId, &readAccessOp) + } + } + } + default: + // unknown or something else, raise error + ctx.Logger().Error("Invalid AccessControlType Received") + } + // update access ops map with the latest node id using a specific access op + dag.AccessOpsMap[accessOp] = append(dag.AccessOpsMap[accessOp], dagNode.NodeId) +} + +// returns completion signaling map and blocking signals map +func (dag *Dag) BuildCompletionSignalMaps() (completionSignalingMap map[int]map[acltypes.AccessOperation][]CompletionSignal, blockingSignalsMap map[int]map[acltypes.AccessOperation][]CompletionSignal) { + // go through every node + for _, node := range dag.NodeMap { + // for each node, assign its completion signaling, and also assign blocking signals for the destination nodes + if outgoingEdges, ok := dag.EdgesMap[node.NodeId]; ok { + for _, edge := range outgoingEdges { + maybeCompletionSignal := edge.GetCompletionSignal() + if maybeCompletionSignal != nil { + completionSignal := *maybeCompletionSignal + // add it to the right blocking signal in the right txindex + toNode := dag.NodeMap[edge.ToNodeId] + blockingSignalsMap[toNode.TxIndex][*edge.AccessOperation] = append(blockingSignalsMap[toNode.TxIndex][*edge.AccessOperation], completionSignal) + // add it to the completion signal for the tx index + completionSignalingMap[node.TxIndex][*edge.AccessOperation] = append(completionSignalingMap[node.TxIndex][*edge.AccessOperation], completionSignal) + } + + } + + } + } + return +} + +type CompletionSignal struct { + FromNodeId int + ToNodeId int + AccessOperation acltypes.AccessOperation +} + +var ( + ErrCycleInDAG = fmt.Errorf("cycle detected in DAG") +) diff --git a/app/graph_test.go b/app/graph_test.go new file mode 100644 index 0000000000..d4e5c8fe13 --- /dev/null +++ b/app/graph_test.go @@ -0,0 +1,90 @@ +package app_test + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/stretchr/testify/require" + "github.com/yourbasic/graph" +) + +func TestCreateGraph(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + wrapper := app.NewTestWrapper(t, tm, valPub) + dag := app.NewDag() + /** + tx1: write to A, read B + tx2: read A, read B + tx3: read A, read B + tx4: write B + expected dag + 1wA -> 1rB =>v 2rA -> 2rB =\ + 3rB ------> 3rA V + \--------------------=> 4wB + **/ + + writeAccessA := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_WRITE, + ResourceType: acltypes.ResourceType_KV, + IdentifierTemplate: "ResourceA", + } + readAccessA := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_READ, + ResourceType: acltypes.ResourceType_KV, + IdentifierTemplate: "ResourceA", + } + writeAccessB := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_WRITE, + ResourceType: acltypes.ResourceType_KV, + IdentifierTemplate: "ResourceB", + } + readAccessB := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_READ, + ResourceType: acltypes.ResourceType_KV, + IdentifierTemplate: "ResourceB", + } + + dag.AddNodeBuildDependency(wrapper.Ctx, 1, writeAccessA) // node id 0 + dag.AddNodeBuildDependency(wrapper.Ctx, 1, readAccessB) // node id 1 + dag.AddNodeBuildDependency(wrapper.Ctx, 2, readAccessA) // node id 2 + dag.AddNodeBuildDependency(wrapper.Ctx, 2, readAccessB) // node id 3 + dag.AddNodeBuildDependency(wrapper.Ctx, 3, readAccessB) // node id 4 + dag.AddNodeBuildDependency(wrapper.Ctx, 3, readAccessA) // node id 5 + dag.AddNodeBuildDependency(wrapper.Ctx, 4, writeAccessB) // node id 6 + + require.Equal( + t, + []app.DagEdge{{0, 1, nil}}, + dag.EdgesMap[0], + ) + require.Equal( + t, + []app.DagEdge{{1, 2, &writeAccessA}, {1, 5, &writeAccessA}, {1, 6, &readAccessB}}, + dag.EdgesMap[1], + ) + require.Equal( + t, + []app.DagEdge{{2, 3, nil}}, + dag.EdgesMap[2], + ) + require.Equal( + t, + []app.DagEdge{{3, 6, &readAccessB}}, + dag.EdgesMap[3], + ) + require.Equal( + t, + []app.DagEdge{{4, 5, nil}, {4, 6, &readAccessB}}, + dag.EdgesMap[4], + ) + require.Equal(t, []app.DagEdge(nil), dag.EdgesMap[5]) + require.Equal(t, []app.DagEdge(nil), dag.EdgesMap[6]) + + // assert dag is acyclic + acyclic := graph.Acyclic(dag) + require.True(t, acyclic) +} diff --git a/app/utils.go b/app/utils.go index 88d1b4e9bf..51ed0501a9 100644 --- a/app/utils.go +++ b/app/utils.go @@ -3,9 +3,6 @@ package app import ( "time" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" - accesscontroltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" abci "github.com/tendermint/tendermint/abci/types" ) @@ -20,193 +17,6 @@ type OptimisticProcessingInfo struct { EndBlockResp abci.ResponseEndBlock } -type DagNode struct { - NodeId int - TxIndex int - AccessOperation accesscontroltypes.AccessOperation -} - -type DagEdge struct { - FromNodeId int - ToNodeId int - AccessOperation *accesscontroltypes.AccessOperation -} - -type Dag struct { - NodeMap map[int]DagNode - EdgesMap map[int][]DagEdge // maps node Id (from node) and contains edge info - AccessOpsMap map[accesscontroltypes.AccessOperation]int // tracks latest node to use a specific access op - TxIndexMap map[int]int // tracks latest node ID for a tx index - NextId int -} - -func (edge *DagEdge) GetCompletionSignal() *CompletionSignal { - // only if there is an access operation - if edge.AccessOperation != nil { - return &CompletionSignal{ - FromNodeId: edge.FromNodeId, - ToNodeId: edge.ToNodeId, - AccessOperation: *edge.AccessOperation, - } - } else { - return nil - } -} - -// Order returns the number of vertices in a graph. -func (dag *Dag) Order() int { - return len(dag.NodeMap) -} - -// Visit calls the do function for each neighbor w of vertex v, used by the graph acyclic validator -func (dag *Dag) Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool) { - for w, _ := range dag.EdgesMap[v] { - // just have cost as zero because we only need for acyclic validation purposes - if do(w, 0) { - return true - } - } - return false -} - -func NewDag() Dag { - return Dag{ - NodeMap: make(map[int]DagNode), - EdgesMap: make(map[int][]DagEdge), - AccessOpsMap: make(map[accesscontroltypes.AccessOperation]int), - TxIndexMap: make(map[int]int), - NextId: 0, - } -} - -func (dag *Dag) AddNode(txIndex int, accessOp types.AccessOperation) DagNode { - dagNode := DagNode{ - NodeId: dag.NextId, - TxIndex: txIndex, - AccessOperation: accessOp, - } - dag.NodeMap[dag.NextId] = dagNode - dag.NextId += 1 - return dagNode -} - -func (dag *Dag) AddEdge(fromIndex int, toIndex int, accessOp *accesscontroltypes.AccessOperation) *DagEdge { - // no-ops if the from or to node doesn't exist - if _, ok := dag.NodeMap[fromIndex]; !ok { - return nil - } - if _, ok := dag.NodeMap[toIndex]; !ok { - return nil - } - newEdge := DagEdge{fromIndex, toIndex, accessOp} - edges, ok := dag.EdgesMap[fromIndex] - if !ok { - edges = []DagEdge{newEdge} - } - dag.EdgesMap[fromIndex] = append(edges, newEdge) - return &newEdge -} - -func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp types.AccessOperation) { - dagNode := dag.AddNode(txIndex, accessOp) - // if in TxIndexMap, make an edge from the previous node index - if lastTxNodeId, ok := dag.TxIndexMap[txIndex]; ok { - // add an edge with no access op - dag.AddEdge(lastTxNodeId, dagNode.NodeId, nil) - // update tx index map - dag.TxIndexMap[txIndex] = dagNode.NodeId - } - // if the blocking access ops are in access ops map, make an edge - switch accessOp.AccessType { - case accesscontroltypes.AccessType_READ: - // if we need to do a read, we need latest write as a dependency - writeAccessOp := accesscontroltypes.AccessOperation{ - AccessType: accesscontroltypes.AccessType_WRITE, - ResourceType: accessOp.GetResourceType(), - IdentifierTemplate: accessOp.GetIdentifierTemplate(), - } - if writeNodeId, ok := dag.AccessOpsMap[writeAccessOp]; ok { - // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) - writeNode := dag.NodeMap[writeNodeId] - // if from a previous transaction, we need to create an edge - if writeNode.TxIndex < dagNode.TxIndex { - dag.AddEdge(writeNode.NodeId, dagNode.NodeId, &writeAccessOp) - } - } - case accesscontroltypes.AccessType_WRITE: - // if we need to do a write, we need read and write as dependencies - writeAccessOp := accesscontroltypes.AccessOperation{ - AccessType: accesscontroltypes.AccessType_WRITE, - ResourceType: accessOp.GetResourceType(), - IdentifierTemplate: accessOp.GetIdentifierTemplate(), - } - if writeNodeId, ok := dag.AccessOpsMap[writeAccessOp]; ok { - // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) - writeNode := dag.NodeMap[writeNodeId] - // if from a previous transaction, we need to create an edge - if writeNode.TxIndex < dagNode.TxIndex { - dag.AddEdge(writeNode.NodeId, dagNode.NodeId, &writeAccessOp) - } - } - readAccessOp := accesscontroltypes.AccessOperation{ - AccessType: accesscontroltypes.AccessType_READ, - ResourceType: accessOp.GetResourceType(), - IdentifierTemplate: accessOp.GetIdentifierTemplate(), - } - if readNodeId, ok := dag.AccessOpsMap[readAccessOp]; ok { - // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) - readNode := dag.NodeMap[readNodeId] - // if from a previous transaction, we need to create an edge - if readNode.TxIndex < dagNode.TxIndex { - dag.AddEdge(readNode.NodeId, dagNode.NodeId, &readAccessOp) - } - } - default: - // unknown or something else, raise error - ctx.Logger().Error("Invalid AccessControlType Received") - } - // update access ops map with the latest node id using a specific access op - dag.AccessOpsMap[accessOp] = dagNode.NodeId -} - -// returns completion signaling map and blocking signals map -func (dag *Dag) BuildCompletionSignalMaps() (completionSignalingMap map[int]map[accesscontroltypes.AccessOperation][]CompletionSignal, blockingSignalsMap map[int]map[accesscontroltypes.AccessOperation][]CompletionSignal) { - // go through every node - for _, node := range dag.NodeMap { - // for each node, assign its completion signaling, and also assign blocking signals for the destination nodes - if outgoingEdges, ok := dag.EdgesMap[node.NodeId]; ok { - var completionSignals []CompletionSignal - for _, edge := range outgoingEdges { - maybeCompletionSignal := edge.GetCompletionSignal() - if maybeCompletionSignal != nil { - completionSignal := *maybeCompletionSignal - completionSignals = append(completionSignals, completionSignal) - // also add it to the right blocking signal in the right txindex - toNode := dag.NodeMap[edge.ToNodeId] - if blockingSignals, ok2 := blockingSignalsMap[toNode.TxIndex][node.AccessOperation]; ok2 { - blockingSignalsMap[toNode.TxIndex][node.AccessOperation] = append(blockingSignals, completionSignal) - } else { - blockingSignalsMap[toNode.TxIndex][node.AccessOperation] = []CompletionSignal{completionSignal} - } - } - } - // assign completion signals for the tx index - if signals, ok3 := completionSignalingMap[node.TxIndex][node.AccessOperation]; ok3 { - completionSignalingMap[node.TxIndex][node.AccessOperation] = append(signals, completionSignals...) - } else { - completionSignalingMap[node.TxIndex][node.AccessOperation] = completionSignals - } - } - } - return -} - -type CompletionSignal struct { - FromNodeId int - ToNodeId int - AccessOperation types.AccessOperation -} - type BlockProcessRequest interface { GetHash() []byte GetTxs() [][]byte From b5b44115472c288772cba16dec722b18bd182593 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Sat, 24 Sep 2022 13:17:50 -0700 Subject: [PATCH 3/8] lint --- app/graph.go | 64 ++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/app/graph.go b/app/graph.go index f6d0676b47..adf7262bab 100644 --- a/app/graph.go +++ b/app/graph.go @@ -8,14 +8,14 @@ import ( ) type DagNode struct { - NodeId int + NodeID int TxIndex int AccessOperation acltypes.AccessOperation } type DagEdge struct { - FromNodeId int - ToNodeId int + FromNodeID int + ToNodeID int AccessOperation *acltypes.AccessOperation } @@ -24,20 +24,19 @@ type Dag struct { EdgesMap map[int][]DagEdge // maps node Id (from node) and contains edge info AccessOpsMap map[acltypes.AccessOperation][]int // tracks latest node to use a specific access op TxIndexMap map[int]int // tracks latest node ID for a tx index - NextId int + NextID int } func (edge *DagEdge) GetCompletionSignal() *CompletionSignal { // only if there is an access operation if edge.AccessOperation != nil { return &CompletionSignal{ - FromNodeId: edge.FromNodeId, - ToNodeId: edge.ToNodeId, + FromNodeID: edge.FromNodeID, + ToNodeID: edge.ToNodeID, AccessOperation: *edge.AccessOperation, } - } else { - return nil } + return nil } // Order returns the number of vertices in a graph. @@ -49,7 +48,7 @@ func (dag Dag) Order() int { func (dag Dag) Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool) { for _, edge := range dag.EdgesMap[v] { // just have cost as zero because we only need for acyclic validation purposes - if do(edge.ToNodeId, 0) { + if do(edge.ToNodeID, 0) { return true } } @@ -62,18 +61,18 @@ func NewDag() Dag { EdgesMap: make(map[int][]DagEdge), AccessOpsMap: make(map[acltypes.AccessOperation][]int), TxIndexMap: make(map[int]int), - NextId: 0, + NextID: 0, } } func (dag *Dag) AddNode(txIndex int, accessOp acltypes.AccessOperation) DagNode { dagNode := DagNode{ - NodeId: dag.NextId, + NodeID: dag.NextID, TxIndex: txIndex, AccessOperation: accessOp, } - dag.NodeMap[dag.NextId] = dagNode - dag.NextId += 1 + dag.NodeMap[dag.NextID] = dagNode + dag.NextID++ return dagNode } @@ -93,12 +92,12 @@ func (dag *Dag) AddEdge(fromIndex int, toIndex int, accessOp *acltypes.AccessOpe func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp acltypes.AccessOperation) { dagNode := dag.AddNode(txIndex, accessOp) // if in TxIndexMap, make an edge from the previous node index - if lastTxNodeId, ok := dag.TxIndexMap[txIndex]; ok { + if lastTxNodeID, ok := dag.TxIndexMap[txIndex]; ok { // add an edge with no access op - dag.AddEdge(lastTxNodeId, dagNode.NodeId, nil) + dag.AddEdge(lastTxNodeID, dagNode.NodeID, nil) } // update tx index map - dag.TxIndexMap[txIndex] = dagNode.NodeId + dag.TxIndexMap[txIndex] = dagNode.NodeID // if the blocking access ops are in access ops map, make an edge switch accessOp.AccessType { case acltypes.AccessType_READ: @@ -109,14 +108,14 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac ResourceType: accessOp.GetResourceType(), IdentifierTemplate: accessOp.GetIdentifierTemplate(), } - if writeNodeIds, ok := dag.AccessOpsMap[writeAccessOp]; ok { - for _, wn := range writeNodeIds { + if writeNodeIDs, ok := dag.AccessOpsMap[writeAccessOp]; ok { + for _, wn := range writeNodeIDs { writeNode := dag.NodeMap[wn] // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) // if from a previous transaction, we need to create an edge if writeNode.TxIndex < dagNode.TxIndex { lastTxNode := dag.NodeMap[dag.TxIndexMap[writeNode.TxIndex]] - dag.AddEdge(lastTxNode.NodeId, dagNode.NodeId, &writeAccessOp) + dag.AddEdge(lastTxNode.NodeID, dagNode.NodeID, &writeAccessOp) } } } @@ -127,15 +126,15 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac ResourceType: accessOp.GetResourceType(), IdentifierTemplate: accessOp.GetIdentifierTemplate(), } - if writeNodeIds, ok := dag.AccessOpsMap[writeAccessOp]; ok { - for _, wn := range writeNodeIds { + if writeNodeIDs, ok := dag.AccessOpsMap[writeAccessOp]; ok { + for _, wn := range writeNodeIDs { // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) writeNode := dag.NodeMap[wn] // if from a previous transaction, we need to create an edge if writeNode.TxIndex < dagNode.TxIndex { // we need to get the last node from that tx lastTxNode := dag.NodeMap[dag.TxIndexMap[writeNode.TxIndex]] - dag.AddEdge(lastTxNode.NodeId, dagNode.NodeId, &writeAccessOp) + dag.AddEdge(lastTxNode.NodeID, dagNode.NodeID, &writeAccessOp) } } } @@ -144,13 +143,13 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac ResourceType: accessOp.GetResourceType(), IdentifierTemplate: accessOp.GetIdentifierTemplate(), } - if readNodeIds, ok := dag.AccessOpsMap[readAccessOp]; ok { - for _, rn := range readNodeIds { + if readNodeIDs, ok := dag.AccessOpsMap[readAccessOp]; ok { + for _, rn := range readNodeIDs { readNode := dag.NodeMap[rn] // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) // if from a previous transaction, we need to create an edge if readNode.TxIndex < dagNode.TxIndex { - dag.AddEdge(readNode.NodeId, dagNode.NodeId, &readAccessOp) + dag.AddEdge(readNode.NodeID, dagNode.NodeID, &readAccessOp) } } } @@ -159,7 +158,7 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac ctx.Logger().Error("Invalid AccessControlType Received") } // update access ops map with the latest node id using a specific access op - dag.AccessOpsMap[accessOp] = append(dag.AccessOpsMap[accessOp], dagNode.NodeId) + dag.AccessOpsMap[accessOp] = append(dag.AccessOpsMap[accessOp], dagNode.NodeID) } // returns completion signaling map and blocking signals map @@ -167,31 +166,28 @@ func (dag *Dag) BuildCompletionSignalMaps() (completionSignalingMap map[int]map[ // go through every node for _, node := range dag.NodeMap { // for each node, assign its completion signaling, and also assign blocking signals for the destination nodes - if outgoingEdges, ok := dag.EdgesMap[node.NodeId]; ok { + if outgoingEdges, ok := dag.EdgesMap[node.NodeID]; ok { for _, edge := range outgoingEdges { maybeCompletionSignal := edge.GetCompletionSignal() if maybeCompletionSignal != nil { completionSignal := *maybeCompletionSignal // add it to the right blocking signal in the right txindex - toNode := dag.NodeMap[edge.ToNodeId] + toNode := dag.NodeMap[edge.ToNodeID] blockingSignalsMap[toNode.TxIndex][*edge.AccessOperation] = append(blockingSignalsMap[toNode.TxIndex][*edge.AccessOperation], completionSignal) // add it to the completion signal for the tx index completionSignalingMap[node.TxIndex][*edge.AccessOperation] = append(completionSignalingMap[node.TxIndex][*edge.AccessOperation], completionSignal) } } - } } return } type CompletionSignal struct { - FromNodeId int - ToNodeId int + FromNodeID int + ToNodeID int AccessOperation acltypes.AccessOperation } -var ( - ErrCycleInDAG = fmt.Errorf("cycle detected in DAG") -) +var ErrCycleInDAG = fmt.Errorf("cycle detected in DAG") From cd278052fec36c4f25441c3edf41776d6ec79902 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 26 Sep 2022 13:02:40 -0700 Subject: [PATCH 4/8] refactor graph edge and completion signals --- app/app.go | 10 ++-- app/graph.go | 118 ++++++++++++++++++++++++++++------------------ app/graph_test.go | 70 +++++++++++++++++---------- 3 files changed, 120 insertions(+), 78 deletions(-) diff --git a/app/app.go b/app/app.go index da52bb4873..873338dd2e 100644 --- a/app/app.go +++ b/app/app.go @@ -874,12 +874,9 @@ func (app *App) ProcessProposalHandler(ctx sdk.Context, req *abci.RequestProcess func (app *App) BuildDependencyDag(ctx sdk.Context, txs [][]byte) (*Dag, error) { // contains the latest msg index for a specific Access Operation - dependencyDag := Dag{ - NodeMap: make(map[int]DagNode), - EdgesMap: make(map[int][]DagEdge), - } + dependencyDag := NewDag() for txIndex, txBytes := range txs { - tx, err := app.txDecoder(txBytes) + tx, err := app.txDecoder(txBytes) // TODO: results in repetitive decoding for txs with runtx decode (potential optimization) if err != nil { return nil, err } @@ -888,7 +885,7 @@ func (app *App) BuildDependencyDag(ctx sdk.Context, txs [][]byte) (*Dag, error) msgDependencies := app.AccessControlKeeper.GetResourceDependencyMapping(ctx, acltypes.GenerateMessageKey(msg)) for _, accessOp := range msgDependencies.AccessOps { // make a new node in the dependency dag - dependencyDag.AddNodeBuildDependency(ctx, txIndex, accessOp) + dependencyDag.AddNodeBuildDependency(txIndex, accessOp) } } @@ -991,6 +988,7 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ Codespace: deliverTxResp.Codespace, }) } + // TODO: handle other errors } } else { // no error, lets process txs concurrently diff --git a/app/graph.go b/app/graph.go index adf7262bab..cf95eedf7b 100644 --- a/app/graph.go +++ b/app/graph.go @@ -3,38 +3,40 @@ package app import ( "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" ) +type DagNodeID int + type DagNode struct { - NodeID int + NodeID DagNodeID TxIndex int AccessOperation acltypes.AccessOperation } type DagEdge struct { - FromNodeID int - ToNodeID int - AccessOperation *acltypes.AccessOperation + FromNodeID DagNodeID + ToNodeID DagNodeID } type Dag struct { - NodeMap map[int]DagNode - EdgesMap map[int][]DagEdge // maps node Id (from node) and contains edge info - AccessOpsMap map[acltypes.AccessOperation][]int // tracks latest node to use a specific access op - TxIndexMap map[int]int // tracks latest node ID for a tx index - NextID int + NodeMap map[DagNodeID]DagNode + EdgesMap map[DagNodeID][]DagEdge // maps node Id (from node) and contains edge info + AccessOpsMap map[acltypes.AccessOperation][]DagNodeID // tracks latest node to use a specific access op + TxIndexMap map[int]DagNodeID // tracks latest node ID for a tx index + NextID DagNodeID } -func (edge *DagEdge) GetCompletionSignal() *CompletionSignal { - // only if there is an access operation - if edge.AccessOperation != nil { - return &CompletionSignal{ - FromNodeID: edge.FromNodeID, - ToNodeID: edge.ToNodeID, - AccessOperation: *edge.AccessOperation, - } +func (dag *Dag) GetCompletionSignal(edge DagEdge) *CompletionSignal { + // only if tx indexes are different + fromNode := dag.NodeMap[edge.FromNodeID] + toNode := dag.NodeMap[edge.ToNodeID] + + return &CompletionSignal{ + FromNodeID: fromNode.NodeID, + ToNodeID: toNode.NodeID, + CompletionAccessOperation: fromNode.AccessOperation, + BlockedAccessOperation: toNode.AccessOperation, } return nil } @@ -46,9 +48,9 @@ func (dag Dag) Order() int { // Visit calls the do function for each neighbor w of vertex v, used by the graph acyclic validator func (dag Dag) Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool) { - for _, edge := range dag.EdgesMap[v] { + for _, edge := range dag.EdgesMap[DagNodeID(v)] { // just have cost as zero because we only need for acyclic validation purposes - if do(edge.ToNodeID, 0) { + if do(int(edge.ToNodeID), 0) { return true } } @@ -57,10 +59,10 @@ func (dag Dag) Visit(v int, do func(w int, c int64) (skip bool)) (aborted bool) func NewDag() Dag { return Dag{ - NodeMap: make(map[int]DagNode), - EdgesMap: make(map[int][]DagEdge), - AccessOpsMap: make(map[acltypes.AccessOperation][]int), - TxIndexMap: make(map[int]int), + NodeMap: make(map[DagNodeID]DagNode), + EdgesMap: make(map[DagNodeID][]DagEdge), + AccessOpsMap: make(map[acltypes.AccessOperation][]DagNodeID), + TxIndexMap: make(map[int]DagNodeID), NextID: 0, } } @@ -76,7 +78,7 @@ func (dag *Dag) AddNode(txIndex int, accessOp acltypes.AccessOperation) DagNode return dagNode } -func (dag *Dag) AddEdge(fromIndex int, toIndex int, accessOp *acltypes.AccessOperation) *DagEdge { +func (dag *Dag) AddEdge(fromIndex DagNodeID, toIndex DagNodeID) *DagEdge { // no-ops if the from or to node doesn't exist if _, ok := dag.NodeMap[fromIndex]; !ok { return nil @@ -84,20 +86,44 @@ func (dag *Dag) AddEdge(fromIndex int, toIndex int, accessOp *acltypes.AccessOpe if _, ok := dag.NodeMap[toIndex]; !ok { return nil } - newEdge := DagEdge{fromIndex, toIndex, accessOp} + newEdge := DagEdge{fromIndex, toIndex} dag.EdgesMap[fromIndex] = append(dag.EdgesMap[fromIndex], newEdge) return &newEdge } -func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp acltypes.AccessOperation) { +// This function is a helper used to build the dependency graph one access operation at a time. +// It will first add a node corresponding to the tx index and access operation (linking it to the previous most recent node for that tx if applicable) +// and then will build edges from any access operations on which the new node is dependent. +// +// This will be accomplished using the AccessOpsMap in dag which keeps track of which nodes access which resources. +// It will then create an edge between the relevant node upon which it is dependent, and this edge can later be used to build the completion signals +// that will allow the dependent goroutines to cordinate execution safely. +// +// It will also register the new node with AccessOpsMap so that future nodes that amy be dependent on this one can properly identify the dependency. +func (dag *Dag) AddNodeBuildDependency(txIndex int, accessOp acltypes.AccessOperation) { dagNode := dag.AddNode(txIndex, accessOp) // if in TxIndexMap, make an edge from the previous node index if lastTxNodeID, ok := dag.TxIndexMap[txIndex]; ok { - // add an edge with no access op - dag.AddEdge(lastTxNodeID, dagNode.NodeID, nil) + // TODO: we actually don't necessarily need these edges, but keeping for now so we can first determine that cycles can't be missed if we remove these + // add an edge between access ops in a transaction + dag.AddEdge(lastTxNodeID, dagNode.NodeID) } // update tx index map dag.TxIndexMap[txIndex] = dagNode.NodeID + + nodeDependencies := dag.GetNodeDependencies(dagNode) + // build edges for each of the dependencies + for _, nodeDependency := range nodeDependencies { + dag.AddEdge(nodeDependency.NodeID, dagNode.NodeID) + } + + // update access ops map with the latest node id using a specific access op + dag.AccessOpsMap[accessOp] = append(dag.AccessOpsMap[accessOp], dagNode.NodeID) +} + +// This helper will identify nodes that are dependencies for the current node, and can then be used for creating edges between then for future completion signals +func (dag *Dag) GetNodeDependencies(node DagNode) (nodeDependencies []DagNode) { + accessOp := node.AccessOperation // if the blocking access ops are in access ops map, make an edge switch accessOp.AccessType { case acltypes.AccessType_READ: @@ -113,13 +139,14 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac writeNode := dag.NodeMap[wn] // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) // if from a previous transaction, we need to create an edge - if writeNode.TxIndex < dagNode.TxIndex { + if writeNode.TxIndex < node.TxIndex { + // this should be the COMMIT access op for the tx lastTxNode := dag.NodeMap[dag.TxIndexMap[writeNode.TxIndex]] - dag.AddEdge(lastTxNode.NodeID, dagNode.NodeID, &writeAccessOp) + nodeDependencies = append(nodeDependencies, lastTxNode) } } } - case acltypes.AccessType_WRITE: + case acltypes.AccessType_WRITE, acltypes.AccessType_UNKNOWN: // if we need to do a write, we need read and write as dependencies writeAccessOp := acltypes.AccessOperation{ AccessType: acltypes.AccessType_WRITE, @@ -131,10 +158,10 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) writeNode := dag.NodeMap[wn] // if from a previous transaction, we need to create an edge - if writeNode.TxIndex < dagNode.TxIndex { + if writeNode.TxIndex < node.TxIndex { // we need to get the last node from that tx lastTxNode := dag.NodeMap[dag.TxIndexMap[writeNode.TxIndex]] - dag.AddEdge(lastTxNode.NodeID, dagNode.NodeID, &writeAccessOp) + nodeDependencies = append(nodeDependencies, lastTxNode) } } } @@ -148,17 +175,13 @@ func (dag *Dag) AddNodeBuildDependency(ctx sdk.Context, txIndex int, accessOp ac readNode := dag.NodeMap[rn] // if accessOp exists already (and from a previous transaction), we need to define a dependency on the previous message (and make a edge between the two) // if from a previous transaction, we need to create an edge - if readNode.TxIndex < dagNode.TxIndex { - dag.AddEdge(readNode.NodeID, dagNode.NodeID, &readAccessOp) + if readNode.TxIndex < node.TxIndex { + nodeDependencies = append(nodeDependencies, readNode) } } } - default: - // unknown or something else, raise error - ctx.Logger().Error("Invalid AccessControlType Received") } - // update access ops map with the latest node id using a specific access op - dag.AccessOpsMap[accessOp] = append(dag.AccessOpsMap[accessOp], dagNode.NodeID) + return } // returns completion signaling map and blocking signals map @@ -168,14 +191,14 @@ func (dag *Dag) BuildCompletionSignalMaps() (completionSignalingMap map[int]map[ // for each node, assign its completion signaling, and also assign blocking signals for the destination nodes if outgoingEdges, ok := dag.EdgesMap[node.NodeID]; ok { for _, edge := range outgoingEdges { - maybeCompletionSignal := edge.GetCompletionSignal() + maybeCompletionSignal := dag.GetCompletionSignal(edge) if maybeCompletionSignal != nil { completionSignal := *maybeCompletionSignal // add it to the right blocking signal in the right txindex toNode := dag.NodeMap[edge.ToNodeID] - blockingSignalsMap[toNode.TxIndex][*edge.AccessOperation] = append(blockingSignalsMap[toNode.TxIndex][*edge.AccessOperation], completionSignal) + blockingSignalsMap[toNode.TxIndex][completionSignal.BlockedAccessOperation] = append(blockingSignalsMap[toNode.TxIndex][completionSignal.BlockedAccessOperation], completionSignal) // add it to the completion signal for the tx index - completionSignalingMap[node.TxIndex][*edge.AccessOperation] = append(completionSignalingMap[node.TxIndex][*edge.AccessOperation], completionSignal) + completionSignalingMap[node.TxIndex][completionSignal.CompletionAccessOperation] = append(completionSignalingMap[node.TxIndex][completionSignal.CompletionAccessOperation], completionSignal) } } @@ -185,9 +208,10 @@ func (dag *Dag) BuildCompletionSignalMaps() (completionSignalingMap map[int]map[ } type CompletionSignal struct { - FromNodeID int - ToNodeID int - AccessOperation acltypes.AccessOperation + FromNodeID DagNodeID + ToNodeID DagNodeID + CompletionAccessOperation acltypes.AccessOperation // this is the access operation that must complete in order to send the signal + BlockedAccessOperation acltypes.AccessOperation // this is the access operation that is blocked by the completion access operation } var ErrCycleInDAG = fmt.Errorf("cycle detected in DAG") diff --git a/app/graph_test.go b/app/graph_test.go index d4e5c8fe13..ebc6b24cb4 100644 --- a/app/graph_test.go +++ b/app/graph_test.go @@ -2,9 +2,7 @@ package app_test import ( "testing" - "time" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" "github.com/sei-protocol/sei-chain/app" "github.com/stretchr/testify/require" @@ -12,21 +10,23 @@ import ( ) func TestCreateGraph(t *testing.T) { - tm := time.Now().UTC() - valPub := secp256k1.GenPrivKey().PubKey() - wrapper := app.NewTestWrapper(t, tm, valPub) dag := app.NewDag() /** - tx1: write to A, read B - tx2: read A, read B - tx3: read A, read B - tx4: write B + tx1: write to A, read B, commit 1 + tx2: read A, read B, commit 2 + tx3: read A, read B, commit 3 + tx4: write B, commit 4 expected dag - 1wA -> 1rB =>v 2rA -> 2rB =\ - 3rB ------> 3rA V - \--------------------=> 4wB + 1wA -> 1rB -> 1c =>v 2rA -> 2rB ----=\---> 2c + 3rB -------------> 3rA -> 3c V + \-----------------------------------=> 4wB -> 4c **/ + commitAccessOp := acltypes.AccessOperation{ + AccessType: acltypes.AccessType_COMMIT, + ResourceType: acltypes.ResourceType_ANY, + IdentifierTemplate: "*", + } writeAccessA := acltypes.AccessOperation{ AccessType: acltypes.AccessType_WRITE, ResourceType: acltypes.ResourceType_KV, @@ -48,41 +48,61 @@ func TestCreateGraph(t *testing.T) { IdentifierTemplate: "ResourceB", } - dag.AddNodeBuildDependency(wrapper.Ctx, 1, writeAccessA) // node id 0 - dag.AddNodeBuildDependency(wrapper.Ctx, 1, readAccessB) // node id 1 - dag.AddNodeBuildDependency(wrapper.Ctx, 2, readAccessA) // node id 2 - dag.AddNodeBuildDependency(wrapper.Ctx, 2, readAccessB) // node id 3 - dag.AddNodeBuildDependency(wrapper.Ctx, 3, readAccessB) // node id 4 - dag.AddNodeBuildDependency(wrapper.Ctx, 3, readAccessA) // node id 5 - dag.AddNodeBuildDependency(wrapper.Ctx, 4, writeAccessB) // node id 6 + dag.AddNodeBuildDependency(1, writeAccessA) // node id 0 + dag.AddNodeBuildDependency(1, readAccessB) // node id 1 + dag.AddNodeBuildDependency(1, commitAccessOp) // node id 2 + dag.AddNodeBuildDependency(2, readAccessA) // node id 3 + dag.AddNodeBuildDependency(2, readAccessB) // node id 4 + dag.AddNodeBuildDependency(2, commitAccessOp) // node id 5 + dag.AddNodeBuildDependency(3, readAccessB) // node id 6 + dag.AddNodeBuildDependency(3, readAccessA) // node id 7 + dag.AddNodeBuildDependency(3, commitAccessOp) // node id 8 + dag.AddNodeBuildDependency(4, writeAccessB) // node id 9 + dag.AddNodeBuildDependency(4, commitAccessOp) // node id 10 require.Equal( t, - []app.DagEdge{{0, 1, nil}}, + []app.DagEdge{{0, 1}}, dag.EdgesMap[0], ) require.Equal( t, - []app.DagEdge{{1, 2, &writeAccessA}, {1, 5, &writeAccessA}, {1, 6, &readAccessB}}, + []app.DagEdge{{1, 2}, {1, 9}}, dag.EdgesMap[1], ) require.Equal( t, - []app.DagEdge{{2, 3, nil}}, + []app.DagEdge{{2, 3}, {2, 7}}, dag.EdgesMap[2], ) require.Equal( t, - []app.DagEdge{{3, 6, &readAccessB}}, + []app.DagEdge{{3, 4}}, dag.EdgesMap[3], ) require.Equal( t, - []app.DagEdge{{4, 5, nil}, {4, 6, &readAccessB}}, + []app.DagEdge{{4, 5}, {4, 9}}, dag.EdgesMap[4], ) require.Equal(t, []app.DagEdge(nil), dag.EdgesMap[5]) - require.Equal(t, []app.DagEdge(nil), dag.EdgesMap[6]) + require.Equal( + t, + []app.DagEdge{{6, 7}, {6, 9}}, + dag.EdgesMap[6], + ) + require.Equal( + t, + []app.DagEdge{{7, 8}}, + dag.EdgesMap[7], + ) + require.Equal(t, []app.DagEdge(nil), dag.EdgesMap[8]) + require.Equal( + t, + []app.DagEdge{{9, 10}}, + dag.EdgesMap[9], + ) + require.Equal(t, []app.DagEdge(nil), dag.EdgesMap[10]) // assert dag is acyclic acyclic := graph.Acyclic(dag) From 46bfe4b1dffb83bab5cf916477cf018d11838a22 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 26 Sep 2022 13:05:04 -0700 Subject: [PATCH 5/8] process seq for any dag errors --- app/app.go | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/app/app.go b/app/app.go index 873338dd2e..3b6e2bbe2a 100644 --- a/app/app.go +++ b/app/app.go @@ -970,25 +970,22 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ dag, err := app.BuildDependencyDag(ctx, txs) txResults := []*abci.ExecTxResult{} if err != nil { - if err == ErrCycleInDAG { - // something went wrong in dag, process txs sequentially - for _, tx := range txs { - // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) - deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ - Tx: tx, - }) - txResults = append(txResults, &abci.ExecTxResult{ - Code: deliverTxResp.Code, - Data: deliverTxResp.Data, - Log: deliverTxResp.Log, - Info: deliverTxResp.Info, - GasWanted: deliverTxResp.GasWanted, - GasUsed: deliverTxResp.GasUsed, - Events: deliverTxResp.Events, - Codespace: deliverTxResp.Codespace, - }) - } - // TODO: handle other errors + // something went wrong in dag, process txs sequentially + for _, tx := range txs { + // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) + deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ + Tx: tx, + }) + txResults = append(txResults, &abci.ExecTxResult{ + Code: deliverTxResp.Code, + Data: deliverTxResp.Data, + Log: deliverTxResp.Log, + Info: deliverTxResp.Info, + GasWanted: deliverTxResp.GasWanted, + GasUsed: deliverTxResp.GasUsed, + Events: deliverTxResp.Events, + Codespace: deliverTxResp.Codespace, + }) } } else { // no error, lets process txs concurrently From c6561b1a0ca0832f4740bab04d0031b230c257b1 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 26 Sep 2022 15:33:36 -0700 Subject: [PATCH 6/8] fmt --- app/graph.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/graph.go b/app/graph.go index cf95eedf7b..3a846cccc2 100644 --- a/app/graph.go +++ b/app/graph.go @@ -31,14 +31,15 @@ func (dag *Dag) GetCompletionSignal(edge DagEdge) *CompletionSignal { // only if tx indexes are different fromNode := dag.NodeMap[edge.FromNodeID] toNode := dag.NodeMap[edge.ToNodeID] - + if fromNode.TxIndex == toNode.TxIndex { + return nil + } return &CompletionSignal{ FromNodeID: fromNode.NodeID, ToNodeID: toNode.NodeID, CompletionAccessOperation: fromNode.AccessOperation, BlockedAccessOperation: toNode.AccessOperation, } - return nil } // Order returns the number of vertices in a graph. @@ -181,7 +182,7 @@ func (dag *Dag) GetNodeDependencies(node DagNode) (nodeDependencies []DagNode) { } } } - return + return nodeDependencies } // returns completion signaling map and blocking signals map From 80fb55d6a4d3dc9c1025e654ef6d5addd0638c46 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 26 Sep 2022 16:05:35 -0700 Subject: [PATCH 7/8] mod tidy --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a8f779dbca..ccbdaf658f 100644 --- a/go.mod +++ b/go.mod @@ -130,7 +130,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => ../sei-cosmos // github.com/sei-protocol/sei-cosmos v0.1.20 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.66 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.13 diff --git a/go.sum b/go.sum index c90ecd48e7..e8cb8cb5c1 100644 --- a/go.sum +++ b/go.sum @@ -1095,6 +1095,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/sei-protocol/sei-cosmos v0.1.66 h1:ow4z4wKm1iN1GoZAaz+kCB6nv+BKSLnhpFvyale785M= +github.com/sei-protocol/sei-cosmos v0.1.66/go.mod h1:3uCdb2FCio9uVQRPdZxZ1GqHqyb1moYH93xtTMa7DL8= github.com/sei-protocol/sei-tendermint v0.1.13 h1:uaMXhi+zpaqcUDlshxjqmPmUI/zSZla2a2ZmOtDK5RM= github.com/sei-protocol/sei-tendermint v0.1.13/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= From 376edb7b115951607c5e293fc7fe7f4462d7e855 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 26 Sep 2022 16:17:41 -0700 Subject: [PATCH 8/8] update sei-cosmos --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ccbdaf658f..fff4ed7b33 100644 --- a/go.mod +++ b/go.mod @@ -130,7 +130,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.66 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.67 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.13 diff --git a/go.sum b/go.sum index e8cb8cb5c1..200fd7cd3f 100644 --- a/go.sum +++ b/go.sum @@ -1095,8 +1095,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.66 h1:ow4z4wKm1iN1GoZAaz+kCB6nv+BKSLnhpFvyale785M= -github.com/sei-protocol/sei-cosmos v0.1.66/go.mod h1:3uCdb2FCio9uVQRPdZxZ1GqHqyb1moYH93xtTMa7DL8= +github.com/sei-protocol/sei-cosmos v0.1.67 h1:AdjpHcaryUaGI4X9nnK13YWxPOguDveH2sH+06gBsCw= +github.com/sei-protocol/sei-cosmos v0.1.67/go.mod h1:Oaj7toqHCkwEEb+sDIWxtfTkPZxOpMXBXDMvIIqUjpw= github.com/sei-protocol/sei-tendermint v0.1.13 h1:uaMXhi+zpaqcUDlshxjqmPmUI/zSZla2a2ZmOtDK5RM= github.com/sei-protocol/sei-tendermint v0.1.13/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=