Commit b807c81e authored by Alessandro Sorniotti's avatar Alessandro Sorniotti Committed by Gerrit Code Review
Browse files

Merge "[FAB-11678] TX processor/verifier: redeem tokens"

parents dc7ff5b3 9994358c
......@@ -8,6 +8,7 @@ package plain
import (
"bytes"
"encoding/hex"
"fmt"
"strconv"
"unicode/utf8"
......@@ -26,6 +27,7 @@ const (
maxUnicodeRuneValue = utf8.MaxRune //U+10FFFF - maximum (and unallocated) code point
compositeKeyNamespace = "\x00"
tokenOutput = "tokenOutput"
tokenRedeem = "tokenRedeem"
tokenTx = "tokenTx"
)
......@@ -73,6 +75,8 @@ func (v *Verifier) checkAction(creator identity.PublicInfo, plainAction *token.P
return v.checkImportAction(creator, action.PlainImport, txID, simulator)
case *token.PlainTokenAction_PlainTransfer:
return v.checkTransferAction(creator, action.PlainTransfer, txID, simulator)
case *token.PlainTokenAction_PlainRedeem:
return v.checkRedeemAction(creator, action.PlainRedeem, txID, simulator)
default:
return &customtx.InvalidTxError{Msg: fmt.Sprintf("unknown plain token action: %T", action)}
}
......@@ -121,6 +125,35 @@ func (v *Verifier) checkTransferAction(creator identity.PublicInfo, transferActi
return nil
}
func (v *Verifier) checkRedeemAction(creator identity.PublicInfo, redeemAction *token.PlainTransfer, txID string, simulator ledger.LedgerReader) error {
// first perform the same checking as transfer
err := v.checkTransferAction(creator, redeemAction, txID, simulator)
if err != nil {
return err
}
// then perform additional checking for redeem outputs
// redeem transaction should not have more than 2 outputs.
outputs := redeemAction.GetOutputs()
if len(outputs) > 2 {
return &customtx.InvalidTxError{Msg: fmt.Sprintf("too many outputs (%d) in a redeem transaction", len(outputs))}
}
// output[0] should always be a redeem output - i.e., owner should be nil
if outputs[0].Owner != nil {
return &customtx.InvalidTxError{Msg: fmt.Sprintf("owner should be nil in a redeem output")}
}
// if output[1] presents, its owner must be same as the creator
if len(outputs) == 2 && !bytes.Equal(creator.Public(), outputs[1].Owner) {
println(hex.EncodeToString(creator.Public()))
println(hex.EncodeToString(outputs[1].Owner))
return &customtx.InvalidTxError{Msg: fmt.Sprintf("wrong owner for remaining tokens, should be original owner %s, but got %s", creator.Public(), outputs[1].Owner)}
}
return nil
}
func (v *Verifier) checkOutputDoesNotExist(index int, txID string, simulator ledger.LedgerReader) error {
outputID, err := createOutputKey(txID, index)
if err != nil {
......@@ -255,6 +288,9 @@ func (v *Verifier) commitAction(plainAction *token.PlainTokenAction, txID string
err = v.commitImportAction(action.PlainImport, txID, simulator)
case *token.PlainTokenAction_PlainTransfer:
err = v.commitTransferAction(action.PlainTransfer, txID, simulator)
case *token.PlainTokenAction_PlainRedeem:
// call the same commit method as transfer because PlainRedeem points to the same type of outputs as transfer
err = v.commitTransferAction(action.PlainRedeem, txID, simulator)
}
return
}
......@@ -274,9 +310,17 @@ func (v *Verifier) commitImportAction(importAction *token.PlainImport, txID stri
return nil
}
// commitTransferAction is called for both transfer and redeem transactions
// Check the owner of each output to determine how to generate the key
func (v *Verifier) commitTransferAction(transferAction *token.PlainTransfer, txID string, simulator ledger.LedgerWriter) error {
var outputID string
var err error
for i, output := range transferAction.GetOutputs() {
outputID, err := createOutputKey(txID, i)
if output.Owner != nil {
outputID, err = createOutputKey(txID, i)
} else {
outputID, err = createRedeemKey(txID, i)
}
if err != nil {
return &customtx.InvalidTxError{Msg: fmt.Sprintf("error creating output ID: %s", err)}
}
......@@ -348,6 +392,12 @@ func createOutputKey(txID string, index int) (string, error) {
return createCompositeKey(tokenOutput, []string{txID, strconv.Itoa(index)})
}
// Create a ledger key for a redeem output in a token transaction, as a function of
// the transaction ID, and the index of the output
func createRedeemKey(txID string, index int) (string, error) {
return createCompositeKey(tokenRedeem, []string{txID, strconv.Itoa(index)})
}
// Create a ledger key for a token transaction, as a function of the transaction ID
func createTxKey(txID string) (string, error) {
return createCompositeKey(tokenTx, []string{txID})
......
......@@ -657,4 +657,219 @@ var _ = Describe("Verifier", func() {
})
})
})
Describe("Test ProcessTx PlainRedeem with memory ledger", func() {
var (
inputIds []*token.InputId
redeemTxID string
redeemTransaction *token.TokenTransaction
)
BeforeEach(func() {
redeemTxID = "r1"
inputIds = []*token.InputId{
{TxId: "0", Index: 0},
}
redeemTransaction = &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: inputIds,
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: 111},
},
},
},
},
},
}
fakePublicInfo.PublicReturns([]byte("owner-1"))
memoryLedger = plain.NewMemoryLedger()
err := verifier.ProcessTx(importTxID, fakePublicInfo, importTransaction, memoryLedger)
Expect(err).NotTo(HaveOccurred())
})
It("processes a redeem transaction with all tokens redeemed", func() {
err := verifier.ProcessTx(redeemTxID, fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).NotTo(HaveOccurred())
// verify we can get the output from "tokenRedeem" for this transaction
po, err := memoryLedger.GetState("tms", string("\x00")+"tokenRedeem"+string("\x00")+redeemTxID+string("\x00")+"0"+string("\x00"))
Expect(err).NotTo(HaveOccurred())
output := &token.PlainOutput{}
err = proto.Unmarshal(po, output)
Expect(err).NotTo(HaveOccurred())
Expect(output).To(Equal(&token.PlainOutput{
Type: "TOK1",
Quantity: 111,
}))
})
It("processes a redeem transaction with some tokens redeemed", func() {
// prepare redeemTransaction with 2 outputs: one for redeemed tokens and another for remaining tokens
redeemTransaction = &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: inputIds,
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: 99},
{Owner: []byte("owner-1"), Type: "TOK1", Quantity: 12},
},
},
},
},
},
}
err := verifier.ProcessTx(redeemTxID, fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).NotTo(HaveOccurred())
// verify we can get 1 output from "tokenRedeem" and 1 output from "tokenOutput" for this transaction
po, err := memoryLedger.GetState("tms", string("\x00")+"tokenRedeem"+string("\x00")+redeemTxID+string("\x00")+"0"+string("\x00"))
Expect(err).NotTo(HaveOccurred())
output := &token.PlainOutput{}
err = proto.Unmarshal(po, output)
Expect(err).NotTo(HaveOccurred())
Expect(output).To(Equal(&token.PlainOutput{
Type: "TOK1",
Quantity: 99,
}))
po, err = memoryLedger.GetState("tms", string("\x00")+"tokenOutput"+string("\x00")+redeemTxID+string("\x00")+"1"+string("\x00"))
Expect(err).NotTo(HaveOccurred())
err = proto.Unmarshal(po, output)
Expect(err).NotTo(HaveOccurred())
Expect(output).To(Equal(&token.PlainOutput{
Owner: []byte("owner-1"),
Type: "TOK1",
Quantity: 12,
}))
spentMarker, err := memoryLedger.GetState("tms", string("\x00")+"tokenInput"+string("\x00")+"0"+string("\x00")+"0"+string("\x00"))
Expect(err).NotTo(HaveOccurred())
Expect(bytes.Equal(spentMarker, plain.TokenInputSpentMarker)).To(BeTrue())
})
Context("when an input has already been spent", func() {
BeforeEach(func() {
err := verifier.ProcessTx(redeemTxID, fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).NotTo(HaveOccurred())
})
It("returns an InvalidTxError", func() {
err := verifier.ProcessTx("r2", fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).To(Equal(&customtx.InvalidTxError{Msg: "input with ID \x00tokenOutput\x000\x000\x00 for transfer has already been spent"}))
})
})
Context("when token sum mismatches in inputs and outputs", func() {
BeforeEach(func() {
redeemTransaction = &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: inputIds,
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: 100},
},
},
},
},
},
}
})
It("returns an error", func() {
err := verifier.ProcessTx(redeemTxID, fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).To(Equal(&customtx.InvalidTxError{
Msg: fmt.Sprintf("token sum mismatch in inputs and outputs for transfer with ID %s (%d vs %d)", redeemTxID, 100, 111)}))
})
})
Context("when inputs have more than one type", func() {
var (
anotherImportTransaction *token.TokenTransaction
anotherImportTxID string
)
BeforeEach(func() {
anotherImportTxID = "2"
anotherImportTransaction = &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainImport{
PlainImport: &token.PlainImport{
Outputs: []*token.PlainOutput{
{Owner: []byte("owner-1"), Type: "TOK2", Quantity: 222},
},
},
},
},
},
}
err := verifier.ProcessTx(anotherImportTxID, fakePublicInfo, anotherImportTransaction, memoryLedger)
Expect(err).NotTo(HaveOccurred())
redeemTransaction = &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: []*token.InputId{
{TxId: "0", Index: 0},
{TxId: "2", Index: 0},
},
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: 300},
},
},
},
},
},
}
})
It("returns an error", func() {
err := verifier.ProcessTx(redeemTxID, fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).To(Equal(&customtx.InvalidTxError{
Msg: fmt.Sprintf("multiple token types in transfer input for txID: %s (TOK1, TOK2)", redeemTxID)}))
})
})
Context("when output for remaining tokens has wrong owner", func() {
BeforeEach(func() {
// set wrong owner in the output for unredeemed tokens
redeemTransaction = &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: inputIds,
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: 99},
{Owner: []byte("owner-2"), Type: "TOK1", Quantity: 12},
},
},
},
},
},
}
})
It("returns an error", func() {
err := verifier.ProcessTx(redeemTxID, fakePublicInfo, redeemTransaction, memoryLedger)
Expect(err).To(MatchError(fmt.Sprintf(fmt.Sprintf("wrong owner for remaining tokens, should be original owner owner-1, but got owner-2"))))
})
})
})
})
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment