Skip to content

Commit

Permalink
add ability to query for token details (hyperledger-labs#612)
Browse files Browse the repository at this point in the history
Signed-off-by: Arne Rutjes <[email protected]>
  • Loading branch information
arner authored Apr 29, 2024
1 parent b934597 commit 298a6a9
Show file tree
Hide file tree
Showing 16 changed files with 686 additions and 345 deletions.
10 changes: 10 additions & 0 deletions token/sdk/tokens/authrorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package tokens

import (
"github.com/hyperledger-labs/fabric-token-sdk/token"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/identity"
token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token"
)

Expand Down Expand Up @@ -75,3 +76,12 @@ func (o *AuthorizationMultiplexer) AmIAnAuditor(tms *token.ManagementService) bo
}
return false
}

// OwnerType returns the type of owner (e.g. 'idemix' or 'htlc') and the identity bytes
func (o *AuthorizationMultiplexer) OwnerType(raw []byte) (string, []byte, error) {
owner, err := identity.UnmarshalTypedIdentity(raw)
if err != nil {
return "", nil, err
}
return owner.Type, owner.Identity, nil
}
2 changes: 1 addition & 1 deletion token/services/auditdb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (d *DB) Append(req *token.Request) error {
if err != nil {
return errors.WithMessagef(err, "begin update for txid [%s] failed", record.Anchor)
}
if err := w.AddTokenRequest(record.Anchor, raw); err != nil {
if err := w.AddTokenRequest(record.Anchor, raw, req.Metadata.Application); err != nil {
w.Rollback()
return errors.WithMessagef(err, "append token request for txid [%s] failed", record.Anchor)
}
Expand Down
107 changes: 71 additions & 36 deletions token/services/db/dbtest/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func TStatus(t *testing.T, db driver.TokenTransactionDB) {

w, err := db.BeginAtomicWrite()
assert.NoError(t, err, "begin")
assert.NoError(t, w.AddTokenRequest("tx1", []byte("request")), "add token request")
assert.NoError(t, w.AddTokenRequest("tx1", []byte("request"), map[string][]byte{}), "add token request")
assert.NoError(t, w.AddTransaction(&tx))
assert.NoError(t, w.AddValidationRecord("tx1", nil), "add validation record")
assert.NoError(t, w.AddMovement(&mv))
Expand Down Expand Up @@ -134,7 +134,7 @@ func TStatus(t *testing.T, db driver.TokenTransactionDB) {
func TStoresTimestamp(t *testing.T, db driver.TokenTransactionDB) {
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
assert.NoError(t, w.AddTokenRequest("tx1", []byte("")))
assert.NoError(t, w.AddTokenRequest("tx1", []byte(""), map[string][]byte{}))
assert.NoError(t, w.AddTransaction(&driver.TransactionRecord{
TxID: "tx1",
ActionType: driver.Transfer,
Expand Down Expand Up @@ -164,9 +164,9 @@ func TStoresTimestamp(t *testing.T, db driver.TokenTransactionDB) {
func TMovements(t *testing.T, db driver.TokenTransactionDB) {
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
assert.NoError(t, w.AddTokenRequest("0", []byte{}))
assert.NoError(t, w.AddTokenRequest("1", []byte{}))
assert.NoError(t, w.AddTokenRequest("2", []byte{}))
assert.NoError(t, w.AddTokenRequest("0", []byte{}, map[string][]byte{}))
assert.NoError(t, w.AddTokenRequest("1", []byte{}, map[string][]byte{}))
assert.NoError(t, w.AddTokenRequest("2", []byte{}, map[string][]byte{}))
assert.NoError(t, w.AddMovement(&driver.MovementRecord{
TxID: "0",
EnrollmentID: "alice",
Expand Down Expand Up @@ -235,15 +235,16 @@ func TTransaction(t *testing.T, db driver.TokenTransactionDB) {
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
tr1 := &driver.TransactionRecord{
TxID: fmt.Sprintf("tx%d", 99),
ActionType: driver.Transfer,
SenderEID: "bob",
RecipientEID: "alice",
TokenType: "magic",
Amount: big.NewInt(10),
Timestamp: lastYear,
}
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte(fmt.Sprintf("token request for %s", tr1.TxID))))
TxID: fmt.Sprintf("tx%d", 99),
ActionType: driver.Transfer,
SenderEID: "bob",
RecipientEID: "alice",
TokenType: "magic",
Amount: big.NewInt(10),
ApplicationMetadata: map[string][]byte{},
Timestamp: lastYear,
}
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte(fmt.Sprintf("token request for %s", tr1.TxID)), map[string][]byte{}))
assert.NoError(t, w.AddTransaction(tr1))

for i := 0; i < 20; i++ {
Expand All @@ -256,13 +257,22 @@ func TTransaction(t *testing.T, db driver.TokenTransactionDB) {
TokenType: "magic",
Amount: big.NewInt(10),
Timestamp: now,
ApplicationMetadata: map[string][]byte{
"this is the first key": {99, 33, 22, 11},
"this is the second key": []byte("with some text as the value " + fmt.Sprintf("tx%d", i)),
},
}
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte(fmt.Sprintf("token request for %s", tr1.TxID))))
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte(fmt.Sprintf("token request for %s", tr1.TxID)), tr1.ApplicationMetadata))
assert.NoError(t, w.AddTransaction(tr1))
txs = append(txs, tr1)
}
assert.NoError(t, w.Commit())

// get one
one := getTransactions(t, db, driver.QueryTransactionsParams{IDs: []string{"tx10"}})
assert.Len(t, one, 1)
assert.Equal(t, "tx10", one[0].TxID)

// get all except last year's
t1 := time.Now().Add(time.Second * 3)
it, err := db.QueryTransactions(driver.QueryTransactionsParams{From: &t0, To: &t1})
Expand Down Expand Up @@ -308,6 +318,28 @@ func TTransaction(t *testing.T, db driver.TokenTransactionDB) {
status, _, err = db.GetStatus("nonexistenttx")
assert.NoError(t, err, "a non existent transaction should return Unknown status but no error")
assert.Equal(t, driver.Unknown, status)

// exclude to self
w, err = db.BeginAtomicWrite()
assert.NoError(t, err)
tr1 = &driver.TransactionRecord{
TxID: "1234",
ActionType: driver.Transfer,
SenderEID: "alice",
RecipientEID: "alice",
TokenType: "magic",
Amount: big.NewInt(10),
ApplicationMetadata: map[string][]byte{},
Timestamp: lastYear,
}
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte(fmt.Sprintf("token request for %s", tr1.TxID)), map[string][]byte{}))
assert.NoError(t, w.AddTransaction(tr1))
assert.NoError(t, w.Commit())
noChange := getTransactions(t, db, driver.QueryTransactionsParams{ExcludeToSelf: true})
assert.Len(t, noChange, 21)
for _, tr := range noChange {
assert.NotEqual(t, tr.TxID, tr1.TxID, "transaction to self should not be included")
}
}

const explanation = "transactions [%s]=[%s]"
Expand All @@ -329,17 +361,18 @@ func assertTxEqual(t *testing.T, exp *driver.TransactionRecord, act *driver.Tran
assert.Equal(t, exp.RecipientEID, act.RecipientEID, expl)
assert.Equal(t, exp.TokenType, act.TokenType, expl)
assert.Equal(t, exp.Amount, act.Amount, expl)
assert.Equal(t, exp.ApplicationMetadata, act.ApplicationMetadata, expl)
assert.WithinDuration(t, exp.Timestamp, act.Timestamp, 3*time.Second)
}

func TTokenRequest(t *testing.T, db driver.TokenTransactionDB) {
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
tr1 := []byte("arbitrary bytes")
err = w.AddTokenRequest("id1", tr1)
err = w.AddTokenRequest("id1", tr1, map[string][]byte{})
assert.NoError(t, err)
tr2 := []byte("arbitrary bytes 2")
err = w.AddTokenRequest("id2", tr2)
err = w.AddTokenRequest("id2", tr2, map[string][]byte{})
assert.NoError(t, err)
assert.NoError(t, w.Commit())
assert.NoError(t, db.SetStatus("id2", driver.Confirmed, ""))
Expand Down Expand Up @@ -439,27 +472,29 @@ func TTokenRequest(t *testing.T, db driver.TokenTransactionDB) {
func TAllowsSameTxID(t *testing.T, db driver.TokenTransactionDB) {
// bob sends 10 to alice
tr1 := &driver.TransactionRecord{
TxID: "1",
ActionType: driver.Transfer,
SenderEID: "bob",
RecipientEID: "alice",
TokenType: "magic",
Amount: big.NewInt(10),
Timestamp: time.Now(),
TxID: "1",
ActionType: driver.Transfer,
SenderEID: "bob",
RecipientEID: "alice",
TokenType: "magic",
ApplicationMetadata: map[string][]byte{},
Amount: big.NewInt(10),
Timestamp: time.Now(),
}
// 1 is sent back to bobs wallet as change
tr2 := &driver.TransactionRecord{
TxID: "1",
ActionType: driver.Transfer,
SenderEID: "bob",
RecipientEID: "bob",
TokenType: "magic",
Amount: big.NewInt(1),
Timestamp: time.Now(),
TxID: "1",
ActionType: driver.Transfer,
SenderEID: "bob",
RecipientEID: "bob",
TokenType: "magic",
ApplicationMetadata: map[string][]byte{},
Amount: big.NewInt(1),
Timestamp: time.Now(),
}
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte{}))
assert.NoError(t, w.AddTokenRequest(tr1.TxID, []byte{}, map[string][]byte{}))
assert.NoError(t, w.AddTransaction(tr1))
assert.NoError(t, w.AddTransaction(tr2))
assert.NoError(t, w.Commit())
Expand All @@ -473,7 +508,7 @@ func TAllowsSameTxID(t *testing.T, db driver.TokenTransactionDB) {
func TRollback(t *testing.T, db driver.TokenTransactionDB) {
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
assert.NoError(t, w.AddTokenRequest("1", []byte("arbitrary bytes")))
assert.NoError(t, w.AddTokenRequest("1", []byte("arbitrary bytes"), map[string][]byte{}))

mr1 := &driver.MovementRecord{
TxID: "1",
Expand Down Expand Up @@ -718,7 +753,7 @@ func TTransactionQueries(t *testing.T, db driver.TokenTransactionDB) {
var previous string
for _, r := range tr {
if r.TxID != previous {
assert.NoError(t, w.AddTokenRequest(r.TxID, []byte{}))
assert.NoError(t, w.AddTokenRequest(r.TxID, []byte{}, map[string][]byte{}))
}
assert.NoError(t, w.AddTransaction(&r))
previous = r.TxID
Expand Down Expand Up @@ -790,7 +825,7 @@ func TValidationRecordQueries(t *testing.T, db driver.TokenTransactionDB) {
w, err := db.BeginAtomicWrite()
assert.NoError(t, err)
for _, e := range exp {
assert.NoError(t, w.AddTokenRequest(e.TxID, e.TokenRequest))
assert.NoError(t, w.AddTokenRequest(e.TxID, e.TokenRequest, map[string][]byte{}))
assert.NoError(t, w.AddValidationRecord(e.TxID, e.Metadata), "AddValidationRecord "+e.TxID)
}
assert.NoError(t, w.Commit(), "Commit")
Expand Down Expand Up @@ -877,7 +912,7 @@ func createTransaction(t *testing.T, db driver.TokenTransactionDB, txID string)
if err != nil {
t.Fatalf("error creating transaction while trying to test something else: %s", err)
}
if err := w.AddTokenRequest(txID, []byte{}); err != nil {
if err := w.AddTokenRequest(txID, []byte{}, map[string][]byte{}); err != nil {
t.Fatalf("error creating token request while trying to test something else: %s", err)
}
tr1 := &driver.TransactionRecord{
Expand Down
5 changes: 5 additions & 0 deletions token/services/db/driver/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ type QueryMovementsParams struct {
// QueryTransactionsParams defines the parameters for querying transactions.
// One can filter by sender, by recipient, and by time range.
type QueryTransactionsParams struct {
// IDs is the list of transaction ids. If nil or empty, all transactions are returned
IDs []string
// ExcludeToSelf can be used to filter out 'change' transactions where the sender and
// recipient have the same enrollment id.
ExcludeToSelf bool
// SenderWallet is the wallet of the sender
// If empty, any sender is accepted
// If the sender does not match but the recipient matches, the transaction is returned
Expand Down
57 changes: 55 additions & 2 deletions token/services/db/driver/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ SPDX-License-Identifier: Apache-2.0
package driver

import (
"errors"
"time"

view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view"
token2 "github.com/hyperledger-labs/fabric-token-sdk/token"
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
Expand All @@ -21,8 +24,12 @@ type TokenRecord struct {
// IssuerRaw represents the serialization of the issuer identity
// if this is an IssuedToken.
IssuerRaw []byte
// OwnerRaw is the serialization of the owner identity
// OwnerRaw is the serialization of the owner TypedIdentity
OwnerRaw []byte
// OwnerType is the deserialized type inside OwnerRaw
OwnerType string
// OwnerIdentity is the deserialized Identity inside OwnerRaw
OwnerIdentity []byte
// Ledger is the raw token as stored on the ledger
Ledger []byte
// LedgerMetadata is the metadata associated to the content of Ledger
Expand All @@ -42,6 +49,46 @@ type TokenRecord struct {
Issuer bool
}

// TokenDetails provides details about an owned (spent or unspent) token
type TokenDetails struct {
// TxID is the ID of the transaction that created the token
TxID string
// Index is the index in the transaction
Index uint64
// OwnerIdentity is the serialization of the owner identity
OwnerIdentity []byte
// OwnerType is the deserialized type inside OwnerRaw
OwnerType string
// OwnerEnrollment is the enrollment id of the owner
OwnerEnrollment string
// Type is the type of token
Type string
// Amount is the Quantity converted to decimal
Amount uint64
// IsSpent is true if the token has been spent
IsSpent bool
// SpentBy is the transactionID that spent this token, if available
SpentBy string
// StoredAt is the moment the token was stored by this wallet
StoredAt time.Time
}

// QueryTokenDetailsParams defines the parameters for querying token details
type QueryTokenDetailsParams struct {
// OwnerEnrollmentID is the optional owner of the token
OwnerEnrollmentID string
// OwnerType is the type of owner, for instance 'idemix' or 'htlc'
OwnerType string
// TokenType (optional) is the type of token
TokenType string
//IDs is an optional list of specific token ids to return
IDs []*token.ID
// TransactionIDs selects tokens that are the output of the provided transaction ids.
TransactionIDs []string
// IncludeDeleted determines whether to include spent tokens. It defaults to false.
IncludeDeleted bool
}

// CertificationDB defines a database to manager token certifications
type CertificationDB interface {
// ExistsCertification returns true if a certification for the passed token exists,
Expand All @@ -61,7 +108,7 @@ type TokenDBTransaction interface {
// TransactionExists returns true if a token with that transaction id exists in the db
TransactionExists(id string) (bool, error)
// GetToken returns the owned tokens and their identifier keys for the passed ids.
GetToken(txID string, index uint64, includeDeleted bool) (*token.Token, error)
GetToken(txID string, index uint64, includeDeleted bool) (*token.Token, []string, error)
// OwnersOf returns the list of owner of a given token
OwnersOf(txID string, index uint64) ([]string, error)
// Delete marks the passed token as deleted by a given identifier (idempotent)
Expand Down Expand Up @@ -114,10 +161,16 @@ type TokenDB interface {
PublicParams() ([]byte, error)
// NewTokenDBTransaction returns a new Transaction to commit atomically multiple operations
NewTokenDBTransaction() (TokenDBTransaction, error)
// QueryTokenDetails provides detailed information about tokens
QueryTokenDetails(params QueryTokenDetailsParams) ([]TokenDetails, error)
}

// TokenDBDriver is the interface for a token database driver
type TokenDBDriver interface {
// Open opens a token database
Open(sp view2.ServiceProvider, tmsID token2.TMSID) (TokenDB, error)
}

var (
ErrTokenDoesNotExist = errors.New("token does not exist")
)
2 changes: 1 addition & 1 deletion token/services/db/driver/ttx.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type AtomicWrite interface {
Rollback()

// AddTokenRequest binds the passed transaction id to the passed token request
AddTokenRequest(txID string, tr []byte) error
AddTokenRequest(txID string, tr []byte, applicationMetadata map[string][]byte) error

// AddMovement adds a movement record to the database transaction.
// Each token transaction can be seen as a list of movements.
Expand Down
Loading

0 comments on commit 298a6a9

Please sign in to comment.