diff --git a/.changelog/2889.feature.1.md b/.changelog/2889.feature.1.md new file mode 100644 index 00000000000..6d189163e7d --- /dev/null +++ b/.changelog/2889.feature.1.md @@ -0,0 +1,5 @@ +go/staking: Add event hashes + +Staking events now have a new `TxHash` field, which contains +the hash of the transaction that caused the event (or the empty +hash in case of block events). diff --git a/.changelog/2889.feature.2.md b/.changelog/2889.feature.2.md new file mode 100644 index 00000000000..724961c5347 --- /dev/null +++ b/.changelog/2889.feature.2.md @@ -0,0 +1,4 @@ +go/consensus: Add GetGenesisDocument + +The consensus client now has a new method to return the original +genesis document. diff --git a/go/consensus/api/api.go b/go/consensus/api/api.go index 1060196646f..c83287b7fad 100644 --- a/go/consensus/api/api.go +++ b/go/consensus/api/api.go @@ -80,6 +80,9 @@ type ClientBackend interface { // WatchBlocks returns a channel that produces a stream of consensus // blocks as they are being finalized. WatchBlocks(ctx context.Context) (<-chan *Block, pubsub.ClosableSubscription, error) + + // GetGenesisDocument returns the original genesis document. + GetGenesisDocument(ctx context.Context) (*genesis.Document, error) } // Block is a consensus block. diff --git a/go/consensus/api/grpc.go b/go/consensus/api/grpc.go index 61b9d4d0566..edf993bba97 100644 --- a/go/consensus/api/grpc.go +++ b/go/consensus/api/grpc.go @@ -34,6 +34,8 @@ var ( methodGetBlock = serviceName.NewMethod("GetBlock", int64(0)) // methodGetTransactions is the GetTransactions method. methodGetTransactions = serviceName.NewMethod("GetTransactions", int64(0)) + // methodGetGenesisDocument is the GetGenesisDocument method. + methodGetGenesisDocument = serviceName.NewMethod("GetGenesisDocument", nil) // methodWatchBlocks is the WatchBlocks method. methodWatchBlocks = serviceName.NewMethod("WatchBlocks", nil) @@ -82,6 +84,10 @@ var ( MethodName: methodGetTransactions.ShortName(), Handler: handlerGetTransactions, }, + { + MethodName: methodGetGenesisDocument.ShortName(), + Handler: handlerGetGenesisDocument, + }, }, Streams: []grpc.StreamDesc{ { @@ -297,6 +303,25 @@ func handlerGetTransactions( // nolint: golint return interceptor(ctx, height, info, handler) } +func handlerGetGenesisDocument( // nolint: golint + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + if interceptor == nil { + return srv.(Backend).GetGenesisDocument(ctx) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodGetGenesisDocument.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).GetGenesisDocument(ctx) + } + return interceptor(ctx, nil, info, handler) +} + func handlerWatchBlocks(srv interface{}, stream grpc.ServerStream) error { if err := stream.RecvMsg(nil); err != nil { return err @@ -498,6 +523,14 @@ func (c *consensusClient) GetTransactions(ctx context.Context, height int64) ([] return rsp, nil } +func (c *consensusClient) GetGenesisDocument(ctx context.Context) (*genesis.Document, error) { + var rsp genesis.Document + if err := c.conn.Invoke(ctx, methodGetGenesisDocument.FullName(), nil, &rsp); err != nil { + return nil, err + } + return &rsp, nil +} + func (c *consensusClient) WatchBlocks(ctx context.Context) (<-chan *Block, pubsub.ClosableSubscription, error) { ctx, sub := pubsub.NewContextSubscription(ctx) diff --git a/go/consensus/tendermint/epochtime/epochtime.go b/go/consensus/tendermint/epochtime/epochtime.go index 9837fb07e25..3cb353e65e1 100644 --- a/go/consensus/tendermint/epochtime/epochtime.go +++ b/go/consensus/tendermint/epochtime/epochtime.go @@ -125,7 +125,12 @@ func (t *tendermintBackend) updateCached(ctx context.Context, block *tmtypes.Blo // New constructs a new tendermint backed epochtime Backend instance, // with the specified epoch interval. func New(ctx context.Context, service service.TendermintService, interval int64) (api.Backend, error) { - base := service.GetGenesis().EpochTime.Base + genDoc, err := service.GetGenesisDocument(ctx) + if err != nil { + return nil, err + } + + base := genDoc.EpochTime.Base r := &tendermintBackend{ logger: logging.GetLogger("epochtime/tendermint"), service: service, diff --git a/go/consensus/tendermint/epochtime_mock/epochtime_mock.go b/go/consensus/tendermint/epochtime_mock/epochtime_mock.go index 0e074cc2dbc..50df46dca52 100644 --- a/go/consensus/tendermint/epochtime_mock/epochtime_mock.go +++ b/go/consensus/tendermint/epochtime_mock/epochtime_mock.go @@ -249,7 +249,12 @@ func New(ctx context.Context, service service.TendermintService) (api.SetableBac } }) - if base := service.GetGenesis().EpochTime.Base; base != 0 { + genDoc, err := service.GetGenesisDocument(ctx) + if err != nil { + return nil, err + } + + if base := genDoc.EpochTime.Base; base != 0 { r.logger.Warn("ignoring non-zero base genesis epoch", "base", base, ) diff --git a/go/consensus/tendermint/service/service.go b/go/consensus/tendermint/service/service.go index a7a0729681e..4de1e7156f1 100644 --- a/go/consensus/tendermint/service/service.go +++ b/go/consensus/tendermint/service/service.go @@ -12,7 +12,6 @@ import ( "github.com/oasislabs/oasis-core/go/common/service" consensus "github.com/oasislabs/oasis-core/go/consensus/api" "github.com/oasislabs/oasis-core/go/consensus/tendermint/abci" - genesis "github.com/oasislabs/oasis-core/go/genesis/api" ) // TendermintService provides Tendermint access to Oasis core backends. @@ -33,9 +32,6 @@ type TendermintService interface { // ABCI multiplexer. SetTransactionAuthHandler(abci.TransactionAuthHandler) error - // GetGenesis will return the oasis genesis document. - GetGenesis() *genesis.Document - // GetHeight returns the Tendermint block height. GetHeight(ctx context.Context) (int64, error) diff --git a/go/consensus/tendermint/staking/staking.go b/go/consensus/tendermint/staking/staking.go index 7c9652d034a..a4173744d32 100644 --- a/go/consensus/tendermint/staking/staking.go +++ b/go/consensus/tendermint/staking/staking.go @@ -11,6 +11,7 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/oasislabs/oasis-core/go/common/cbor" + "github.com/oasislabs/oasis-core/go/common/crypto/hash" "github.com/oasislabs/oasis-core/go/common/crypto/signature" "github.com/oasislabs/oasis-core/go/common/logging" "github.com/oasislabs/oasis-core/go/common/pubsub" @@ -37,6 +38,14 @@ type tendermintBackend struct { closedCh chan struct{} } +// Extend the abci Event struct with the transaction hash if the event was +// the result of a transaction. Block events have Hash set to the empty hash. +type abciEventWithHash struct { + abcitypes.Event + + TxHash hash.Hash +} + func (tb *tendermintBackend) TotalSupply(ctx context.Context, height int64) (*quantity.Quantity, error) { q, err := tb.querier.QueryAt(ctx, height) if err != nil { @@ -142,6 +151,23 @@ func (tb *tendermintBackend) StateToGenesis(ctx context.Context, height int64) ( return q.Genesis(ctx) } +func convertTmBlockEvents(beginBlockEvents []abcitypes.Event, endBlockEvents []abcitypes.Event) []abciEventWithHash { + var tmEvents []abciEventWithHash + for _, bbe := range beginBlockEvents { + var ev abciEventWithHash + ev.Event = bbe + ev.TxHash.Empty() + tmEvents = append(tmEvents, ev) + } + for _, ebe := range endBlockEvents { + var ev abciEventWithHash + ev.Event = ebe + ev.TxHash.Empty() + tmEvents = append(tmEvents, ev) + } + return tmEvents +} + func (tb *tendermintBackend) GetEvents(ctx context.Context, height int64) ([]api.Event, error) { // Get block results at given height. var results *tmrpctypes.ResultBlockResults @@ -154,10 +180,33 @@ func (tb *tendermintBackend) GetEvents(ctx context.Context, height int64) ([]api return nil, err } + // Get transactions at given height. + txns, err := tb.service.GetTransactions(ctx, height) + if err != nil { + tb.logger.Error("failed to get tendermint transactions", + "err", err, + "height", height, + ) + return nil, err + } + // Decode events from block results. - tmEvents := append(results.BeginBlockEvents, results.EndBlockEvents...) - for _, txResults := range results.TxsResults { - tmEvents = append(tmEvents, txResults.Events...) + tmEvents := convertTmBlockEvents(results.BeginBlockEvents, results.EndBlockEvents) + for txIdx, txResults := range results.TxsResults { + // The order of transactions in txns and results.TxsResults is + // supposed to match, so the same index in both slices refers to the + // same transaction. + + // Generate hash of transaction. + evHash := hash.NewFromBytes(txns[txIdx]) + + // Append hash to each event. + for _, tmEv := range txResults.Events { + var ev abciEventWithHash + ev.Event = tmEv + ev.TxHash = evHash + tmEvents = append(tmEvents, ev) + } } return tb.onABCIEvents(ctx, tmEvents, height, false) } @@ -210,17 +259,26 @@ func (tb *tendermintBackend) worker(ctx context.Context) { } func (tb *tendermintBackend) onEventDataNewBlock(ctx context.Context, ev tmtypes.EventDataNewBlock) { - events := append([]abcitypes.Event{}, ev.ResultBeginBlock.GetEvents()...) - events = append(events, ev.ResultEndBlock.GetEvents()...) + events := convertTmBlockEvents(ev.ResultBeginBlock.GetEvents(), ev.ResultEndBlock.GetEvents()) _, _ = tb.onABCIEvents(ctx, events, ev.Block.Header.Height, true) } func (tb *tendermintBackend) onEventDataTx(ctx context.Context, tx tmtypes.EventDataTx) { - _, _ = tb.onABCIEvents(ctx, tx.Result.Events, tx.Height, true) + evHash := hash.NewFromBytes(tx.Tx) + + var events []abciEventWithHash + for _, tmEv := range tx.Result.Events { + var ev abciEventWithHash + ev.Event = tmEv + ev.TxHash = evHash + events = append(events, ev) + } + + _, _ = tb.onABCIEvents(ctx, events, tx.Height, true) } -func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []abcitypes.Event, height int64, doBroadcast bool) ([]api.Event, error) { +func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []abciEventWithHash, height int64, doBroadcast bool) ([]api.Event, error) { var events []api.Event for _, tmEv := range tmEvents { // Ignore events that don't relate to the staking app. @@ -228,6 +286,8 @@ func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []ab continue } + eh := tmEv.TxHash + for _, pair := range tmEv.GetAttributes() { key := pair.GetKey() val := pair.GetValue() @@ -250,7 +310,7 @@ func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []ab if doBroadcast { tb.escrowNotifier.Broadcast(ee) } else { - events = append(events, api.Event{EscrowEvent: ee}) + events = append(events, api.Event{TxHash: eh, EscrowEvent: ee}) } } else if bytes.Equal(key, app.KeyTransfer) { // Transfer event. @@ -269,7 +329,7 @@ func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []ab if doBroadcast { tb.transferNotifier.Broadcast(&e) } else { - events = append(events, api.Event{TransferEvent: &e}) + events = append(events, api.Event{TxHash: eh, TransferEvent: &e}) } } else if bytes.Equal(key, app.KeyReclaimEscrow) { // Reclaim escrow event. @@ -290,7 +350,7 @@ func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []ab if doBroadcast { tb.escrowNotifier.Broadcast(ee) } else { - events = append(events, api.Event{EscrowEvent: ee}) + events = append(events, api.Event{TxHash: eh, EscrowEvent: ee}) } } else if bytes.Equal(key, app.KeyAddEscrow) { // Add escrow event. @@ -311,7 +371,7 @@ func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []ab if doBroadcast { tb.escrowNotifier.Broadcast(ee) } else { - events = append(events, api.Event{EscrowEvent: ee}) + events = append(events, api.Event{TxHash: eh, EscrowEvent: ee}) } } else if bytes.Equal(key, app.KeyBurn) { // Burn event. @@ -330,7 +390,7 @@ func (tb *tendermintBackend) onABCIEvents(context context.Context, tmEvents []ab if doBroadcast { tb.burnNotifier.Broadcast(&e) } else { - events = append(events, api.Event{BurnEvent: &e}) + events = append(events, api.Event{TxHash: eh, BurnEvent: &e}) } } } diff --git a/go/consensus/tendermint/tendermint.go b/go/consensus/tendermint/tendermint.go index 163e46e63a3..afedb57ad35 100644 --- a/go/consensus/tendermint/tendermint.go +++ b/go/consensus/tendermint/tendermint.go @@ -56,7 +56,6 @@ import ( tmstaking "github.com/oasislabs/oasis-core/go/consensus/tendermint/staking" epochtimeAPI "github.com/oasislabs/oasis-core/go/epochtime/api" genesisAPI "github.com/oasislabs/oasis-core/go/genesis/api" - "github.com/oasislabs/oasis-core/go/genesis/file" keymanagerAPI "github.com/oasislabs/oasis-core/go/keymanager/api" cmbackground "github.com/oasislabs/oasis-core/go/oasis-node/cmd/common/background" cmflags "github.com/oasislabs/oasis-core/go/oasis-node/cmd/common/flags" @@ -361,16 +360,9 @@ func (t *tendermintService) StateToGenesis(ctx context.Context, blockHeight int6 blockHeight = blk.Header.Height // Get initial genesis doc. - genesisFileProvider, err := file.DefaultFileProvider() + genesisDoc, err := t.GetGenesisDocument(ctx) if err != nil { - t.Logger.Error("failed getting genesis file provider", - "err", err, - ) - return nil, err - } - genesisDoc, err := genesisFileProvider.GetGenesisDocument() - if err != nil { - t.Logger.Error("failed getting genesis document from file provider", + t.Logger.Error("failed getting genesis document", "err", err, ) return nil, err @@ -449,6 +441,10 @@ func (t *tendermintService) StateToGenesis(ctx context.Context, blockHeight int6 }, nil } +func (t *tendermintService) GetGenesisDocument(ctx context.Context) (*genesisAPI.Document, error) { + return t.genesis, nil +} + func (t *tendermintService) RegisterHaltHook(hook func(context.Context, int64, epochtimeAPI.EpochTime)) { if !t.initialized() { return @@ -618,10 +614,6 @@ func (t *tendermintService) SetTransactionAuthHandler(handler abci.TransactionAu return t.mux.SetTransactionAuthHandler(handler) } -func (t *tendermintService) GetGenesis() *genesisAPI.Document { - return t.genesis -} - func (t *tendermintService) TransactionAuthHandler() consensusAPI.TransactionAuthHandler { return t.mux.TransactionAuthHandler() } diff --git a/go/consensus/tests/tester.go b/go/consensus/tests/tester.go index 2ad2e005628..b6209671061 100644 --- a/go/consensus/tests/tester.go +++ b/go/consensus/tests/tester.go @@ -28,6 +28,10 @@ func ConsensusImplementationTests(t *testing.T, backend consensus.ClientBackend) ctx, cancel := context.WithTimeout(context.Background(), recvTimeout) defer cancel() + genDoc, err := backend.GetGenesisDocument(ctx) + require.NoError(err, "GetGenesisDocument") + require.NotNil(genDoc, "returned genesis document should not be nil") + blk, err := backend.GetBlock(ctx, consensus.HeightLatest) require.NoError(err, "GetBlock") require.NotNil(blk, "returned block should not be nil") diff --git a/go/staking/api/api.go b/go/staking/api/api.go index 349811fc38a..ed413066cb9 100644 --- a/go/staking/api/api.go +++ b/go/staking/api/api.go @@ -5,6 +5,7 @@ import ( "context" "fmt" + "github.com/oasislabs/oasis-core/go/common/crypto/hash" "github.com/oasislabs/oasis-core/go/common/crypto/signature" "github.com/oasislabs/oasis-core/go/common/errors" "github.com/oasislabs/oasis-core/go/common/pubsub" @@ -156,6 +157,8 @@ type EscrowEvent struct { // Event signifies a staking event, returned via GetEvents. type Event struct { + TxHash hash.Hash `json:"tx_hash,omitempty"` + TransferEvent *TransferEvent `json:"transfer,omitempty"` BurnEvent *BurnEvent `json:"burn,omitempty"` EscrowEvent *EscrowEvent `json:"escrow,omitempty"` diff --git a/go/staking/tests/tester.go b/go/staking/tests/tester.go index a56110892cd..4627565c8ea 100644 --- a/go/staking/tests/tester.go +++ b/go/staking/tests/tester.go @@ -193,6 +193,7 @@ func testTransfer(t *testing.T, state *stakingTestsState, backend api.Backend, c if evt.TransferEvent != nil { if evt.TransferEvent.From.Equal(ev.From) && evt.TransferEvent.To.Equal(ev.To) && evt.TransferEvent.Tokens.Cmp(&ev.Tokens) == 0 { gotIt = true + require.True(!evt.TxHash.IsEmpty(), "GetEvents should return valid txn hash") break } }