From 148cb242260ff749ddcb0683358f20bfecc628d3 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 8 May 2019 00:16:21 -0700 Subject: [PATCH 01/49] kvstore is a private member --- store/kvStore.go | 26 +++++++++++++++----------- store/plasmaStore.go | 4 ++-- store/utxoStore.go | 4 ++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/store/kvStore.go b/store/kvStore.go index c9fa420..c727c47 100644 --- a/store/kvStore.go +++ b/store/kvStore.go @@ -4,32 +4,36 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -type KVStore struct { +type kvStore struct { contextKey sdk.StoreKey } -func NewKVStore(contextKey sdk.StoreKey) KVStore { - return KVStore{ +func NewKVStore(contextKey sdk.StoreKey) kvStore { + return kvStore{ contextKey: contextKey, } } -func (kvstore KVStore) Set(ctx sdk.Context, key []byte, value []byte) { - store := ctx.KVStore(kvstore.contextKey) +func (kv kvStore) Set(ctx sdk.Context, key []byte, value []byte) { + store := ctx.KVStore(kv.contextKey) store.Set(key, value) } -func (kvstore KVStore) Get(ctx sdk.Context, key []byte) []byte { - store := ctx.KVStore(kvstore.contextKey) +func (kv kvStore) Get(ctx sdk.Context, key []byte) []byte { + store := ctx.KVStore(kv.contextKey) return store.Get(key) } -func (kvstore KVStore) Delete(ctx sdk.Context, key []byte) { - store := ctx.KVStore(kvstore.contextKey) +func (kv kvStore) Delete(ctx sdk.Context, key []byte) { + store := ctx.KVStore(kv.contextKey) store.Delete(key) } -func (kvstore KVStore) Has(ctx sdk.Context, key []byte) bool { - store := ctx.KVStore(kvstore.contextKey) +func (kv kvStore) Has(ctx sdk.Context, key []byte) bool { + store := ctx.KVStore(kv.contextKey) return store.Has(key) } + +func (kv kvStore) KVStore(ctx sdk.Context) sdk.KVStore { + return ctx.KVStore(kv.contextKey) +} diff --git a/store/plasmaStore.go b/store/plasmaStore.go index e781330..8567420 100644 --- a/store/plasmaStore.go +++ b/store/plasmaStore.go @@ -10,7 +10,7 @@ import ( ) type PlasmaStore struct { - KVStore + kvStore } const ( @@ -22,7 +22,7 @@ const ( func NewPlasmaStore(ctxKey sdk.StoreKey) PlasmaStore { return PlasmaStore{ - KVStore: NewKVStore(ctxKey), + kvStore: NewKVStore(ctxKey), } } diff --git a/store/utxoStore.go b/store/utxoStore.go index d08fbf0..35ee30c 100644 --- a/store/utxoStore.go +++ b/store/utxoStore.go @@ -118,12 +118,12 @@ func (u UTXO) SpenderPositions() []plasma.Position { /* Store */ type UTXOStore struct { - KVStore + kvStore } func NewUTXOStore(ctxKey sdk.StoreKey) UTXOStore { return UTXOStore{ - KVStore: NewKVStore(ctxKey), + kvStore: NewKVStore(ctxKey), } } From c8126e719adfba19ff41fd0c23bc6e7a69f31f8b Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 8 May 2019 14:15:20 -0700 Subject: [PATCH 02/49] query routes for utxo --- client/plasmacli/query/account.go | 29 +++++------ client/plasmacli/query/info.go | 25 +++++---- query/account.go | 41 +++++++++++++++ query/block.go | 6 +++ query/querier.go | 85 +++++++++++++++++++++++++++++++ server/app/app.go | 12 ++++- 6 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 query/account.go create mode 100644 query/block.go create mode 100644 query/querier.go diff --git a/client/plasmacli/query/account.go b/client/plasmacli/query/account.go index 98005aa..19aaf73 100644 --- a/client/plasmacli/query/account.go +++ b/client/plasmacli/query/account.go @@ -1,14 +1,14 @@ package query import ( + "bytes" + "encoding/json" "fmt" ks "github.com/FourthState/plasma-mvp-sidechain/client/store" - "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/FourthState/plasma-mvp-sidechain/query" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" - "github.com/ethereum/go-ethereum/rlp" "github.com/spf13/cobra" - "math/big" ) func init() { @@ -28,25 +28,22 @@ var balanceCmd = &cobra.Command{ return err } - res, err := ctx.QuerySubspace(addr.Bytes(), "utxo") + queryRoute := fmt.Sprintf("custom/utxo/balance/%s", addr.Hex()) + data, err := ctx.Query(queryRoute, nil) if err != nil { return err } - total := big.NewInt(0) - utxo := store.UTXO{} - for _, pair := range res { - if err := rlp.DecodeBytes(pair.Value, &utxo); err != nil { - return err - } - - if !utxo.Spent { - fmt.Printf("Position: %s , Amount: %d\n", utxo.Position, utxo.Output.Amount.Uint64()) - total = total.Add(total, utxo.Output.Amount) - } + var resp query.BalanceResp + if err := json.Unmarshal(data, &resp); err != nil { + return err + } else if !bytes.Equal(resp.Address[:], addr[:]) { + return fmt.Errorf("Mismatch in Account and Response Address.\nAccount: 0x%x\n Response: 0x%x\n", + addr, resp.Address) } - fmt.Printf("Total: %d\n", total.Uint64()) + fmt.Printf("Address: %0x\n", resp.Address) + fmt.Printf("Total: %s\n", resp.Total) return nil }, } diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index 481be85..a9381b2 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -1,12 +1,13 @@ package query import ( + "bytes" + "encoding/json" "fmt" - "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/FourthState/plasma-mvp-sidechain/query" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" "github.com/spf13/cobra" "strings" ) @@ -25,19 +26,25 @@ var infoCmd = &cobra.Command{ if !common.IsHexAddress(addrStr) { return fmt.Errorf("Invalid address provided. Please use hex format") } + addr := common.HexToAddress(addrStr) + fmt.Printf("Querying information for 0x%x\n", addr) // query for all utxos owned by this address - res, err := ctx.QuerySubspace(common.HexToAddress(addrStr).Bytes(), "utxo") + queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) + data, err := ctx.Query(queryRoute, nil) if err != nil { return err } - utxo := store.UTXO{} - for i, pair := range res { - if err := rlp.DecodeBytes(pair.Value, &utxo); err != nil { - return err - } + var resp query.InfoResp + if err := json.Unmarshal(data, &resp); err != nil { + return err + } else if !bytes.Equal(resp.Address[:], addr[:]) { + return fmt.Errorf("Mismatch in Account and Response Address.\nAccount: 0x%x\n Response: 0x%x\n", + addr, resp.Address) + } + for i, utxo := range resp.Utxos { fmt.Printf("UTXO %d\n", i) fmt.Printf("Position: %s, Amount: %s, Spent: %t\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent) @@ -58,8 +65,6 @@ var infoCmd = &cobra.Command{ fmt.Printf("End UTXO %d info\n\n", i) } - fmt.Println() - return nil }, } diff --git a/query/account.go b/query/account.go new file mode 100644 index 0000000..79a572b --- /dev/null +++ b/query/account.go @@ -0,0 +1,41 @@ +package query + +import ( + "errors" + "github.com/FourthState/plasma-mvp-sidechain/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "math/big" +) + +func queryBalance(ctx sdk.Context, utxoStore store.UTXOStore, addr common.Address) (*big.Int, error) { + iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) + total := big.NewInt(0) + for ; iter.Valid(); iter.Next() { + utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) + if !ok { + return nil, errors.New("invalid key retrieved") + } + + if !utxo.Spent { + total = total.Add(total, utxo.Output.Amount) + } + } + + return total, nil +} + +func queryInfo(ctx sdk.Context, utxoStore store.UTXOStore, addr common.Address) ([]store.UTXO, error) { + var utxos []store.UTXO + iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) + for ; iter.Valid(); iter.Next() { + utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) + if !ok { + return nil, errors.New("invalid key retrieved") + } + + utxos = append(utxos, utxo) + } + + return utxos, nil +} diff --git a/query/block.go b/query/block.go new file mode 100644 index 0000000..ae0d604 --- /dev/null +++ b/query/block.go @@ -0,0 +1,6 @@ +package query + +import ( +//"github.com/FourthState/plasma-mvp-sidechain/store" +//sdk "github.com/cosmos/cosmos-sdk/types" +) diff --git a/query/querier.go b/query/querier.go new file mode 100644 index 0000000..39645a2 --- /dev/null +++ b/query/querier.go @@ -0,0 +1,85 @@ +package query + +import ( + "encoding/json" + "github.com/FourthState/plasma-mvp-sidechain/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + abci "github.com/tendermint/tendermint/abci/types" + "math/big" +) + +const ( + QueryBalance = "balance" + QueryInfo = "info" +) + +type BalanceResp struct { + Address common.Address + Total *big.Int +} + +type InfoResp struct { + Address common.Address + Utxos []store.UTXO +} + +func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + if len(path) == 0 { + return nil, sdk.ErrUnknownRequest("path not specified") + } + + switch path[0] { + case QueryBalance: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("balance query follows balance/
") + } + addr := common.HexToAddress(path[1]) + total, err := queryBalance(ctx, utxoStore, addr) + if err != nil { + return nil, sdk.ErrInternal("failed query balance") + } + data, err := json.Marshal(BalanceResp{addr, total}) + if err != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil + + case QueryInfo: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("info query follows /info/
") + } + addr := common.HexToAddress(path[1]) + utxos, err := queryInfo(ctx, utxoStore, addr) + if err != nil { + return nil, sdk.ErrInternal("failed utxo retrieval") + } + data, err := json.Marshal(InfoResp{addr, utxos}) + if err != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil + + default: + return nil, sdk.ErrUnknownRequest("unregistered endpoint") + } + } +} + +const ( + QueryBlock = "block" +) + +func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + if len(path) == 0 { + return nil, sdk.ErrUnknownRequest("path not specified") + } + + switch path[0] { + default: + return nil, sdk.ErrUnknownRequest("unregistered endpoint") + } + } +} diff --git a/server/app/app.go b/server/app/app.go index 998bc76..94e7115 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -8,6 +8,7 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/handlers" "github.com/FourthState/plasma-mvp-sidechain/msgs" "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/query" "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" @@ -58,15 +59,17 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio baseApp.SetCommitMultiStoreTracer(traceStore) utxoStoreKey := sdk.NewKVStoreKey("utxo") + utxoStore := store.NewUTXOStore(utxoStoreKey) plasmaStoreKey := sdk.NewKVStoreKey("plasma") + plasmaStore := store.NewPlasmaStore(plasmaStoreKey) app := &PlasmaMVPChain{ BaseApp: baseApp, cdc: cdc, txIndex: 0, feeAmount: big.NewInt(0), // we do not use `utils.BigZero` because the feeAmount is going to be updated - utxoStore: store.NewUTXOStore(utxoStoreKey), - plasmaStore: store.NewPlasmaStore(plasmaStoreKey), + utxoStore: utxoStore, + plasmaStore: plasmaStore, } // set configs @@ -108,6 +111,11 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.utxoStore, app.plasmaStore, nextTxIndex, feeUpdater)) app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.utxoStore, nextTxIndex, plasmaClient)) + // custom queriers + app.QueryRouter(). + AddRoute("utxo", query.NewUtxoQuerier(utxoStore)). + AddRoute("plasma", query.NewPlasmaQuerier(plasmaStore)) + // Set the AnteHandler app.SetAnteHandler(handlers.NewAnteHandler(app.utxoStore, app.plasmaStore, plasmaClient)) From 5f8b0f3c5e471d43f9562f750bff7e7ee8b9ecb2 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 8 May 2019 15:18:08 -0700 Subject: [PATCH 03/49] message when no utxos are available --- client/plasmacli/query/info.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index a9381b2..da0d356 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -65,6 +65,10 @@ var infoCmd = &cobra.Command{ fmt.Printf("End UTXO %d info\n\n", i) } + if len(resp.Utxos) == 0 { + fmt.Println("no information available for this address") + } + return nil }, } From 7e1a4b9ac693a6ba61da44ce19688afd3afeae84 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 9 May 2019 13:56:33 -0700 Subject: [PATCH 04/49] change to deposit, block, and tx store --- store/account.go | 24 +++ store/{plasmaStore.go => blockStore.go} | 29 +-- store/blockStore_test.go | 1 + store/depositStore.go | 69 +++++++ store/depositStore_test.go | 1 + store/txStore.go | 147 +++++++++++++++ store/{utxoStore_test.go => txStore_test.go} | 0 store/utils.go | 10 -- store/utxoStore.go | 178 ------------------- 9 files changed, 250 insertions(+), 209 deletions(-) create mode 100644 store/account.go rename store/{plasmaStore.go => blockStore.go} (67%) create mode 100644 store/blockStore_test.go create mode 100644 store/depositStore.go create mode 100644 store/depositStore_test.go create mode 100644 store/txStore.go rename store/{utxoStore_test.go => txStore_test.go} (100%) delete mode 100644 store/utxoStore.go diff --git a/store/account.go b/store/account.go new file mode 100644 index 0000000..d23aa7d --- /dev/null +++ b/store/account.go @@ -0,0 +1,24 @@ +package store + +import ( + "math/big" +) + +/* Account */ +type Account struct { + Balance *big.Int // total amount avaliable to be spent + // Unspent [][32]utxoCache // hashes of unspent transaction outputs + // Spent [][32]utxoCache // hashes of spent transaction outputs +} + +//func (a *Account) EncodeRLP(w io.writer) error { + +//} + +//func (a *Account) DecodeRLP(s *rlp.Stream) error { + +//} + +/* Account helper functions */ +// Ret balance +// Get unspent utxos diff --git a/store/plasmaStore.go b/store/blockStore.go similarity index 67% rename from store/plasmaStore.go rename to store/blockStore.go index cce6eb7..269e4bd 100644 --- a/store/plasmaStore.go +++ b/store/blockStore.go @@ -9,24 +9,23 @@ import ( "math/big" ) -type PlasmaStore struct { +type BlockStore struct { kvStore } const ( - confirmSigKey = "confirmSignature" blockKey = "block" plasmaBlockNumKey = "plasmaBlockNum" plasmaToTmKey = "plasmatotm" ) -func NewPlasmaStore(ctxKey sdk.StoreKey) PlasmaStore { - return PlasmaStore{ +func NewBlockStore(ctxKey sdk.StoreKey) BlockStore { + return BlockStore{ kvStore: NewKVStore(ctxKey), } } -func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (plasma.Block, bool) { +func (store BlockStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (plasma.Block, bool) { key := prefixKey(blockKey, blockHeight.Bytes()) data := store.Get(ctx, key) if data == nil { @@ -35,14 +34,14 @@ func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (plasma block := plasma.Block{} if err := rlp.DecodeBytes(data, &block); err != nil { - panic(fmt.Sprintf("plasma store corrupted: %s", err)) + panic(fmt.Sprintf("block store corrupted: %s", err)) } return block, true } // StoreBlock will store the plasma block and return the plasma block number in which it was stored under -func (store PlasmaStore) StoreBlock(ctx sdk.Context, tmBlockHeight *big.Int, block plasma.Block) *big.Int { +func (store BlockStore) StoreBlock(ctx sdk.Context, tmBlockHeight *big.Int, block plasma.Block) *big.Int { plasmaBlockNum := store.NextPlasmaBlockNum(ctx) plasmaBlockKey := prefixKey(blockKey, plasmaBlockNum.Bytes()) @@ -63,19 +62,7 @@ func (store PlasmaStore) StoreBlock(ctx sdk.Context, tmBlockHeight *big.Int, blo return plasmaBlockNum } -func (store PlasmaStore) StoreConfirmSignatures(ctx sdk.Context, position plasma.Position, confirmSignatures [][65]byte) { - key := prefixKey(confirmSigKey, position.Bytes()) - - var sigs []byte - sigs = append(sigs, confirmSignatures[0][:]...) - if len(confirmSignatures) == 2 { - sigs = append(sigs, confirmSignatures[1][:]...) - } - - store.Set(ctx, key, sigs) -} - -func (store PlasmaStore) NextPlasmaBlockNum(ctx sdk.Context) *big.Int { +func (store BlockStore) NextPlasmaBlockNum(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) if data == nil { @@ -90,7 +77,7 @@ func (store PlasmaStore) NextPlasmaBlockNum(ctx sdk.Context) *big.Int { return plasmaBlockNum } -func (store PlasmaStore) CurrentPlasmaBlockNum(ctx sdk.Context) *big.Int { +func (store BlockStore) CurrentPlasmaBlockNum(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) if data == nil { diff --git a/store/blockStore_test.go b/store/blockStore_test.go new file mode 100644 index 0000000..72440ea --- /dev/null +++ b/store/blockStore_test.go @@ -0,0 +1 @@ +package store diff --git a/store/depositStore.go b/store/depositStore.go new file mode 100644 index 0000000..c0bc32f --- /dev/null +++ b/store/depositStore.go @@ -0,0 +1,69 @@ +package store + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/rlp" + "math/big" +) + +type Deposit struct { + Output plasma.Output + Spent bool + Spender [32]byte +} + +/* Deposit Store */ +type DepositStore struct { + kvStore +} + +func NewDepositStore(ctxKey sdk.StoreKey) DepositStore { + return DepositStore{ + kvStore: NewKVStore(ctxKey), + } +} + +func (store DepositStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, bool) { + data := store.Get(ctx, nonce.Bytes()) + if data == nil { + return Deposit{}, false + } + + var deposit Deposit + if err := rlp.DecodeBytes(data, &deposit); err != nil { + panic(fmt.Sprintf("deposit store corrupted: %s", err)) + } + + return deposit, true +} + +func (store DepositStore) HasDeposit(ctx sdk.Context, nonce *big.Int) bool { + return store.Has(ctx, nonce.Bytes()) +} + +func (store DepositStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { + data, err := rlp.EncodeToBytes(&deposit) + if err != nil { + panic(fmt.Sprintf("error marshaling deposit with nonce %s: %s", nonce, err)) + } + + store.Set(ctx, nonce.Bytes(), data) +} + +func (store DepositStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender [32]byte) sdk.Result { + deposit, ok := store.GetDeposit(ctx, nonce) + if !ok { + return sdk.ErrUnknownRequest(fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() + } else if deposit.Spent { + return sdk.ErrUnknownRequest(fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() + } + + deposit.Spent = true + deposit.Spender = spender + + store.StoreDeposit(ctx, nonce, deposit) + + return sdk.Result{} +} diff --git a/store/depositStore_test.go b/store/depositStore_test.go new file mode 100644 index 0000000..72440ea --- /dev/null +++ b/store/depositStore_test.go @@ -0,0 +1 @@ +package store diff --git a/store/txStore.go b/store/txStore.go new file mode 100644 index 0000000..183f250 --- /dev/null +++ b/store/txStore.go @@ -0,0 +1,147 @@ +package store + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + accountKey = "acc" + hashKey = "hash" + positionKey = "pos" +) + +/* Wrap plasma transaction with spend information */ +type Transaction struct { + Transaction plasma.Transaction + Spent []bool + Spenders [][32]byte +} + +/* Wrap plasma output with spend information */ +type Output struct { + Output plasma.Output + Spent bool + Spender [32]byte +} + +/* Transaction Store */ +type TxStore struct { + kvStore +} + +func NewTxStore(ctxKey sdk.StoreKey) TxStore { + return TxStore{ + kvStore: NewKVStore(ctxKey), + } +} + +func (store TxStore) GetAccount(ctx sdk.Context, addr common.Address) (Account, bool) { + key := prefixKey(accountKey, addr.Bytes()) + data := store.Get(ctx, key) + if data == nil { + return Account{}, false + } + + var acc Account + if err := rlp.DecodeBytes(data, &acc); err != nil { + panic(fmt.Sprintf("transaction store corrupted: %s", err)) + } + + return acc, true +} + +func (store TxStore) GetTx(ctx sdk.Context, hash [32]byte) (Transaction, bool) { + key := prefixKey(hashKey, hash[:]) + data := store.Get(ctx, key) + if data == nil { + return Transaction{}, false + } + + var tx Transaction + if err := rlp.DecodeBytes(data, &tx); err != nil { + panic(fmt.Sprintf("transaction store corrupted: %s", err)) + } + + return tx, true +} + +// Return the output at the specified position +// along with if it has been spent and what transaction spent it +func (store TxStore) GetUTXO(ctx sdk.Context, pos plasma.Position) (Output, bool) { + key := prefixKey(positionKey, pos.Bytes()) + data := store.Get(ctx, key) + var hash [32]byte + copy(data[:32], hash[:]) + + tx, ok := store.GetTx(ctx, hash) + if !ok { + return Output{}, ok + } + + output := Output{ + Output: tx.Transaction.OutputAt(pos.OutputIndex), + Spent: tx.Spent[pos.OutputIndex], + Spender: tx.Spenders[pos.OutputIndex], + } + + return output, ok +} + +func (store TxStore) HasTx(ctx sdk.Context, hash [32]byte) bool { + key := prefixKey(hashKey, hash[:]) + return store.Has(ctx, key) +} + +func (store TxStore) HasAccount(ctx sdk.Context, addr common.Address) bool { + key := prefixKey(accountKey, addr.Bytes()) + return store.Has(ctx, key) +} + +func (store TxStore) HasUTXO(ctx sdk.Context, pos plasma.Position) bool { + key := prefixKey(positionKey, pos.Bytes()) + data := store.Get(ctx, key) + var hash [32]byte + copy(data[:32], hash[:]) + + return store.HasTx(ctx, hash) +} + +func (store TxStore) StoreTx(ctx sdk.Context, tx Transaction) { + data, err := rlp.EncodeToBytes(&tx) + if err != nil { + panic(fmt.Sprintf("error marshaling transaction: %s", err)) + } + + key := prefixKey(hashKey, tx.Transaction.TxHash()) + store.Set(ctx, key, data) +} + +func (store TxStore) StoreUTXO(ctx sdk.Context, pos plasma.Position, hash [32]byte) { + data, err := rlp.EncodeToBytes(&pos) + if err != nil { + panic(fmt.Sprintf("error marshaling position %s: %s", pos, err)) + } + + key := prefixKey(positionKey, pos.Bytes()) + store.Set(ctx, key, data) +} + +func (store TxStore) SpendUTXO(ctx sdk.Context, hash [32]byte, outputIndex int, spenderKey [32]byte) sdk.Result { + tx, ok := store.GetTx(ctx, hash) + if !ok { + return sdk.ErrUnknownRequest(fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", outputIndex, hash)).Result() + } else if tx.Spent[outputIndex] { + return sdk.ErrUnauthorized(fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", outputIndex, hash)).Result() + } + + tx.Spent[outputIndex] = true + tx.Spenders[outputIndex] = spenderKey + + store.StoreTx(ctx, tx) + + return sdk.Result{} +} diff --git a/store/utxoStore_test.go b/store/txStore_test.go similarity index 100% rename from store/utxoStore_test.go rename to store/txStore_test.go diff --git a/store/utils.go b/store/utils.go index 32d2f74..10652bc 100644 --- a/store/utils.go +++ b/store/utils.go @@ -2,8 +2,6 @@ package store import ( "bytes" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - ethcmn "github.com/ethereum/go-ethereum/common" ) const ( @@ -18,11 +16,3 @@ func prefixKey(prefix string, key []byte) []byte { return buffer.Bytes() } - -func GetUTXOStoreKey(addr ethcmn.Address, pos plasma.Position) []byte { - return append(addr.Bytes(), pos.Bytes()...) -} - -func GetStoreKey(utxo UTXO) []byte { - return GetUTXOStoreKey(utxo.Output.Owner, utxo.Position) -} diff --git a/store/utxoStore.go b/store/utxoStore.go deleted file mode 100644 index 35ee30c..0000000 --- a/store/utxoStore.go +++ /dev/null @@ -1,178 +0,0 @@ -package store - -import ( - "fmt" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" - "io" -) - -type UTXO struct { - InputKeys [][]byte // keys to retrieve the inputs of this output - SpenderKeys [][]byte // keys to retrieve the spenders of this output - ConfirmationHash []byte // confirmation hash of the input transaction - MerkleHash []byte // merkle hash of the input transaction - - Output plasma.Output - Spent bool - Position plasma.Position -} - -type utxo struct { - InputKeys [][]byte - SpenderKeys [][]byte - ConfirmationHash []byte - MerkleHash []byte - Output []byte - Spent bool - Position []byte -} - -func (u *UTXO) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &utxo{ - u.InputKeys, - u.SpenderKeys, - u.ConfirmationHash, - u.MerkleHash, - u.Output.Bytes(), - u.Spent, - u.Position.Bytes(), - }) -} - -func (u *UTXO) DecodeRLP(s *rlp.Stream) error { - utxo := utxo{} - if err := s.Decode(&utxo); err != nil { - return err - } - if err := rlp.DecodeBytes(utxo.Output, &u.Output); err != nil { - return err - } - if err := rlp.DecodeBytes(utxo.Position, &u.Position); err != nil { - return err - } - - u.InputKeys = utxo.InputKeys - u.SpenderKeys = utxo.SpenderKeys - u.ConfirmationHash = utxo.ConfirmationHash - u.MerkleHash = utxo.MerkleHash - u.Spent = utxo.Spent - - return nil -} - -/* UTXO helper functions */ - -func (u UTXO) InputAddresses() []common.Address { - var result []common.Address - for _, key := range u.InputKeys { - addr := key[:common.AddressLength] - result = append(result, common.BytesToAddress(addr[:])) - } - - return result -} - -func (u UTXO) InputPositions() []plasma.Position { - var result []plasma.Position - for _, key := range u.InputKeys { - bytes := key[common.AddressLength:] - pos := plasma.Position{} - if err := rlp.DecodeBytes(bytes, &pos); err != nil { - panic(fmt.Errorf("utxo store corrupted %s", err)) - } - - result = append(result, pos) - } - - return result -} - -func (u UTXO) SpenderAddresses() []common.Address { - var result []common.Address - for _, key := range u.SpenderKeys { - addr := key[:common.AddressLength] - result = append(result, common.BytesToAddress(addr[:])) - } - - return result -} - -func (u UTXO) SpenderPositions() []plasma.Position { - var result []plasma.Position - for _, key := range u.SpenderKeys { - bytes := key[common.AddressLength:] - pos := plasma.Position{} - if err := rlp.DecodeBytes(bytes, &pos); err != nil { - panic(fmt.Errorf("utxo store corrupted %s", err)) - } - - result = append(result, pos) - } - - return result -} - -/* Store */ - -type UTXOStore struct { - kvStore -} - -func NewUTXOStore(ctxKey sdk.StoreKey) UTXOStore { - return UTXOStore{ - kvStore: NewKVStore(ctxKey), - } -} - -func (store UTXOStore) GetUTXOWithKey(ctx sdk.Context, key []byte) (UTXO, bool) { - data := store.Get(ctx, key) - if data == nil { - return UTXO{}, false - } - - var utxo UTXO - if err := rlp.DecodeBytes(data, &utxo); err != nil { - panic(fmt.Sprintf("utxo store corrupted: %s", err)) - } - - return utxo, true -} - -func (store UTXOStore) GetUTXO(ctx sdk.Context, addr common.Address, pos plasma.Position) (UTXO, bool) { - key := GetUTXOStoreKey(addr, pos) - return store.GetUTXOWithKey(ctx, key) -} - -func (store UTXOStore) HasUTXO(ctx sdk.Context, addr common.Address, pos plasma.Position) bool { - key := GetUTXOStoreKey(addr, pos) - return store.Has(ctx, key) -} - -func (store UTXOStore) StoreUTXO(ctx sdk.Context, utxo UTXO) { - key := GetStoreKey(utxo) - data, err := rlp.EncodeToBytes(&utxo) - if err != nil { - panic(fmt.Sprintf("Error marshaling utxo: %s", err)) - } - - store.Set(ctx, key, data) -} - -func (store UTXOStore) SpendUTXO(ctx sdk.Context, addr common.Address, pos plasma.Position, spenderKeys [][]byte) sdk.Result { - utxo, ok := store.GetUTXO(ctx, addr, pos) - if !ok { - return sdk.ErrUnknownRequest(fmt.Sprintf("output with address 0x%x and position %v does not exist", addr, pos)).Result() - } else if utxo.Spent { - return sdk.ErrUnauthorized(fmt.Sprintf("output with address 0x%x and position %v is already spent", addr, pos)).Result() - } - - utxo.Spent = true - utxo.SpenderKeys = spenderKeys - - store.StoreUTXO(ctx, utxo) - - return sdk.Result{} -} From 1736b1ec2a7a56ebed0d8facb7678987441155ae Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Thu, 9 May 2019 17:59:36 -0700 Subject: [PATCH 05/49] block querier. store utxo already satisfies rlp interface --- client/plasmacli/query/account.go | 4 --- client/plasmacli/query/block.go | 29 +++++++++----------- client/plasmacli/query/info.go | 4 --- query/block.go | 6 ----- query/querier.go | 19 +++++++++++++ server/app/app.go | 2 +- store/plasmaStore.go | 40 +++++++++++++++++++++------- store/plasmaStore_test.go | 31 ++++++++++++++++++++++ store/utxoStore.go | 44 ------------------------------- 9 files changed, 94 insertions(+), 85 deletions(-) delete mode 100644 query/block.go create mode 100644 store/plasmaStore_test.go diff --git a/client/plasmacli/query/account.go b/client/plasmacli/query/account.go index 19aaf73..01432d1 100644 --- a/client/plasmacli/query/account.go +++ b/client/plasmacli/query/account.go @@ -1,7 +1,6 @@ package query import ( - "bytes" "encoding/json" "fmt" ks "github.com/FourthState/plasma-mvp-sidechain/client/store" @@ -37,9 +36,6 @@ var balanceCmd = &cobra.Command{ var resp query.BalanceResp if err := json.Unmarshal(data, &resp); err != nil { return err - } else if !bytes.Equal(resp.Address[:], addr[:]) { - return fmt.Errorf("Mismatch in Account and Response Address.\nAccount: 0x%x\n Response: 0x%x\n", - addr, resp.Address) } fmt.Printf("Address: %0x\n", resp.Address) diff --git a/client/plasmacli/query/block.go b/client/plasmacli/query/block.go index dcdca85..c2d1c5a 100644 --- a/client/plasmacli/query/block.go +++ b/client/plasmacli/query/block.go @@ -1,12 +1,11 @@ package query import ( + "encoding/json" "fmt" - "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/query" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/ethereum/go-ethereum/rlp" "github.com/spf13/cobra" - "math/big" "strings" ) @@ -20,28 +19,24 @@ var blockCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.NewCLIContext().WithTrustNode(true) - blockNum, ok := new(big.Int).SetString(strings.TrimSpace(args[0]), 10) - if !ok { - return fmt.Errorf("block number must be provided in decimal format") - } + num := strings.TrimSpace(args[0]) - key := append([]byte("block::"), blockNum.Bytes()...) - data, err := ctx.QueryStore(key, "plasma") + queryPath := fmt.Sprintf("custom/plasma/block/%s", num) + data, err := ctx.Query(queryPath, nil) if err != nil { - fmt.Println("error querying store") return err } - if data == nil { - return fmt.Errorf("plasma block does not exist") - } - block := plasma.Block{} - if err := rlp.DecodeBytes(data, &block); err != nil { - fmt.Println("error decoding") + fmt.Println(string(data), "\n") + + var resp query.BlockResp + if err := json.Unmarshal(data, &resp); err != nil { return err } - fmt.Printf("Header: %x\nTxs: %d\nFee: %d\n", block.Header, block.TxnCount, block.FeeAmount) + fmt.Printf("Block Header: 0x%x\n", resp.Header) + fmt.Printf("Transaction Count: %d, FeeAmount: %d\n", resp.TxnCount, resp.FeeAmount) + fmt.Printf("Tendermint BlockHeight: %d", resp.TMBlockHeight) return nil }, } diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index da0d356..0bdc8ef 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -1,7 +1,6 @@ package query import ( - "bytes" "encoding/json" "fmt" "github.com/FourthState/plasma-mvp-sidechain/query" @@ -39,9 +38,6 @@ var infoCmd = &cobra.Command{ var resp query.InfoResp if err := json.Unmarshal(data, &resp); err != nil { return err - } else if !bytes.Equal(resp.Address[:], addr[:]) { - return fmt.Errorf("Mismatch in Account and Response Address.\nAccount: 0x%x\n Response: 0x%x\n", - addr, resp.Address) } for i, utxo := range resp.Utxos { diff --git a/query/block.go b/query/block.go deleted file mode 100644 index ae0d604..0000000 --- a/query/block.go +++ /dev/null @@ -1,6 +0,0 @@ -package query - -import ( -//"github.com/FourthState/plasma-mvp-sidechain/store" -//sdk "github.com/cosmos/cosmos-sdk/types" -) diff --git a/query/querier.go b/query/querier.go index 39645a2..41315c8 100644 --- a/query/querier.go +++ b/query/querier.go @@ -71,6 +71,8 @@ const ( QueryBlock = "block" ) +type BlockResp = store.Block + func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { @@ -78,6 +80,23 @@ func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { } switch path[0] { + case QueryBlock: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("block query follows /plasma/block/") + } + blockNum, ok := new(big.Int).SetString(path[1], 10) + if !ok { + return nil, sdk.ErrUnknownRequest("block number must be provided in deicmal format") + } + block, ok := plasmaStore.GetBlock(ctx, blockNum) + if !ok { + return nil, sdk.ErrUnknownRequest("nonexistent plasma block") + } + data, err := json.Marshal(block) + if err != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil default: return nil, sdk.ErrUnknownRequest("unregistered endpoint") } diff --git a/server/app/app.go b/server/app/app.go index 94e7115..ecb171d 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -159,7 +159,7 @@ func (app *PlasmaMVPChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) return abci.ResponseEndBlock{} } - tmBlockHeight := big.NewInt(ctx.BlockHeight()) + tmBlockHeight := uint64(ctx.BlockHeight()) var header [32]byte copy(header[:], ctx.BlockHeader().DataHash) diff --git a/store/plasmaStore.go b/store/plasmaStore.go index 8567420..764c869 100644 --- a/store/plasmaStore.go +++ b/store/plasmaStore.go @@ -6,6 +6,7 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/rlp" + "io" "math/big" ) @@ -13,11 +14,35 @@ type PlasmaStore struct { kvStore } +type Block struct { + plasma.Block + TMBlockHeight uint64 +} + +type block struct { + PlasmaBlock plasma.Block + TMBlockHeight uint64 +} + +func (b *Block) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &block{b.Block, b.TMBlockHeight}) +} + +func (b *Block) DecodeRLP(s *rlp.Stream) error { + var block block + if err := s.Decode(&block); err != nil { + return err + } + + b.Block = block.PlasmaBlock + b.TMBlockHeight = block.TMBlockHeight + return nil +} + const ( confirmSigKey = "confirmSignature" blockKey = "block" plasmaBlockNumKey = "plasmaBlockNum" - plasmaToTmKey = "plasmatotm" ) func NewPlasmaStore(ctxKey sdk.StoreKey) PlasmaStore { @@ -26,14 +51,14 @@ func NewPlasmaStore(ctxKey sdk.StoreKey) PlasmaStore { } } -func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (plasma.Block, bool) { +func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, bool) { key := prefixKey(blockKey, blockHeight.Bytes()) data := store.Get(ctx, key) if data == nil { - return plasma.Block{}, false + return Block{}, false } - block := plasma.Block{} + block := Block{} if err := rlp.DecodeBytes(data, &block); err != nil { panic(fmt.Sprintf("plasma store corrupted: %s", err)) } @@ -42,11 +67,11 @@ func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (plasma } // StoreBlock will store the plasma block and return the plasma block number in which it was stored under -func (store PlasmaStore) StoreBlock(ctx sdk.Context, tmBlockHeight *big.Int, block plasma.Block) *big.Int { +func (store PlasmaStore) StoreBlock(ctx sdk.Context, tmBlockHeight uint64, block plasma.Block) *big.Int { plasmaBlockNum := store.NextPlasmaBlockNum(ctx) plasmaBlockKey := prefixKey(blockKey, plasmaBlockNum.Bytes()) - plasmaBlockData, err := rlp.EncodeToBytes(&block) + plasmaBlockData, err := rlp.EncodeToBytes(&Block{block, tmBlockHeight}) if err != nil { panic(fmt.Sprintf("error rlp encoding block: %s", err)) } @@ -57,9 +82,6 @@ func (store PlasmaStore) StoreBlock(ctx sdk.Context, tmBlockHeight *big.Int, blo // latest plasma block number store.Set(ctx, []byte(plasmaBlockNumKey), plasmaBlockNum.Bytes()) - // plasma block number => tendermint block numper - store.Set(ctx, prefixKey(plasmaToTmKey, plasmaBlockNum.Bytes()), tmBlockHeight.Bytes()) - return plasmaBlockNum } diff --git a/store/plasmaStore_test.go b/store/plasmaStore_test.go new file mode 100644 index 0000000..421b707 --- /dev/null +++ b/store/plasmaStore_test.go @@ -0,0 +1,31 @@ +package store + +import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + "reflect" + "testing" +) + +func TestBlockSerialization(t *testing.T) { + plasmaBlock := plasma.Block{} + plasmaBlock.Header[0] = byte(10) + plasmaBlock.TxnCount = 3 + plasmaBlock.FeeAmount = utils.Big2 + + block := Block{ + Block: plasmaBlock, + TMBlockHeight: 2, + } + + bytes, err := rlp.EncodeToBytes(&block) + require.NoError(t, err) + + recoveredBlock := Block{} + err = rlp.DecodeBytes(bytes, &recoveredBlock) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(block, recoveredBlock), "mismatch in serialized and deserialized block") +} diff --git a/store/utxoStore.go b/store/utxoStore.go index 35ee30c..136acd6 100644 --- a/store/utxoStore.go +++ b/store/utxoStore.go @@ -6,7 +6,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - "io" ) type UTXO struct { @@ -20,49 +19,6 @@ type UTXO struct { Position plasma.Position } -type utxo struct { - InputKeys [][]byte - SpenderKeys [][]byte - ConfirmationHash []byte - MerkleHash []byte - Output []byte - Spent bool - Position []byte -} - -func (u *UTXO) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &utxo{ - u.InputKeys, - u.SpenderKeys, - u.ConfirmationHash, - u.MerkleHash, - u.Output.Bytes(), - u.Spent, - u.Position.Bytes(), - }) -} - -func (u *UTXO) DecodeRLP(s *rlp.Stream) error { - utxo := utxo{} - if err := s.Decode(&utxo); err != nil { - return err - } - if err := rlp.DecodeBytes(utxo.Output, &u.Output); err != nil { - return err - } - if err := rlp.DecodeBytes(utxo.Position, &u.Position); err != nil { - return err - } - - u.InputKeys = utxo.InputKeys - u.SpenderKeys = utxo.SpenderKeys - u.ConfirmationHash = utxo.ConfirmationHash - u.MerkleHash = utxo.MerkleHash - u.Spent = utxo.Spent - - return nil -} - /* UTXO helper functions */ func (u UTXO) InputAddresses() []common.Address { From 960027be2fd4a614fac7167ba26c9978ca76703a Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 9 May 2019 18:44:22 -0700 Subject: [PATCH 06/49] adjusting inputs outputs to be variable, updated app --- handlers/anteHandler.go | 41 +++++++++++++-------------------- plasma/transaction.go | 17 +++++++++----- server/app/app.go | 50 ++++++++++++++++++----------------------- store/txStore.go | 7 +++--- store/txStore_test.go | 33 +++++++++++++++------------ 5 files changed, 72 insertions(+), 76 deletions(-) diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index 234bd28..eec6bb1 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -20,23 +20,23 @@ type plasmaConn interface { HasTxBeenExited(*big.Int, plasma.Position) bool } -func NewAnteHandler(utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, client plasmaConn) sdk.AnteHandler { +func NewAnteHandler(txStore store.TxStore, depositStore store.DepositStore, blockStore store.BlockStore, client plasmaConn) sdk.AnteHandler { return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { msg := tx.GetMsgs()[0] // tx should only have one msg switch mtype := msg.Type(); mtype { case "include_deposit": depositMsg := msg.(msgs.IncludeDepositMsg) - return includeDepositAnteHandler(ctx, utxoStore, plasmaStore, depositMsg, client) + return includeDepositAnteHandler(ctx, depositStore, blockStore, depositMsg, client) case "spend_utxo": spendMsg := msg.(msgs.SpendMsg) - return spendMsgAnteHandler(ctx, spendMsg, utxoStore, plasmaStore, client) + return spendMsgAnteHandler(ctx, spendMsg, txStore, depositStore, blockStore, client) default: - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "Msg is not of type SpendMsg or IncludeDepositMsg").Result(), true + return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "msg is not of type SpendMsg or IncludeDepositMsg").Result(), true } } } -func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { +func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, txStore store.TxStore, depositStore store.DepositStore, blockStore store.BlockStore, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { var totalInputAmt, totalOutputAmt *big.Int // attempt to recover signers @@ -46,7 +46,7 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, utxoStore stor } /* validate the first input */ - amt, res := validateInput(ctx, spendMsg.Input0, common.BytesToAddress(signers[0]), utxoStore, plasmaStore, client) + amt, res := validateInput(ctx, spendMsg.Input0, common.BytesToAddress(signers[0]), txStore, blockStore, client) if !res.IsOK() { return ctx, res, true } @@ -58,26 +58,16 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, utxoStore stor totalInputAmt = amt - // store confirm signatures - if !spendMsg.Input0.Position.IsDeposit() && spendMsg.Input0.TxIndex < 1<<16-1 { - plasmaStore.StoreConfirmSignatures(ctx, spendMsg.Input0.Position, spendMsg.Input0.ConfirmSignatures) - } - /* validate second input if applicable */ if spendMsg.HasSecondInput() { if len(signers) != 2 { return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "second signature not present").Result(), true } - amt, res = validateInput(ctx, spendMsg.Input1, common.BytesToAddress(signers[1]), utxoStore, plasmaStore, client) + amt, res = validateInput(ctx, spendMsg.Input1, common.BytesToAddress(signers[1]), txStore, blockStore, client) if !res.IsOK() { return ctx, res, true } - // store confirm signature - if !spendMsg.Input1.Position.IsDeposit() && spendMsg.Input1.TxIndex < 1<<16-1 { - plasmaStore.StoreConfirmSignatures(ctx, spendMsg.Input1.Position, spendMsg.Input1.ConfirmSignatures) - } - totalInputAmt = totalInputAmt.Add(totalInputAmt, amt) } @@ -97,7 +87,7 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, utxoStore stor } // validates the inputs against the utxo store and returns the amount of the respective input -func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, client plasmaConn) (*big.Int, sdk.Result) { +func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, txStore store.TxStore, blockStore store.BlockStore, client plasmaConn) (*big.Int, sdk.Result) { var amt *big.Int // inputUTXO must be owned by the signer due to the prefix so we do not need to @@ -135,12 +125,12 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, u } // validates the input's confirm signatures -func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputUTXO store.UTXO) sdk.Result { - if len(inputUTXO.InputKeys) != len(input.ConfirmSignatures) { +func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx store.Transaction) sdk.Result { + if len(input.ConfirmSignatures) > 0 && (inputTx.Transaction.HasSecondInput) { return msgs.ErrInvalidTransaction(DefaultCodespace, "incorrect number of confirm signatures").Result() } - confirmationHash := utils.ToEthSignedMessageHash(inputUTXO.ConfirmationHash[:])[:] + confirmationHash := utils.ToEthSignedMessageHash(inputTx.ConfirmationHash[:])[:] for i, key := range inputUTXO.InputKeys { address := key[:common.AddressLength] @@ -154,19 +144,18 @@ func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputUTXO st return sdk.Result{} } -func includeDepositAnteHandler(ctx sdk.Context, utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, msg msgs.IncludeDepositMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { - depositPosition := plasma.NewPosition(big.NewInt(0), 0, 0, msg.DepositNonce) - if utxoStore.HasUTXO(ctx, msg.Owner, depositPosition) { +func includeDepositAnteHandler(ctx sdk.Context, depositStore store.DepositStore, blockStore store.BlockStore, msg msgs.IncludeDepositMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { + if depositStore.HasDeposit(ctx, msg.Owner, msg.DepositNonce) { return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, already exists in store", msg.DepositNonce.String()).Result(), true } - _, threshold, ok := client.GetDeposit(plasmaStore.CurrentPlasmaBlockNum(ctx), msg.DepositNonce) + _, threshold, ok := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), msg.DepositNonce) if !ok && threshold == nil { return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, does not exist.", msg.DepositNonce.String()).Result(), true } if !ok { return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has not finalized yet. Please wait at least %d blocks before resubmitting", msg.DepositNonce.String(), threshold.Int64()).Result(), true } - exited := client.HasTxBeenExited(plasmaStore.CurrentPlasmaBlockNum(ctx), depositPosition) + exited := client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), depositPosition) if exited { return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has already exitted from rootchain", msg.DepositNonce.String()).Result(), true } diff --git a/plasma/transaction.go b/plasma/transaction.go index 90c9c05..aaf5e3d 100644 --- a/plasma/transaction.go +++ b/plasma/transaction.go @@ -14,10 +14,8 @@ import ( // Transaction represents a spend of inputs. Fields should not be accessed directly type Transaction struct { - Input0 Input - Input1 Input - Output0 Output - Output1 Output + Inputs []Input + Outputs []Output Fee *big.Int } @@ -46,7 +44,7 @@ type rawTx struct { // EncodeRLP satisfies the rlp interface for Transaction func (tx *Transaction) EncodeRLP(w io.Writer) error { - t := &rawTx{tx.toTxList(), [2][65]byte{tx.Input0.Signature, tx.Input1.Signature}} + t := &rawTx{tx.toTxList(), tx.Sigs()} return rlp.Encode(w, t) } @@ -120,6 +118,15 @@ func (tx Transaction) MerkleHash() []byte { return hash[:] } +// Sigs returns the signatures that signed over this transaction +// These signatures were generated by the inputs +func (tx Transaction) Sigs() (sigs [2][65]byte) { + for i, input := range tx.Inputs { + sigs[i] = input.Signature + } + return sigs +} + // HasSecondInput is an indicator for the existence of a second input func (tx Transaction) HasSecondInput() bool { return !tx.Input1.Position.IsNilPosition() diff --git a/server/app/app.go b/server/app/app.go index 56b4ec5..ede3470 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -37,8 +37,9 @@ type PlasmaMVPChain struct { feeAmount *big.Int // persistent stores - utxoStore store.UTXOStore - plasmaStore store.PlasmaStore + txStore store.TxStore + depositStore store.DepositStore + blockStore store.BlockStore // smart contract connection ethConnection *eth.Plasma @@ -58,18 +59,22 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio cdc := MakeCodec() baseApp.SetCommitMultiStoreTracer(traceStore) - utxoStoreKey := sdk.NewKVStoreKey("utxo") - utxoStore := store.NewUTXOStore(utxoStoreKey) - plasmaStoreKey := sdk.NewKVStoreKey("plasma") - plasmaStore := store.NewPlasmaStore(plasmaStoreKey) + txStoreKey := sdk.NewKVStoreKey("transaction") + txStore := store.NewUTXOStore(txStoreKey) + depositStoreKey := sdk.NewKVStoreKey("deposit") + depositStore := store.NewDepositStore(depositStoreKey) + blockStoreKey := sdk.NewKVStoreKey("block") + blockStore := store.NewPlasmaStore(blockStoreKey) + app := &PlasmaMVPChain{ BaseApp: baseApp, cdc: cdc, txIndex: 0, feeAmount: big.NewInt(0), // we do not use `utils.BigZero` because the feeAmount is going to be updated - utxoStore: utxoStore, - plasmaStore: plasmaStore, + blockStore: plasmaStore, + depositStore: depositStore, + txStore: txStore, } // set configs @@ -108,16 +113,16 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio app.feeAmount = app.feeAmount.Add(app.feeAmount, amt) return nil } - app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.utxoStore, app.plasmaStore, nextTxIndex, feeUpdater)) - app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.utxoStore, app.plasmaStore, nextTxIndex, plasmaClient)) + app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.txStore, app.depositStore, app.blockStore, nextTxIndex, feeUpdater)) + app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.depositStore, app.blockStore, nextTxIndex, plasmaClient)) // custom queriers app.QueryRouter(). - AddRoute("utxo", query.NewUtxoQuerier(utxoStore)). - AddRoute("plasma", query.NewPlasmaQuerier(plasmaStore)) + AddRoute("tx", query.NewUtxoQuerier(utxoStore)). + AddRoute("block", query.NewPlasmaQuerier(plasmaStore)) // Set the AnteHandler - app.SetAnteHandler(handlers.NewAnteHandler(app.utxoStore, app.plasmaStore, plasmaClient)) + app.SetAnteHandler(handlers.NewAnteHandler(app.txStore, app.depositStore, app.blockStore, plasmaClient)) // set the rest of the chain flow app.SetEndBlocker(app.endBlocker) @@ -125,7 +130,7 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio // mount and load stores // IAVL store used by default. `fauxMerkleMode` defaults to false - app.MountStores(utxoStoreKey, plasmaStoreKey) + app.MountStores(txStoreKey, depositStore, blockStoreKey) if err := app.LoadLatestVersion(utxoStoreKey); err != nil { fmt.Println(err) os.Exit(1) @@ -155,7 +160,7 @@ func (app *PlasmaMVPChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) // skip if the block is empty if app.txIndex == 0 { // try to commit any headers in the store - app.ethConnection.CommitPlasmaHeaders(ctx, app.plasmaStore) + app.ethConnection.CommitPlasmaHeaders(ctx, app.blockStore) return abci.ResponseEndBlock{} } @@ -164,19 +169,8 @@ func (app *PlasmaMVPChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) var header [32]byte copy(header[:], ctx.BlockHeader().DataHash) block := plasma.NewBlock(header, app.txIndex, app.feeAmount) - plasmaBlockNum := app.plasmaStore.StoreBlock(ctx, tmBlockHeight, block) - app.ethConnection.CommitPlasmaHeaders(ctx, app.plasmaStore) - - if app.feeAmount.Sign() != 0 { - // add a utxo in the store with position 2^16-1 - utxo := store.UTXO{ - Position: plasma.NewPosition(plasmaBlockNum, 1<<16-1, 0, nil), - Output: plasma.NewOutput(app.operatorAddress, app.feeAmount), - Spent: false, - } - - app.utxoStore.StoreUTXO(ctx, utxo) - } + plasmaBlockNum := app.blockStore.StoreBlock(ctx, tmBlockHeight, block) + app.ethConnection.CommitPlasmaHeaders(ctx, app.blockStore) app.txIndex = 0 app.feeAmount = big.NewInt(0) diff --git a/store/txStore.go b/store/txStore.go index 183f250..0d4d2f8 100644 --- a/store/txStore.go +++ b/store/txStore.go @@ -16,9 +16,10 @@ const ( /* Wrap plasma transaction with spend information */ type Transaction struct { - Transaction plasma.Transaction - Spent []bool - Spenders [][32]byte + Transaction plasma.Transaction + ConfirmationHash []byte + Spent []bool + Spenders [][32]byte } /* Wrap plasma output with spend information */ diff --git a/store/txStore_test.go b/store/txStore_test.go index feabaff..2053870 100644 --- a/store/txStore_test.go +++ b/store/txStore_test.go @@ -10,27 +10,32 @@ import ( "testing" ) -func TestUTXOSerialization(t *testing.T) { - keys := [][]byte{[]byte("hamdi")} +func TestTxSerialization(t *testing.T) { hashes := []byte("allam") + var sigs [65]byte + // no default attributes - utxo := UTXO{ - InputKeys: keys, - SpenderKeys: keys, - ConfirmationHash: hashes, - MerkleHash: hashes, + transaction := plasma.Transaction{ + Input0: plasma.NewInput(plasma.NewPosition(utils.Big1, 15, 1, utils.Big0), sigs, [][65]byte{}), + Input1: plasma.NewInput(plasma.NewPosition(utils.Big0, 0, 0, utils.Big1), sigs, [][65]byte{}), + Output0: plasma.NewOutput(common.HexToAddress("1"), utils.Big1), + Output1: plasma.NewOutput(common.HexToAddress("2"), utils.Big2), + Fee: utils.Big1, + } - Output: plasma.NewOutput(common.HexToAddress("1"), utils.Big1), - Position: plasma.NewPosition(utils.Big1, 0, 0, utils.Big1), - Spent: true, + tx := Transaction{ + Transaction: transaction, + Spent: []bool{false, false}, + Spenders: [][32]byte{}, + ConfirmationHash: hashes, } - bytes, err := rlp.EncodeToBytes(&utxo) + bytes, err := rlp.EncodeToBytes(&tx) require.NoError(t, err) - recoveredUTXO := UTXO{} - err = rlp.DecodeBytes(bytes, &recoveredUTXO) + recoveredTx := Transaction{} + err = rlp.DecodeBytes(bytes, &recoveredTx) require.NoError(t, err) - require.True(t, reflect.DeepEqual(utxo, recoveredUTXO), "mismatch in serialized and deserialized utxos") + require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in serialized and deserialized transactions") } From ffc5ad1e61ad6cfc8bf22e3193b9232b7d5f1456 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Thu, 9 May 2019 19:17:22 -0700 Subject: [PATCH 07/49] file rename. remove resp objects --- .../plasmacli/query/{account.go => balance.go} | 9 ++++----- client/plasmacli/query/block.go | 4 +--- client/plasmacli/query/info.go | 10 +++++----- query/querier.go | 18 ++---------------- 4 files changed, 12 insertions(+), 29 deletions(-) rename client/plasmacli/query/{account.go => balance.go} (78%) diff --git a/client/plasmacli/query/account.go b/client/plasmacli/query/balance.go similarity index 78% rename from client/plasmacli/query/account.go rename to client/plasmacli/query/balance.go index 01432d1..1b06eb4 100644 --- a/client/plasmacli/query/account.go +++ b/client/plasmacli/query/balance.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" ks "github.com/FourthState/plasma-mvp-sidechain/client/store" - "github.com/FourthState/plasma-mvp-sidechain/query" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" "github.com/spf13/cobra" @@ -33,13 +32,13 @@ var balanceCmd = &cobra.Command{ return err } - var resp query.BalanceResp - if err := json.Unmarshal(data, &resp); err != nil { + var total string + if err := json.Unmarshal(data, &total); err != nil { return err } - fmt.Printf("Address: %0x\n", resp.Address) - fmt.Printf("Total: %s\n", resp.Total) + fmt.Printf("Address: %0x\n", addr) + fmt.Printf("Total: %s\n", total) return nil }, } diff --git a/client/plasmacli/query/block.go b/client/plasmacli/query/block.go index c2d1c5a..d1ba59d 100644 --- a/client/plasmacli/query/block.go +++ b/client/plasmacli/query/block.go @@ -27,8 +27,6 @@ var blockCmd = &cobra.Command{ return err } - fmt.Println(string(data), "\n") - var resp query.BlockResp if err := json.Unmarshal(data, &resp); err != nil { return err @@ -36,7 +34,7 @@ var blockCmd = &cobra.Command{ fmt.Printf("Block Header: 0x%x\n", resp.Header) fmt.Printf("Transaction Count: %d, FeeAmount: %d\n", resp.TxnCount, resp.FeeAmount) - fmt.Printf("Tendermint BlockHeight: %d", resp.TMBlockHeight) + fmt.Printf("Tendermint BlockHeight: %d\n", resp.TMBlockHeight) return nil }, } diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index 0bdc8ef..3a65406 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -3,7 +3,7 @@ package query import ( "encoding/json" "fmt" - "github.com/FourthState/plasma-mvp-sidechain/query" + "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" "github.com/ethereum/go-ethereum/common" @@ -35,12 +35,12 @@ var infoCmd = &cobra.Command{ return err } - var resp query.InfoResp - if err := json.Unmarshal(data, &resp); err != nil { + var utxos []store.UTXO + if err := json.Unmarshal(data, &utxos); err != nil { return err } - for i, utxo := range resp.Utxos { + for i, utxo := range utxos { fmt.Printf("UTXO %d\n", i) fmt.Printf("Position: %s, Amount: %s, Spent: %t\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent) @@ -61,7 +61,7 @@ var infoCmd = &cobra.Command{ fmt.Printf("End UTXO %d info\n\n", i) } - if len(resp.Utxos) == 0 { + if len(utxos) == 0 { fmt.Println("no information available for this address") } diff --git a/query/querier.go b/query/querier.go index 41315c8..71c1980 100644 --- a/query/querier.go +++ b/query/querier.go @@ -14,16 +14,6 @@ const ( QueryInfo = "info" ) -type BalanceResp struct { - Address common.Address - Total *big.Int -} - -type InfoResp struct { - Address common.Address - Utxos []store.UTXO -} - func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { @@ -40,11 +30,7 @@ func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { if err != nil { return nil, sdk.ErrInternal("failed query balance") } - data, err := json.Marshal(BalanceResp{addr, total}) - if err != nil { - return nil, sdk.ErrInternal("serialization error") - } - return data, nil + return []byte(total.String()), nil case QueryInfo: if len(path) != 2 { @@ -55,7 +41,7 @@ func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { if err != nil { return nil, sdk.ErrInternal("failed utxo retrieval") } - data, err := json.Marshal(InfoResp{addr, utxos}) + data, err := json.Marshal(utxos) if err != nil { return nil, sdk.ErrInternal("serialization error") } From 87501a1cfe65a7fcd46bae8e98b7f893c66a4e70 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 10 May 2019 11:58:04 -0700 Subject: [PATCH 08/49] refactored plasma/, updated tests --- plasma/transaction.go | 192 +++++++++++++++++-------------------- plasma/transaction_test.go | 58 ++++------- 2 files changed, 109 insertions(+), 141 deletions(-) diff --git a/plasma/transaction.go b/plasma/transaction.go index aaf5e3d..60c21a4 100644 --- a/plasma/transaction.go +++ b/plasma/transaction.go @@ -59,13 +59,13 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { confirmSigs0 := parseSig(t.Tx.Input0ConfirmSigs) confirmSigs1 := parseSig(t.Tx.Input1ConfirmSigs) - tx.Input0 = NewInput(NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum0[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex0[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex0[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce0[:]).Int64())), - t.Sigs[0], confirmSigs0) - tx.Input1 = NewInput(NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum1[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex1[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex1[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce1[:]).Int64())), - t.Sigs[1], confirmSigs1) + tx.Inputs = append(tx.Inputs, NewInput(NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum0[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex0[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex0[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce0[:]).Int64())), + t.Sigs[0], confirmSigs0)) + tx.Inputs = append(tx.Inputs, NewInput(NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum1[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex1[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex1[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce1[:]).Int64())), + t.Sigs[1], confirmSigs1)) // set signatures if applicable - tx.Output0 = NewOutput(t.Tx.NewOwner0, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount0[:]).Int64())) - tx.Output1 = NewOutput(t.Tx.NewOwner1, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount1[:]).Int64())) + tx.Outputs = append(tx.Outputs, NewOutput(t.Tx.NewOwner0, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount0[:]).Int64()))) + tx.Outputs = append(tx.Outputs, NewOutput(t.Tx.NewOwner1, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount1[:]).Int64()))) tx.Fee = big.NewInt(new(big.Int).SetBytes(t.Tx.Fee[:]).Int64()) return nil @@ -73,28 +73,29 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { func (tx Transaction) ValidateBasic() error { // validate inputs - if err := tx.Input0.ValidateBasic(); err != nil { - return fmt.Errorf("invalid first input { %s }", err) - } - if tx.Input0.Position.IsNilPosition() { - return fmt.Errorf("first input cannot be nil") - } - if err := tx.Input1.ValidateBasic(); err != nil { - return fmt.Errorf("invalid second input { %s }", err) + for i, input := range tx.Inputs { + if err := input.ValidateBasic(); err != nil { + return fmt.Errorf("invalid input with index %d: %s", i, err) + } + if input.Position.IsNilPosition() { + return fmt.Errorf("input position cannot be nil") + } } - if tx.Input0.Position.String() == tx.Input1.Position.String() { - return fmt.Errorf("same position cannot be spent twice") + + if len(tx.Inputs) > 1 { + if tx.Inputs[0].Position.String() == tx.Inputs[1].Position.String() { + return fmt.Errorf("same position cannot be spent twice") + } } // validate outputs - if err := tx.Output0.ValidateBasic(); err != nil { - return fmt.Errorf("invalid first output { %s }", err) - } - if utils.IsZeroAddress(tx.Output0.Owner) || tx.Output0.Amount.Sign() == 0 { - return fmt.Errorf("first output must have a valid address and non-zero amount") - } - if err := tx.Output1.ValidateBasic(); err != nil { - return fmt.Errorf("invalid second output { %s }", err) + for i, output := range tx.Outputs { + if err := output.ValidateBasic(); err != nil { + return fmt.Errorf("invalid output with index %d: %s", i, err) + } + if utils.IsZeroAddress(output.Owner) || output.Amount.Sign() == 0 { + return fmt.Errorf("output with index %d must have a valid address and non-zero amount", i) + } } return nil @@ -127,37 +128,15 @@ func (tx Transaction) Sigs() (sigs [2][65]byte) { return sigs } -// HasSecondInput is an indicator for the existence of a second input -func (tx Transaction) HasSecondInput() bool { - return !tx.Input1.Position.IsNilPosition() -} - -// HasSecondOutput is an indicator if the second output is used in this transaction -func (tx Transaction) HasSecondOutput() bool { - return !utils.IsZeroAddress(tx.Output1.Owner) -} - -// OutputAt is a selector for the outputs -func (tx Transaction) OutputAt(i uint8) Output { - if i == 0 { - return tx.Output0 +func (tx Transaction) String() (str string) { + for i, input := range tx.Inputs { + str += fmt.Sprintf("Input %d: %s\n", i, input) } - - return tx.Output1 -} - -// InputAt is a selector for the inputs -func (tx Transaction) InputAt(i uint8) Input { - if i == 0 { - return tx.Input0 + for i, output := range tx.Outputs { + str += fmt.Sprintf("Output %d: %s\n", i, output) } - - return tx.Input1 -} - -func (tx Transaction) String() string { - return fmt.Sprintf("Input0: %s\nInput1: %s\nOutput0: %s\nOutput1: %s\nFee: %s\n", - tx.Input0, tx.Input1, tx.Output0, tx.Output1, tx.Fee) + str += fmt.Sprintf("Fee: %s\n", tx.Fee) + return str } /* Helpers */ @@ -167,22 +146,20 @@ func (tx Transaction) toTxList() txList { // pointer safety if a transaction // object was ever created with Transaction{} txList := txList{} - if tx.Input0.BlockNum == nil { - tx.Input0.BlockNum = utils.Big0 - } else if tx.Input1.BlockNum == nil { - tx.Input1.BlockNum = utils.Big0 - } - if tx.Input0.DepositNonce == nil { - tx.Input0.DepositNonce = utils.Big0 - } else if tx.Input1.DepositNonce == nil { - tx.Input1.DepositNonce = utils.Big0 + for i, input := range tx.Inputs { + if input.BlockNum == nil { + tx.Inputs[i].BlockNum = utils.Big0 + } + if input.DepositNonce == nil { + tx.Inputs[i].DepositNonce = utils.Big0 + } } - if tx.Output0.Amount == nil { - tx.Output0.Amount = utils.Big0 - } else if tx.Output1.Amount == nil { - tx.Output1.Amount = utils.Big0 + for i, output := range tx.Outputs { + if output.Amount == nil { + tx.Outputs[i].Amount = utils.Big0 + } } if tx.Fee == nil { @@ -191,53 +168,60 @@ func (tx Transaction) toTxList() txList { // fill in txList with values // Input 0 - if len(tx.Input0.BlockNum.Bytes()) > 0 { - copy(txList.BlkNum0[32-len(tx.Input0.BlockNum.Bytes()):], tx.Input0.BlockNum.Bytes()) + input := tx.Inputs[0] + if len(input.BlockNum.Bytes()) > 0 { + copy(txList.BlkNum0[32-len(input.BlockNum.Bytes()):], input.BlockNum.Bytes()) } - txList.TxIndex0[31] = byte(tx.Input0.TxIndex) - txList.TxIndex0[30] = byte(tx.Input0.TxIndex >> 8) - txList.OIndex0[31] = byte(tx.Input0.OutputIndex) - if len(tx.Input0.DepositNonce.Bytes()) > 0 { - copy(txList.DepositNonce0[32-len(tx.Input0.DepositNonce.Bytes()):], tx.Input0.DepositNonce.Bytes()) + txList.TxIndex0[31] = byte(input.TxIndex) + txList.TxIndex0[30] = byte(input.TxIndex >> 8) + txList.OIndex0[31] = byte(input.OutputIndex) + if len(input.DepositNonce.Bytes()) > 0 { + copy(txList.DepositNonce0[32-len(input.DepositNonce.Bytes()):], input.DepositNonce.Bytes()) } - switch len(tx.Input0.ConfirmSignatures) { + switch len(input.ConfirmSignatures) { case 1: - copy(txList.Input0ConfirmSigs[:65], tx.Input0.ConfirmSignatures[0][:]) + copy(txList.Input0ConfirmSigs[:65], input.ConfirmSignatures[0][:]) case 2: - copy(txList.Input0ConfirmSigs[:65], tx.Input0.ConfirmSignatures[0][:]) - copy(txList.Input0ConfirmSigs[65:], tx.Input0.ConfirmSignatures[1][:]) + copy(txList.Input0ConfirmSigs[:65], input.ConfirmSignatures[0][:]) + copy(txList.Input0ConfirmSigs[65:], input.ConfirmSignatures[1][:]) } // Input 1 - if len(tx.Input1.BlockNum.Bytes()) > 0 { - copy(txList.BlkNum1[32-len(tx.Input1.BlockNum.Bytes()):], tx.Input1.BlockNum.Bytes()) - } - txList.TxIndex1[31] = byte(tx.Input1.TxIndex) - txList.TxIndex1[30] = byte(tx.Input1.TxIndex >> 8) - txList.OIndex1[31] = byte(tx.Input1.OutputIndex) - if len(tx.Input1.DepositNonce.Bytes()) > 0 { - copy(txList.DepositNonce1[32-len(tx.Input1.DepositNonce.Bytes()):], tx.Input1.DepositNonce.Bytes()) + if len(tx.Inputs) > 1 { + input = tx.Inputs[1] + if len(input.BlockNum.Bytes()) > 0 { + copy(txList.BlkNum1[32-len(input.BlockNum.Bytes()):], input.BlockNum.Bytes()) + } + txList.TxIndex1[31] = byte(input.TxIndex) + txList.TxIndex1[30] = byte(input.TxIndex >> 8) + txList.OIndex1[31] = byte(input.OutputIndex) + if len(input.DepositNonce.Bytes()) > 0 { + copy(txList.DepositNonce1[32-len(input.DepositNonce.Bytes()):], input.DepositNonce.Bytes()) + } + + switch len(input.ConfirmSignatures) { + case 1: + copy(txList.Input1ConfirmSigs[:65], input.ConfirmSignatures[0][:]) + case 2: + copy(txList.Input1ConfirmSigs[:65], input.ConfirmSignatures[0][:]) + copy(txList.Input1ConfirmSigs[65:], input.ConfirmSignatures[1][:]) + } } - - switch len(tx.Input1.ConfirmSignatures) { - case 1: - copy(txList.Input1ConfirmSigs[:65], tx.Input1.ConfirmSignatures[0][:]) - case 2: - copy(txList.Input1ConfirmSigs[:65], tx.Input1.ConfirmSignatures[0][:]) - copy(txList.Input1ConfirmSigs[65:], tx.Input1.ConfirmSignatures[1][:]) - } - // Outputs and Fee - txList.NewOwner0 = tx.Output0.Owner - if len(tx.Output0.Amount.Bytes()) > 0 { - copy(txList.Amount0[32-len(tx.Output0.Amount.Bytes()):], tx.Output0.Amount.Bytes()) - } - txList.NewOwner1 = tx.Output1.Owner - if len(tx.Output1.Amount.Bytes()) > 0 { - copy(txList.Amount1[32-len(tx.Output1.Amount.Bytes()):], tx.Output1.Amount.Bytes()) - } - if len(tx.Fee.Bytes()) > 0 { - copy(txList.Fee[32-len(tx.Fee.Bytes()):], tx.Fee.Bytes()) + output := tx.Outputs[0] + txList.NewOwner0 = output.Owner + if len(output.Amount.Bytes()) > 0 { + copy(txList.Amount0[32-len(output.Amount.Bytes()):], output.Amount.Bytes()) + } + if len(tx.Outputs) > 1 { + output = tx.Outputs[1] + txList.NewOwner1 = output.Owner + if len(output.Amount.Bytes()) > 0 { + copy(txList.Amount1[32-len(output.Amount.Bytes()):], output.Amount.Bytes()) + } + if len(tx.Fee.Bytes()) > 0 { + copy(txList.Fee[32-len(tx.Fee.Bytes()):], tx.Fee.Bytes()) + } } return txList } diff --git a/plasma/transaction_test.go b/plasma/transaction_test.go index 0ad18e1..89d3305 100644 --- a/plasma/transaction_test.go +++ b/plasma/transaction_test.go @@ -20,15 +20,15 @@ func TestTransactionSerialization(t *testing.T) { pos, _ := FromPositionString("(1.10000.1.0)") confirmSig0 := make([][65]byte, 1) copy(confirmSig0[0][65-len([]byte("confirm sig")):], []byte("confirm sig")) - tx.Input0 = NewInput(pos, [65]byte{}, confirmSig0) - tx.Input0.Signature[1] = byte(1) + tx.Inputs = append(tx.Inputs, NewInput(pos, [65]byte{}, confirmSig0)) + tx.Inputs[0].Signature[1] = byte(1) pos, _ = FromPositionString("(0.0.0.1)") confirmSig1 := make([][65]byte, 2) copy(confirmSig1[0][65-len([]byte("the second confirm sig")):], []byte("the second confirm sig")) copy(confirmSig1[1][65-len([]byte("a very long string turned into bytes")):], []byte("a very long string turned into bytes")) - tx.Input1 = NewInput(pos, [65]byte{}, confirmSig1) - tx.Output0 = NewOutput(common.HexToAddress("1"), one) - tx.Output1 = NewOutput(common.HexToAddress("0"), zero) + tx.Inputs = append(tx.Inputs, NewInput(pos, [65]byte{}, confirmSig1)) + tx.Outputs = append(tx.Outputs, NewOutput(common.HexToAddress("1"), one)) + tx.Outputs = append(tx.Outputs, NewOutput(common.HexToAddress("0"), zero)) tx.Fee = big.NewInt(1) bytes, err := rlp.EncodeToBytes(tx) @@ -67,40 +67,32 @@ func TestTransactionValidation(t *testing.T) { validationCase{ reason: "tx with an empty first input", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.0)"), emptySig, nil), - Input1: NewInput(GetPosition("(0.0.0.0)"), emptySig, nil), - Output0: NewOutput(utils.ZeroAddress, utils.Big0), - Output1: NewOutput(utils.ZeroAddress, utils.Big0), + Inputs: []Input{NewInput(GetPosition("(0.0.0.0)"), emptySig, nil)}, + Outputs: []Output{NewOutput(utils.ZeroAddress, utils.Big0)}, Fee: utils.Big0, }, }, validationCase{ reason: "tx with no recipient", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig), - Input1: NewInput(GetPosition("(0.0.0.0)"), emptySig, nil), - Output0: NewOutput(utils.ZeroAddress, utils.Big1), - Output1: NewOutput(utils.ZeroAddress, utils.Big0), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig)}, + Outputs: []Output{NewOutput(utils.ZeroAddress, utils.Big1)}, Fee: utils.Big0, }, }, validationCase{ reason: "tx with no output amount", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig), - Input1: NewInput(GetPosition("(0.0.0.0)"), emptySig, nil), - Output0: NewOutput(addr, utils.Big0), - Output1: NewOutput(utils.ZeroAddress, utils.Big0), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig)}, + Outputs: []Output{NewOutput(addr, utils.Big0)}, Fee: utils.Big0, }, }, validationCase{ reason: "tx with the same position for both inputs", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig), - Input1: NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig), - Output0: NewOutput(addr, utils.Big0), - Output1: NewOutput(utils.ZeroAddress, utils.Big0), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig), NewInput(GetPosition("(0.0.0.1)"), sampleSig, sampleConfirmSig)}, + Outputs: []Output{NewOutput(addr, utils.Big0)}, Fee: utils.Big0, }, }, @@ -115,40 +107,32 @@ func TestTransactionValidation(t *testing.T) { validationCase{ reason: "tx with one input and one output", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil), - Input1: NewInput(GetPosition("(0.0.0.0)"), emptySig, nil), - Output0: NewOutput(addr, utils.Big1), - Output1: NewOutput(utils.ZeroAddress, utils.Big0), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil)}, + Outputs: []Output{NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, }, validationCase{ reason: "tx with one input and two output", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil), - Input1: NewInput(GetPosition("(0.0.0.0)"), emptySig, nil), - Output0: NewOutput(addr, utils.Big1), - Output1: NewOutput(addr, utils.Big1), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil)}, + Outputs: []Output{NewOutput(addr, utils.Big1), NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, }, validationCase{ reason: "tx with two input and one output", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil), - Input1: NewInput(GetPosition("(1.0.1.0)"), sampleSig, sampleConfirmSig), - Output0: NewOutput(addr, utils.Big1), - Output1: NewOutput(utils.ZeroAddress, utils.Big0), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil), NewInput(GetPosition("(1.0.1.0)"), sampleSig, sampleConfirmSig)}, + Outputs: []Output{NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, }, validationCase{ reason: "tx with two input and two outputs", Transaction: Transaction{ - Input0: NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil), - Input1: NewInput(GetPosition("(1.0.1.0)"), sampleSig, sampleConfirmSig), - Output0: NewOutput(addr, utils.Big1), - Output1: NewOutput(addr, utils.Big1), + Inputs: []Input{NewInput(GetPosition("(0.0.0.1)"), sampleSig, nil), NewInput(GetPosition("(1.0.1.0)"), sampleSig, sampleConfirmSig)}, + Outputs: []Output{NewOutput(addr, utils.Big1), NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, }, From c4358e26a5f0c778283fa1a9e3c5713e998b311c Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 10 May 2019 12:13:30 -0700 Subject: [PATCH 09/49] updated msgs/ --- msgs/depositMsg.go | 4 ++-- msgs/errors.go | 12 ++++++++---- msgs/spendMsg.go | 8 ++++---- msgs/spendMsg_test.go | 6 ++---- plasma/transaction.go | 17 +++++++++++------ 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/msgs/depositMsg.go b/msgs/depositMsg.go index f4c7192..cac2705 100644 --- a/msgs/depositMsg.go +++ b/msgs/depositMsg.go @@ -36,10 +36,10 @@ func (msg IncludeDepositMsg) GetSignBytes() []byte { func (msg IncludeDepositMsg) ValidateBasic() sdk.Error { if msg.DepositNonce.Sign() != 1 { - return ErrInvalidTransaction(DefaultCodespace, "DepositNonce must be greater than 0") + return ErrInvalidIncludeDepositMsg(DefaultCodespace, "DepositNonce must be greater than 0") } if utils.IsZeroAddress(msg.Owner) { - return ErrInvalidTransaction(DefaultCodespace, "Owner must have non-zero address") + return ErrInvalidIncludeDepositMsg(DefaultCodespace, "Owner must have non-zero address") } return nil } diff --git a/msgs/errors.go b/msgs/errors.go index 6a5a879..b5872bc 100644 --- a/msgs/errors.go +++ b/msgs/errors.go @@ -4,13 +4,17 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Reserve errors 100 ~ 199 const ( DefaultCodespace sdk.CodespaceType = "msgs" - CodeInvalidTransaction sdk.CodeType = 1 + CodeInvalidSpendMsg sdk.CodeType = 1 + CodeInvalidIncludeDepositMsg sdk.CodeType = 2 ) -func ErrInvalidTransaction(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeInvalidTransaction, msg, args...) +func ErrInvalidSpendMsg(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeInvalidSpendMsg, msg, args...) +} + +func ErrInvalidIncludeDepositMsg(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeInvalidIncludeDepositMsg, msg, args...) } diff --git a/msgs/spendMsg.go b/msgs/spendMsg.go index e4ab0a2..2cfe0f0 100644 --- a/msgs/spendMsg.go +++ b/msgs/spendMsg.go @@ -29,15 +29,15 @@ func (msg SpendMsg) GetSigners() []sdk.AccAddress { var addrs []sdk.AccAddress // recover first owner - pubKey, err := crypto.SigToPub(txHash, msg.Input0.Signature[:]) + pubKey, err := crypto.SigToPub(txHash, msg.Inputs[0].Signature[:]) if err != nil { return nil } addrs = append(addrs, sdk.AccAddress(crypto.PubkeyToAddress(*pubKey).Bytes())) - if msg.HasSecondInput() { + if len(msg.Inputs) > 1 { // recover the second owner - pubKey, err = crypto.SigToPub(txHash, msg.Input1.Signature[:]) + pubKey, err = crypto.SigToPub(txHash, msg.Inputs[1].Signature[:]) if err != nil { return nil } @@ -53,7 +53,7 @@ func (msg SpendMsg) GetSignBytes() []byte { func (msg SpendMsg) ValidateBasic() sdk.Error { if err := msg.Transaction.ValidateBasic(); err != nil { - return ErrInvalidTransaction(DefaultCodespace, err.Error()) + return ErrInvalidSpendMsg(DefaultCodespace, err.Error()) } return nil diff --git a/msgs/spendMsg_test.go b/msgs/spendMsg_test.go index 6076794..bc56990 100644 --- a/msgs/spendMsg_test.go +++ b/msgs/spendMsg_test.go @@ -13,10 +13,8 @@ import ( func TestSpendMsgSerialization(t *testing.T) { msg := SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 1, 0, nil), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 1, 1, nil), [65]byte{}, nil), - Output0: plasma.NewOutput(common.HexToAddress("1"), utils.Big1), - Output1: plasma.NewOutput(common.Address{}, nil), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 1, 0, nil), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(utils.Big1, 1, 1, nil), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(common.HexToAddress("1"), utils.Big1)}, Fee: utils.Big1, }, } diff --git a/plasma/transaction.go b/plasma/transaction.go index 60c21a4..a83fc6d 100644 --- a/plasma/transaction.go +++ b/plasma/transaction.go @@ -61,11 +61,15 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { tx.Inputs = append(tx.Inputs, NewInput(NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum0[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex0[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex0[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce0[:]).Int64())), t.Sigs[0], confirmSigs0)) - tx.Inputs = append(tx.Inputs, NewInput(NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum1[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex1[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex1[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce1[:]).Int64())), - t.Sigs[1], confirmSigs1)) + pos := NewPosition(big.NewInt(new(big.Int).SetBytes(t.Tx.BlkNum1[:]).Int64()), uint16(new(big.Int).SetBytes(t.Tx.TxIndex1[:]).Int64()), uint8(new(big.Int).SetBytes(t.Tx.OIndex1[:]).Int64()), big.NewInt(new(big.Int).SetBytes(t.Tx.DepositNonce1[:]).Int64())) + if !pos.IsNilPosition() { + tx.Inputs = append(tx.Inputs, NewInput(pos, t.Sigs[1], confirmSigs1)) + } // set signatures if applicable tx.Outputs = append(tx.Outputs, NewOutput(t.Tx.NewOwner0, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount0[:]).Int64()))) - tx.Outputs = append(tx.Outputs, NewOutput(t.Tx.NewOwner1, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount1[:]).Int64()))) + if !utils.IsZeroAddress(t.Tx.NewOwner1) { + tx.Outputs = append(tx.Outputs, NewOutput(t.Tx.NewOwner1, big.NewInt(new(big.Int).SetBytes(t.Tx.Amount1[:]).Int64()))) + } tx.Fee = big.NewInt(new(big.Int).SetBytes(t.Tx.Fee[:]).Int64()) return nil @@ -219,10 +223,11 @@ func (tx Transaction) toTxList() txList { if len(output.Amount.Bytes()) > 0 { copy(txList.Amount1[32-len(output.Amount.Bytes()):], output.Amount.Bytes()) } - if len(tx.Fee.Bytes()) > 0 { - copy(txList.Fee[32-len(tx.Fee.Bytes()):], tx.Fee.Bytes()) - } } + if len(tx.Fee.Bytes()) > 0 { + copy(txList.Fee[32-len(tx.Fee.Bytes()):], tx.Fee.Bytes()) + } + return txList } From 0943e9c954baf77681a2ca4856c7d9b7535fff7d Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 10 May 2019 14:30:14 -0700 Subject: [PATCH 10/49] refactored handlers/ still need to update tests --- client/plasmacli/eth/balance.go | 4 +- client/plasmacli/eth/challenge.go | 2 +- client/plasmacli/eth/getDeposits.go | 4 +- client/plasmacli/eth/getExits.go | 2 +- client/plasmacli/eth/rootchain.go | 2 +- client/plasmacli/eth/withdraw.go | 4 +- handlers/anteHandler.go | 100 ++++++++++++++-------------- handlers/depositHandler.go | 17 ++--- handlers/errors.go | 15 +++++ handlers/spendMsgHandler.go | 56 ++++++---------- plasma/input.go | 2 +- plasma/position.go | 2 +- plasma/position_test.go | 4 +- store/depositStore.go | 6 +- store/txStore.go | 62 +++++++++-------- 15 files changed, 146 insertions(+), 136 deletions(-) diff --git a/client/plasmacli/eth/balance.go b/client/plasmacli/eth/balance.go index 339f69b..c2f68c6 100644 --- a/client/plasmacli/eth/balance.go +++ b/client/plasmacli/eth/balance.go @@ -15,8 +15,8 @@ func init() { var balanceCmd = &cobra.Command{ Use: "balance ", - Short: "Query for balance avaliable for withdraw from rootchain", - Long: `Query for balance avaliable for withdrawal from rootchain. + Short: "Query for balance available for withdraw from rootchain", + Long: `Query for balance available for withdrawal from rootchain. Usage: plasmacli eth query balance diff --git a/client/plasmacli/eth/challenge.go b/client/plasmacli/eth/challenge.go index b4098d1..103c237 100644 --- a/client/plasmacli/eth/challenge.go +++ b/client/plasmacli/eth/challenge.go @@ -79,7 +79,7 @@ Usage: var result *tm.ResultTx result, confirmSignatures, err = getProof(owner, challengingPos) if err != nil { - fmt.Errorf("failed to retrieve exit information: { %s }", err) + return fmt.Errorf("failed to retrieve exit information: { %s }", err) } txBytes = result.Tx diff --git a/client/plasmacli/eth/getDeposits.go b/client/plasmacli/eth/getDeposits.go index 7877bc8..c07a05e 100644 --- a/client/plasmacli/eth/getDeposits.go +++ b/client/plasmacli/eth/getDeposits.go @@ -16,8 +16,8 @@ func init() { var getDepositsCmd = &cobra.Command{ Use: "deposit ", - Short: "Query for a deposit that occured on the rootchain", - Long: `Queries for deposits that occured on the rootchain. + Short: "Query for a deposit that occurred on the rootchain", + Long: `Queries for deposits that occurred on the rootchain. Usage: plasmacli eth query deposit diff --git a/client/plasmacli/eth/getExits.go b/client/plasmacli/eth/getExits.go index fa0f2a6..960826c 100644 --- a/client/plasmacli/eth/getExits.go +++ b/client/plasmacli/eth/getExits.go @@ -97,7 +97,7 @@ Usage: } if err := displayExits(curr, lim, addr, viper.GetBool(depositsF)); err != nil { - return fmt.Errorf("failure occured while querying exits: { %s }", err) + return fmt.Errorf("failure occurred while querying exits: { %s }", err) } return nil diff --git a/client/plasmacli/eth/rootchain.go b/client/plasmacli/eth/rootchain.go index a80552c..5b4c754 100644 --- a/client/plasmacli/eth/rootchain.go +++ b/client/plasmacli/eth/rootchain.go @@ -13,7 +13,7 @@ var rootchainCmd = &cobra.Command{ Use: "rootchain", Short: "Display rootchain contract information", Long: `Display last committed block, total contract balance, total withdraw balance, minimum exit bond, and operator address. -Total contract balance does not include total withdraw balance. The total withdraw balance are exits that have been finalized, but not transfered yet.`, +Total contract balance does not include total withdraw balance. The total withdraw balance are exits that have been finalized, but not transferred yet.`, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, agrs []string) error { lastCommittedBlock, err := rc.contract.LastCommittedBlock(nil) diff --git a/client/plasmacli/eth/withdraw.go b/client/plasmacli/eth/withdraw.go index f005456..3a46124 100644 --- a/client/plasmacli/eth/withdraw.go +++ b/client/plasmacli/eth/withdraw.go @@ -16,8 +16,8 @@ func init() { var withdrawCmd = &cobra.Command{ Use: "withdraw ", - Short: "Withdraw all avaliable funds from rootchain contract", - Long: `Withdraw all avaliable funds from the rootchain contract + Short: "Withdraw all available funds from rootchain contract", + Long: `Withdraw all available funds from the rootchain contract Usage: plasmacli eth withdraw --gas-limit 30000`, diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index eec6bb1..98296dd 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -8,7 +8,6 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/FourthState/plasma-mvp-sidechain/utils" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "math/big" ) @@ -31,7 +30,7 @@ func NewAnteHandler(txStore store.TxStore, depositStore store.DepositStore, bloc spendMsg := msg.(msgs.SpendMsg) return spendMsgAnteHandler(ctx, spendMsg, txStore, depositStore, blockStore, client) default: - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "msg is not of type SpendMsg or IncludeDepositMsg").Result(), true + return ctx, ErrInvalidTransaction(DefaultCodespace, "msg is not of type SpendMsg or IncludeDepositMsg").Result(), true } } } @@ -42,80 +41,75 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, txStore store. // attempt to recover signers signers := spendMsg.GetSigners() if len(signers) == 0 { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "failed recovering signers").Result(), true + return ctx, ErrInvalidTransaction(DefaultCodespace, "failed recovering signers").Result(), true } - /* validate the first input */ - amt, res := validateInput(ctx, spendMsg.Input0, common.BytesToAddress(signers[0]), txStore, blockStore, client) - if !res.IsOK() { - return ctx, res, true + if len(signers) != len(spendMsg.Inputs) { + return ctx, ErrInvalidSignature(DefaultCodespace, "number of signers does not equal number of signatures").Result(), true } - // must cover the fee - if amt.Cmp(spendMsg.Fee) < 0 { - return ctx, ErrInsufficientFee(DefaultCodespace, "first input has an insufficient amount to pay the fee").Result(), true - } - - totalInputAmt = amt - - /* validate second input if applicable */ - if spendMsg.HasSecondInput() { - if len(signers) != 2 { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "second signature not present").Result(), true - } - amt, res = validateInput(ctx, spendMsg.Input1, common.BytesToAddress(signers[1]), txStore, blockStore, client) + /* validate inputs */ + for i, input := range spendMsg.Inputs { + amt, res := validateInput(ctx, input, txStore, blockStore, client) if !res.IsOK() { return ctx, res, true } + // first input must cover the fee + if i == 0 && amt.Cmp(spendMsg.Fee) < 0 { + return ctx, ErrInsufficientFee(DefaultCodespace, "first input has an insufficient amount to pay the fee").Result(), true + } + totalInputAmt = totalInputAmt.Add(totalInputAmt, amt) } // input0 + input1 (totalInputAmt) == output0 + output1 + Fee (totalOutputAmt) - totalOutputAmt = spendMsg.Output0.Amount - if spendMsg.HasSecondOutput() { - totalOutputAmt = new(big.Int).Add(new(big.Int).Add(totalOutputAmt, spendMsg.Output1.Amount), spendMsg.Fee) - } else { - totalOutputAmt = new(big.Int).Add(totalOutputAmt, spendMsg.Fee) + for _, output := range spendMsg.Outputs { + totalOutputAmt = new(big.Int).Add(totalOutputAmt, output.Amount) } + totalOutputAmt = new(big.Int).Add(totalOutputAmt, spendMsg.Fee) if totalInputAmt.Cmp(totalOutputAmt) != 0 { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "inputs do not equal Outputs (+ fee)").Result(), true + return ctx, ErrInvalidTransaction(DefaultCodespace, "inputs do not equal Outputs (+ fee)").Result(), true } return ctx, sdk.Result{}, false } // validates the inputs against the utxo store and returns the amount of the respective input -func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, txStore store.TxStore, blockStore store.BlockStore, client plasmaConn) (*big.Int, sdk.Result) { +func validateInput(ctx sdk.Context, input plasma.Input, txStore store.TxStore, blockStore store.BlockStore, client plasmaConn) (*big.Int, sdk.Result) { var amt *big.Int // inputUTXO must be owned by the signer due to the prefix so we do not need to // check the owner of the position - inputUTXO, ok := utxoStore.GetUTXO(ctx, signer, input.Position) + inputUTXO, ok := txStore.GetUTXO(ctx, input.Position) if !ok { - return nil, msgs.ErrInvalidTransaction(DefaultCodespace, "input, %v, does not exist by owner %x", input.Position, signer).Result() + return nil, ErrInvalidInput(DefaultCodespace, "input, %v, does not exist", input.Position).Result() } if inputUTXO.Spent { - return nil, msgs.ErrInvalidTransaction(DefaultCodespace, "input, %v, already spent", input.Position).Result() + return nil, ErrInvalidInput(DefaultCodespace, "input, %v, already spent", input.Position).Result() } - if client.HasTxBeenExited(plasmaStore.CurrentPlasmaBlockNum(ctx), input.Position) { + if client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), input.Position) { return nil, ErrExitedInput(DefaultCodespace, "input, %v, utxo has exitted", input.Position).Result() } + tx, ok := txStore.GetTxWithPosition(ctx, input.Position) + if !ok { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to retrieve the transaction that input with position %s belongs to", input.Position)).Result() + } + // validate confirm signatures if not a fee utxo or deposit utxo if input.TxIndex < 1<<16-1 && input.DepositNonce.Sign() == 0 { - res := validateConfirmSignatures(ctx, input, inputUTXO) + res := validateConfirmSignatures(ctx, input, tx, txStore) if !res.IsOK() { return nil, res } } // check if the parent utxo has exited - for _, key := range inputUTXO.InputKeys { - utxo, _ := utxoStore.GetUTXOWithKey(ctx, key) - if client.HasTxBeenExited(plasmaStore.CurrentPlasmaBlockNum(ctx), utxo.Position) { - return nil, sdk.ErrUnauthorized(fmt.Sprintf("a parent of the input has exited. Owner: %x, Position: %v", utxo.Output.Owner, utxo.Position)).Result() + for _, in := range tx.Transaction.Inputs { + if client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), in.Position) { + return nil, sdk.ErrUnauthorized(fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() } } @@ -125,19 +119,22 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, t } // validates the input's confirm signatures -func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx store.Transaction) sdk.Result { - if len(input.ConfirmSignatures) > 0 && (inputTx.Transaction.HasSecondInput) { - return msgs.ErrInvalidTransaction(DefaultCodespace, "incorrect number of confirm signatures").Result() +func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx store.Transaction, txStore store.TxStore) sdk.Result { + if len(input.ConfirmSignatures) > 0 && len(inputTx.Transaction.Inputs) != len(input.ConfirmSignatures) { + return ErrInvalidTransaction(DefaultCodespace, "incorrect number of confirm signatures").Result() } confirmationHash := utils.ToEthSignedMessageHash(inputTx.ConfirmationHash[:])[:] - for i, key := range inputUTXO.InputKeys { - address := key[:common.AddressLength] + for i, in := range inputTx.Transaction.Inputs { + utxo, ok := txStore.GetUTXO(ctx, in.Position) + if !ok { + return sdk.ErrInternal(fmt.Sprintf("failed to retrieve input utxo with position %s", in.Position)).Result() + } pubKey, _ := crypto.SigToPub(confirmationHash, input.ConfirmSignatures[i][:]) signer := crypto.PubkeyToAddress(*pubKey) - if !bytes.Equal(signer[:], address) { - return sdk.ErrUnauthorized(fmt.Sprintf("confirm signature not signed by the correct address. Got: %x. Expected %x", signer, address)).Result() + if !bytes.Equal(signer[:], utxo.Output.Owner[:]) { + return sdk.ErrUnauthorized(fmt.Sprintf("confirm signature not signed by the correct address. Got: %x. Expected %x", signer, utxo.Output.Owner)).Result() } } @@ -145,19 +142,24 @@ func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx stor } func includeDepositAnteHandler(ctx sdk.Context, depositStore store.DepositStore, blockStore store.BlockStore, msg msgs.IncludeDepositMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { - if depositStore.HasDeposit(ctx, msg.Owner, msg.DepositNonce) { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, already exists in store", msg.DepositNonce.String()).Result(), true + if depositStore.HasDeposit(ctx, msg.DepositNonce) { + return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, already exists in store", msg.DepositNonce.String()).Result(), true } - _, threshold, ok := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), msg.DepositNonce) + deposit, threshold, ok := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), msg.DepositNonce) if !ok && threshold == nil { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, does not exist.", msg.DepositNonce.String()).Result(), true + return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, does not exist.", msg.DepositNonce.String()).Result(), true } if !ok { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has not finalized yet. Please wait at least %d blocks before resubmitting", msg.DepositNonce.String(), threshold.Int64()).Result(), true + return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has not finalized yet. Please wait at least %d blocks before resubmitting", msg.DepositNonce.String(), threshold.Int64()).Result(), true + } + if !bytes.Equal(deposit.Owner[:], msg.Owner[:]) { + return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, does not equal the owner specified in the include-deposit Msg", msg.DepositNonce).Result(), true } + + depositPosition := plasma.NewPosition(big.NewInt(0), 0, 0, msg.DepositNonce) exited := client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), depositPosition) if exited { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has already exitted from rootchain", msg.DepositNonce.String()).Result(), true + return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has already exitted from rootchain", msg.DepositNonce.String()).Result(), true } return ctx, sdk.Result{}, false } diff --git a/handlers/depositHandler.go b/handlers/depositHandler.go index 308a99d..8a84af2 100644 --- a/handlers/depositHandler.go +++ b/handlers/depositHandler.go @@ -2,31 +2,28 @@ package handlers import ( "github.com/FourthState/plasma-mvp-sidechain/msgs" - "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" sdk "github.com/cosmos/cosmos-sdk/types" - "math/big" ) -func NewDepositHandler(utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, nextTxIndex NextTxIndex, client plasmaConn) sdk.Handler { +func NewDepositHandler(depositStore store.DepositStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, client plasmaConn) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { depositMsg, ok := msg.(msgs.IncludeDepositMsg) if !ok { panic("Msg does not implement IncludeDepositMsg") } - depositPosition := plasma.NewPosition(big.NewInt(0), 0, 0, depositMsg.DepositNonce) // Increment txIndex so that it doesn't collide with SpendMsg nextTxIndex() - deposit, _, _ := client.GetDeposit(plasmaStore.CurrentPlasmaBlockNum(ctx), depositMsg.DepositNonce) + deposit, _, _ := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), depositMsg.DepositNonce) - utxo := store.UTXO{ - Output: plasma.NewOutput(deposit.Owner, deposit.Amount), - Position: depositPosition, - Spent: false, + dep := store.Deposit{ + Deposit: deposit, + Spent: false, + Spender: nil, } - utxoStore.StoreUTXO(ctx, utxo) + depositStore.StoreDeposit(ctx, depositMsg.DepositNonce, dep) return sdk.Result{} } } diff --git a/handlers/errors.go b/handlers/errors.go index 2dbaf5a..fdd0b68 100644 --- a/handlers/errors.go +++ b/handlers/errors.go @@ -10,6 +10,9 @@ const ( CodeInsufficientFee sdk.CodeType = 1 CodeExitedInput sdk.CodeType = 2 CodeSignatureVerificationFailure sdk.CodeType = 3 + CodeInvalidTransaction sdk.CodeType = 4 + CodeInvalidSignature sdk.CodeType = 5 + CodeInvalidInput sdk.CodeType = 6 ) func ErrInsufficientFee(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { @@ -23,3 +26,15 @@ func ErrExitedInput(codespace sdk.CodespaceType, msg string, args ...interface{} func ErrSignatureVerificationFailure(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { return sdk.NewError(codespace, CodeSignatureVerificationFailure, msg, args) } + +func ErrInvalidTransaction(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeInvalidTransaction, msg, args) +} + +func ErrInvalidSignature(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeInvalidSignature, msg, args) +} + +func ErrInvalidInput(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, msg, args) +} diff --git a/handlers/spendMsgHandler.go b/handlers/spendMsgHandler.go index 3f799b2..932de3e 100644 --- a/handlers/spendMsgHandler.go +++ b/handlers/spendMsgHandler.go @@ -6,7 +6,6 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" "math/big" ) @@ -16,7 +15,7 @@ type NextTxIndex func() uint16 // FeeUpdater updates the aggregate fee amount in a block type FeeUpdater func(amt *big.Int) sdk.Error -func NewSpendHandler(utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, nextTxIndex NextTxIndex, feeUpdater FeeUpdater) sdk.Handler { +func NewSpendHandler(txStore store.TxStore, depositStore store.DepositStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, feeUpdater FeeUpdater) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { spendMsg, ok := msg.(msgs.SpendMsg) if !ok { @@ -24,35 +23,30 @@ func NewSpendHandler(utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, n } txIndex := nextTxIndex() - blockHeight := plasmaStore.NextPlasmaBlockNum(ctx) + blockHeight := blockStore.NextPlasmaBlockNum(ctx) // construct the confirmation hash merkleHash := spendMsg.MerkleHash() header := ctx.BlockHeader().DataHash confirmationHash := sha256.Sum256(append(merkleHash, header...)) - // positional part of these keys addeded when the new positions are created - var spenderKeys [][]byte - var positions []plasma.Position - positions = append(positions, plasma.NewPosition(blockHeight, txIndex, 0, nil)) - spenderKeys = append(spenderKeys, append(spendMsg.Output0.Owner[:], positions[0].Bytes()...)) - if spendMsg.HasSecondOutput() { - positions = append(positions, plasma.NewPosition(blockHeight, txIndex, 1, nil)) - spenderKeys = append(spenderKeys, append(spendMsg.Output1.Owner[:], positions[1].Bytes()...)) + /* Store Transaction */ + tx := store.Transaction{ + Transaction: spendMsg.Transaction, + Spent: make([]bool, len(spendMsg.Outputs)), + Spenders: make([][]byte, len(spendMsg.Outputs)), + ConfirmationHash: confirmationHash[:], } + txStore.StoreTx(ctx, tx) - var inputKeys [][]byte - for i, key := range spendMsg.GetSigners() { - inputKeys = append(inputKeys, append(key[:], spendMsg.InputAt(uint8(i)).Position.Bytes()...)) - } - - // try to spend the inputs. Abort if the inputs don't exist or have been spent - res := utxoStore.SpendUTXO(ctx, common.BytesToAddress(inputKeys[0][:common.AddressLength]), spendMsg.Input0.Position, spenderKeys) - if !res.IsOK() { - return res - } - if spendMsg.HasSecondInput() { - res := utxoStore.SpendUTXO(ctx, common.BytesToAddress(inputKeys[1][:common.AddressLength]), spendMsg.Input1.Position, spenderKeys) + /* Spend Inputs */ + for _, input := range spendMsg.Inputs { + var res sdk.Result + if input.Position.IsDeposit() { + res = depositStore.SpendDeposit(ctx, input.Position.DepositNonce, spendMsg.TxHash()) + } else { + res = txStore.SpendUTXO(ctx, input.Position, spendMsg.TxHash()) + } if !res.IsOK() { return res } @@ -63,18 +57,10 @@ func NewSpendHandler(utxoStore store.UTXOStore, plasmaStore store.PlasmaStore, n return sdk.ErrInternal("error updating the aggregate fee").Result() } - // create new outputs - for i, _ := range spenderKeys { - utxo := store.UTXO{ - InputKeys: inputKeys, - ConfirmationHash: confirmationHash[:], - MerkleHash: merkleHash, - Output: spendMsg.OutputAt(uint8(i)), - Spent: false, - Position: positions[i], - } - - utxoStore.StoreUTXO(ctx, utxo) + /* Create Outputs */ + for i, _ := range spendMsg.Outputs { + pos := plasma.NewPosition(blockHeight, txIndex, uint8(i), big.NewInt(0)) + txStore.StoreUTXO(ctx, pos, spendMsg.TxHash()) } return sdk.Result{} diff --git a/plasma/input.go b/plasma/input.go index 30473be..1672c36 100644 --- a/plasma/input.go +++ b/plasma/input.go @@ -35,7 +35,7 @@ func (i Input) ValidateBasic() error { var emptySig [65]byte if i.Position.IsNilPosition() { if !bytes.Equal(i.Signature[:], emptySig[:]) || len(i.ConfirmSignatures) > 0 { - return fmt.Errorf("nil input should not specifiy a signature nor confirm signatures") + return fmt.Errorf("nil input should not specify a signature nor confirm signatures") } } else { if err := i.Position.ValidateBasic(); err != nil { diff --git a/plasma/position.go b/plasma/position.go index 4243baf..f9f31b2 100644 --- a/plasma/position.go +++ b/plasma/position.go @@ -86,7 +86,7 @@ func (p Position) ValidateBasic() error { // deposit position if p.IsDeposit() { if p.BlockNum.Sign() > 0 || p.TxIndex > 0 || p.OutputIndex > 0 { - return fmt.Errorf("chain postion must be all zero if a deposit nonce is specified. (0.0.0.nonce)") + return fmt.Errorf("chain position must be all zero if a deposit nonce is specified. (0.0.0.nonce)") } } else { if p.BlockNum.Sign() == 0 { diff --git a/plasma/position_test.go b/plasma/position_test.go index 51cb923..8c70a24 100644 --- a/plasma/position_test.go +++ b/plasma/position_test.go @@ -56,9 +56,9 @@ func TestPositionValidation(t *testing.T) { pos, _ := FromPositionString(posStr) require.NoError(t, pos.ValidateBasic(), "valid position marked as an error") - // specifiy both deposit nonce and chain position + // specify both deposit nonce and chain position cases := []string{ - // mutual exclusivity between deposit nonce and chain position requried + // mutual exclusivity between deposit nonce and chain position required "(1.0.0.5)", "(0.1.0.5)", "(0.0.1.5)", diff --git a/store/depositStore.go b/store/depositStore.go index c0bc32f..38d15f9 100644 --- a/store/depositStore.go +++ b/store/depositStore.go @@ -9,9 +9,9 @@ import ( ) type Deposit struct { - Output plasma.Output + Deposit plasma.Deposit Spent bool - Spender [32]byte + Spender []byte } /* Deposit Store */ @@ -52,7 +52,7 @@ func (store DepositStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit store.Set(ctx, nonce.Bytes(), data) } -func (store DepositStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender [32]byte) sdk.Result { +func (store DepositStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { return sdk.ErrUnknownRequest(fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() diff --git a/store/txStore.go b/store/txStore.go index 0d4d2f8..1e25a33 100644 --- a/store/txStore.go +++ b/store/txStore.go @@ -19,14 +19,14 @@ type Transaction struct { Transaction plasma.Transaction ConfirmationHash []byte Spent []bool - Spenders [][32]byte + Spenders [][]byte } /* Wrap plasma output with spend information */ type Output struct { Output plasma.Output Spent bool - Spender [32]byte + Spender []byte } /* Transaction Store */ @@ -40,6 +40,8 @@ func NewTxStore(ctxKey sdk.StoreKey) TxStore { } } +// Return the account at the associated address +// Returns nothing if the account does no exist func (store TxStore) GetAccount(ctx sdk.Context, addr common.Address) (Account, bool) { key := prefixKey(accountKey, addr.Bytes()) data := store.Get(ctx, key) @@ -55,8 +57,9 @@ func (store TxStore) GetAccount(ctx sdk.Context, addr common.Address) (Account, return acc, true } -func (store TxStore) GetTx(ctx sdk.Context, hash [32]byte) (Transaction, bool) { - key := prefixKey(hashKey, hash[:]) +// Return the transaction with the provided transaction hash +func (store TxStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { + key := prefixKey(hashKey, hash) data := store.Get(ctx, key) if data == nil { return Transaction{}, false @@ -70,13 +73,18 @@ func (store TxStore) GetTx(ctx sdk.Context, hash [32]byte) (Transaction, bool) { return tx, true } +// Return the transaction that contains the provided position as an output +func (store TxStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + return store.GetTx(ctx, hash) +} + // Return the output at the specified position // along with if it has been spent and what transaction spent it func (store TxStore) GetUTXO(ctx sdk.Context, pos plasma.Position) (Output, bool) { key := prefixKey(positionKey, pos.Bytes()) - data := store.Get(ctx, key) - var hash [32]byte - copy(data[:32], hash[:]) + hash := store.Get(ctx, key) tx, ok := store.GetTx(ctx, hash) if !ok { @@ -84,7 +92,7 @@ func (store TxStore) GetUTXO(ctx sdk.Context, pos plasma.Position) (Output, bool } output := Output{ - Output: tx.Transaction.OutputAt(pos.OutputIndex), + Output: tx.Transaction.Outputs[pos.OutputIndex], Spent: tx.Spent[pos.OutputIndex], Spender: tx.Spenders[pos.OutputIndex], } @@ -92,25 +100,27 @@ func (store TxStore) GetUTXO(ctx sdk.Context, pos plasma.Position) (Output, bool return output, ok } -func (store TxStore) HasTx(ctx sdk.Context, hash [32]byte) bool { - key := prefixKey(hashKey, hash[:]) +// Checks if a transaction exists using the transaction hash provided +func (store TxStore) HasTx(ctx sdk.Context, hash []byte) bool { + key := prefixKey(hashKey, hash) return store.Has(ctx, key) } +// Checks if an account exists for the provided address func (store TxStore) HasAccount(ctx sdk.Context, addr common.Address) bool { key := prefixKey(accountKey, addr.Bytes()) return store.Has(ctx, key) } +// Checks if the utxo exists using its position func (store TxStore) HasUTXO(ctx sdk.Context, pos plasma.Position) bool { key := prefixKey(positionKey, pos.Bytes()) - data := store.Get(ctx, key) - var hash [32]byte - copy(data[:32], hash[:]) + hash := store.Get(ctx, key) return store.HasTx(ctx, hash) } +// Add a mapping from transaction hash to transaction func (store TxStore) StoreTx(ctx sdk.Context, tx Transaction) { data, err := rlp.EncodeToBytes(&tx) if err != nil { @@ -121,26 +131,26 @@ func (store TxStore) StoreTx(ctx sdk.Context, tx Transaction) { store.Set(ctx, key, data) } -func (store TxStore) StoreUTXO(ctx sdk.Context, pos plasma.Position, hash [32]byte) { - data, err := rlp.EncodeToBytes(&pos) - if err != nil { - panic(fmt.Sprintf("error marshaling position %s: %s", pos, err)) - } - +// Add a mapping from position to transaction hash +func (store TxStore) StoreUTXO(ctx sdk.Context, pos plasma.Position, hash []byte) { key := prefixKey(positionKey, pos.Bytes()) - store.Set(ctx, key, data) + store.Set(ctx, key, hash) } -func (store TxStore) SpendUTXO(ctx sdk.Context, hash [32]byte, outputIndex int, spenderKey [32]byte) sdk.Result { +// Updates Spent and Spender in transaction that contains this utxo +func (store TxStore) SpendUTXO(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + tx, ok := store.GetTx(ctx, hash) if !ok { - return sdk.ErrUnknownRequest(fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", outputIndex, hash)).Result() - } else if tx.Spent[outputIndex] { - return sdk.ErrUnauthorized(fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", outputIndex, hash)).Result() + return sdk.ErrUnknownRequest(fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() + } else if tx.Spent[pos.OutputIndex] { + return sdk.ErrUnauthorized(fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() } - tx.Spent[outputIndex] = true - tx.Spenders[outputIndex] = spenderKey + tx.Spent[pos.OutputIndex] = true + tx.Spenders[pos.OutputIndex] = spender store.StoreTx(ctx, tx) From 65a44f063cc6befa454b3fbbb49228218e9f2bb4 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 10 May 2019 14:48:53 -0700 Subject: [PATCH 11/49] Fix build errors and test issue in store/ --- plasma/transaction_test.go | 2 -- store/blockStore.go | 4 ++-- store/txStore_test.go | 8 +++----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/plasma/transaction_test.go b/plasma/transaction_test.go index 89d3305..71a4b58 100644 --- a/plasma/transaction_test.go +++ b/plasma/transaction_test.go @@ -13,7 +13,6 @@ import ( func TestTransactionSerialization(t *testing.T) { one := big.NewInt(1) - zero := big.NewInt(0) // contstruct a transaction tx := &Transaction{} @@ -28,7 +27,6 @@ func TestTransactionSerialization(t *testing.T) { copy(confirmSig1[1][65-len([]byte("a very long string turned into bytes")):], []byte("a very long string turned into bytes")) tx.Inputs = append(tx.Inputs, NewInput(pos, [65]byte{}, confirmSig1)) tx.Outputs = append(tx.Outputs, NewOutput(common.HexToAddress("1"), one)) - tx.Outputs = append(tx.Outputs, NewOutput(common.HexToAddress("0"), zero)) tx.Fee = big.NewInt(1) bytes, err := rlp.EncodeToBytes(tx) diff --git a/store/blockStore.go b/store/blockStore.go index 2ba2060..1f2ec7d 100644 --- a/store/blockStore.go +++ b/store/blockStore.go @@ -50,7 +50,7 @@ func NewBlockStore(ctxKey sdk.StoreKey) BlockStore { } } -func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, bool) { +func (store BlockStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, bool) { key := prefixKey(blockKey, blockHeight.Bytes()) data := store.Get(ctx, key) if data == nil { @@ -66,7 +66,7 @@ func (store PlasmaStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, } // StoreBlock will store the plasma block and return the plasma block number in which it was stored under -func (store PlasmaStore) StoreBlock(ctx sdk.Context, tmBlockHeight uint64, block plasma.Block) *big.Int { +func (store BlockStore) StoreBlock(ctx sdk.Context, tmBlockHeight uint64, block plasma.Block) *big.Int { plasmaBlockNum := store.NextPlasmaBlockNum(ctx) plasmaBlockKey := prefixKey(blockKey, plasmaBlockNum.Bytes()) diff --git a/store/txStore_test.go b/store/txStore_test.go index 2053870..a725a1c 100644 --- a/store/txStore_test.go +++ b/store/txStore_test.go @@ -16,17 +16,15 @@ func TestTxSerialization(t *testing.T) { // no default attributes transaction := plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(utils.Big1, 15, 1, utils.Big0), sigs, [][65]byte{}), - Input1: plasma.NewInput(plasma.NewPosition(utils.Big0, 0, 0, utils.Big1), sigs, [][65]byte{}), - Output0: plasma.NewOutput(common.HexToAddress("1"), utils.Big1), - Output1: plasma.NewOutput(common.HexToAddress("2"), utils.Big2), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(utils.Big1, 15, 1, utils.Big0), sigs, [][65]byte{}), plasma.NewInput(plasma.NewPosition(utils.Big0, 0, 0, utils.Big1), sigs, [][65]byte{})}, + Outputs: []plasma.Output{plasma.NewOutput(common.HexToAddress("1"), utils.Big1), plasma.NewOutput(common.HexToAddress("2"), utils.Big2)}, Fee: utils.Big1, } tx := Transaction{ Transaction: transaction, Spent: []bool{false, false}, - Spenders: [][32]byte{}, + Spenders: [][]byte{}, ConfirmationHash: hashes, } From e8e54185c0f4dd84da0e59e974ef59b842361122 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sat, 11 May 2019 11:19:44 -0700 Subject: [PATCH 12/49] added tests for deposit and block stores --- store/blockStore_test.go | 72 +++++++++++++++++++++++++++++++++ store/depositStore.go | 4 +- store/depositStore_test.go | 82 ++++++++++++++++++++++++++++++++++++++ store/errors.go | 20 ++++++++++ store/plasmaStore_test.go | 31 -------------- store/store_test.go | 21 ++++++++++ 6 files changed, 197 insertions(+), 33 deletions(-) create mode 100644 store/errors.go delete mode 100644 store/plasmaStore_test.go create mode 100644 store/store_test.go diff --git a/store/blockStore_test.go b/store/blockStore_test.go index 72440ea..fdd7fbf 100644 --- a/store/blockStore_test.go +++ b/store/blockStore_test.go @@ -1 +1,73 @@ package store + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + "math/big" + "reflect" + "testing" +) + +// Test that a block can be serialized and deserialized +func TestBlockSerialization(t *testing.T) { + // Construct Block + plasmaBlock := plasma.Block{} + plasmaBlock.Header[0] = byte(10) + plasmaBlock.TxnCount = 3 + plasmaBlock.FeeAmount = utils.Big2 + + block := Block{ + Block: plasmaBlock, + TMBlockHeight: 2, + } + + // RLP Encode + bytes, err := rlp.EncodeToBytes(&block) + require.NoError(t, err) + + // RLP Decode + recoveredBlock := Block{} + err = rlp.DecodeBytes(bytes, &recoveredBlock) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(block, recoveredBlock), "mismatch in serialized and deserialized block") +} + +// Test that the plasma block number increments correctly +func TestPlasmaBlockStorage(t *testing.T) { + ctx, key := setup() + blockStore := NewBlockStore(key) + + for i := int64(1); i <= 10; i++ { + // Retrieve nonexistent blocks + recoveredBlock, ok := blockStore.GetBlock(ctx, big.NewInt(i)) + require.Empty(t, recoveredBlock, "did not return empty struct for nonexistent block") + require.False(t, ok, "did not return error on nonexistent block") + + // Check increment + plasmaBlockNum := blockStore.NextPlasmaBlockNum(ctx) + require.Equal(t, plasmaBlockNum, big.NewInt(i), fmt.Sprintf("plasma block increment returned %d on iteration %d", plasmaBlockNum, i)) + + // Create and store new block + var header [32]byte + hash := crypto.Keccak256([]byte("a plasma block header")) + copy(header[:], hash[:]) + + plasmaBlock := plasma.NewBlock(header, uint16(i*13), big.NewInt(i*123)) + block := Block{plasmaBlock, uint64(i * 1123)} + blockNum := blockStore.StoreBlock(ctx, uint64(i*1123), plasmaBlock) + require.Equal(t, blockNum, plasmaBlockNum, "inconsistency in plasma block number after storing new block") + + recoveredBlock, ok = blockStore.GetBlock(ctx, blockNum) + require.True(t, ok, "error when retrieving block") + require.True(t, reflect.DeepEqual(block, recoveredBlock), fmt.Sprintf("mismatch in stored block and retrieved block, iteration %d", i)) + + currPlasmaBlock := blockStore.CurrentPlasmaBlockNum(ctx) + require.Equal(t, currPlasmaBlock, blockNum, fmt.Sprintf("stored block number returned %d, current plasma block returned %d", blockNum, currPlasmaBlock)) + + } +} diff --git a/store/depositStore.go b/store/depositStore.go index 38d15f9..2de2122 100644 --- a/store/depositStore.go +++ b/store/depositStore.go @@ -55,9 +55,9 @@ func (store DepositStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit func (store DepositStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { - return sdk.ErrUnknownRequest(fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() + return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() } else if deposit.Spent { - return sdk.ErrUnknownRequest(fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() + return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() } deposit.Spent = true diff --git a/store/depositStore_test.go b/store/depositStore_test.go index 72440ea..490aa0f 100644 --- a/store/depositStore_test.go +++ b/store/depositStore_test.go @@ -1 +1,83 @@ package store + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + "math/big" + "reflect" + "testing" +) + +// Test that the Deposit can be serialized and deserialized without loss of information +func TestDepositSerialization(t *testing.T) { + // Construct deposit + plasmaDeposit := plasma.Deposit{ + Owner: common.BytesToAddress([]byte("an ethereum address")), + Amount: big.NewInt(12312310), + EthBlockNum: big.NewInt(100123123), + } + + deposit := Deposit{ + Deposit: plasmaDeposit, + Spent: true, + Spender: []byte{}, + } + + // RLP Encode + bytes, err := rlp.EncodeToBytes(&deposit) + require.NoError(t, err) + + // RLP Decode + recoveredDeposit := Deposit{} + err = rlp.DecodeBytes(bytes, &recoveredDeposit) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in serialized and deserialized deposits") +} + +// Test Get, Has, Store, Spend functions +func TestDeposits(t *testing.T) { + ctx, key := setup() + depositStore := NewDepositStore(key) + + addr := common.BytesToAddress([]byte("asdfasdf")) + for i := int64(1); i <= 15; i++ { + nonce := big.NewInt(i) + hash := []byte("hash that the deposit was spent in") + + // Retrieve/Spend nonexistent deposits + exists := depositStore.HasDeposit(ctx, nonce) + require.False(t, exists, "returned true for nonexistent deposit") + recoveredDeposit, ok := depositStore.GetDeposit(ctx, nonce) + require.Empty(t, recoveredDeposit, "did not return empty struct for nonexistent deposit") + require.False(t, ok, "did not return error on nonexistent deposit") + res := depositStore.SpendDeposit(ctx, nonce, hash) + require.Equal(t, res.Code, CodeOutputDNE, "did not return that deposit does not exist") + + // Create and store new deposit + plasmaDeposit := plasma.NewDeposit(addr, big.NewInt(i*4123), big.NewInt(i*123)) + deposit := Deposit{plasmaDeposit, false, []byte{}} + depositStore.StoreDeposit(ctx, nonce, deposit) + + exists = depositStore.HasDeposit(ctx, nonce) + require.True(t, exists, "returned false for deposit that was stored") + recoveredDeposit, ok = depositStore.GetDeposit(ctx, nonce) + require.True(t, ok, "error when retrieving deposit") + require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), fmt.Sprintf("mismatch in stored deposit and retrieved deposit on iteration %d", i)) + + // Spend Deposit + res = depositStore.SpendDeposit(ctx, nonce, hash) + require.True(t, res.IsOK(), "returned error when spending deposit") + res = depositStore.SpendDeposit(ctx, nonce, hash) + require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") + + deposit.Spent = true + deposit.Spender = hash + recoveredDeposit, ok = depositStore.GetDeposit(ctx, nonce) + require.True(t, ok, "error when retrieving deposit") + require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in stored and retrieved deposit") + } +} diff --git a/store/errors.go b/store/errors.go new file mode 100644 index 0000000..9db98ef --- /dev/null +++ b/store/errors.go @@ -0,0 +1,20 @@ +package store + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + DefaultCodespace sdk.CodespaceType = "store" + + CodeOutputDNE sdk.CodeType = 1 + CodeOutputSpent sdk.CodeType = 2 +) + +func ErrOutputDNE(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeOutputDNE, msg, args) +} + +func ErrOutputSpent(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeOutputSpent, msg, args) +} diff --git a/store/plasmaStore_test.go b/store/plasmaStore_test.go deleted file mode 100644 index 421b707..0000000 --- a/store/plasmaStore_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package store - -import ( - "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/utils" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/require" - "reflect" - "testing" -) - -func TestBlockSerialization(t *testing.T) { - plasmaBlock := plasma.Block{} - plasmaBlock.Header[0] = byte(10) - plasmaBlock.TxnCount = 3 - plasmaBlock.FeeAmount = utils.Big2 - - block := Block{ - Block: plasmaBlock, - TMBlockHeight: 2, - } - - bytes, err := rlp.EncodeToBytes(&block) - require.NoError(t, err) - - recoveredBlock := Block{} - err = rlp.DecodeBytes(bytes, &recoveredBlock) - require.NoError(t, err) - - require.True(t, reflect.DeepEqual(block, recoveredBlock), "mismatch in serialized and deserialized block") -} diff --git a/store/store_test.go b/store/store_test.go new file mode 100644 index 0000000..446c983 --- /dev/null +++ b/store/store_test.go @@ -0,0 +1,21 @@ +package store + +import ( + cosmosStore "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +func setup() (ctx sdk.Context, key sdk.StoreKey) { + db := db.NewMemDB() + ms := cosmosStore.NewCommitMultiStore(db) + + ctx = sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + key = sdk.NewKVStoreKey("store") + ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + + return ctx, key +} From 4a9f92a2d9a13e69068edd91b755d984f289f76e Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sat, 11 May 2019 14:20:07 -0700 Subject: [PATCH 13/49] added tests to store, failing on deep equal --- store/txStore.go | 4 +- store/txStore_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/store/txStore.go b/store/txStore.go index 1e25a33..d455796 100644 --- a/store/txStore.go +++ b/store/txStore.go @@ -144,9 +144,9 @@ func (store TxStore) SpendUTXO(ctx sdk.Context, pos plasma.Position, spender []b tx, ok := store.GetTx(ctx, hash) if !ok { - return sdk.ErrUnknownRequest(fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() + return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() } else if tx.Spent[pos.OutputIndex] { - return sdk.ErrUnauthorized(fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() + return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() } tx.Spent[pos.OutputIndex] = true diff --git a/store/txStore_test.go b/store/txStore_test.go index a725a1c..a398ec7 100644 --- a/store/txStore_test.go +++ b/store/txStore_test.go @@ -1,15 +1,22 @@ package store import ( + "fmt" "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/utils" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" "reflect" "testing" ) +func GetPosition(posStr string) plasma.Position { + pos, _ := plasma.FromPositionString(posStr) + return pos +} + func TestTxSerialization(t *testing.T) { hashes := []byte("allam") var sigs [65]byte @@ -37,3 +44,119 @@ func TestTxSerialization(t *testing.T) { require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in serialized and deserialized transactions") } + +// Test Get, Has, Store, Spend functions +func TestTransactions(t *testing.T) { + // Setup + ctx, key := setup() + txStore := NewTxStore(key) + + privKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privKey.PublicKey) + + sampleSig := [65]byte{} + sampleSig[0] = byte(1) + sampleConfirmSig := [][65]byte{sampleSig} + confirmationHash := []byte("confirmation hash") + + type validationCase struct { + reason string + plasma.Transaction + plasma.Position + } + + txs := []validationCase{ + validationCase{ + reason: "tx with two inputs and one output", + Transaction: plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(GetPosition("(122.3.1.0)"), sampleSig, nil), plasma.NewInput(GetPosition("(17622.13.5.0)"), sampleSig, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1)}, + Fee: utils.Big0, + }, + Position: GetPosition("(8765.6847.0.0)"), + }, + validationCase{ + reason: "tx with one input and two output", + Transaction: plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(GetPosition("(4.1234.1.0)"), sampleSig, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1), plasma.NewOutput(addr, utils.Big1)}, + Fee: utils.Big0, + }, + Position: GetPosition("(4354.8765.0.0)"), + }, + validationCase{ + reason: "tx with two input and one output", + Transaction: plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(GetPosition("(123.1.0.0)"), sampleSig, nil), plasma.NewInput(GetPosition("(10.12.1.0)"), sampleSig, sampleConfirmSig)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1)}, + Fee: utils.Big0, + }, + Position: GetPosition("(11.123.0.0)"), + }, + validationCase{ + reason: "tx with two input and two outputs", + Transaction: plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(GetPosition("(132.231.1.0)"), sampleSig, nil), plasma.NewInput(GetPosition("(635.927.1.0)"), sampleSig, sampleConfirmSig)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1), plasma.NewOutput(addr, utils.Big1)}, + Fee: utils.Big0, + }, + Position: GetPosition("(1121234.12.0.0)"), + }, + } + + for i, plasmaTx := range txs { + pos := plasmaTx.Position + // Retrieve/Spend nonexistent txs + exists := txStore.HasTx(ctx, plasmaTx.Transaction.TxHash()) + require.False(t, exists, "returned true for nonexistent transaction") + recoveredTx, ok := txStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) + require.Empty(t, recoveredTx, "did not return empty struct for nonexistent transaction") + require.False(t, ok, "did not return error on nonexistent transaction") + + // Create and store new transaction + tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs))} + txStore.StoreTx(ctx, tx) + + // Create Outputs + for j, _ := range plasmaTx.Transaction.Outputs { + p := plasma.NewPosition(pos.BlockNum, pos.TxIndex, uint8(j), pos.DepositNonce) + exists = txStore.HasUTXO(ctx, p) + require.False(t, exists, "returned true for nonexistent output") + res := txStore.SpendUTXO(ctx, p, plasmaTx.Transaction.MerkleHash()) + require.Equal(t, res.Code, CodeOutputDNE, "did not return that utxo does not exist") + + txStore.StoreUTXO(ctx, p, plasmaTx.Transaction.TxHash()) + exists = txStore.HasUTXO(ctx, p) + require.True(t, exists, "returned false for stored utxo") + } + + // Check for Tx + exists = txStore.HasTx(ctx, plasmaTx.Transaction.TxHash()) + require.True(t, exists, "returned false for transaction that was stored") + recoveredTx, ok = txStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) + require.True(t, ok, "error when retrieving transaction") + require.Equal(t, tx, recoveredTx, fmt.Sprintf("mismatch in stored transaction and retrieved transaction before spends on case %d", i)) + + // Spend Outputs + for j, _ := range plasmaTx.Transaction.Outputs { + p := plasma.NewPosition(pos.BlockNum, pos.TxIndex, uint8(j), pos.DepositNonce) + recoveredTx, ok = txStore.GetTxWithPosition(ctx, p) + require.True(t, ok, "error when retrieving transaction") + require.True(t, reflect.DeepEqual(tx, recoveredTx), fmt.Sprintf("mismatch in stored transaction and retrieved transaction on case %d", i)) + + res := txStore.SpendUTXO(ctx, p, plasmaTx.Transaction.MerkleHash()) + require.True(t, res.IsOK(), "returned error when spending utxo") + res = txStore.SpendUTXO(ctx, p, plasmaTx.Transaction.MerkleHash()) + require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") + + tx.Spent[j] = true + tx.Spenders[j] = plasmaTx.Transaction.MerkleHash() + recoveredTx, ok = txStore.GetTxWithPosition(ctx, p) + require.True(t, ok, "error when retrieving transaction") + require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in stored and retrieved transaction") + recoveredTx, ok = txStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) + require.True(t, ok, "error when retrieving transaction") + require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in stored and retrieved transaction") + } + } +} From 591b3ac4056e96d040136ebdfbd9dc76e5e65902 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Sat, 11 May 2019 15:41:54 -0700 Subject: [PATCH 14/49] wip rest server --- client/plasmacli/main.go | 20 ++-- client/plasmacli/query/balance.go | 20 ++-- client/plasmacli/query/block.go | 30 ++++-- client/plasmacli/query/info.go | 30 ++++-- client/plasmacli/rest.go | 172 ++++++++++++++++++++++++++++++ client/plasmacli/spend.go | 28 ++--- go.mod | 3 +- go.sum | 2 + query/account.go | 41 ------- server/app/app.go | 5 +- {query => store}/querier.go | 47 ++++++-- 11 files changed, 291 insertions(+), 107 deletions(-) create mode 100644 client/plasmacli/rest.go delete mode 100644 query/account.go rename {query => store}/querier.go (65%) diff --git a/client/plasmacli/main.go b/client/plasmacli/main.go index a32d543..532c505 100644 --- a/client/plasmacli/main.go +++ b/client/plasmacli/main.go @@ -44,22 +44,19 @@ func init() { func main() { cobra.EnableCommandSorting = false - rootCmd.Flags().String(client.FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") + rootCmd.PersistentFlags().String(client.FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") + rootCmd.PersistentFlags().Bool(client.FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)") rootCmd.PersistentFlags().StringP(store.DirFlag, "d", homeDir, "directory for plasmacli") - if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { - fmt.Println(err) - } - if err := viper.BindPFlags(rootCmd.Flags()); err != nil { + viper.Set(client.FlagListenAddr, "tcp://localhost:1317") + + if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { fmt.Println(err) } - viper.Set(client.FlagListenAddr, "tcp://localhost:1317") - viper.AddConfigPath(homeDir) plasmaDir := filepath.Join(homeDir, "plasma.toml") if _, err := os.Stat(plasmaDir); os.IsNotExist(err) { - config.WritePlasmaConfigFile(plasmaDir, config.DefaultPlasmaConfig()) } @@ -69,18 +66,17 @@ func main() { eth.ProveCmd(), includeCmd, client.LineBreak, + signCmd, spendCmd, client.LineBreak, + keys.KeysCmd(), client.LineBreak, + versionCmd, ) - Execute() -} - -func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/client/plasmacli/query/balance.go b/client/plasmacli/query/balance.go index 1b06eb4..3141377 100644 --- a/client/plasmacli/query/balance.go +++ b/client/plasmacli/query/balance.go @@ -1,11 +1,11 @@ package query import ( - "encoding/json" "fmt" ks "github.com/FourthState/plasma-mvp-sidechain/client/store" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" + "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" ) @@ -26,19 +26,23 @@ var balanceCmd = &cobra.Command{ return err } - queryRoute := fmt.Sprintf("custom/utxo/balance/%s", addr.Hex()) - data, err := ctx.Query(queryRoute, nil) + total, err := Balance(ctx, addr) if err != nil { return err } - var total string - if err := json.Unmarshal(data, &total); err != nil { - return err - } - fmt.Printf("Address: %0x\n", addr) fmt.Printf("Total: %s\n", total) return nil }, } + +func Balance(ctx context.CLIContext, addr common.Address) (string, error) { + queryRoute := fmt.Sprintf("custom/utxo/balance/%s", addr.Hex()) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return "", err + } + + return string(data), nil +} diff --git a/client/plasmacli/query/block.go b/client/plasmacli/query/block.go index d1ba59d..1883639 100644 --- a/client/plasmacli/query/block.go +++ b/client/plasmacli/query/block.go @@ -3,7 +3,7 @@ package query import ( "encoding/json" "fmt" - "github.com/FourthState/plasma-mvp-sidechain/query" + "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/cosmos/cosmos-sdk/client/context" "github.com/spf13/cobra" "strings" @@ -21,20 +21,30 @@ var blockCmd = &cobra.Command{ ctx := context.NewCLIContext().WithTrustNode(true) num := strings.TrimSpace(args[0]) - queryPath := fmt.Sprintf("custom/plasma/block/%s", num) - data, err := ctx.Query(queryPath, nil) + block, err := Block(ctx, num) if err != nil { return err } - var resp query.BlockResp - if err := json.Unmarshal(data, &resp); err != nil { - return err - } + fmt.Printf("Block Header: 0x%x\n", block.Header) + fmt.Printf("Transaction Count: %d, FeeAmount: %d\n", block.TxnCount, block.FeeAmount) + fmt.Printf("Tendermint BlockHeight: %d\n", block.TMBlockHeight) - fmt.Printf("Block Header: 0x%x\n", resp.Header) - fmt.Printf("Transaction Count: %d, FeeAmount: %d\n", resp.TxnCount, resp.FeeAmount) - fmt.Printf("Tendermint BlockHeight: %d\n", resp.TMBlockHeight) return nil }, } + +func Block(ctx context.CLIContext, num string) (store.Block, error) { + queryPath := fmt.Sprintf("custom/plasma/block/%s", num) + data, err := ctx.Query(queryPath, nil) + if err != nil { + return store.Block{}, err + } + + var block store.Block + if err := json.Unmarshal(data, &block); err != nil { + return store.Block{}, err + } + + return block, nil +} diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index 3a65406..9e318be 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" "strings" @@ -20,26 +19,19 @@ var infoCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Short: "Information on owned utxos valid and invalid", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCLIContext().WithCodec(codec.New()) + ctx := context.NewCLIContext() addrStr := strings.TrimSpace(args[0]) if !common.IsHexAddress(addrStr) { return fmt.Errorf("Invalid address provided. Please use hex format") } addr := common.HexToAddress(addrStr) - fmt.Printf("Querying information for 0x%x\n", addr) + fmt.Printf("Querying information for 0x%x\n\n", addr) - // query for all utxos owned by this address - queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) - data, err := ctx.Query(queryRoute, nil) + utxos, err := Info(ctx, addr) if err != nil { return err } - var utxos []store.UTXO - if err := json.Unmarshal(data, &utxos); err != nil { - return err - } - for i, utxo := range utxos { fmt.Printf("UTXO %d\n", i) fmt.Printf("Position: %s, Amount: %s, Spent: %t\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent) @@ -68,3 +60,19 @@ var infoCmd = &cobra.Command{ return nil }, } + +func Info(ctx context.CLIContext, addr common.Address) ([]store.UTXO, error) { + // query for all utxos owned by this address + queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return nil, err + } + + var utxos []store.UTXO + if err := json.Unmarshal(data, &utxos); err != nil { + return nil, err + } + + return utxos, nil +} diff --git a/client/plasmacli/rest.go b/client/plasmacli/rest.go new file mode 100644 index 0000000..1f0ce70 --- /dev/null +++ b/client/plasmacli/rest.go @@ -0,0 +1,172 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "github.com/FourthState/plasma-mvp-sidechain/client/plasmacli/query" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/server/app" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/lcd" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "github.com/gorilla/mux" + "github.com/tendermint/tendermint/types" + "io/ioutil" + "net/http" +) + +func init() { + cdc := app.MakeCodec() + rootCmd.AddCommand(lcd.ServeCommand(cdc, registerRoutes)) +} + +// RegisterRoutes - Central function to define routes that get registered by the main application +func registerRoutes(rs *lcd.RestServer) { + ctx := rs.CliCtx.WithTrustNode(true) + r := rs.Mux + + r.HandleFunc("/balance/{address}", balanceHandler(ctx)).Methods("GET") + r.HandleFunc("/block/{num}", blockHandler(ctx)).Methods("GET") + r.HandleFunc("/info/{address}", infoHandler(ctx)).Methods("GET") + r.HandleFunc("/submit", submitHandler(ctx)).Methods("POST") + + r.HandleFunc("/blocks", blocksHandler(ctx)).Methods("GET") +} + +func balanceHandler(ctx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + addr := vars["address"] + if !common.IsHexAddress(addr) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("address must be a 20-byte hex string")) + return + } + + total, err := query.Balance(ctx, common.HexToAddress(addr)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + // Log the error + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte(total)) + } +} + +func infoHandler(ctx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + addr := vars["address"] + if !common.IsHexAddress(addr) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("address must be a 20-byte hex string")) + return + } + + utxos, err := query.Info(ctx, common.HexToAddress(addr)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + // Log the error + return + } + + data, err := json.Marshal(utxos) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + // log the error + return + } + + w.WriteHeader(http.StatusOK) + w.Write(data) + } +} + +func submitHandler(ctx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + txBytes, err := ioutil.ReadAll(hex.NewDecoder(r.Body)) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("unable to read transaction bytes. Body must be in hex format")) + return + } + + var tx plasma.Transaction + if err := rlp.DecodeBytes(txBytes, &tx); err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("malformed tx bytes")) + return + } + + if err := tx.ValidateBasic(); err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + // deliver the tx + _, err = ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(http.StatusOK) + } +} + +// retrieve full information about a block +func blockHandler(ctx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + num := mux.Vars(r)["num"] + + if num == "0" || num == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("plasma block start at 1")) + return + } + + block, err := query.Block(ctx, num) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + // TODO: check the against the codespace type + // maybe the block does not exist? + return + } + + type resp struct { + plasma.Block + Txs []types.Tx + } + + // Query the tendermint block + height := int64(block.TMBlockHeight) + tmBlock, err := ctx.Client.Block(&height) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + // log the error + return + } + + data, err := json.Marshal(resp{block.Block, tmBlock.Block.Data.Txs}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(data) + } +} + +// retrieve metadata about the last 10 blocks +func blocksHandler(ctx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + } +} diff --git a/client/plasmacli/spend.go b/client/plasmacli/spend.go index 5df14d2..a9cebd7 100644 --- a/client/plasmacli/spend.go +++ b/client/plasmacli/spend.go @@ -46,7 +46,7 @@ Usage: Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - ctx := context.NewCLIContext() + //ctx := context.NewCLIContext() // parse accounts var accs []string @@ -161,19 +161,23 @@ Usage: return err } - // broadcast to the node - if viper.GetBool(asyncF) { - if _, err := ctx.BroadcastTxAsync(txBytes); err != nil { - return err - } - } else { - res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) - if err != nil { - return err + fmt.Printf("%x\n", txBytes) + + /* + // broadcast to the node + if viper.GetBool(asyncF) { + if _, err := ctx.BroadcastTxAsync(txBytes); err != nil { + return err + } + } else { + res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + return err + } + fmt.Printf("Committed at block %d. Hash 0x%x\n", res.Height, res.TxHash) } - fmt.Printf("Committed at block %d. Hash 0x%x\n", res.Height, res.TxHash) - } + */ return nil }, } diff --git a/go.mod b/go.mod index 6f24fa6..b61d38d 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/go-logfmt/logfmt v0.4.0 // indirect github.com/google/gofuzz v1.0.0 // indirect github.com/google/uuid v1.1.1 // indirect - github.com/gorilla/mux v1.7.0 // indirect + github.com/gorilla/mux v1.7.0 github.com/gorilla/websocket v1.4.0 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -38,6 +38,7 @@ require ( github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect github.com/prometheus/common v0.2.0 // indirect github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137 // indirect + github.com/rakyll/statik v0.1.6 // indirect github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 // indirect github.com/rjeczalik/notify v0.9.2 // indirect github.com/rs/cors v1.6.0 // indirect diff --git a/go.sum b/go.sum index 6282b2a..51c2d8e 100644 --- a/go.sum +++ b/go.sum @@ -134,6 +134,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137 h1:3l8oligPtjd4JuM+OZ+U8sjtwFGJs98cdWsqs6QZRWs= github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= +github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= diff --git a/query/account.go b/query/account.go deleted file mode 100644 index 79a572b..0000000 --- a/query/account.go +++ /dev/null @@ -1,41 +0,0 @@ -package query - -import ( - "errors" - "github.com/FourthState/plasma-mvp-sidechain/store" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "math/big" -) - -func queryBalance(ctx sdk.Context, utxoStore store.UTXOStore, addr common.Address) (*big.Int, error) { - iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) - total := big.NewInt(0) - for ; iter.Valid(); iter.Next() { - utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) - if !ok { - return nil, errors.New("invalid key retrieved") - } - - if !utxo.Spent { - total = total.Add(total, utxo.Output.Amount) - } - } - - return total, nil -} - -func queryInfo(ctx sdk.Context, utxoStore store.UTXOStore, addr common.Address) ([]store.UTXO, error) { - var utxos []store.UTXO - iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) - for ; iter.Valid(); iter.Next() { - utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) - if !ok { - return nil, errors.New("invalid key retrieved") - } - - utxos = append(utxos, utxo) - } - - return utxos, nil -} diff --git a/server/app/app.go b/server/app/app.go index ecb171d..78346c6 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -8,7 +8,6 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/handlers" "github.com/FourthState/plasma-mvp-sidechain/msgs" "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/query" "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" @@ -113,8 +112,8 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio // custom queriers app.QueryRouter(). - AddRoute("utxo", query.NewUtxoQuerier(utxoStore)). - AddRoute("plasma", query.NewPlasmaQuerier(plasmaStore)) + AddRoute("utxo", store.NewUtxoQuerier(utxoStore)). + AddRoute("plasma", store.NewPlasmaQuerier(plasmaStore)) // Set the AnteHandler app.SetAnteHandler(handlers.NewAnteHandler(app.utxoStore, app.plasmaStore, plasmaClient)) diff --git a/query/querier.go b/store/querier.go similarity index 65% rename from query/querier.go rename to store/querier.go index 71c1980..55fb452 100644 --- a/query/querier.go +++ b/store/querier.go @@ -1,8 +1,7 @@ -package query +package store import ( "encoding/json" - "github.com/FourthState/plasma-mvp-sidechain/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" abci "github.com/tendermint/tendermint/abci/types" @@ -14,7 +13,7 @@ const ( QueryInfo = "info" ) -func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { +func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, sdk.ErrUnknownRequest("path not specified") @@ -39,10 +38,10 @@ func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { addr := common.HexToAddress(path[1]) utxos, err := queryInfo(ctx, utxoStore, addr) if err != nil { - return nil, sdk.ErrInternal("failed utxo retrieval") + return nil, err } - data, err := json.Marshal(utxos) - if err != nil { + data, e := json.Marshal(utxos) + if e != nil { return nil, sdk.ErrInternal("serialization error") } return data, nil @@ -53,13 +52,43 @@ func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { } } +func queryBalance(ctx sdk.Context, utxoStore UTXOStore, addr common.Address) (*big.Int, sdk.Error) { + iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) + total := big.NewInt(0) + for ; iter.Valid(); iter.Next() { + utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) + if !ok { + return nil, sdk.ErrInternal("failed utxo retrieval") + } + + if !utxo.Spent { + total = total.Add(total, utxo.Output.Amount) + } + } + + return total, nil +} + +func queryInfo(ctx sdk.Context, utxoStore UTXOStore, addr common.Address) ([]UTXO, sdk.Error) { + var utxos []UTXO + iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) + for ; iter.Valid(); iter.Next() { + utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) + if !ok { + return nil, sdk.ErrInternal("failed utxo retrieval") + } + + utxos = append(utxos, utxo) + } + + return utxos, nil +} + const ( QueryBlock = "block" ) -type BlockResp = store.Block - -func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { +func NewPlasmaQuerier(plasmaStore PlasmaStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, sdk.ErrUnknownRequest("path not specified") From db089bf739d8e7cb3c1357bcc0fa7c0e44c3835f Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Sat, 11 May 2019 15:58:52 -0700 Subject: [PATCH 15/49] revert spend to original functionality --- client/plasmacli/spend.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/client/plasmacli/spend.go b/client/plasmacli/spend.go index a9cebd7..5df14d2 100644 --- a/client/plasmacli/spend.go +++ b/client/plasmacli/spend.go @@ -46,7 +46,7 @@ Usage: Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - //ctx := context.NewCLIContext() + ctx := context.NewCLIContext() // parse accounts var accs []string @@ -161,23 +161,19 @@ Usage: return err } - fmt.Printf("%x\n", txBytes) - - /* - // broadcast to the node - if viper.GetBool(asyncF) { - if _, err := ctx.BroadcastTxAsync(txBytes); err != nil { - return err - } - } else { - res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) - if err != nil { - return err - } - fmt.Printf("Committed at block %d. Hash 0x%x\n", res.Height, res.TxHash) + // broadcast to the node + if viper.GetBool(asyncF) { + if _, err := ctx.BroadcastTxAsync(txBytes); err != nil { + return err + } + } else { + res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + return err } + fmt.Printf("Committed at block %d. Hash 0x%x\n", res.Height, res.TxHash) + } - */ return nil }, } From f22b65ce61fe204c0fb30fb1ffd120c1d14fee2a Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 13 May 2019 15:41:11 -0700 Subject: [PATCH 16/49] store test bug fix --- store/txStore_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/store/txStore_test.go b/store/txStore_test.go index a398ec7..a38dab6 100644 --- a/store/txStore_test.go +++ b/store/txStore_test.go @@ -115,6 +115,9 @@ func TestTransactions(t *testing.T) { // Create and store new transaction tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs))} + for i, _ := range tx.Spenders { + tx.Spenders[i] = []byte{} + } txStore.StoreTx(ctx, tx) // Create Outputs From e79b8e56f80d17a88377f8e0ac6ed69374b38a18 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 14 May 2019 11:45:19 -0700 Subject: [PATCH 17/49] added account, updated querier --- store/account.go | 22 +++++------- store/account_test.go | 31 ++++++++++++++++ store/errors.go | 5 +++ store/querier.go | 46 +++++++++--------------- store/txStore.go | 84 ++++++++++++++++++++++++++++++++++++++++++- store/txStore_test.go | 2 +- 6 files changed, 144 insertions(+), 46 deletions(-) create mode 100644 store/account_test.go diff --git a/store/account.go b/store/account.go index d23aa7d..36b10ed 100644 --- a/store/account.go +++ b/store/account.go @@ -1,24 +1,18 @@ package store import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" "math/big" ) /* Account */ type Account struct { - Balance *big.Int // total amount avaliable to be spent - // Unspent [][32]utxoCache // hashes of unspent transaction outputs - // Spent [][32]utxoCache // hashes of spent transaction outputs + Balance *big.Int // total amount avaliable to be spent + Unspent []plasma.Position // position of unspent transaction outputs + Spent []plasma.Position // position of spent transaction outputs } -//func (a *Account) EncodeRLP(w io.writer) error { - -//} - -//func (a *Account) DecodeRLP(s *rlp.Stream) error { - -//} - -/* Account helper functions */ -// Ret balance -// Get unspent utxos +// Returns the amount avaliable to be spent +func (acc Account) GetBalance() *big.Int { + return acc.Balance +} diff --git a/store/account_test.go b/store/account_test.go new file mode 100644 index 0000000..d343676 --- /dev/null +++ b/store/account_test.go @@ -0,0 +1,31 @@ +package store + +import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + "math/big" + "reflect" + "testing" +) + +// Test that an account can be serialized and deserialized +func TestAccountSerialization(t *testing.T) { + // Construct Account + acc := Account{ + Balance: big.NewInt(234578), + Unspent: []plasma.Position{GetPosition("(8745.1239.1.0)"), GetPosition("(23409.12456.0.0)"), GetPosition("(894301.1.1.0)"), GetPosition("(0.0.0.540124)")}, + Spent: []plasma.Position{GetPosition("0.0.0.3"), GetPosition("7.734.1.3")}, + } + + // RLP Encode + bytes, err := rlp.EncodeToBytes(&acc) + require.NoError(t, err) + + // RLP Decode + recoveredAcc := Account{} + err = rlp.DecodeBytes(bytes, &recoveredAcc) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(acc, recoveredAcc), "mismatch in serialized and deserialized account") +} diff --git a/store/errors.go b/store/errors.go index 9db98ef..10a7e56 100644 --- a/store/errors.go +++ b/store/errors.go @@ -9,6 +9,7 @@ const ( CodeOutputDNE sdk.CodeType = 1 CodeOutputSpent sdk.CodeType = 2 + CodeAccountDNE sdk.CodeType = 3 ) func ErrOutputDNE(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { @@ -18,3 +19,7 @@ func ErrOutputDNE(codespace sdk.CodespaceType, msg string, args ...interface{}) func ErrOutputSpent(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { return sdk.NewError(codespace, CodeOutputSpent, msg, args) } + +func ErrAccountDNE(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { + return sdk.NewError(codespace, CodeAccountDNE, msg, args) +} diff --git a/store/querier.go b/store/querier.go index 55fb452..28a6ab4 100644 --- a/store/querier.go +++ b/store/querier.go @@ -2,6 +2,7 @@ package store import ( "encoding/json" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" abci "github.com/tendermint/tendermint/abci/types" @@ -13,7 +14,7 @@ const ( QueryInfo = "info" ) -func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { +func NewTxQuerier(txStore TxStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, sdk.ErrUnknownRequest("path not specified") @@ -25,7 +26,7 @@ func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { return nil, sdk.ErrUnknownRequest("balance query follows balance/
") } addr := common.HexToAddress(path[1]) - total, err := queryBalance(ctx, utxoStore, addr) + total, err := queryBalance(ctx, txStore, addr) if err != nil { return nil, sdk.ErrInternal("failed query balance") } @@ -36,7 +37,7 @@ func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { return nil, sdk.ErrUnknownRequest("info query follows /info/
") } addr := common.HexToAddress(path[1]) - utxos, err := queryInfo(ctx, utxoStore, addr) + utxos, err := queryInfo(ctx, txStore, addr) if err != nil { return nil, err } @@ -52,43 +53,28 @@ func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { } } -func queryBalance(ctx sdk.Context, utxoStore UTXOStore, addr common.Address) (*big.Int, sdk.Error) { - iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) - total := big.NewInt(0) - for ; iter.Valid(); iter.Next() { - utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) - if !ok { - return nil, sdk.ErrInternal("failed utxo retrieval") - } - - if !utxo.Spent { - total = total.Add(total, utxo.Output.Amount) - } +func queryBalance(ctx sdk.Context, txStore TxStore, addr common.Address) (*big.Int, sdk.Error) { + acc, ok := txStore.GetAccount(ctx, addr) + if !ok { + return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) } - return total, nil + return acc.GetBalance(), nil } -func queryInfo(ctx sdk.Context, utxoStore UTXOStore, addr common.Address) ([]UTXO, sdk.Error) { - var utxos []UTXO - iter := sdk.KVStorePrefixIterator(utxoStore.KVStore(ctx), addr.Bytes()) - for ; iter.Valid(); iter.Next() { - utxo, ok := utxoStore.GetUTXOWithKey(ctx, iter.Key()) - if !ok { - return nil, sdk.ErrInternal("failed utxo retrieval") - } - - utxos = append(utxos, utxo) +func queryInfo(ctx sdk.Context, txStore TxStore, addr common.Address) ([]Output, sdk.Error) { + acc, ok := txStore.GetAccount(ctx, addr) + if !ok { + return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) } - - return utxos, nil + return txStore.GetUnspentForAccount(ctx, acc), nil } const ( QueryBlock = "block" ) -func NewPlasmaQuerier(plasmaStore PlasmaStore) sdk.Querier { +func NewBlockQuerier(blockStore BlockStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, sdk.ErrUnknownRequest("path not specified") @@ -103,7 +89,7 @@ func NewPlasmaQuerier(plasmaStore PlasmaStore) sdk.Querier { if !ok { return nil, sdk.ErrUnknownRequest("block number must be provided in deicmal format") } - block, ok := plasmaStore.GetBlock(ctx, blockNum) + block, ok := blockStore.GetBlock(ctx, blockNum) if !ok { return nil, sdk.ErrUnknownRequest("nonexistent plasma block") } diff --git a/store/txStore.go b/store/txStore.go index d455796..e201d92 100644 --- a/store/txStore.go +++ b/store/txStore.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" + "math/big" ) const ( @@ -20,6 +21,7 @@ type Transaction struct { ConfirmationHash []byte Spent []bool Spenders [][]byte + Position plasma.Position } /* Wrap plasma output with spend information */ @@ -120,6 +122,17 @@ func (store TxStore) HasUTXO(ctx sdk.Context, pos plasma.Position) bool { return store.HasTx(ctx, hash) } +// Store the given Account +func (store TxStore) StoreAccount(ctx sdk.Context, addr common.Address, acc Account) { + key := prefixKey(accountKey, addr.Bytes()) + data, err := rlp.EncodeToBytes(&acc) + if err != nil { + panic(fmt.Sprintf("error marshaling transaction: %s", err)) + } + + store.Set(ctx, key, data) +} + // Add a mapping from transaction hash to transaction func (store TxStore) StoreTx(ctx sdk.Context, tx Transaction) { data, err := rlp.EncodeToBytes(&tx) @@ -129,6 +142,7 @@ func (store TxStore) StoreTx(ctx sdk.Context, tx Transaction) { key := prefixKey(hashKey, tx.Transaction.TxHash()) store.Set(ctx, key, data) + store.storeUTXOsWithAccount(ctx, tx) } // Add a mapping from position to transaction hash @@ -137,7 +151,7 @@ func (store TxStore) StoreUTXO(ctx sdk.Context, pos plasma.Position, hash []byte store.Set(ctx, key, hash) } -// Updates Spent and Spender in transaction that contains this utxo +// Updates Spent, Spender fields and Account associated with this utxo func (store TxStore) SpendUTXO(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { key := prefixKey(positionKey, pos.Bytes()) hash := store.Get(ctx, key) @@ -153,6 +167,74 @@ func (store TxStore) SpendUTXO(ctx sdk.Context, pos plasma.Position, spender []b tx.Spenders[pos.OutputIndex] = spender store.StoreTx(ctx, tx) + store.spendUTXOWithAccount(ctx, pos, tx.Transaction) return sdk.Result{} } + +/* Helpers */ + +func (store TxStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []Output) { + for _, p := range acc.Unspent { + utxo, ok := store.GetUTXO(ctx, p) + if ok { + utxos = append(utxos, utxo) + } + } + return utxos +} + +func (store TxStore) StoreDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { + store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) +} + +func (store TxStore) storeUTXOsWithAccount(ctx sdk.Context, tx Transaction) { + for i, output := range tx.Transaction.Outputs { + store.addToAccount(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) + } +} + +func (store TxStore) spendDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { + store.subtractFromAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) +} + +func (store TxStore) spendUTXOWithAccount(ctx sdk.Context, pos plasma.Position, plasmaTx plasma.Transaction) { + store.subtractFromAccount(ctx, plasmaTx.Outputs[pos.OutputIndex].Owner, plasmaTx.Outputs[pos.OutputIndex].Amount, pos) +} + +func (store TxStore) addToAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { + acc, ok := store.GetAccount(ctx, addr) + if !ok { + acc = Account{big.NewInt(0), make([]plasma.Position, 0), make([]plasma.Position, 0)} + } + + acc.Balance = new(big.Int).Add(acc.Balance, amount) + acc.Unspent = append(acc.Unspent, pos) + store.StoreAccount(ctx, addr, acc) +} + +func (store TxStore) subtractFromAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { + acc, ok := store.GetAccount(ctx, addr) + if !ok { + panic(fmt.Sprintf("transaction store has been corrupted")) + } + + // Update Account + acc.Balance = new(big.Int).Sub(acc.Balance, amount) + if acc.Balance.Sign() == -1 { + panic(fmt.Sprintf("account with address 0x%x has a negative balance", addr)) + } + + removePosition(acc.Unspent, pos) + acc.Spent = append(acc.Spent, pos) + store.StoreAccount(ctx, addr, acc) +} + +func removePosition(positions []plasma.Position, pos plasma.Position) []plasma.Position { + for i, p := range positions { + if p.String() == pos.String() { + positions = append(positions[:i], positions[i+1:]...) + } + } + return positions +} diff --git a/store/txStore_test.go b/store/txStore_test.go index a38dab6..abde293 100644 --- a/store/txStore_test.go +++ b/store/txStore_test.go @@ -114,7 +114,7 @@ func TestTransactions(t *testing.T) { require.False(t, ok, "did not return error on nonexistent transaction") // Create and store new transaction - tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs))} + tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), GetPosition("(4567.1.1.0)")} for i, _ := range tx.Spenders { tx.Spenders[i] = []byte{} } From 714b0ed93dc3895788bd440afea63e19dc36fe6d Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Wed, 15 May 2019 11:57:47 -0700 Subject: [PATCH 18/49] commit current work, prep to merge deposit/tx stores --- handlers/anteHandler_test.go | 262 ++++++++++++++++++----------------- handlers/depositHandler.go | 4 +- handlers/handler_test.go | 19 +-- handlers/spendMsgHandler.go | 12 +- server/app/app.go | 2 +- store/txStore.go | 2 +- 6 files changed, 159 insertions(+), 142 deletions(-) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index b275d74..1035bd9 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -21,13 +21,21 @@ var ( badPrivKey, _ = crypto.GenerateKey() ) -type inputUTXO struct { - BlockNum *big.Int - TxIndex uint16 - OIndex uint8 - DepositNonce *big.Int - Address common.Address - Spent bool +type Tx struct { + Transaction plasma.Transaction + ConfirmationHash []byte + Spent []bool + Spenders [][]byte + Position plasma.Position +} + +type Deposit struct { + Owner common.Address + Nonce *big.Int + EthBlockNum *big.Int + Amount *big.Int + Spent bool + Spender []byte } // cook the plasma connection @@ -41,7 +49,7 @@ func (p conn) HasTxBeenExited(tmBlock *big.Int, pos plasma.Position) bool { retu var _ plasmaConn = conn{} -// cook up different plasma connection that will always claim input exitted +// cook up different plasma connection that will always claim Inputs exitted type exitConn struct{} // all deposits should be in an amount of 10eth owner by addr(defined above) @@ -52,16 +60,16 @@ func (p exitConn) HasTxBeenExited(tmBlock *big.Int, pos plasma.Position) bool { func TestAnteChecks(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() - handler := NewAnteHandler(utxoStore, plasmaStore, conn{}) - - // cook up some input UTXOs to start in UTXO store - inputs := []inputUTXO{ - {nil, 0, 0, utils.Big1, addr, false}, - {nil, 0, 0, utils.Big2, addr, false}, - {nil, 0, 0, big.NewInt(3), addr, true}, + ctx, txStore, depositStore, blockStore := setup() + handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) + + // cook up some input deposits + inputs := []Deposit{ + {addr, big.NewInt(1), big.NewInt(100), big.NewInt(10), false, []byte{}}, + {addr, big.NewInt(2), big.NewInt(101), big.NewInt(10), false, []byte{}}, + {addr, big.NewInt(3), big.NewInt(102), big.NewInt(10), false, []byte{}}, } - setupInputs(ctx, utxoStore, inputs...) + setupDeposits(ctx, depositStore, inputs...) type validationCase struct { reason string @@ -70,16 +78,14 @@ func TestAnteChecks(t *testing.T) { // cases to check for. cases with signature checks will get set subsequent to this step // array of pointers because we are setting signatures after using `range` - // since InputKeys not set, confirm signatures will simply be 0 bytes + // since InputsKeys not set, confirm signatures will simply be 0 bytes invalidCases := []*validationCase{ &validationCase{ reason: "incorrect first signature", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big0), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(common.Address{}, utils.Big0), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, }, @@ -88,10 +94,8 @@ func TestAnteChecks(t *testing.T) { reason: "incorrect second signature", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(20)), - Output1: plasma.NewOutput(common.Address{}, utils.Big0), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(20))}, Fee: utils.Big0, }, }, @@ -100,10 +104,8 @@ func TestAnteChecks(t *testing.T) { reason: "no signatures", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(20)), - Output1: plasma.NewOutput(common.Address{}, utils.Big0), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(20))}, Fee: utils.Big0, }, }, @@ -112,10 +114,8 @@ func TestAnteChecks(t *testing.T) { reason: "invalid fee", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(20)), - Output1: plasma.NewOutput(common.Address{}, utils.Big0), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(20))}, Fee: big.NewInt(20), }, }, @@ -124,46 +124,38 @@ func TestAnteChecks(t *testing.T) { reason: "unbalanced transaction", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(10)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big1, }, }, }, &validationCase{ - reason: "input deposit utxo does not exist", + reason: "Inputs deposit utxo does not exist", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, big.NewInt(4)), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(10)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, big.NewInt(4)), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, }, }, &validationCase{ - reason: "input transaction utxo does not exist", + reason: "Inputs transaction utxo does not exist", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(utils.Big1, 3, 1, nil), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(10)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(utils.Big1, 3, 1, nil), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, }, }, &validationCase{ - reason: "input utxo already spent", + reason: "Inputs utxo already spent", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, big.NewInt(3)), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(10)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, big.NewInt(3)), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, }, @@ -173,21 +165,21 @@ func TestAnteChecks(t *testing.T) { // set invalid first signature txHash := utils.ToEthSignedMessageHash(invalidCases[0].SpendMsg.TxHash()) sig, _ := crypto.Sign(txHash, badPrivKey) - copy(invalidCases[0].SpendMsg.Input0.Signature[:], sig) + copy(invalidCases[0].SpendMsg.Inputs[0].Signature[:], sig) // set invalid second signature but correct first signature txHash = utils.ToEthSignedMessageHash(invalidCases[1].SpendMsg.TxHash()) sig, _ = crypto.Sign(txHash, badPrivKey) - copy(invalidCases[1].SpendMsg.Input1.Signature[:], sig) + copy(invalidCases[1].SpendMsg.Inputs[1].Signature[:], sig) sig, _ = crypto.Sign(txHash, privKey) - copy(invalidCases[1].SpendMsg.Input0.Signature[:], sig) + copy(invalidCases[1].SpendMsg.Inputs[0].Signature[:], sig) // set valid signatures for remaining cases for _, txCase := range invalidCases[3:] { txHash = utils.ToEthSignedMessageHash(txCase.SpendMsg.TxHash()) sig, _ = crypto.Sign(txHash, privKey) - copy(txCase.SpendMsg.Input0.Signature[:], sig[:]) - copy(txCase.SpendMsg.Input1.Signature[:], sig[:]) + copy(txCase.SpendMsg.Inputs[0].Signature[:], sig[:]) + copy(txCase.SpendMsg.Inputs[1].Signature[:], sig[:]) } for _, txCase := range invalidCases { @@ -197,29 +189,27 @@ func TestAnteChecks(t *testing.T) { } } -func TestAnteExitedInput(t *testing.T) { +func TestAnteExitedInputs(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() - handler := NewAnteHandler(utxoStore, plasmaStore, exitConn{}) + ctx, txStore, depositStore, blockStore := setup() + handler := NewAnteHandler(txStore, depositStore, blockStore, exitConn{}) - // place input in store - input := inputUTXO{ - BlockNum: utils.Big1, - TxIndex: 0, - OIndex: 0, - DepositNonce: nil, - Spent: false, + // place Inputs in store + inputs := Tx{ + Transaction: , Address: addr, + ConfirmationHash: , + Spent: []bool{false}, + Spenders: [][]byte{}, + Position: []plasma.Position[] } - setupInputs(ctx, utxoStore, input) + setupTxs(ctx, txStore, inputs) // create msg spendMsg := msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(9)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(9))}, Fee: utils.Big1, }, } @@ -227,47 +217,45 @@ func TestAnteExitedInput(t *testing.T) { // set signature txHash := utils.ToEthSignedMessageHash(spendMsg.TxHash()) sig, _ := crypto.Sign(txHash, privKey) - copy(spendMsg.Input0.Signature[:], sig[:]) + copy(spendMsg.Inputs[0].Signature[:], sig[:]) _, res, abort := handler(ctx, spendMsg, false) - require.False(t, res.IsOK(), "Result OK even though input exitted") - require.True(t, abort, "Did not abort tx even though input exitted") + require.False(t, res.IsOK(), "Result OK even though inputs exitted") + require.True(t, abort, "Did not abort tx even though inputs exitted") // TODO: test case where grandparent exitted but parent didn't } func TestAnteInvalidConfirmSig(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() - handler := NewAnteHandler(utxoStore, plasmaStore, conn{}) + ctx, txStore, depositStore, blockStore := setup() + handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) - // place input in store - inputs := []inputUTXO{ - {nil, 0, 0, utils.Big1, addr, false}, - {nil, 0, 0, utils.Big2, addr, true}, + // place inputs in store + inputs := []Deposit{ + {addr, utils.Big1, big.NewInt(50), big.NewInt(10), false, []byte{}}, + {addr, utils.Big2, big.NewInt(55), big.NewInt(10), false, []byte{}}, } - setupInputs(ctx, utxoStore, inputs...) + setupDeposits(ctx, depositStore, inputs...) parentTx := msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(common.Address{}, nil), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, } // set regular transaction utxo in store // parent input was 0.0.0.2 - // must create input key and confirmation hash + // must create confirmation hash // also need confirm sig of parent in order to spend - inputKey := store.GetUTXOStoreKey(addr, plasma.NewPosition(nil, 0, 0, utils.Big2)) + InputsKey := store.GetUTXOStoreKey(addr, plasma.NewPosition(nil, 0, 0, utils.Big2)) confBytes := sha256.Sum256(append(parentTx.MerkleHash(), ctx.BlockHeader().DataHash...)) confHash := utils.ToEthSignedMessageHash(confBytes[:]) badConfSig, _ := crypto.Sign(confHash, badPrivKey) inputUTXO := store.UTXO{ - InputKeys: [][]byte{inputKey}, + InputsKeys: [][]byte{InputsKey}, ConfirmationHash: confBytes[:], Output: plasma.Output{ Owner: addr, @@ -284,10 +272,8 @@ func TestAnteInvalidConfirmSig(t *testing.T) { // create msg spendMsg := msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{invalidConfirmSig}), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(9)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{invalidConfirmSig}), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(9))}, Fee: utils.Big1, }, } @@ -295,8 +281,8 @@ func TestAnteInvalidConfirmSig(t *testing.T) { // set signature txHash := utils.ToEthSignedMessageHash(spendMsg.TxHash()) sig, _ := crypto.Sign(txHash, privKey) - copy(spendMsg.Input0.Signature[:], sig[:]) - copy(spendMsg.Input1.Signature[:], sig[:]) + copy(spendMsg.Inputs[0].Signature[:], sig[:]) + copy(spendMsg.Inputs[1].Signature[:], sig[:]) _, res, abort := handler(ctx, spendMsg, false) require.False(t, res.IsOK(), "tx OK with invalid parent confirm sig") @@ -306,22 +292,20 @@ func TestAnteInvalidConfirmSig(t *testing.T) { func TestAnteValidTx(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() - handler := NewAnteHandler(utxoStore, plasmaStore, conn{}) + ctx, txStore, depositStore, blockStore := setup() + handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) - // place input in store - inputs := []inputUTXO{ + // place inputs in store + inputs := []InputUTXO{ {nil, 0, 0, utils.Big1, addr, false}, {nil, 0, 0, utils.Big2, addr, true}, } - setupInputs(ctx, utxoStore, inputs...) + setupInputs(ctx, txStore, inputs...) parentTx := msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(common.Address{}, nil), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, } @@ -330,7 +314,7 @@ func TestAnteValidTx(t *testing.T) { // parent input was 0.0.0.2 // must create input key and confirmation hash // also need confirm sig of parent in order to spend - inputKey := store.GetUTXOStoreKey(addr, plasma.NewPosition(nil, 0, 0, utils.Big2)) + InputKey := store.GetUTXOStoreKey(addr, plasma.NewPosition(nil, 0, 0, utils.Big2)) confBytes := sha256.Sum256(append(parentTx.MerkleHash(), ctx.BlockHeader().DataHash...)) confHash := utils.ToEthSignedMessageHash(confBytes[:]) confSig, _ := crypto.Sign(confHash, privKey) @@ -352,10 +336,8 @@ func TestAnteValidTx(t *testing.T) { // create msg spendMsg := msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{confirmSig}), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), - Output0: plasma.NewOutput(addr, big.NewInt(10)), - Output1: plasma.NewOutput(addr, big.NewInt(9)), + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(utils.Big1, 0, 0, nil), [65]byte{}, [][65]byte{confirmSig}), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(9))}, Fee: utils.Big1, }, } @@ -363,8 +345,8 @@ func TestAnteValidTx(t *testing.T) { // set signature txHash := utils.ToEthSignedMessageHash(spendMsg.TxHash()) sig, _ := crypto.Sign(txHash, privKey) - copy(spendMsg.Input0.Signature[:], sig[:]) - copy(spendMsg.Input1.Signature[:], sig[:]) + copy(spendMsg.Inputs[0].Signature[:], sig[:]) + copy(spendMsg.Inputs[1].Signature[:], sig[:]) _, res, abort := handler(ctx, spendMsg, false) require.True(t, res.IsOK(), "Valid tx does not have OK result") @@ -377,11 +359,11 @@ func TestAnteValidTx(t *testing.T) { func TestAnteDeposit(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() - handler := NewAnteHandler(utxoStore, plasmaStore, conn{}) + ctx, txStore, depositStore, blockStore := setup() + handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) // place input in store - inputs := []inputUTXO{ + inputs := []InputUTXO{ {nil, 0, 0, utils.Big1, addr, false}, {nil, 0, 0, utils.Big2, addr, true}, } @@ -429,9 +411,9 @@ func (d dneConn) HasTxBeenExited(tmBlock *big.Int, pos plasma.Position) bool { r func TestAnteDepositUnfinal(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() + ctx, txStore, depositStore, blockStore := setup() // connection always returns unfinalized deposits - handler := NewAnteHandler(utxoStore, plasmaStore, unfinalConn{}) + handler := NewAnteHandler(txStore, depositStore, blockStore, unfinalConn{}) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -447,9 +429,9 @@ func TestAnteDepositUnfinal(t *testing.T) { func TestAnteDepositExitted(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() + ctx, txStore, depositStore, blockStore := setup() // connection always returns exitted deposits - handler := NewAnteHandler(utxoStore, plasmaStore, exitConn{}) + handler := NewAnteHandler(utxoStore, depositStore, blockStore, exitConn{}) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -465,9 +447,9 @@ func TestAnteDepositExitted(t *testing.T) { func TestAnteDepositDNE(t *testing.T) { // setup - ctx, utxoStore, plasmaStore := setup() + ctx, txStore, depositStore, blockStore := setup() // connection always returns exitted deposits - handler := NewAnteHandler(utxoStore, plasmaStore, dneConn{}) + handler := NewAnteHandler(txStore, depositStore, blockStore, dneConn{}) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -481,16 +463,36 @@ func TestAnteDepositDNE(t *testing.T) { } -func setupInputs(ctx sdk.Context, utxoStore store.UTXOStore, inputs ...inputUTXO) { +func setupDeposits(ctx sdk.Context, txStore store.TxStore, depositStore store.DepositStore, inputs ...InputUTXO) { for _, i := range inputs { - utxo := store.UTXO{ - Output: plasma.Output{ - Owner: i.Address, - Amount: big.NewInt(10), - }, - Spent: i.Spent, - Position: plasma.NewPosition(i.BlockNum, i.TxIndex, i.OIndex, i.DepositNonce), + deposit := store.Deposit{ + Deposit: plasma.Deposit{ + Owner: i.Owner, + Amount: i.Amount, + EthBlockNum: i.EthBlockNum + }, + Spent: i.Spent, + Spender: i.Spender, + } + depositStore.StoreDeposit(ctx, i.Nonce, deposit) + txStore.StoreDepositWithAccount(ctx, i.Nonce, deposit) + } +} + +func setupTxs(ctx sdk.Context, txStore store.TxStore, inputs ...InputUTXO) { + for _, i := range inputs { + pos := plasma.NewPosition(i.BlockNum, i.TxIndex, i.OIndex, i.DepositNonce) + if pos.IsDeposit() { + } else { + tx := store.Transaction{ + Output: plasma.Output{ + Owner: i.Address, + Amount: big.NewInt(10), + }, + Spent: i.Spent, + Position: pos, + } } - utxoStore.StoreUTXO(ctx, utxo) + } } diff --git a/handlers/depositHandler.go b/handlers/depositHandler.go index 8a84af2..0d57d6b 100644 --- a/handlers/depositHandler.go +++ b/handlers/depositHandler.go @@ -6,7 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func NewDepositHandler(depositStore store.DepositStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, client plasmaConn) sdk.Handler { +func NewDepositHandler(depositStore store.DepositStore, txStore store.TxStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, client plasmaConn) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { depositMsg, ok := msg.(msgs.IncludeDepositMsg) if !ok { @@ -24,6 +24,8 @@ func NewDepositHandler(depositStore store.DepositStore, blockStore store.BlockSt Spender: nil, } depositStore.StoreDeposit(ctx, depositMsg.DepositNonce, dep) + txStore.StoreDepositWithAccount(ctx, depositMsg.DepositNonce, deposit) + return sdk.Result{} } } diff --git a/handlers/handler_test.go b/handlers/handler_test.go index 93bfd72..c72d67b 100644 --- a/handlers/handler_test.go +++ b/handlers/handler_test.go @@ -9,21 +9,24 @@ import ( "github.com/tendermint/tendermint/libs/log" ) -func setup() (sdk.Context, store.UTXOStore, store.PlasmaStore) { +func setup() (sdk.Context, store.TxStore, store.DepositStore, store.BlockStore) { db := db.NewMemDB() ms := cosmosStore.NewCommitMultiStore(db) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - plasmaStoreKey := sdk.NewKVStoreKey("plasma") - utxoStoreKey := sdk.NewKVStoreKey("utxo") + blockStoreKey := sdk.NewKVStoreKey("block") + depositStoreKey := sdk.NewKvStoreKey("deposit") + txStoreKey := sdk.NewKVStoreKey("tx") - ms.MountStoreWithDB(plasmaStoreKey, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(utxoStoreKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(blockStoreKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(depositStoreKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(txStoreKey, sdk.StoreTypeIAVL, db) ms.LoadLatestVersion() - plasmaStore := store.NewPlasmaStore(plasmaStoreKey) - utxoStore := store.NewUTXOStore(utxoStoreKey) + blockStore := store.NewBlockStore(blockStoreKey) + depositStore := store.NewDepositStore(depositStoreKey) + txStore := store.NewUTXOStore(txStoreKey) - return ctx, utxoStore, plasmaStore + return ctx, txStore, depositStore, blockStore } diff --git a/handlers/spendMsgHandler.go b/handlers/spendMsgHandler.go index 932de3e..d60ba1d 100644 --- a/handlers/spendMsgHandler.go +++ b/handlers/spendMsgHandler.go @@ -2,6 +2,7 @@ package handlers import ( "crypto/sha256" + "fmt" "github.com/FourthState/plasma-mvp-sidechain/msgs" "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" @@ -36,6 +37,7 @@ func NewSpendHandler(txStore store.TxStore, depositStore store.DepositStore, blo Spent: make([]bool, len(spendMsg.Outputs)), Spenders: make([][]byte, len(spendMsg.Outputs)), ConfirmationHash: confirmationHash[:], + Position: plasma.NewPosition(blockHeight, txIndex, 0, big.NewInt(0)), } txStore.StoreTx(ctx, tx) @@ -43,7 +45,15 @@ func NewSpendHandler(txStore store.TxStore, depositStore store.DepositStore, blo for _, input := range spendMsg.Inputs { var res sdk.Result if input.Position.IsDeposit() { - res = depositStore.SpendDeposit(ctx, input.Position.DepositNonce, spendMsg.TxHash()) + // Spend deposit and update account + nonce := input.Position.DepositNonce + deposit, ok := depositStore.GetDeposit(ctx, nonce) + if !ok { + panic(fmt.Sprintf("deposit store corrupted")) + } + + res = depositStore.SpendDeposit(ctx, nonce, spendMsg.TxHash()) + txStore.SpendDepositWithAccount(ctx, nonce, deposit.Deposit) } else { res = txStore.SpendUTXO(ctx, input.Position, spendMsg.TxHash()) } diff --git a/server/app/app.go b/server/app/app.go index e035446..1f9f2af 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -113,7 +113,7 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio return nil } app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.txStore, app.depositStore, app.blockStore, nextTxIndex, feeUpdater)) - app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.depositStore, app.blockStore, nextTxIndex, plasmaClient)) + app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.depositStore, app.txStore, app.blockStore, nextTxIndex, plasmaClient)) // custom queriers app.QueryRouter(). diff --git a/store/txStore.go b/store/txStore.go index e201d92..493e660 100644 --- a/store/txStore.go +++ b/store/txStore.go @@ -194,7 +194,7 @@ func (store TxStore) storeUTXOsWithAccount(ctx sdk.Context, tx Transaction) { } } -func (store TxStore) spendDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { +func (store TxStore) SpendDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { store.subtractFromAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) } From 5c790dbac2e05619fb93d28597d33c3d928fe374 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Wed, 15 May 2019 12:49:26 -0700 Subject: [PATCH 19/49] updated output store --- store/account.go | 18 -- store/account_test.go | 31 -- store/depositStore.go | 17 - store/outputStore.go | 300 ++++++++++++++++++ .../{txStore_test.go => outputStore_test.go} | 28 -- store/store_test.go | 7 + store/txStore.go | 240 -------------- store/types.go | 36 +++ store/types_test.go | 83 +++++ 9 files changed, 426 insertions(+), 334 deletions(-) delete mode 100644 store/account.go delete mode 100644 store/account_test.go create mode 100644 store/outputStore.go rename store/{txStore_test.go => outputStore_test.go} (85%) delete mode 100644 store/txStore.go create mode 100644 store/types.go create mode 100644 store/types_test.go diff --git a/store/account.go b/store/account.go deleted file mode 100644 index 36b10ed..0000000 --- a/store/account.go +++ /dev/null @@ -1,18 +0,0 @@ -package store - -import ( - "github.com/FourthState/plasma-mvp-sidechain/plasma" - "math/big" -) - -/* Account */ -type Account struct { - Balance *big.Int // total amount avaliable to be spent - Unspent []plasma.Position // position of unspent transaction outputs - Spent []plasma.Position // position of spent transaction outputs -} - -// Returns the amount avaliable to be spent -func (acc Account) GetBalance() *big.Int { - return acc.Balance -} diff --git a/store/account_test.go b/store/account_test.go deleted file mode 100644 index d343676..0000000 --- a/store/account_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package store - -import ( - "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/require" - "math/big" - "reflect" - "testing" -) - -// Test that an account can be serialized and deserialized -func TestAccountSerialization(t *testing.T) { - // Construct Account - acc := Account{ - Balance: big.NewInt(234578), - Unspent: []plasma.Position{GetPosition("(8745.1239.1.0)"), GetPosition("(23409.12456.0.0)"), GetPosition("(894301.1.1.0)"), GetPosition("(0.0.0.540124)")}, - Spent: []plasma.Position{GetPosition("0.0.0.3"), GetPosition("7.734.1.3")}, - } - - // RLP Encode - bytes, err := rlp.EncodeToBytes(&acc) - require.NoError(t, err) - - // RLP Decode - recoveredAcc := Account{} - err = rlp.DecodeBytes(bytes, &recoveredAcc) - require.NoError(t, err) - - require.True(t, reflect.DeepEqual(acc, recoveredAcc), "mismatch in serialized and deserialized account") -} diff --git a/store/depositStore.go b/store/depositStore.go index 2de2122..e52c411 100644 --- a/store/depositStore.go +++ b/store/depositStore.go @@ -8,23 +8,6 @@ import ( "math/big" ) -type Deposit struct { - Deposit plasma.Deposit - Spent bool - Spender []byte -} - -/* Deposit Store */ -type DepositStore struct { - kvStore -} - -func NewDepositStore(ctxKey sdk.StoreKey) DepositStore { - return DepositStore{ - kvStore: NewKVStore(ctxKey), - } -} - func (store DepositStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, bool) { data := store.Get(ctx, nonce.Bytes()) if data == nil { diff --git a/store/outputStore.go b/store/outputStore.go new file mode 100644 index 0000000..7fde5c9 --- /dev/null +++ b/store/outputStore.go @@ -0,0 +1,300 @@ +package store + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "math/big" +) + +const ( + accountKey = "acc" + hashKey = "hash" + positionKey = "pos" + depositKey = "deposit" +) + +/* Output Store */ +type OutputStore struct { + kvStore +} + +func NewOutputStore(ctxKey sdk.StoreKey) OutputStore { + return OutputStore{ + kvStore: NewKVStore(ctxKey), + } +} + +// ----------------------------------------------------------------------------- +/* Getters */ + +// GetAccount returns the account at the associated address +func (store OutputStore) GetAccount(ctx sdk.Context, addr common.Address) (Account, bool) { + key := prefixKey(accountKey, addr.Bytes()) + data := store.Get(ctx, key) + if data == nil { + return Account{}, false + } + + var acc Account + if err := rlp.DecodeBytes(data, &acc); err != nil { + panic(fmt.Sprintf("transaction store corrupted: %s", err)) + } + + return acc, true +} + +// GetDeposit returns the deposit at the given nonce +func (store OutputStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, bool) { + key := prefixKey(depositKey, nonce.Bytes()) + data := store.Get(ctx, key) + if data == nil { + return Deposit{}, false + } + + var deposit Deposit + if err := rlp.DecodeBytes(data, &deposit); err != nil { + panic(fmt.Sprintf("deposit store corrupted: %s", err)) + } + + return deposit, true +} + +// GetTx returns the transaction with the provided transaction hash +func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { + key := prefixKey(hashKey, hash) + data := store.Get(ctx, key) + if data == nil { + return Transaction{}, false + } + + var tx Transaction + if err := rlp.DecodeBytes(data, &tx); err != nil { + panic(fmt.Sprintf("transaction store corrupted: %s", err)) + } + + return tx, true +} + +// GetTxWithPosition returns the transaction that contains the provided position as an output +func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + return store.GetTx(ctx, hash) +} + +// GetOutput returns the output at the given position +func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output, bool) { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + + tx, ok := store.GetTx(ctx, hash) + if !ok { + return Output{}, ok + } + + output := Output{ + Output: tx.Transaction.Outputs[pos.OutputIndex], + Spent: tx.Spent[pos.OutputIndex], + Spender: tx.Spenders[pos.OutputIndex], + } + + return output, ok +} + +// ----------------------------------------------------------------------------- +/* Has */ + +// HasTx returns whether an account at the given address exists +func (store OutputStore) HasAccount(ctx sdk.Context, addr common.Address) bool { + key := prefixKey(accountKey, addr.Bytes()) + return store.Has(ctx, key) +} + +// HasDeposit returns whether a deposit with the given nonce exists +func (store OutputStore) HasDeposit(ctx sdk.Context, nonce *big.Int) bool { + key := prefixKey(depositKey, nonce.Bytes()) + return store.Has(ctx, key) +} + +// HasTx returns whether a transaction with the given transaction hash exists +func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { + key := prefixKey(hashKey, hash) + return store.Has(ctx, key) +} + +// HasOutput returns whether an output with the given position exists +func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + + return store.HasTx(ctx, hash) +} + +// ----------------------------------------------------------------------------- +/* Set */ + +// SetDeposit adds an unspent deposit to the output store +// The deposit owner's account is updated +func (store OutputStore) SetDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { + data, err := rlp.EncodeToBytes(&deposit) + if err != nil { + panic(fmt.Sprintf("error marshaling deposit with nonce %s: %s", nonce, err)) + } + + key := prefixKey(depositKey, nonce.Bytes()) + store.Set(ctx, key, data) +} + +// SetAccount overwrites the account stored at the given address +func (store OutputStore) SetAccount(ctx sdk.Context, addr common.Address, acc Account) { + key := prefixKey(accountKey, addr.Bytes()) + data, err := rlp.EncodeToBytes(&acc) + if err != nil { + panic(fmt.Sprintf("error marshaling transaction: %s", err)) + } + + store.Set(ctx, key, data) +} + +// SetTx adds a mapping from transaction hash to transaction +// Accounts are updated using the transaction's outputs +func (store OutputStore) SetTx(ctx sdk.Context, tx Transaction) { + data, err := rlp.EncodeToBytes(&tx) + if err != nil { + panic(fmt.Sprintf("error marshaling transaction: %s", err)) + } + + key := prefixKey(hashKey, tx.Transaction.TxHash()) + store.Set(ctx, key, data) + store.storeUTXOsWithAccount(ctx, tx) +} + +// SetOutput adds a mapping from position to transaction hash +func (store OutputStore) SetOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { + key := prefixKey(positionKey, pos.Bytes()) + store.Set(ctx, key, hash) +} + +// ----------------------------------------------------------------------------- +/* Spend */ + +// SpendDeposit changes the deposit to be spent +// Updates the account of the deposit owner +func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { + deposit, ok := store.GetDeposit(ctx, nonce) + if !ok { + return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() + } else if deposit.Spent { + return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() + } + + deposit.Spent = true + deposit.Spender = spender + + store.StoreDeposit(ctx, nonce, deposit) + spendDepositWithAccount(ctx, nonce, deposit) + return sdk.Result{} +} + +// SpendOutput changes the output to be spent +// Updates the account of the output owner +func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + + tx, ok := store.GetTx(ctx, hash) + if !ok { + return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() + } else if tx.Spent[pos.OutputIndex] { + return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() + } + + tx.Spent[pos.OutputIndex] = true + tx.Spenders[pos.OutputIndex] = spender + + store.StoreTx(ctx, tx) + store.spendUTXOWithAccount(ctx, pos, tx.Transaction) + + return sdk.Result{} +} + +// ----------------------------------------------------------------------------- +/* Helpers */ + +// GetUnspentForAccount returns the unspent outputs that belong to the given account +func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []Output) { + for _, p := range acc.Unspent { + utxo, ok := store.GetUTXO(ctx, p) + if ok { + utxos = append(utxos, utxo) + } + } + return utxos +} + +// addDepositToAccount adds the deposit to the deposit owner's account +func (store OutputStore) addDepositToAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { + store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) +} + +// addOutputsToAccount adds all the outputs in the given transaction to the respectively owners' accounts +func (store OutputStore) addOutputsToAccount(ctx sdk.Context, tx Transaction) { + for i, output := range tx.Transaction.Outputs { + store.addToAccount(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) + } +} + +// spendDepositFromAccount marks the deposit as spent from the deposit owner's account +func (store OutputStore) spendDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { + store.subtractFromAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) +} + +// spendOutputFromAccount marks the output as spent form the owner's account +func (store OutputStore) spendOutputFromAccount(ctx sdk.Context, pos plasma.Position, plasmaTx plasma.Transaction) { + store.subtractFromAccount(ctx, plasmaTx.Outputs[pos.OutputIndex].Owner, plasmaTx.Outputs[pos.OutputIndex].Amount, pos) +} + +// addToAccount adds the passed in amount to the account with the given address +// adds the position provided to the list of unspent positions within the account +func (store OutputStore) addToAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { + acc, ok := store.GetAccount(ctx, addr) + if !ok { + acc = Account{big.NewInt(0), make([]plasma.Position, 0), make([]plasma.Position, 0)} + } + + acc.Balance = new(big.Int).Add(acc.Balance, amount) + acc.Unspent = append(acc.Unspent, pos) + store.StoreAccount(ctx, addr, acc) +} + +// subtractFromAccount subtracts the passed in amount from the account with the given address +// moves the provided position from the unspent list to the spent list +func (store OutputStore) subtractFromAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { + acc, ok := store.GetAccount(ctx, addr) + if !ok { + panic(fmt.Sprintf("transaction store has been corrupted")) + } + + // Update Account + acc.Balance = new(big.Int).Sub(acc.Balance, amount) + if acc.Balance.Sign() == -1 { + panic(fmt.Sprintf("account with address 0x%x has a negative balance", addr)) + } + + removePosition(acc.Unspent, pos) + acc.Spent = append(acc.Spent, pos) + store.StoreAccount(ctx, addr, acc) +} + +// helper function to remove a position from the unspent list +func removePosition(positions []plasma.Position, pos plasma.Position) []plasma.Position { + for i, p := range positions { + if p.String() == pos.String() { + positions = append(positions[:i], positions[i+1:]...) + } + } + return positions +} diff --git a/store/txStore_test.go b/store/outputStore_test.go similarity index 85% rename from store/txStore_test.go rename to store/outputStore_test.go index abde293..31283b7 100644 --- a/store/txStore_test.go +++ b/store/outputStore_test.go @@ -17,34 +17,6 @@ func GetPosition(posStr string) plasma.Position { return pos } -func TestTxSerialization(t *testing.T) { - hashes := []byte("allam") - var sigs [65]byte - - // no default attributes - transaction := plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(utils.Big1, 15, 1, utils.Big0), sigs, [][65]byte{}), plasma.NewInput(plasma.NewPosition(utils.Big0, 0, 0, utils.Big1), sigs, [][65]byte{})}, - Outputs: []plasma.Output{plasma.NewOutput(common.HexToAddress("1"), utils.Big1), plasma.NewOutput(common.HexToAddress("2"), utils.Big2)}, - Fee: utils.Big1, - } - - tx := Transaction{ - Transaction: transaction, - Spent: []bool{false, false}, - Spenders: [][]byte{}, - ConfirmationHash: hashes, - } - - bytes, err := rlp.EncodeToBytes(&tx) - require.NoError(t, err) - - recoveredTx := Transaction{} - err = rlp.DecodeBytes(bytes, &recoveredTx) - require.NoError(t, err) - - require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in serialized and deserialized transactions") -} - // Test Get, Has, Store, Spend functions func TestTransactions(t *testing.T) { // Setup diff --git a/store/store_test.go b/store/store_test.go index 446c983..f2dcf8a 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -8,6 +8,8 @@ import ( "github.com/tendermint/tendermint/libs/log" ) +/* This file contains helper functions for testing */ + func setup() (ctx sdk.Context, key sdk.StoreKey) { db := db.NewMemDB() ms := cosmosStore.NewCommitMultiStore(db) @@ -19,3 +21,8 @@ func setup() (ctx sdk.Context, key sdk.StoreKey) { return ctx, key } + +func getPosition(posStr string) plasma.Position { + pos, _ := plasma.FromPositionString(posStr) + return pos +} diff --git a/store/txStore.go b/store/txStore.go deleted file mode 100644 index 493e660..0000000 --- a/store/txStore.go +++ /dev/null @@ -1,240 +0,0 @@ -package store - -import ( - "fmt" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" - "math/big" -) - -const ( - accountKey = "acc" - hashKey = "hash" - positionKey = "pos" -) - -/* Wrap plasma transaction with spend information */ -type Transaction struct { - Transaction plasma.Transaction - ConfirmationHash []byte - Spent []bool - Spenders [][]byte - Position plasma.Position -} - -/* Wrap plasma output with spend information */ -type Output struct { - Output plasma.Output - Spent bool - Spender []byte -} - -/* Transaction Store */ -type TxStore struct { - kvStore -} - -func NewTxStore(ctxKey sdk.StoreKey) TxStore { - return TxStore{ - kvStore: NewKVStore(ctxKey), - } -} - -// Return the account at the associated address -// Returns nothing if the account does no exist -func (store TxStore) GetAccount(ctx sdk.Context, addr common.Address) (Account, bool) { - key := prefixKey(accountKey, addr.Bytes()) - data := store.Get(ctx, key) - if data == nil { - return Account{}, false - } - - var acc Account - if err := rlp.DecodeBytes(data, &acc); err != nil { - panic(fmt.Sprintf("transaction store corrupted: %s", err)) - } - - return acc, true -} - -// Return the transaction with the provided transaction hash -func (store TxStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { - key := prefixKey(hashKey, hash) - data := store.Get(ctx, key) - if data == nil { - return Transaction{}, false - } - - var tx Transaction - if err := rlp.DecodeBytes(data, &tx); err != nil { - panic(fmt.Sprintf("transaction store corrupted: %s", err)) - } - - return tx, true -} - -// Return the transaction that contains the provided position as an output -func (store TxStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { - key := prefixKey(positionKey, pos.Bytes()) - hash := store.Get(ctx, key) - return store.GetTx(ctx, hash) -} - -// Return the output at the specified position -// along with if it has been spent and what transaction spent it -func (store TxStore) GetUTXO(ctx sdk.Context, pos plasma.Position) (Output, bool) { - key := prefixKey(positionKey, pos.Bytes()) - hash := store.Get(ctx, key) - - tx, ok := store.GetTx(ctx, hash) - if !ok { - return Output{}, ok - } - - output := Output{ - Output: tx.Transaction.Outputs[pos.OutputIndex], - Spent: tx.Spent[pos.OutputIndex], - Spender: tx.Spenders[pos.OutputIndex], - } - - return output, ok -} - -// Checks if a transaction exists using the transaction hash provided -func (store TxStore) HasTx(ctx sdk.Context, hash []byte) bool { - key := prefixKey(hashKey, hash) - return store.Has(ctx, key) -} - -// Checks if an account exists for the provided address -func (store TxStore) HasAccount(ctx sdk.Context, addr common.Address) bool { - key := prefixKey(accountKey, addr.Bytes()) - return store.Has(ctx, key) -} - -// Checks if the utxo exists using its position -func (store TxStore) HasUTXO(ctx sdk.Context, pos plasma.Position) bool { - key := prefixKey(positionKey, pos.Bytes()) - hash := store.Get(ctx, key) - - return store.HasTx(ctx, hash) -} - -// Store the given Account -func (store TxStore) StoreAccount(ctx sdk.Context, addr common.Address, acc Account) { - key := prefixKey(accountKey, addr.Bytes()) - data, err := rlp.EncodeToBytes(&acc) - if err != nil { - panic(fmt.Sprintf("error marshaling transaction: %s", err)) - } - - store.Set(ctx, key, data) -} - -// Add a mapping from transaction hash to transaction -func (store TxStore) StoreTx(ctx sdk.Context, tx Transaction) { - data, err := rlp.EncodeToBytes(&tx) - if err != nil { - panic(fmt.Sprintf("error marshaling transaction: %s", err)) - } - - key := prefixKey(hashKey, tx.Transaction.TxHash()) - store.Set(ctx, key, data) - store.storeUTXOsWithAccount(ctx, tx) -} - -// Add a mapping from position to transaction hash -func (store TxStore) StoreUTXO(ctx sdk.Context, pos plasma.Position, hash []byte) { - key := prefixKey(positionKey, pos.Bytes()) - store.Set(ctx, key, hash) -} - -// Updates Spent, Spender fields and Account associated with this utxo -func (store TxStore) SpendUTXO(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { - key := prefixKey(positionKey, pos.Bytes()) - hash := store.Get(ctx, key) - - tx, ok := store.GetTx(ctx, hash) - if !ok { - return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() - } else if tx.Spent[pos.OutputIndex] { - return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() - } - - tx.Spent[pos.OutputIndex] = true - tx.Spenders[pos.OutputIndex] = spender - - store.StoreTx(ctx, tx) - store.spendUTXOWithAccount(ctx, pos, tx.Transaction) - - return sdk.Result{} -} - -/* Helpers */ - -func (store TxStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []Output) { - for _, p := range acc.Unspent { - utxo, ok := store.GetUTXO(ctx, p) - if ok { - utxos = append(utxos, utxo) - } - } - return utxos -} - -func (store TxStore) StoreDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { - store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) -} - -func (store TxStore) storeUTXOsWithAccount(ctx sdk.Context, tx Transaction) { - for i, output := range tx.Transaction.Outputs { - store.addToAccount(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) - } -} - -func (store TxStore) SpendDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { - store.subtractFromAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) -} - -func (store TxStore) spendUTXOWithAccount(ctx sdk.Context, pos plasma.Position, plasmaTx plasma.Transaction) { - store.subtractFromAccount(ctx, plasmaTx.Outputs[pos.OutputIndex].Owner, plasmaTx.Outputs[pos.OutputIndex].Amount, pos) -} - -func (store TxStore) addToAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { - acc, ok := store.GetAccount(ctx, addr) - if !ok { - acc = Account{big.NewInt(0), make([]plasma.Position, 0), make([]plasma.Position, 0)} - } - - acc.Balance = new(big.Int).Add(acc.Balance, amount) - acc.Unspent = append(acc.Unspent, pos) - store.StoreAccount(ctx, addr, acc) -} - -func (store TxStore) subtractFromAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { - acc, ok := store.GetAccount(ctx, addr) - if !ok { - panic(fmt.Sprintf("transaction store has been corrupted")) - } - - // Update Account - acc.Balance = new(big.Int).Sub(acc.Balance, amount) - if acc.Balance.Sign() == -1 { - panic(fmt.Sprintf("account with address 0x%x has a negative balance", addr)) - } - - removePosition(acc.Unspent, pos) - acc.Spent = append(acc.Spent, pos) - store.StoreAccount(ctx, addr, acc) -} - -func removePosition(positions []plasma.Position, pos plasma.Position) []plasma.Position { - for i, p := range positions { - if p.String() == pos.String() { - positions = append(positions[:i], positions[i+1:]...) - } - } - return positions -} diff --git a/store/types.go b/store/types.go new file mode 100644 index 0000000..34c0b3c --- /dev/null +++ b/store/types.go @@ -0,0 +1,36 @@ +package store + +import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "math/big" +) + +/* Account */ +type Account struct { + Balance *big.Int // total amount avaliable to be spent + Unspent []plasma.Position // position of unspent transaction outputs + Spent []plasma.Position // position of spent transaction outputs +} + +/* Wrap plasma deposit with spend information */ +type Deposit struct { + Deposit plasma.Deposit + Spent bool + Spender []byte +} + +/* Wrap plasma output with spend information */ +type Output struct { + Output plasma.Output + Spent bool + Spender []byte +} + +/* Wrap plasma transaction with spend information */ +type Transaction struct { + Transaction plasma.Transaction + ConfirmationHash []byte + Spent []bool + Spenders [][]byte + Position plasma.Position +} diff --git a/store/types_test.go b/store/types_test.go new file mode 100644 index 0000000..0777c4a --- /dev/null +++ b/store/types_test.go @@ -0,0 +1,83 @@ +package store + +import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + "math/big" + "reflect" + "testing" +) + +// Test that an account can be serialized and deserialized +func TestAccountSerialization(t *testing.T) { + // Construct Account + acc := Account{ + Balance: big.NewInt(234578), + Unspent: []plasma.Position{getPosition("(8745.1239.1.0)"), getPosition("(23409.12456.0.0)"), getPosition("(894301.1.1.0)"), getPosition("(0.0.0.540124)")}, + Spent: []plasma.Position{getPosition("0.0.0.3"), getPosition("7.734.1.3")}, + } + + bytes, err := rlp.EncodeToBytes(&acc) + require.NoError(t, err) + + recoveredAcc := Account{} + err = rlp.DecodeBytes(bytes, &recoveredAcc) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(acc, recoveredAcc), "mismatch in serialized and deserialized account") +} + +// Test that the Deposit can be serialized and deserialized without loss of information +func TestDepositSerialization(t *testing.T) { + // Construct deposit + plasmaDeposit := plasma.Deposit{ + Owner: common.BytesToAddress([]byte("an ethereum address")), + Amount: big.NewInt(12312310), + EthBlockNum: big.NewInt(100123123), + } + + deposit := Deposit{ + Deposit: plasmaDeposit, + Spent: true, + Spender: []byte{}, + } + + bytes, err := rlp.EncodeToBytes(&deposit) + require.NoError(t, err) + + recoveredDeposit := Deposit{} + err = rlp.DecodeBytes(bytes, &recoveredDeposit) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in serialized and deserialized deposit") +} + +// Test that Transaction can be serialized and deserialized without loss of information +func TestTxSerialization(t *testing.T) { + hashes := []byte("fourthstate") + var sigs [65]byte + + // Construct Transaction + transaction := plasma.Transaction{ + Inputs: []plasma.Input{plasma.NewInput(getPosition("(1.15.1.0)"), sigs, [][65]byte{}), plasma.NewInput(getPosition("(0.0.0.1)")), sigs, [][65]byte{}}, + Outputs: []plasma.Output{plasma.NewOutput(common.HexToAddress("1"), utils.Big1), plasma.NewOutput(common.HexToAddress("2"), utils.Big2)}, + Fee: utils.Big1, + } + + tx := Transaction{ + Transaction: transaction, + Spent: []bool{false, false}, + Spenders: [][]byte{}, + ConfirmationHash: hashes, + } + + bytes, err := rlp.EncodeToBytes(&tx) + require.NoError(t, err) + + recoveredTx := Transaction{} + err = rlp.DecodeBytes(bytes, &recoveredTx) + require.NoError(t, err) + + require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in serialized and deserialized transactions") +} From b8582c346d2532d402bae8e02d5f37ab898f7c29 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Wed, 15 May 2019 13:33:34 -0700 Subject: [PATCH 20/49] deleted depositStore, fixed bugs in outputStore --- store/depositStore.go | 52 ----------------- store/depositStore_test.go | 83 --------------------------- store/outputStore.go | 74 ++++++++++++------------ store/outputStore_test.go | 112 +++++++++++++++++++++++++------------ store/querier.go | 18 +++--- store/store_test.go | 1 + store/types_test.go | 4 +- 7 files changed, 125 insertions(+), 219 deletions(-) delete mode 100644 store/depositStore.go delete mode 100644 store/depositStore_test.go diff --git a/store/depositStore.go b/store/depositStore.go deleted file mode 100644 index e52c411..0000000 --- a/store/depositStore.go +++ /dev/null @@ -1,52 +0,0 @@ -package store - -import ( - "fmt" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/rlp" - "math/big" -) - -func (store DepositStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, bool) { - data := store.Get(ctx, nonce.Bytes()) - if data == nil { - return Deposit{}, false - } - - var deposit Deposit - if err := rlp.DecodeBytes(data, &deposit); err != nil { - panic(fmt.Sprintf("deposit store corrupted: %s", err)) - } - - return deposit, true -} - -func (store DepositStore) HasDeposit(ctx sdk.Context, nonce *big.Int) bool { - return store.Has(ctx, nonce.Bytes()) -} - -func (store DepositStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { - data, err := rlp.EncodeToBytes(&deposit) - if err != nil { - panic(fmt.Sprintf("error marshaling deposit with nonce %s: %s", nonce, err)) - } - - store.Set(ctx, nonce.Bytes(), data) -} - -func (store DepositStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { - deposit, ok := store.GetDeposit(ctx, nonce) - if !ok { - return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() - } else if deposit.Spent { - return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() - } - - deposit.Spent = true - deposit.Spender = spender - - store.StoreDeposit(ctx, nonce, deposit) - - return sdk.Result{} -} diff --git a/store/depositStore_test.go b/store/depositStore_test.go deleted file mode 100644 index 490aa0f..0000000 --- a/store/depositStore_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package store - -import ( - "fmt" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/require" - "math/big" - "reflect" - "testing" -) - -// Test that the Deposit can be serialized and deserialized without loss of information -func TestDepositSerialization(t *testing.T) { - // Construct deposit - plasmaDeposit := plasma.Deposit{ - Owner: common.BytesToAddress([]byte("an ethereum address")), - Amount: big.NewInt(12312310), - EthBlockNum: big.NewInt(100123123), - } - - deposit := Deposit{ - Deposit: plasmaDeposit, - Spent: true, - Spender: []byte{}, - } - - // RLP Encode - bytes, err := rlp.EncodeToBytes(&deposit) - require.NoError(t, err) - - // RLP Decode - recoveredDeposit := Deposit{} - err = rlp.DecodeBytes(bytes, &recoveredDeposit) - require.NoError(t, err) - - require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in serialized and deserialized deposits") -} - -// Test Get, Has, Store, Spend functions -func TestDeposits(t *testing.T) { - ctx, key := setup() - depositStore := NewDepositStore(key) - - addr := common.BytesToAddress([]byte("asdfasdf")) - for i := int64(1); i <= 15; i++ { - nonce := big.NewInt(i) - hash := []byte("hash that the deposit was spent in") - - // Retrieve/Spend nonexistent deposits - exists := depositStore.HasDeposit(ctx, nonce) - require.False(t, exists, "returned true for nonexistent deposit") - recoveredDeposit, ok := depositStore.GetDeposit(ctx, nonce) - require.Empty(t, recoveredDeposit, "did not return empty struct for nonexistent deposit") - require.False(t, ok, "did not return error on nonexistent deposit") - res := depositStore.SpendDeposit(ctx, nonce, hash) - require.Equal(t, res.Code, CodeOutputDNE, "did not return that deposit does not exist") - - // Create and store new deposit - plasmaDeposit := plasma.NewDeposit(addr, big.NewInt(i*4123), big.NewInt(i*123)) - deposit := Deposit{plasmaDeposit, false, []byte{}} - depositStore.StoreDeposit(ctx, nonce, deposit) - - exists = depositStore.HasDeposit(ctx, nonce) - require.True(t, exists, "returned false for deposit that was stored") - recoveredDeposit, ok = depositStore.GetDeposit(ctx, nonce) - require.True(t, ok, "error when retrieving deposit") - require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), fmt.Sprintf("mismatch in stored deposit and retrieved deposit on iteration %d", i)) - - // Spend Deposit - res = depositStore.SpendDeposit(ctx, nonce, hash) - require.True(t, res.IsOK(), "returned error when spending deposit") - res = depositStore.SpendDeposit(ctx, nonce, hash) - require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") - - deposit.Spent = true - deposit.Spender = hash - recoveredDeposit, ok = depositStore.GetDeposit(ctx, nonce) - require.True(t, ok, "error when retrieving deposit") - require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in stored and retrieved deposit") - } -} diff --git a/store/outputStore.go b/store/outputStore.go index 7fde5c9..c904733 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -136,9 +136,8 @@ func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { // ----------------------------------------------------------------------------- /* Set */ -// SetDeposit adds an unspent deposit to the output store -// The deposit owner's account is updated -func (store OutputStore) SetDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { +// SetDeposit overwrites the deposit stored with the given nonce +func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { data, err := rlp.EncodeToBytes(&deposit) if err != nil { panic(fmt.Sprintf("error marshaling deposit with nonce %s: %s", nonce, err)) @@ -149,7 +148,7 @@ func (store OutputStore) SetDeposit(ctx sdk.Context, nonce *big.Int, deposit Dep } // SetAccount overwrites the account stored at the given address -func (store OutputStore) SetAccount(ctx sdk.Context, addr common.Address, acc Account) { +func (store OutputStore) setAccount(ctx sdk.Context, addr common.Address, acc Account) { key := prefixKey(accountKey, addr.Bytes()) data, err := rlp.EncodeToBytes(&acc) if err != nil { @@ -159,9 +158,8 @@ func (store OutputStore) SetAccount(ctx sdk.Context, addr common.Address, acc Ac store.Set(ctx, key, data) } -// SetTx adds a mapping from transaction hash to transaction -// Accounts are updated using the transaction's outputs -func (store OutputStore) SetTx(ctx sdk.Context, tx Transaction) { +// SetTx overwrites the mapping from transaction hash to transaction +func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { data, err := rlp.EncodeToBytes(&tx) if err != nil { panic(fmt.Sprintf("error marshaling transaction: %s", err)) @@ -169,20 +167,39 @@ func (store OutputStore) SetTx(ctx sdk.Context, tx Transaction) { key := prefixKey(hashKey, tx.Transaction.TxHash()) store.Set(ctx, key, data) - store.storeUTXOsWithAccount(ctx, tx) } // SetOutput adds a mapping from position to transaction hash -func (store OutputStore) SetOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { +func (store OutputStore) setOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { key := prefixKey(positionKey, pos.Bytes()) store.Set(ctx, key, hash) } +// ----------------------------------------------------------------------------- +/* Store */ + +// StoreDeposit adds an unspent deposit +// Updates the deposit owner's account +func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { + store.setDeposit(ctx, nonce, Deposit{deposit, false, make([]byte, 0)}) + store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) +} + +// StoreTx adds the transaction +// Updates the output owner's accounts +func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { + store.setTx(ctx, tx) + for i, output := range tx.Transaction.Outputs { + store.addToAccount(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) + store.setOutput(ctx, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0)), tx.Transaction.TxHash()) + } +} + // ----------------------------------------------------------------------------- /* Spend */ // SpendDeposit changes the deposit to be spent -// Updates the account of the deposit owner +// Updates the account of the deposit owner in the SetDeposit call func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { @@ -194,8 +211,9 @@ func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender [ deposit.Spent = true deposit.Spender = spender - store.StoreDeposit(ctx, nonce, deposit) - spendDepositWithAccount(ctx, nonce, deposit) + store.setDeposit(ctx, nonce, deposit) + store.subtractFromAccount(ctx, deposit.Deposit.Owner, deposit.Deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) + return sdk.Result{} } @@ -215,8 +233,8 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend tx.Spent[pos.OutputIndex] = true tx.Spenders[pos.OutputIndex] = spender - store.StoreTx(ctx, tx) - store.spendUTXOWithAccount(ctx, pos, tx.Transaction) + store.setTx(ctx, tx) + store.subtractFromAccount(ctx, tx.Transaction.Outputs[pos.OutputIndex].Owner, tx.Transaction.Outputs[pos.OutputIndex].Amount, pos) return sdk.Result{} } @@ -227,7 +245,7 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend // GetUnspentForAccount returns the unspent outputs that belong to the given account func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []Output) { for _, p := range acc.Unspent { - utxo, ok := store.GetUTXO(ctx, p) + utxo, ok := store.GetOutput(ctx, p) if ok { utxos = append(utxos, utxo) } @@ -235,28 +253,6 @@ func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utx return utxos } -// addDepositToAccount adds the deposit to the deposit owner's account -func (store OutputStore) addDepositToAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { - store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) -} - -// addOutputsToAccount adds all the outputs in the given transaction to the respectively owners' accounts -func (store OutputStore) addOutputsToAccount(ctx sdk.Context, tx Transaction) { - for i, output := range tx.Transaction.Outputs { - store.addToAccount(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) - } -} - -// spendDepositFromAccount marks the deposit as spent from the deposit owner's account -func (store OutputStore) spendDepositWithAccount(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { - store.subtractFromAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) -} - -// spendOutputFromAccount marks the output as spent form the owner's account -func (store OutputStore) spendOutputFromAccount(ctx sdk.Context, pos plasma.Position, plasmaTx plasma.Transaction) { - store.subtractFromAccount(ctx, plasmaTx.Outputs[pos.OutputIndex].Owner, plasmaTx.Outputs[pos.OutputIndex].Amount, pos) -} - // addToAccount adds the passed in amount to the account with the given address // adds the position provided to the list of unspent positions within the account func (store OutputStore) addToAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { @@ -267,7 +263,7 @@ func (store OutputStore) addToAccount(ctx sdk.Context, addr common.Address, amou acc.Balance = new(big.Int).Add(acc.Balance, amount) acc.Unspent = append(acc.Unspent, pos) - store.StoreAccount(ctx, addr, acc) + store.setAccount(ctx, addr, acc) } // subtractFromAccount subtracts the passed in amount from the account with the given address @@ -286,7 +282,7 @@ func (store OutputStore) subtractFromAccount(ctx sdk.Context, addr common.Addres removePosition(acc.Unspent, pos) acc.Spent = append(acc.Spent, pos) - store.StoreAccount(ctx, addr, acc) + store.setAccount(ctx, addr, acc) } // helper function to remove a position from the unspent list diff --git a/store/outputStore_test.go b/store/outputStore_test.go index 31283b7..1c3be2a 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -6,22 +6,61 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" + "math/big" "reflect" "testing" ) -func GetPosition(posStr string) plasma.Position { - pos, _ := plasma.FromPositionString(posStr) - return pos +// Test Get, Has, Store, Spend functions for deposits +func TestDeposits(t *testing.T) { + ctx, key := setup() + outputStore := NewOutputStore(key) + + addr := common.BytesToAddress([]byte("asdfasdf")) + for i := int64(1); i <= 15; i++ { + nonce := big.NewInt(i) + hash := []byte("hash that the deposit was spent in") + + // Retrieve/Spend nonexistent deposits + exists := outputStore.HasDeposit(ctx, nonce) + require.False(t, exists, "returned true for nonexistent deposit") + recoveredDeposit, ok := outputStore.GetDeposit(ctx, nonce) + require.Empty(t, recoveredDeposit, "did not return empty struct for nonexistent deposit") + require.False(t, ok, "did not return error on nonexistent deposit") + res := outputStore.SpendDeposit(ctx, nonce, hash) + require.Equal(t, res.Code, CodeOutputDNE, "did not return that deposit does not exist") + + // Create and store new deposit + plasmaDeposit := plasma.NewDeposit(addr, big.NewInt(i*4123), big.NewInt(i*123)) + deposit := Deposit{plasmaDeposit, false, []byte{}} + outputStore.StoreDeposit(ctx, nonce, plasmaDeposit) + + exists = outputStore.HasDeposit(ctx, nonce) + require.True(t, exists, "returned false for deposit that was stored") + recoveredDeposit, ok = outputStore.GetDeposit(ctx, nonce) + require.True(t, ok, "error when retrieving deposit") + require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), fmt.Sprintf("mismatch in stored deposit and retrieved deposit on iteration %d", i)) + + // Spend Deposit + res = outputStore.SpendDeposit(ctx, nonce, hash) + require.True(t, res.IsOK(), "returned error when spending deposit") + res = outputStore.SpendDeposit(ctx, nonce, hash) + require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") + + deposit.Spent = true + deposit.Spender = hash + recoveredDeposit, ok = outputStore.GetDeposit(ctx, nonce) + require.True(t, ok, "error when retrieving deposit") + require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in stored and retrieved deposit") + } } -// Test Get, Has, Store, Spend functions +// Test Get, Has, Store, Spend functions for transactions func TestTransactions(t *testing.T) { // Setup ctx, key := setup() - txStore := NewTxStore(key) + outputStore := NewOutputStore(key) privKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privKey.PublicKey) @@ -41,95 +80,98 @@ func TestTransactions(t *testing.T) { validationCase{ reason: "tx with two inputs and one output", Transaction: plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(GetPosition("(122.3.1.0)"), sampleSig, nil), plasma.NewInput(GetPosition("(17622.13.5.0)"), sampleSig, nil)}, + Inputs: []plasma.Input{plasma.NewInput(getPosition("(122.3.1.0)"), sampleSig, nil), plasma.NewInput(getPosition("(17622.13.5.0)"), sampleSig, nil)}, Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, - Position: GetPosition("(8765.6847.0.0)"), + Position: getPosition("(8765.6847.0.0)"), }, validationCase{ reason: "tx with one input and two output", Transaction: plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(GetPosition("(4.1234.1.0)"), sampleSig, nil)}, + Inputs: []plasma.Input{plasma.NewInput(getPosition("(4.1234.1.0)"), sampleSig, nil)}, Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1), plasma.NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, - Position: GetPosition("(4354.8765.0.0)"), + Position: getPosition("(4354.8765.0.0)"), }, validationCase{ reason: "tx with two input and one output", Transaction: plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(GetPosition("(123.1.0.0)"), sampleSig, nil), plasma.NewInput(GetPosition("(10.12.1.0)"), sampleSig, sampleConfirmSig)}, + Inputs: []plasma.Input{plasma.NewInput(getPosition("(123.1.0.0)"), sampleSig, nil), plasma.NewInput(getPosition("(10.12.1.0)"), sampleSig, sampleConfirmSig)}, Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, - Position: GetPosition("(11.123.0.0)"), + Position: getPosition("(11.123.0.0)"), }, validationCase{ reason: "tx with two input and two outputs", Transaction: plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(GetPosition("(132.231.1.0)"), sampleSig, nil), plasma.NewInput(GetPosition("(635.927.1.0)"), sampleSig, sampleConfirmSig)}, + Inputs: []plasma.Input{plasma.NewInput(getPosition("(132.231.1.0)"), sampleSig, nil), plasma.NewInput(getPosition("(635.927.1.0)"), sampleSig, sampleConfirmSig)}, Outputs: []plasma.Output{plasma.NewOutput(addr, utils.Big1), plasma.NewOutput(addr, utils.Big1)}, Fee: utils.Big0, }, - Position: GetPosition("(1121234.12.0.0)"), + Position: getPosition("(1121234.12.0.0)"), }, } for i, plasmaTx := range txs { pos := plasmaTx.Position // Retrieve/Spend nonexistent txs - exists := txStore.HasTx(ctx, plasmaTx.Transaction.TxHash()) + exists := outputStore.HasTx(ctx, plasmaTx.Transaction.TxHash()) require.False(t, exists, "returned true for nonexistent transaction") - recoveredTx, ok := txStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) + recoveredTx, ok := outputStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) require.Empty(t, recoveredTx, "did not return empty struct for nonexistent transaction") require.False(t, ok, "did not return error on nonexistent transaction") + // Retrieve/Spend nonexistent Outputs + for j, _ := range plasmaTx.Transaction.Outputs { + p := plasma.NewPosition(pos.BlockNum, pos.TxIndex, uint8(j), pos.DepositNonce) + exists = outputStore.HasOutput(ctx, p) + require.False(t, exists, "returned true for nonexistent output") + res := outputStore.SpendOutput(ctx, p, plasmaTx.Transaction.MerkleHash()) + require.Equal(t, res.Code, CodeOutputDNE, "did not return that Output does not exist") + } + // Create and store new transaction - tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), GetPosition("(4567.1.1.0)")} + tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), getPosition("(4567.1.1.0)")} for i, _ := range tx.Spenders { tx.Spenders[i] = []byte{} } - txStore.StoreTx(ctx, tx) + outputStore.StoreTx(ctx, tx) - // Create Outputs + // Check for outputs for j, _ := range plasmaTx.Transaction.Outputs { p := plasma.NewPosition(pos.BlockNum, pos.TxIndex, uint8(j), pos.DepositNonce) - exists = txStore.HasUTXO(ctx, p) - require.False(t, exists, "returned true for nonexistent output") - res := txStore.SpendUTXO(ctx, p, plasmaTx.Transaction.MerkleHash()) - require.Equal(t, res.Code, CodeOutputDNE, "did not return that utxo does not exist") - - txStore.StoreUTXO(ctx, p, plasmaTx.Transaction.TxHash()) - exists = txStore.HasUTXO(ctx, p) - require.True(t, exists, "returned false for stored utxo") + exists = outputStore.HasOutput(ctx, p) + require.True(t, exists, "returned false for stored Output") } // Check for Tx - exists = txStore.HasTx(ctx, plasmaTx.Transaction.TxHash()) + exists = outputStore.HasTx(ctx, plasmaTx.Transaction.TxHash()) require.True(t, exists, "returned false for transaction that was stored") - recoveredTx, ok = txStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) + recoveredTx, ok = outputStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) require.True(t, ok, "error when retrieving transaction") require.Equal(t, tx, recoveredTx, fmt.Sprintf("mismatch in stored transaction and retrieved transaction before spends on case %d", i)) // Spend Outputs for j, _ := range plasmaTx.Transaction.Outputs { p := plasma.NewPosition(pos.BlockNum, pos.TxIndex, uint8(j), pos.DepositNonce) - recoveredTx, ok = txStore.GetTxWithPosition(ctx, p) + recoveredTx, ok = outputStore.GetTxWithPosition(ctx, p) require.True(t, ok, "error when retrieving transaction") require.True(t, reflect.DeepEqual(tx, recoveredTx), fmt.Sprintf("mismatch in stored transaction and retrieved transaction on case %d", i)) - res := txStore.SpendUTXO(ctx, p, plasmaTx.Transaction.MerkleHash()) - require.True(t, res.IsOK(), "returned error when spending utxo") - res = txStore.SpendUTXO(ctx, p, plasmaTx.Transaction.MerkleHash()) + res := outputStore.SpendOutput(ctx, p, plasmaTx.Transaction.MerkleHash()) + require.True(t, res.IsOK(), "returned error when spending Output") + res = outputStore.SpendOutput(ctx, p, plasmaTx.Transaction.MerkleHash()) require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") tx.Spent[j] = true tx.Spenders[j] = plasmaTx.Transaction.MerkleHash() - recoveredTx, ok = txStore.GetTxWithPosition(ctx, p) + recoveredTx, ok = outputStore.GetTxWithPosition(ctx, p) require.True(t, ok, "error when retrieving transaction") require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in stored and retrieved transaction") - recoveredTx, ok = txStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) + recoveredTx, ok = outputStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) require.True(t, ok, "error when retrieving transaction") require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in stored and retrieved transaction") } diff --git a/store/querier.go b/store/querier.go index 28a6ab4..5722fbb 100644 --- a/store/querier.go +++ b/store/querier.go @@ -14,7 +14,7 @@ const ( QueryInfo = "info" ) -func NewTxQuerier(txStore TxStore) sdk.Querier { +func NewOutputQuerier(outputStore OutputStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, sdk.ErrUnknownRequest("path not specified") @@ -26,7 +26,7 @@ func NewTxQuerier(txStore TxStore) sdk.Querier { return nil, sdk.ErrUnknownRequest("balance query follows balance/
") } addr := common.HexToAddress(path[1]) - total, err := queryBalance(ctx, txStore, addr) + total, err := queryBalance(ctx, outputStore, addr) if err != nil { return nil, sdk.ErrInternal("failed query balance") } @@ -37,7 +37,7 @@ func NewTxQuerier(txStore TxStore) sdk.Querier { return nil, sdk.ErrUnknownRequest("info query follows /info/
") } addr := common.HexToAddress(path[1]) - utxos, err := queryInfo(ctx, txStore, addr) + utxos, err := queryInfo(ctx, outputStore, addr) if err != nil { return nil, err } @@ -53,21 +53,21 @@ func NewTxQuerier(txStore TxStore) sdk.Querier { } } -func queryBalance(ctx sdk.Context, txStore TxStore, addr common.Address) (*big.Int, sdk.Error) { - acc, ok := txStore.GetAccount(ctx, addr) +func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) (*big.Int, sdk.Error) { + acc, ok := outputStore.GetAccount(ctx, addr) if !ok { return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) } - return acc.GetBalance(), nil + return acc.Balance, nil } -func queryInfo(ctx sdk.Context, txStore TxStore, addr common.Address) ([]Output, sdk.Error) { - acc, ok := txStore.GetAccount(ctx, addr) +func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]Output, sdk.Error) { + acc, ok := outputStore.GetAccount(ctx, addr) if !ok { return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) } - return txStore.GetUnspentForAccount(ctx, acc), nil + return outputStore.GetUnspentForAccount(ctx, acc), nil } const ( diff --git a/store/store_test.go b/store/store_test.go index f2dcf8a..9bc4142 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -1,6 +1,7 @@ package store import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" cosmosStore "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" diff --git a/store/types_test.go b/store/types_test.go index 0777c4a..e98c29f 100644 --- a/store/types_test.go +++ b/store/types_test.go @@ -2,6 +2,8 @@ package store import ( "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" "math/big" @@ -60,7 +62,7 @@ func TestTxSerialization(t *testing.T) { // Construct Transaction transaction := plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(getPosition("(1.15.1.0)"), sigs, [][65]byte{}), plasma.NewInput(getPosition("(0.0.0.1)")), sigs, [][65]byte{}}, + Inputs: []plasma.Input{plasma.NewInput(getPosition("(1.15.1.0)"), sigs, [][65]byte{}), plasma.NewInput(getPosition("(0.0.0.1)"), sigs, [][65]byte{})}, Outputs: []plasma.Output{plasma.NewOutput(common.HexToAddress("1"), utils.Big1), plasma.NewOutput(common.HexToAddress("2"), utils.Big2)}, Fee: utils.Big1, } From 9ea2295e3e87270d3d02061f5be3ad38d826a042 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 16 May 2019 13:51:03 -0700 Subject: [PATCH 21/49] refactored handlers/ tests --- eth/plasma.go | 6 +- eth/plasma_test.go | 24 ++--- eth/test_helper.go | 10 +- handlers/anteHandler.go | 61 ++++++----- handlers/anteHandler_test.go | 175 ++++++++++++++----------------- handlers/depositHandler.go | 10 +- handlers/depositHandler_test.go | 19 ++-- handlers/handler_test.go | 21 ++-- handlers/spendMsgHandler.go | 23 ++-- handlers/spendMsgHandler_test.go | 36 +++---- server/app/app.go | 34 +++--- store/outputStore.go | 21 +++- store/outputStore_test.go | 12 +-- 13 files changed, 219 insertions(+), 233 deletions(-) diff --git a/eth/plasma.go b/eth/plasma.go index 7586611..1b28550 100644 --- a/eth/plasma.go +++ b/eth/plasma.go @@ -84,7 +84,7 @@ func (plasma *Plasma) OperatorAddress() (common.Address, error) { // CommitPlasmaHeaders will commit all new non-committed headers to the smart contract. // the commitmentRate interval must pass since the last commitment -func (plasma *Plasma) CommitPlasmaHeaders(ctx sdk.Context, plasmaStore store.PlasmaStore) error { +func (plasma *Plasma) CommitPlasmaHeaders(ctx sdk.Context, blockStore store.BlockStore) error { // only the contract operator can submit blocks. The commitment duration must also pass if plasma.operatorSession == nil || time.Since(plasma.lastBlockSubmission).Seconds() < plasma.commitmentRate.Seconds() { return nil @@ -107,7 +107,7 @@ func (plasma *Plasma) CommitPlasmaHeaders(ctx sdk.Context, plasmaStore store.Pla feesPerBlock []*big.Int ) - block, ok := plasmaStore.GetBlock(ctx, blockNum) + block, ok := blockStore.GetBlock(ctx, blockNum) if !ok { // no blocks to submit plasma.logger.Info("no plasma blocks to commit") return nil @@ -119,7 +119,7 @@ func (plasma *Plasma) CommitPlasmaHeaders(ctx sdk.Context, plasmaStore store.Pla feesPerBlock = append(feesPerBlock, block.FeeAmount) blockNum = blockNum.Add(blockNum, utils.Big1) - block, ok = plasmaStore.GetBlock(ctx, blockNum) + block, ok = blockStore.GetBlock(ctx, blockNum) } plasma.logger.Info(fmt.Sprintf("committing %d plasma blocks. first block num: %s", len(headers), firstBlockNum)) diff --git a/eth/plasma_test.go b/eth/plasma_test.go index 242ad94..55bf0f4 100644 --- a/eth/plasma_test.go +++ b/eth/plasma_test.go @@ -63,8 +63,8 @@ func TestSubmitBlock(t *testing.T) { privKey, _ := crypto.HexToECDSA(operatorPrivKey) plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), client, 1, commitmentRate, logger, true, privKey) - // Setup context and plasma store - ctx, plasmaStore := setup() + // Setup context and block store + ctx, blockStore := setup() time.Sleep(2 * time.Second) @@ -77,10 +77,10 @@ func TestSubmitBlock(t *testing.T) { FeeAmount: big.NewInt(int64(i + 2)), } expectedBlocks = append(expectedBlocks, block) - plasmaStore.StoreBlock(ctx, big.NewInt(int64(i)), block) + blockStore.StoreBlock(ctx, uint64(i), block) } - err := plasma.CommitPlasmaHeaders(ctx, plasmaStore) + err := plasma.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") @@ -124,14 +124,14 @@ func TestDepositFinalityBound(t *testing.T) { _, err = plasma.operatorSession.Deposit(operatorAddress) require.NoError(t, err, "error sending a deposit tx") - // Setup context and plasma store - ctx, plasmaStore := setup() + // Setup context and block store + ctx, blockStore := setup() // Reset operatorSession plasma.operatorSession.TransactOpts.Value = nil var block plasmaTypes.Block - // Must restore old blocks since we're using fresh plasmaStore but using old contract + // Must restore old blocks since we're using fresh blockStore but using old contract // that already has submitted blocks. Store blocks 1-3 to get plasmaConn to submit new block 3 for i := 1; i < 4; i++ { block = plasmaTypes.Block{ @@ -139,14 +139,14 @@ func TestDepositFinalityBound(t *testing.T) { TxnCount: uint16(i + 1), FeeAmount: big.NewInt(int64(i + 2)), } - plasmaStore.StoreBlock(ctx, big.NewInt(int64(i)), block) + blockStore.StoreBlock(ctx, uint64(i), block) } - err = plasma.CommitPlasmaHeaders(ctx, plasmaStore) + err = plasma.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") - err = plasma.CommitPlasmaHeaders(ctx, plasmaStore) + err = plasma.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") // Try to retrieve deposit from before peg @@ -168,9 +168,9 @@ func TestDepositFinalityBound(t *testing.T) { TxnCount: uint16(2), FeeAmount: big.NewInt(3), } - plasmaStore.StoreBlock(ctx, big.NewInt(4), block) + blockStore.StoreBlock(ctx, uint64(4), block) - err = plasma.CommitPlasmaHeaders(ctx, plasmaStore) + err = plasma.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") // Try to retrieve deposit once peg has advanced AND finality bound reached. diff --git a/eth/test_helper.go b/eth/test_helper.go index f0deb30..559651a 100644 --- a/eth/test_helper.go +++ b/eth/test_helper.go @@ -9,18 +9,18 @@ import ( "github.com/tendermint/tendermint/libs/log" ) -func setup() (sdk.Context, store.PlasmaStore) { +func setup() (sdk.Context, store.BlockStore) { db := db.NewMemDB() ms := cosmosStore.NewCommitMultiStore(db) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) - plasmaStoreKey := sdk.NewKVStoreKey("plasma") + blockStoreKey := sdk.NewKVStoreKey("block") - ms.MountStoreWithDB(plasmaStoreKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(blockStoreKey, sdk.StoreTypeIAVL, db) ms.LoadLatestVersion() - plasmaStore := store.NewPlasmaStore(plasmaStoreKey) + blockStore := store.NewBlockStore(blockStoreKey) - return ctx, plasmaStore + return ctx, blockStore } diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index 98296dd..8aefacd 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -8,6 +8,7 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/FourthState/plasma-mvp-sidechain/utils" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "math/big" ) @@ -19,24 +20,26 @@ type plasmaConn interface { HasTxBeenExited(*big.Int, plasma.Position) bool } -func NewAnteHandler(txStore store.TxStore, depositStore store.DepositStore, blockStore store.BlockStore, client plasmaConn) sdk.AnteHandler { +func NewAnteHandler(outputStore store.OutputStore, blockStore store.BlockStore, client plasmaConn) sdk.AnteHandler { return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { msg := tx.GetMsgs()[0] // tx should only have one msg switch mtype := msg.Type(); mtype { case "include_deposit": depositMsg := msg.(msgs.IncludeDepositMsg) - return includeDepositAnteHandler(ctx, depositStore, blockStore, depositMsg, client) + return includeDepositAnteHandler(ctx, outputStore, blockStore, depositMsg, client) case "spend_utxo": spendMsg := msg.(msgs.SpendMsg) - return spendMsgAnteHandler(ctx, spendMsg, txStore, depositStore, blockStore, client) + return spendMsgAnteHandler(ctx, spendMsg, outputStore, blockStore, client) default: return ctx, ErrInvalidTransaction(DefaultCodespace, "msg is not of type SpendMsg or IncludeDepositMsg").Result(), true } } } -func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, txStore store.TxStore, depositStore store.DepositStore, blockStore store.BlockStore, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { +func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, outputStore store.OutputStore, blockStore store.BlockStore, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { var totalInputAmt, totalOutputAmt *big.Int + totalInputAmt = big.NewInt(0) + totalOutputAmt = big.NewInt(0) // attempt to recover signers signers := spendMsg.GetSigners() @@ -49,8 +52,8 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, txStore store. } /* validate inputs */ - for i, input := range spendMsg.Inputs { - amt, res := validateInput(ctx, input, txStore, blockStore, client) + for i, signer := range signers { + amt, res := validateInput(ctx, spendMsg.Inputs[i], common.BytesToAddress(signer), outputStore, blockStore, client) if !res.IsOK() { return ctx, res, true } @@ -76,16 +79,19 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, txStore store. return ctx, sdk.Result{}, false } -// validates the inputs against the utxo store and returns the amount of the respective input -func validateInput(ctx sdk.Context, input plasma.Input, txStore store.TxStore, blockStore store.BlockStore, client plasmaConn) (*big.Int, sdk.Result) { +// validates the inputs against the output store and returns the amount of the respective input +func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, outputStore store.OutputStore, blockStore store.BlockStore, client plasmaConn) (*big.Int, sdk.Result) { var amt *big.Int // inputUTXO must be owned by the signer due to the prefix so we do not need to // check the owner of the position - inputUTXO, ok := txStore.GetUTXO(ctx, input.Position) + inputUTXO, ok := outputStore.GetOutput(ctx, input.Position) if !ok { return nil, ErrInvalidInput(DefaultCodespace, "input, %v, does not exist", input.Position).Result() } + if !bytes.Equal(inputUTXO.Output.Owner[:], signer[:]) { + return nil, ErrSignatureVerificationFailure(DefaultCodespace, fmt.Sprintf("transaction was not signed by correct address. Got: 0x%x. Expected: 0x%x", signer, inputUTXO.Output.Owner)).Result() + } if inputUTXO.Spent { return nil, ErrInvalidInput(DefaultCodespace, "input, %v, already spent", input.Position).Result() } @@ -93,23 +99,23 @@ func validateInput(ctx sdk.Context, input plasma.Input, txStore store.TxStore, b return nil, ErrExitedInput(DefaultCodespace, "input, %v, utxo has exitted", input.Position).Result() } - tx, ok := txStore.GetTxWithPosition(ctx, input.Position) - if !ok { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to retrieve the transaction that input with position %s belongs to", input.Position)).Result() - } + // validate inputs/confirmation signatures if not a fee utxo or deposit utxo + if !input.IsDeposit() && !input.IsFee() { + tx, ok := outputStore.GetTxWithPosition(ctx, input.Position) + if !ok { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to retrieve the transaction that input with position %s belongs to", input.Position)).Result() + } - // validate confirm signatures if not a fee utxo or deposit utxo - if input.TxIndex < 1<<16-1 && input.DepositNonce.Sign() == 0 { - res := validateConfirmSignatures(ctx, input, tx, txStore) + res := validateConfirmSignatures(ctx, input, tx, outputStore) if !res.IsOK() { return nil, res } - } - // check if the parent utxo has exited - for _, in := range tx.Transaction.Inputs { - if client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), in.Position) { - return nil, sdk.ErrUnauthorized(fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() + // check if the parent utxo has exited + for _, in := range tx.Transaction.Inputs { + if client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), in.Position) { + return nil, ErrExitedInput(DefaultCodespace, fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() + } } } @@ -119,30 +125,29 @@ func validateInput(ctx sdk.Context, input plasma.Input, txStore store.TxStore, b } // validates the input's confirm signatures -func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx store.Transaction, txStore store.TxStore) sdk.Result { +func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx store.Transaction, outputStore store.OutputStore) sdk.Result { if len(input.ConfirmSignatures) > 0 && len(inputTx.Transaction.Inputs) != len(input.ConfirmSignatures) { return ErrInvalidTransaction(DefaultCodespace, "incorrect number of confirm signatures").Result() } - confirmationHash := utils.ToEthSignedMessageHash(inputTx.ConfirmationHash[:])[:] for i, in := range inputTx.Transaction.Inputs { - utxo, ok := txStore.GetUTXO(ctx, in.Position) + utxo, ok := outputStore.GetOutput(ctx, in.Position) if !ok { - return sdk.ErrInternal(fmt.Sprintf("failed to retrieve input utxo with position %s", in.Position)).Result() + return ErrInvalidInput(DefaultCodespace, fmt.Sprintf("failed to retrieve input utxo with position %s", in.Position)).Result() } pubKey, _ := crypto.SigToPub(confirmationHash, input.ConfirmSignatures[i][:]) signer := crypto.PubkeyToAddress(*pubKey) if !bytes.Equal(signer[:], utxo.Output.Owner[:]) { - return sdk.ErrUnauthorized(fmt.Sprintf("confirm signature not signed by the correct address. Got: %x. Expected %x", signer, utxo.Output.Owner)).Result() + return ErrSignatureVerificationFailure(DefaultCodespace, fmt.Sprintf("confirm signature not signed by the correct address. Got: %x. Expected: %x", signer, utxo.Output.Owner)).Result() } } return sdk.Result{} } -func includeDepositAnteHandler(ctx sdk.Context, depositStore store.DepositStore, blockStore store.BlockStore, msg msgs.IncludeDepositMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { - if depositStore.HasDeposit(ctx, msg.DepositNonce) { +func includeDepositAnteHandler(ctx sdk.Context, outputStore store.OutputStore, blockStore store.BlockStore, msg msgs.IncludeDepositMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { + if outputStore.HasDeposit(ctx, msg.DepositNonce) { return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, already exists in store", msg.DepositNonce.String()).Result(), true } deposit, threshold, ok := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), msg.DepositNonce) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index 1035bd9..b4f414e 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -2,6 +2,7 @@ package handlers import ( "crypto/sha256" + "fmt" "github.com/FourthState/plasma-mvp-sidechain/msgs" "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" @@ -22,20 +23,18 @@ var ( ) type Tx struct { - Transaction plasma.Transaction + Transaction plasma.Transaction ConfirmationHash []byte - Spent []bool - Spenders [][]byte - Position plasma.Position + Spent []bool + Spenders [][]byte + Position plasma.Position } type Deposit struct { - Owner common.Address - Nonce *big.Int + Owner common.Address + Nonce *big.Int EthBlockNum *big.Int - Amount *big.Int - Spent bool - Spender []byte + Amount *big.Int } // cook the plasma connection @@ -60,16 +59,17 @@ func (p exitConn) HasTxBeenExited(tmBlock *big.Int, pos plasma.Position) bool { func TestAnteChecks(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() - handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) + ctx, outputStore, blockStore := setup() + handler := NewAnteHandler(outputStore, blockStore, conn{}) // cook up some input deposits inputs := []Deposit{ - {addr, big.NewInt(1), big.NewInt(100), big.NewInt(10), false, []byte{}}, - {addr, big.NewInt(2), big.NewInt(101), big.NewInt(10), false, []byte{}}, - {addr, big.NewInt(3), big.NewInt(102), big.NewInt(10), false, []byte{}}, + {addr, big.NewInt(1), big.NewInt(100), big.NewInt(10)}, + {addr, big.NewInt(2), big.NewInt(101), big.NewInt(10)}, + {addr, big.NewInt(3), big.NewInt(102), big.NewInt(10)}, } - setupDeposits(ctx, depositStore, inputs...) + setupDeposits(ctx, outputStore, inputs...) + outputStore.SpendDeposit(ctx, big.NewInt(3), []byte("spender hash")) type validationCase struct { reason string @@ -191,19 +191,18 @@ func TestAnteChecks(t *testing.T) { func TestAnteExitedInputs(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() - handler := NewAnteHandler(txStore, depositStore, blockStore, exitConn{}) + ctx, outputStore, blockStore := setup() + handler := NewAnteHandler(outputStore, blockStore, exitConn{}) - // place Inputs in store + // place inputs in store inputs := Tx{ - Transaction: , - Address: addr, - ConfirmationHash: , - Spent: []bool{false}, - Spenders: [][]byte{}, - Position: []plasma.Position[] + Transaction: plasma.Transaction{[]plasma.Input{plasma.NewInput(getPosition("10.0.0.0"), [65]byte{}, nil)}, []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, big.NewInt(0)}, + ConfirmationHash: []byte("confirmation hash"), + Spent: []bool{false}, + Spenders: [][]byte{}, + Position: getPosition("(1.0.0.0)"), } - setupTxs(ctx, txStore, inputs) + setupTxs(ctx, outputStore, inputs) // create msg spendMsg := msgs.SpendMsg{ @@ -228,15 +227,15 @@ func TestAnteExitedInputs(t *testing.T) { func TestAnteInvalidConfirmSig(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() - handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) + ctx, outputStore, blockStore := setup() + handler := NewAnteHandler(outputStore, blockStore, conn{}) // place inputs in store inputs := []Deposit{ - {addr, utils.Big1, big.NewInt(50), big.NewInt(10), false, []byte{}}, - {addr, utils.Big2, big.NewInt(55), big.NewInt(10), false, []byte{}}, + {addr, utils.Big1, big.NewInt(50), big.NewInt(10)}, + {addr, utils.Big2, big.NewInt(55), big.NewInt(10)}, } - setupDeposits(ctx, depositStore, inputs...) + setupDeposits(ctx, outputStore, inputs...) parentTx := msgs.SpendMsg{ Transaction: plasma.Transaction{ @@ -246,24 +245,22 @@ func TestAnteInvalidConfirmSig(t *testing.T) { }, } - // set regular transaction utxo in store + // set regular transaction output in store // parent input was 0.0.0.2 // must create confirmation hash // also need confirm sig of parent in order to spend - InputsKey := store.GetUTXOStoreKey(addr, plasma.NewPosition(nil, 0, 0, utils.Big2)) confBytes := sha256.Sum256(append(parentTx.MerkleHash(), ctx.BlockHeader().DataHash...)) confHash := utils.ToEthSignedMessageHash(confBytes[:]) badConfSig, _ := crypto.Sign(confHash, badPrivKey) - inputUTXO := store.UTXO{ - InputsKeys: [][]byte{InputsKey}, - ConfirmationHash: confBytes[:], - Output: plasma.Output{ - Owner: addr, - Amount: big.NewInt(10), - }, - Position: plasma.NewPosition(utils.Big1, 0, 0, nil), + + tx := store.Transaction{ + Transaction: parentTx.Transaction, + ConfirmationHash: confHash[:], + Spent: []bool{false}, + Spenders: make([][]byte, len(parentTx.Transaction.Outputs)), + Position: getPosition("(1.0.0.0)"), } - utxoStore.StoreUTXO(ctx, inputUTXO) + outputStore.StoreTx(ctx, tx) // store confirm sig into correct format var invalidConfirmSig [65]byte @@ -292,15 +289,15 @@ func TestAnteInvalidConfirmSig(t *testing.T) { func TestAnteValidTx(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() - handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) + ctx, outputStore, blockStore := setup() + handler := NewAnteHandler(outputStore, blockStore, conn{}) // place inputs in store - inputs := []InputUTXO{ - {nil, 0, 0, utils.Big1, addr, false}, - {nil, 0, 0, utils.Big2, addr, true}, + inputs := []Deposit{ + {addr, utils.Big1, big.NewInt(1000), big.NewInt(10)}, + {addr, utils.Big2, big.NewInt(1200), big.NewInt(10)}, } - setupInputs(ctx, txStore, inputs...) + setupDeposits(ctx, outputStore, inputs...) parentTx := msgs.SpendMsg{ Transaction: plasma.Transaction{ @@ -314,20 +311,19 @@ func TestAnteValidTx(t *testing.T) { // parent input was 0.0.0.2 // must create input key and confirmation hash // also need confirm sig of parent in order to spend - InputKey := store.GetUTXOStoreKey(addr, plasma.NewPosition(nil, 0, 0, utils.Big2)) confBytes := sha256.Sum256(append(parentTx.MerkleHash(), ctx.BlockHeader().DataHash...)) confHash := utils.ToEthSignedMessageHash(confBytes[:]) confSig, _ := crypto.Sign(confHash, privKey) - inputUTXO := store.UTXO{ - InputKeys: [][]byte{inputKey}, + + tx := store.Transaction{ + Transaction: parentTx.Transaction, ConfirmationHash: confBytes[:], - Output: plasma.Output{ - Owner: addr, - Amount: big.NewInt(10), - }, - Position: plasma.NewPosition(utils.Big1, 0, 0, nil), + Spent: []bool{false}, + Spenders: make([][]byte, len(parentTx.Transaction.Outputs)), + Position: getPosition("(1.0.0.0)"), } - utxoStore.StoreUTXO(ctx, inputUTXO) + outputStore.StoreTx(ctx, tx) + outputStore.SpendDeposit(ctx, big.NewInt(2), tx.Transaction.TxHash()) // store confirm sig into correct format var confirmSig [65]byte @@ -349,9 +345,8 @@ func TestAnteValidTx(t *testing.T) { copy(spendMsg.Inputs[1].Signature[:], sig[:]) _, res, abort := handler(ctx, spendMsg, false) - require.True(t, res.IsOK(), "Valid tx does not have OK result") + require.True(t, res.IsOK(), fmt.Sprintf("Valid tx does not have OK result: %s", res.Log)) require.False(t, abort, "Valid tx aborted") - } /*=====================================================================================================================================*/ @@ -359,15 +354,15 @@ func TestAnteValidTx(t *testing.T) { func TestAnteDeposit(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() - handler := NewAnteHandler(txStore, depositStore, blockStore, conn{}) + ctx, outputStore, blockStore := setup() + handler := NewAnteHandler(outputStore, blockStore, conn{}) // place input in store - inputs := []InputUTXO{ - {nil, 0, 0, utils.Big1, addr, false}, - {nil, 0, 0, utils.Big2, addr, true}, + inputs := []Deposit{ + {addr, utils.Big1, big.NewInt(150), big.NewInt(10)}, + {addr, utils.Big2, big.NewInt(200), big.NewInt(10)}, } - setupInputs(ctx, utxoStore, inputs...) + setupDeposits(ctx, outputStore, inputs...) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -411,9 +406,9 @@ func (d dneConn) HasTxBeenExited(tmBlock *big.Int, pos plasma.Position) bool { r func TestAnteDepositUnfinal(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() + ctx, outputStore, blockStore := setup() // connection always returns unfinalized deposits - handler := NewAnteHandler(txStore, depositStore, blockStore, unfinalConn{}) + handler := NewAnteHandler(outputStore, blockStore, unfinalConn{}) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -429,9 +424,9 @@ func TestAnteDepositUnfinal(t *testing.T) { func TestAnteDepositExitted(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() + ctx, outputStore, blockStore := setup() // connection always returns exitted deposits - handler := NewAnteHandler(utxoStore, depositStore, blockStore, exitConn{}) + handler := NewAnteHandler(outputStore, blockStore, exitConn{}) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -447,9 +442,9 @@ func TestAnteDepositExitted(t *testing.T) { func TestAnteDepositDNE(t *testing.T) { // setup - ctx, txStore, depositStore, blockStore := setup() + ctx, outputStore, blockStore := setup() // connection always returns exitted deposits - handler := NewAnteHandler(txStore, depositStore, blockStore, dneConn{}) + handler := NewAnteHandler(outputStore, blockStore, dneConn{}) msg := msgs.IncludeDepositMsg{ DepositNonce: big.NewInt(3), @@ -463,36 +458,26 @@ func TestAnteDepositDNE(t *testing.T) { } -func setupDeposits(ctx sdk.Context, txStore store.TxStore, depositStore store.DepositStore, inputs ...InputUTXO) { +func setupDeposits(ctx sdk.Context, outputStore store.OutputStore, inputs ...Deposit) { for _, i := range inputs { - deposit := store.Deposit{ - Deposit: plasma.Deposit{ - Owner: i.Owner, - Amount: i.Amount, - EthBlockNum: i.EthBlockNum - }, - Spent: i.Spent, - Spender: i.Spender, - } - depositStore.StoreDeposit(ctx, i.Nonce, deposit) - txStore.StoreDepositWithAccount(ctx, i.Nonce, deposit) + deposit := plasma.Deposit{ + Owner: i.Owner, + Amount: i.Amount, + EthBlockNum: i.EthBlockNum, + } + outputStore.StoreDeposit(ctx, i.Nonce, deposit) } } -func setupTxs(ctx sdk.Context, txStore store.TxStore, inputs ...InputUTXO) { +func setupTxs(ctx sdk.Context, outputStore store.OutputStore, inputs ...Tx) { for _, i := range inputs { - pos := plasma.NewPosition(i.BlockNum, i.TxIndex, i.OIndex, i.DepositNonce) - if pos.IsDeposit() { - } else { - tx := store.Transaction{ - Output: plasma.Output{ - Owner: i.Address, - Amount: big.NewInt(10), - }, - Spent: i.Spent, - Position: pos, - } + tx := store.Transaction{ + Transaction: i.Transaction, + ConfirmationHash: i.ConfirmationHash, + Spent: i.Spent, + Spenders: i.Spenders, + Position: i.Position, } - + outputStore.StoreTx(ctx, tx) } } diff --git a/handlers/depositHandler.go b/handlers/depositHandler.go index 0d57d6b..da35a4c 100644 --- a/handlers/depositHandler.go +++ b/handlers/depositHandler.go @@ -6,7 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func NewDepositHandler(depositStore store.DepositStore, txStore store.TxStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, client plasmaConn) sdk.Handler { +func NewDepositHandler(outputStore store.OutputStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, client plasmaConn) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { depositMsg, ok := msg.(msgs.IncludeDepositMsg) if !ok { @@ -18,13 +18,7 @@ func NewDepositHandler(depositStore store.DepositStore, txStore store.TxStore, b deposit, _, _ := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), depositMsg.DepositNonce) - dep := store.Deposit{ - Deposit: deposit, - Spent: false, - Spender: nil, - } - depositStore.StoreDeposit(ctx, depositMsg.DepositNonce, dep) - txStore.StoreDepositWithAccount(ctx, depositMsg.DepositNonce, deposit) + outputStore.StoreDeposit(ctx, depositMsg.DepositNonce, deposit) return sdk.Result{} } diff --git a/handlers/depositHandler_test.go b/handlers/depositHandler_test.go index f279503..89aadf3 100644 --- a/handlers/depositHandler_test.go +++ b/handlers/depositHandler_test.go @@ -2,18 +2,17 @@ package handlers import ( "github.com/FourthState/plasma-mvp-sidechain/msgs" - "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/stretchr/testify/require" "math/big" "testing" ) func TestIncludeDeposit(t *testing.T) { - // plasmaStore is at next block height 1 - ctx, utxoStore, plasmaStore := setup() + // blockStore is at next block height 1 + ctx, outputStore, blockStore := setup() // Give deposit a cooked connection that will always provide deposit with given position - depositHandler := NewDepositHandler(utxoStore, plasmaStore, nextTxIndex, conn{}) + depositHandler := NewDepositHandler(outputStore, blockStore, nextTxIndex, conn{}) // create a msg that spends the first input and creates two outputs msg := msgs.IncludeDepositMsg{ @@ -23,12 +22,10 @@ func TestIncludeDeposit(t *testing.T) { depositHandler(ctx, msg) - plasmaPosition := plasma.NewPosition(nil, 0, 0, big.NewInt(5)) - utxo, ok := utxoStore.GetUTXO(ctx, addr, plasmaPosition) + deposit, ok := outputStore.GetDeposit(ctx, big.NewInt(5)) - require.True(t, ok, "UTXO does not exist in store") - require.Equal(t, addr, utxo.Output.Owner, "UTXO has wrong owner") - require.Equal(t, big.NewInt(10), utxo.Output.Amount, "UTXO has wrong amount") - require.False(t, utxo.Spent, "Deposit UTXO is incorrectly marked as spent") - require.Equal(t, [][]byte{}, utxo.InputKeys, "Deposit UTXO has input keys set to non-nil value") + require.True(t, ok, "deposit does not exist in store") + require.Equal(t, addr, deposit.Deposit.Owner, "deposit has wrong owner") + require.Equal(t, big.NewInt(10), deposit.Deposit.Amount, "deposit has wrong amount") + require.False(t, deposit.Spent, "Deposit is incorrectly marked as spent") } diff --git a/handlers/handler_test.go b/handlers/handler_test.go index c72d67b..7064b9b 100644 --- a/handlers/handler_test.go +++ b/handlers/handler_test.go @@ -1,6 +1,7 @@ package handlers import ( + "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" cosmosStore "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -9,24 +10,28 @@ import ( "github.com/tendermint/tendermint/libs/log" ) -func setup() (sdk.Context, store.TxStore, store.DepositStore, store.BlockStore) { +/* This file contains helper functions for testing */ + +func setup() (sdk.Context, store.OutputStore, store.BlockStore) { db := db.NewMemDB() ms := cosmosStore.NewCommitMultiStore(db) ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) blockStoreKey := sdk.NewKVStoreKey("block") - depositStoreKey := sdk.NewKvStoreKey("deposit") - txStoreKey := sdk.NewKVStoreKey("tx") + outputStoreKey := sdk.NewKVStoreKey("output") ms.MountStoreWithDB(blockStoreKey, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(depositStoreKey, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(txStoreKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(outputStoreKey, sdk.StoreTypeIAVL, db) ms.LoadLatestVersion() blockStore := store.NewBlockStore(blockStoreKey) - depositStore := store.NewDepositStore(depositStoreKey) - txStore := store.NewUTXOStore(txStoreKey) + outputStore := store.NewOutputStore(outputStoreKey) + + return ctx, outputStore, blockStore +} - return ctx, txStore, depositStore, blockStore +func getPosition(posStr string) plasma.Position { + pos, _ := plasma.FromPositionString(posStr) + return pos } diff --git a/handlers/spendMsgHandler.go b/handlers/spendMsgHandler.go index d60ba1d..cba668e 100644 --- a/handlers/spendMsgHandler.go +++ b/handlers/spendMsgHandler.go @@ -2,7 +2,6 @@ package handlers import ( "crypto/sha256" - "fmt" "github.com/FourthState/plasma-mvp-sidechain/msgs" "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" @@ -16,7 +15,7 @@ type NextTxIndex func() uint16 // FeeUpdater updates the aggregate fee amount in a block type FeeUpdater func(amt *big.Int) sdk.Error -func NewSpendHandler(txStore store.TxStore, depositStore store.DepositStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, feeUpdater FeeUpdater) sdk.Handler { +func NewSpendHandler(outputStore store.OutputStore, blockStore store.BlockStore, nextTxIndex NextTxIndex, feeUpdater FeeUpdater) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { spendMsg, ok := msg.(msgs.SpendMsg) if !ok { @@ -39,24 +38,17 @@ func NewSpendHandler(txStore store.TxStore, depositStore store.DepositStore, blo ConfirmationHash: confirmationHash[:], Position: plasma.NewPosition(blockHeight, txIndex, 0, big.NewInt(0)), } - txStore.StoreTx(ctx, tx) + outputStore.StoreTx(ctx, tx) /* Spend Inputs */ for _, input := range spendMsg.Inputs { var res sdk.Result if input.Position.IsDeposit() { - // Spend deposit and update account - nonce := input.Position.DepositNonce - deposit, ok := depositStore.GetDeposit(ctx, nonce) - if !ok { - panic(fmt.Sprintf("deposit store corrupted")) - } - - res = depositStore.SpendDeposit(ctx, nonce, spendMsg.TxHash()) - txStore.SpendDepositWithAccount(ctx, nonce, deposit.Deposit) + res = outputStore.SpendDeposit(ctx, input.Position.DepositNonce, spendMsg.TxHash()) } else { - res = txStore.SpendUTXO(ctx, input.Position, spendMsg.TxHash()) + res = outputStore.SpendOutput(ctx, input.Position, spendMsg.TxHash()) } + if !res.IsOK() { return res } @@ -68,10 +60,7 @@ func NewSpendHandler(txStore store.TxStore, depositStore store.DepositStore, blo } /* Create Outputs */ - for i, _ := range spendMsg.Outputs { - pos := plasma.NewPosition(blockHeight, txIndex, uint8(i), big.NewInt(0)) - txStore.StoreUTXO(ctx, pos, spendMsg.TxHash()) - } + outputStore.StoreTx(ctx, tx) return sdk.Result{} } diff --git a/handlers/spendMsgHandler_test.go b/handlers/spendMsgHandler_test.go index b6765dd..7c04bea 100644 --- a/handlers/spendMsgHandler_test.go +++ b/handlers/spendMsgHandler_test.go @@ -3,7 +3,6 @@ package handlers import ( "github.com/FourthState/plasma-mvp-sidechain/msgs" "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/FourthState/plasma-mvp-sidechain/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -17,58 +16,55 @@ var nextTxIndex = func() uint16 { return 0 } var feeUpdater = func(num *big.Int) sdk.Error { return nil } func TestSpend(t *testing.T) { - // plasmaStore is at next block height 1 - ctx, utxoStore, plasmaStore := setup() + // blockStore is at next block height 1 + ctx, outputStore, blockStore := setup() privKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privKey.PublicKey) - spendHandler := NewSpendHandler(utxoStore, plasmaStore, nextTxIndex, feeUpdater) + spendHandler := NewSpendHandler(outputStore, blockStore, nextTxIndex, feeUpdater) // store inputs to be spent pos := plasma.NewPosition(utils.Big0, 0, 0, utils.Big1) - utxo := store.UTXO{ - Output: plasma.NewOutput(addr, big.NewInt(20)), - // position (0,0,0,1) - Position: pos, - Spent: false, + deposit := plasma.Deposit{ + Owner: addr, + Amount: big.NewInt(20), + EthBlockNum: big.NewInt(1000), } - utxoStore.StoreUTXO(ctx, utxo) + outputStore.StoreDeposit(ctx, pos.DepositNonce, deposit) // create a msg that spends the first input and creates two outputs newOwner := common.HexToAddress("1") msg := msgs.SpendMsg{ Transaction: plasma.Transaction{ - Input0: plasma.NewInput(pos, [65]byte{}, nil), - Input1: plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil), - Output0: plasma.NewOutput(newOwner, big.NewInt(10)), - Output1: plasma.NewOutput(newOwner, big.NewInt(10)), + Inputs: []plasma.Input{plasma.NewInput(pos, [65]byte{}, nil)}, + Outputs: []plasma.Output{plasma.NewOutput(newOwner, big.NewInt(10)), plasma.NewOutput(newOwner, big.NewInt(10))}, Fee: utils.Big0, }, } // fill in the signature sig, err := crypto.Sign(utils.ToEthSignedMessageHash(msg.TxHash()), privKey) - copy(msg.Input0.Signature[:], sig) + copy(msg.Inputs[0].Signature[:], sig) err = msg.ValidateBasic() require.NoError(t, err) res := spendHandler(ctx, msg) require.Truef(t, res.IsOK(), "failed to handle spend: %s", res) - // check that the utxo store reflects the spend - utxo, ok := utxoStore.GetUTXO(ctx, addr, pos) + // check that the output store reflects the spend + dep, ok := outputStore.GetDeposit(ctx, pos.DepositNonce) require.True(t, ok, "input to the msg does not exist in the store") - require.True(t, utxo.Spent, "input not marked as spent after the handler") + require.True(t, dep.Spent, "input not marked as spent after the handler") // new first output was created at BlockHeight 1 and txIndex 0 and outputIndex 0 pos = plasma.NewPosition(utils.Big1, 0, 0, nil) - utxo, ok = utxoStore.GetUTXO(ctx, newOwner, pos) + utxo, ok := outputStore.GetOutput(ctx, pos) require.True(t, ok, "new output was not created") require.False(t, utxo.Spent, "new output marked as spent") require.Equal(t, utxo.Output.Amount, big.NewInt(10), "new output has incorrect amount") // new second output was created at BlockHeight 0 and txIndex 0 and outputIndex 1 pos = plasma.NewPosition(utils.Big1, 0, 1, nil) - utxo, ok = utxoStore.GetUTXO(ctx, newOwner, pos) + utxo, ok = outputStore.GetOutput(ctx, pos) require.True(t, ok, "new output was not created") require.False(t, utxo.Spent, "new output marked as spent") require.Equal(t, utxo.Output.Amount, big.NewInt(10), "new output has incorrect amount") diff --git a/server/app/app.go b/server/app/app.go index 1f9f2af..cc75126 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -36,9 +36,8 @@ type PlasmaMVPChain struct { feeAmount *big.Int // persistent stores - txStore store.TxStore - depositStore store.DepositStore - blockStore store.BlockStore + outputStore store.OutputStore + blockStore store.BlockStore // smart contract connection ethConnection *eth.Plasma @@ -58,12 +57,10 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio cdc := MakeCodec() baseApp.SetCommitMultiStoreTracer(traceStore) - txStoreKey := sdk.NewKVStoreKey("transaction") - txStore := store.NewUTXOStore(txStoreKey) - depositStoreKey := sdk.NewKVStoreKey("deposit") - depositStore := store.NewDepositStore(depositStoreKey) + outputStoreKey := sdk.NewKVStoreKey("outputs") + outputStore := store.NewOutputStore(outputStoreKey) blockStoreKey := sdk.NewKVStoreKey("block") - blockStore := store.NewPlasmaStore(blockStoreKey) + blockStore := store.NewBlockStore(blockStoreKey) app := &PlasmaMVPChain{ BaseApp: baseApp, @@ -71,9 +68,8 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio txIndex: 0, feeAmount: big.NewInt(0), // we do not use `utils.BigZero` because the feeAmount is going to be updated - blockStore: plasmaStore, - depositStore: depositStore, - txStore: txStore, + blockStore: blockStore, + outputStore: outputStore, } // set configs @@ -112,16 +108,16 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio app.feeAmount = app.feeAmount.Add(app.feeAmount, amt) return nil } - app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.txStore, app.depositStore, app.blockStore, nextTxIndex, feeUpdater)) - app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.depositStore, app.txStore, app.blockStore, nextTxIndex, plasmaClient)) + app.Router().AddRoute(msgs.SpendMsgRoute, handlers.NewSpendHandler(app.outputStore, app.blockStore, nextTxIndex, feeUpdater)) + app.Router().AddRoute(msgs.IncludeDepositMsgRoute, handlers.NewDepositHandler(app.outputStore, app.blockStore, nextTxIndex, plasmaClient)) // custom queriers app.QueryRouter(). - AddRoute("tx", query.NewUtxoQuerier(utxoStore)). - AddRoute("block", query.NewPlasmaQuerier(plasmaStore)) + AddRoute("tx", store.NewOutputQuerier(outputStore)). + AddRoute("block", store.NewBlockQuerier(blockStore)) // Set the AnteHandler - app.SetAnteHandler(handlers.NewAnteHandler(app.txStore, app.depositStore, app.blockStore, plasmaClient)) + app.SetAnteHandler(handlers.NewAnteHandler(app.outputStore, app.blockStore, plasmaClient)) // set the rest of the chain flow app.SetEndBlocker(app.endBlocker) @@ -129,8 +125,8 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio // mount and load stores // IAVL store used by default. `fauxMerkleMode` defaults to false - app.MountStores(txStoreKey, depositStore, blockStoreKey) - if err := app.LoadLatestVersion(utxoStoreKey); err != nil { + app.MountStores(outputStoreKey, blockStoreKey) + if err := app.LoadLatestVersion(outputStoreKey); err != nil { fmt.Println(err) os.Exit(1) } @@ -168,7 +164,7 @@ func (app *PlasmaMVPChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) var header [32]byte copy(header[:], ctx.BlockHeader().DataHash) block := plasma.NewBlock(header, app.txIndex, app.feeAmount) - plasmaBlockNum := app.blockStore.StoreBlock(ctx, tmBlockHeight, block) + app.blockStore.StoreBlock(ctx, tmBlockHeight, block) app.ethConnection.CommitPlasmaHeaders(ctx, app.blockStore) app.txIndex = 0 diff --git a/store/outputStore.go b/store/outputStore.go index c904733..9c4977b 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -87,6 +87,11 @@ func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) // GetOutput returns the output at the given position func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output, bool) { + // allow deposits to returned as an output + if pos.IsDeposit() { + return store.depositToOutput(ctx, pos.DepositNonce) + } + key := prefixKey(positionKey, pos.Bytes()) hash := store.Get(ctx, key) @@ -253,6 +258,20 @@ func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utx return utxos } +// depositToOutput retrieves the deposit with the given nonce, and returns it as an output +func (store OutputStore) depositToOutput(ctx sdk.Context, nonce *big.Int) (Output, bool) { + deposit, ok := store.GetDeposit(ctx, nonce) + if !ok { + return Output{}, ok + } + output := Output{ + Output: plasma.NewOutput(deposit.Deposit.Owner, deposit.Deposit.Amount), + Spent: deposit.Spent, + Spender: deposit.Spender, + } + return output, ok +} + // addToAccount adds the passed in amount to the account with the given address // adds the position provided to the list of unspent positions within the account func (store OutputStore) addToAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { @@ -280,7 +299,7 @@ func (store OutputStore) subtractFromAccount(ctx sdk.Context, addr common.Addres panic(fmt.Sprintf("account with address 0x%x has a negative balance", addr)) } - removePosition(acc.Unspent, pos) + acc.Unspent = removePosition(acc.Unspent, pos) acc.Spent = append(acc.Spent, pos) store.setAccount(ctx, addr, acc) } diff --git a/store/outputStore_test.go b/store/outputStore_test.go index 1c3be2a..ae461be 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -134,7 +134,7 @@ func TestTransactions(t *testing.T) { } // Create and store new transaction - tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), getPosition("(4567.1.1.0)")} + tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Position} for i, _ := range tx.Spenders { tx.Spenders[i] = []byte{} } @@ -144,7 +144,7 @@ func TestTransactions(t *testing.T) { for j, _ := range plasmaTx.Transaction.Outputs { p := plasma.NewPosition(pos.BlockNum, pos.TxIndex, uint8(j), pos.DepositNonce) exists = outputStore.HasOutput(ctx, p) - require.True(t, exists, "returned false for stored Output") + require.True(t, exists, fmt.Sprintf("returned false for stored output with index %d on case %d", j, i)) } // Check for Tx @@ -162,18 +162,18 @@ func TestTransactions(t *testing.T) { require.True(t, reflect.DeepEqual(tx, recoveredTx), fmt.Sprintf("mismatch in stored transaction and retrieved transaction on case %d", i)) res := outputStore.SpendOutput(ctx, p, plasmaTx.Transaction.MerkleHash()) - require.True(t, res.IsOK(), "returned error when spending Output") + require.True(t, res.IsOK(), "returned error when spending output") res = outputStore.SpendOutput(ctx, p, plasmaTx.Transaction.MerkleHash()) - require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") + require.Equal(t, res.Code, CodeOutputSpent, fmt.Sprintf("allowed output with index %d to be spent twice on case %d", j, i)) tx.Spent[j] = true tx.Spenders[j] = plasmaTx.Transaction.MerkleHash() recoveredTx, ok = outputStore.GetTxWithPosition(ctx, p) require.True(t, ok, "error when retrieving transaction") - require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in stored and retrieved transaction") + require.True(t, reflect.DeepEqual(tx, recoveredTx), fmt.Sprintf("mismatch in stored and retrieved transaction on case %d", i)) recoveredTx, ok = outputStore.GetTx(ctx, plasmaTx.Transaction.TxHash()) require.True(t, ok, "error when retrieving transaction") - require.True(t, reflect.DeepEqual(tx, recoveredTx), "mismatch in stored and retrieved transaction") + require.True(t, reflect.DeepEqual(tx, recoveredTx), fmt.Sprintf("mismatch in stored and retrieved transaction on case %d", i)) } } } From 0691dec490e1a6c95aa03f1c27a89de64df7e3c3 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 16 May 2019 14:20:54 -0700 Subject: [PATCH 22/49] added fee support to store/ with tests --- store/outputStore.go | 124 ++++++++++++++++++++++++++++---------- store/outputStore_test.go | 44 ++++++++++++++ 2 files changed, 136 insertions(+), 32 deletions(-) diff --git a/store/outputStore.go b/store/outputStore.go index 9c4977b..88289f4 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -11,9 +11,10 @@ import ( const ( accountKey = "acc" + depositKey = "deposit" + feeKey = "fee" hashKey = "hash" positionKey = "pos" - depositKey = "deposit" ) /* Output Store */ @@ -62,27 +63,20 @@ func (store OutputStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, b return deposit, true } -// GetTx returns the transaction with the provided transaction hash -func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { - key := prefixKey(hashKey, hash) +// GetFee returns the fee at the given position +func (store OutputStore) GetFee(ctx sdk.Context, pos plasma.Position) (Output, bool) { + key := prefixKey(feeKey, pos.Bytes()) data := store.Get(ctx, key) if data == nil { - return Transaction{}, false + return Output{}, false } - var tx Transaction - if err := rlp.DecodeBytes(data, &tx); err != nil { - panic(fmt.Sprintf("transaction store corrupted: %s", err)) + var fee Output + if err := rlp.DecodeBytes(data, &fee); err != nil { + panic(fmt.Sprintf("output store corrupted: %s", err)) } - return tx, true -} - -// GetTxWithPosition returns the transaction that contains the provided position as an output -func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { - key := prefixKey(positionKey, pos.Bytes()) - hash := store.Get(ctx, key) - return store.GetTx(ctx, hash) + return fee, true } // GetOutput returns the output at the given position @@ -109,10 +103,33 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output return output, ok } +// GetTx returns the transaction with the provided transaction hash +func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { + key := prefixKey(hashKey, hash) + data := store.Get(ctx, key) + if data == nil { + return Transaction{}, false + } + + var tx Transaction + if err := rlp.DecodeBytes(data, &tx); err != nil { + panic(fmt.Sprintf("transaction store corrupted: %s", err)) + } + + return tx, true +} + +// GetTxWithPosition returns the transaction that contains the provided position as an output +func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { + key := prefixKey(positionKey, pos.Bytes()) + hash := store.Get(ctx, key) + return store.GetTx(ctx, hash) +} + // ----------------------------------------------------------------------------- /* Has */ -// HasTx returns whether an account at the given address exists +// HasAccount returns whether an account at the given address exists func (store OutputStore) HasAccount(ctx sdk.Context, addr common.Address) bool { key := prefixKey(accountKey, addr.Bytes()) return store.Has(ctx, key) @@ -124,9 +141,9 @@ func (store OutputStore) HasDeposit(ctx sdk.Context, nonce *big.Int) bool { return store.Has(ctx, key) } -// HasTx returns whether a transaction with the given transaction hash exists -func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { - key := prefixKey(hashKey, hash) +// HasFee returns whether a fee with the given position exists +func (store OutputStore) HasFee(ctx sdk.Context, pos plasma.Position) bool { + key := prefixKey(feeKey, pos.Bytes()) return store.Has(ctx, key) } @@ -138,9 +155,26 @@ func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { return store.HasTx(ctx, hash) } +// HasTx returns whether a transaction with the given transaction hash exists +func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { + key := prefixKey(hashKey, hash) + return store.Has(ctx, key) +} + // ----------------------------------------------------------------------------- /* Set */ +// SetAccount overwrites the account stored at the given address +func (store OutputStore) setAccount(ctx sdk.Context, addr common.Address, acc Account) { + key := prefixKey(accountKey, addr.Bytes()) + data, err := rlp.EncodeToBytes(&acc) + if err != nil { + panic(fmt.Sprintf("error marshaling transaction: %s", err)) + } + + store.Set(ctx, key, data) +} + // SetDeposit overwrites the deposit stored with the given nonce func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { data, err := rlp.EncodeToBytes(&deposit) @@ -152,17 +186,23 @@ func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Dep store.Set(ctx, key, data) } -// SetAccount overwrites the account stored at the given address -func (store OutputStore) setAccount(ctx sdk.Context, addr common.Address, acc Account) { - key := prefixKey(accountKey, addr.Bytes()) - data, err := rlp.EncodeToBytes(&acc) +// setFee overwrites the fee stored with the given position +func (store OutputStore) setFee(ctx sdk.Context, pos plasma.Position, fee Output) { + data, err := rlp.EncodeToBytes(&fee) if err != nil { - panic(fmt.Sprintf("error marshaling transaction: %s", err)) + panic(fmt.Sprintf("error marshaling fee with position %s: %s", pos, err)) } + key := prefixKey(feeKey, pos.Bytes()) store.Set(ctx, key, data) } +// SetOutput adds a mapping from position to transaction hash +func (store OutputStore) setOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { + key := prefixKey(positionKey, pos.Bytes()) + store.Set(ctx, key, hash) +} + // SetTx overwrites the mapping from transaction hash to transaction func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { data, err := rlp.EncodeToBytes(&tx) @@ -174,12 +214,6 @@ func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { store.Set(ctx, key, data) } -// SetOutput adds a mapping from position to transaction hash -func (store OutputStore) setOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { - key := prefixKey(positionKey, pos.Bytes()) - store.Set(ctx, key, hash) -} - // ----------------------------------------------------------------------------- /* Store */ @@ -190,6 +224,13 @@ func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit p store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) } +// StoreFee adds an unspent fee +// Updates the fee owner's account +func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output plasma.Output) { + store.setFee(ctx, pos, Output{output, false, make([]byte, 0)}) + store.addToAccount(ctx, output.Owner, output.Amount, pos) +} + // StoreTx adds the transaction // Updates the output owner's accounts func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { @@ -204,7 +245,7 @@ func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { /* Spend */ // SpendDeposit changes the deposit to be spent -// Updates the account of the deposit owner in the SetDeposit call +// Updates the account of the deposit owner func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { @@ -222,6 +263,25 @@ func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender [ return sdk.Result{} } +// SpendFee changes the fee to be spent +// Updates the account of the fee owner +func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { + fee, ok := store.GetFee(ctx, pos) + if !ok { + return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("fee with position %s does not exist", pos)).Result() + } else if fee.Spent { + return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("fee with position %s is already spent", pos)).Result() + } + + fee.Spent = true + fee.Spender = spender + + store.setFee(ctx, pos, fee) + store.subtractFromAccount(ctx, fee.Output.Owner, fee.Output.Amount, pos) + + return sdk.Result{} +} + // SpendOutput changes the output to be spent // Updates the account of the output owner func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { diff --git a/store/outputStore_test.go b/store/outputStore_test.go index ae461be..5807b78 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -56,6 +56,50 @@ func TestDeposits(t *testing.T) { } } +// Test Get, Has, Store, Spend functions for fees +func TestFees(t *testing.T) { + ctx, key := setup() + outputStore := NewOutputStore(key) + + addr := common.BytesToAddress([]byte("asdfasdf")) + for i := int64(1); i <= 15; i++ { + pos := plasma.NewPosition(big.NewInt(int64(i)), 1<<16-1, 0, big.NewInt(0)) + hash := []byte("hash that the fee was spent in") + + // Retrieve/Spend nonexistent fee + exists := outputStore.HasFee(ctx, pos) + require.False(t, exists, "returned true for nonexistent fee") + recoveredFee, ok := outputStore.GetFee(ctx, pos) + require.Empty(t, recoveredFee, "did not return empty struct for nonexistent fee") + require.False(t, ok, "did not return error on nonexistent fee") + res := outputStore.SpendFee(ctx, pos, hash) + require.Equal(t, res.Code, CodeOutputDNE, "did not return that fee does not exist") + + // Create and store new fee + output := plasma.NewOutput(addr, big.NewInt(int64(1000*i))) + fee := Output{output, false, make([]byte, 0)} + outputStore.StoreFee(ctx, pos, output) + + exists = outputStore.HasFee(ctx, pos) + require.True(t, exists, "returned false for fee that was stored") + recoveredFee, ok = outputStore.GetFee(ctx, pos) + require.True(t, ok, "error when retrieving fee") + require.True(t, reflect.DeepEqual(fee, recoveredFee), fmt.Sprintf("mismatch in stored fee and retrieved fee on iteration %d", i)) + + // Spend Fee + res = outputStore.SpendFee(ctx, pos, hash) + require.True(t, res.IsOK(), "returned error when spending fee") + res = outputStore.SpendFee(ctx, pos, hash) + require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") + + fee.Spent = true + fee.Spender = hash + recoveredFee, ok = outputStore.GetFee(ctx, pos) + require.True(t, ok, "error when retrieving fee") + require.True(t, reflect.DeepEqual(fee, recoveredFee), "mismatch in stored and retrieved fee") + } +} + // Test Get, Has, Store, Spend functions for transactions func TestTransactions(t *testing.T) { // Setup From 97361004f0cf19d8a32d9874ae992ea8d9e255e3 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 31 May 2019 12:10:37 -0700 Subject: [PATCH 23/49] in progress work being committed for review --- client/plasmacli/eth/prove.go | 58 ++++++++++++++++++---------------- client/plasmacli/query/info.go | 22 ++++--------- client/plasmacli/spend.go | 2 +- handlers/anteHandler_test.go | 25 +++++++++++++-- plasma/transaction.go | 8 +++++ store/outputStore.go | 22 ++++++++++--- store/querier.go | 2 +- store/types.go | 7 ++++ 8 files changed, 94 insertions(+), 52 deletions(-) diff --git a/client/plasmacli/eth/prove.go b/client/plasmacli/eth/prove.go index 126f9d5..fea0ba0 100644 --- a/client/plasmacli/eth/prove.go +++ b/client/plasmacli/eth/prove.go @@ -4,10 +4,10 @@ import ( "fmt" ks "github.com/FourthState/plasma-mvp-sidechain/client/store" "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/store" - "github.com/cosmos/cosmos-sdk/client/context" + // "github.com/FourthState/plasma-mvp-sidechain/store" + // "github.com/cosmos/cosmos-sdk/client/context" ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" + // "github.com/ethereum/go-ethereum/rlp" "github.com/spf13/cobra" tm "github.com/tendermint/tendermint/rpc/core/types" ) @@ -73,30 +73,32 @@ var proveCmd = &cobra.Command{ // Returns transaction results for given position // Trusts connected full node + +//TODO: REDO func getProof(addr ethcmn.Address, position plasma.Position) (*tm.ResultTx, []byte, error) { - ctx := context.NewCLIContext().WithTrustNode(true) - - // query for the output - key := append(addr.Bytes(), position.Bytes()...) - res, err := ctx.QueryStore(key, "utxo") - if err != nil { - return &tm.ResultTx{}, nil, err - } - - utxo := store.UTXO{} - if err := rlp.DecodeBytes(res, &utxo); err != nil { - return &tm.ResultTx{}, nil, err - } - - // query tm node for information about this tx - result, err := ctx.Client.Tx(utxo.MerkleHash, true) - if err != nil { - return &tm.ResultTx{}, nil, err - } - - // Look for confirmation signatures - key = append([]byte("confirmSignature"), utxo.Position.Bytes()...) - sigs, err := ctx.QueryStore(key, "plasma") - - return result, sigs, nil + /* ctx := context.NewCLIContext().WithTrustNode(true) + + // query for the output + key := append(store.Key, position.Bytes()...) + res, err := ctx.QueryStore(key, "outputs") + if err != nil { + return &tm.ResultTx{}, nil, err + } + + utxo := store.Output{} + if err := rlp.DecodeBytes(res, &utxo); err != nil { + return &tm.ResultTx{}, nil, err + } + + // query tm node for information about this tx + result, err := ctx.Client.Tx(utxo.MerkleHash, true) + if err != nil { + return &tm.ResultTx{}, nil, err + } + + // Look for confirmation signatures + key = append([]byte("confirmSignature"), utxo.Position.Bytes()...) + sigs, err := ctx.QueryStore(key, "plasma") + */ + return &tm.ResultTx{}, []byte{}, nil } diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index 9e318be..a84a410 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -34,20 +34,12 @@ var infoCmd = &cobra.Command{ for i, utxo := range utxos { fmt.Printf("UTXO %d\n", i) - fmt.Printf("Position: %s, Amount: %s, Spent: %t\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent) - + fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Tx.Position, utxo.Output.Output.Amount.String(), utxo.Output.Spent, utxo.Output.Spender) + fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.Tx.Transaction.TxHash(), utxo.Tx.ConfirmationHash) // print inputs if applicable - inputAddresses := utxo.InputAddresses() - positions := utxo.InputPositions() - for i, _ := range inputAddresses { - fmt.Printf("Input Owner %d, Position: %s\n", i, positions[i]) - } - - // print spenders if applicable - spenderAddresses := utxo.SpenderAddresses() - positions = utxo.SpenderPositions() - for i, _ := range spenderAddresses { - fmt.Printf("Spender Owner %d, Position: %s\n", i, positions[i]) + positions := utxo.Tx.Transaction.InputPositions() + for i, p := range positions { + fmt.Printf("Input %d Position: %s\n", i, p) } fmt.Printf("End UTXO %d info\n\n", i) @@ -61,7 +53,7 @@ var infoCmd = &cobra.Command{ }, } -func Info(ctx context.CLIContext, addr common.Address) ([]store.UTXO, error) { +func Info(ctx context.CLIContext, addr common.Address) ([]store.QueryOutput, error) { // query for all utxos owned by this address queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) data, err := ctx.Query(queryRoute, nil) @@ -69,7 +61,7 @@ func Info(ctx context.CLIContext, addr common.Address) ([]store.UTXO, error) { return nil, err } - var utxos []store.UTXO + var utxos []store.QueryOutput if err := json.Unmarshal(data, &utxos); err != nil { return nil, err } diff --git a/client/plasmacli/spend.go b/client/plasmacli/spend.go index 5df14d2..c4c6df8 100644 --- a/client/plasmacli/spend.go +++ b/client/plasmacli/spend.go @@ -108,7 +108,7 @@ Usage: // generate outputs // use change to determine outcome of second output - tx.Output0 = plasma.NewOutput(toAddrs[0], amounts[0]) + tx.Outputs[0] = plasma.NewOutput(toAddrs[0], amounts[0]) if len(toAddrs) > 1 { if change.Sign() == 0 { tx.Output1 = plasma.NewOutput(toAddrs[1], amounts[1]) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index b4f414e..1ec16d9 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -37,6 +37,11 @@ type Deposit struct { Amount *big.Int } +type Output struct { + Output plasma.Output + Position plasma.Position +} + // cook the plasma connection type conn struct{} @@ -62,14 +67,22 @@ func TestAnteChecks(t *testing.T) { ctx, outputStore, blockStore := setup() handler := NewAnteHandler(outputStore, blockStore, conn{}) + feePosition := getPosition("(100.65535.0.0)") // cook up some input deposits inputs := []Deposit{ {addr, big.NewInt(1), big.NewInt(100), big.NewInt(10)}, {addr, big.NewInt(2), big.NewInt(101), big.NewInt(10)}, {addr, big.NewInt(3), big.NewInt(102), big.NewInt(10)}, } + fee := Output{ + Output: plasma.NewOutput(addr, big.NewInt(10)), + Position: feePosition, + } + setupDeposits(ctx, outputStore, inputs...) - outputStore.SpendDeposit(ctx, big.NewInt(3), []byte("spender hash")) + setupFees(ctx, outputStore, fee) + + outputStore.SpendFee(ctx, feePosition, []byte("spender hash")) type validationCase struct { reason string @@ -94,7 +107,7 @@ func TestAnteChecks(t *testing.T) { reason: "incorrect second signature", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big2), [65]byte{}, nil)}, + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(feePosition, [65]byte{}, nil)}, Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(20))}, Fee: utils.Big0, }, @@ -154,7 +167,7 @@ func TestAnteChecks(t *testing.T) { reason: "Inputs utxo already spent", SpendMsg: msgs.SpendMsg{ Transaction: plasma.Transaction{ - Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(plasma.NewPosition(nil, 0, 0, big.NewInt(3)), [65]byte{}, nil)}, + Inputs: []plasma.Input{plasma.NewInput(plasma.NewPosition(nil, 0, 0, utils.Big1), [65]byte{}, nil), plasma.NewInput(feePosition, [65]byte{}, nil)}, Outputs: []plasma.Output{plasma.NewOutput(addr, big.NewInt(10)), plasma.NewOutput(addr, big.NewInt(10))}, Fee: utils.Big0, }, @@ -458,6 +471,12 @@ func TestAnteDepositDNE(t *testing.T) { } +func setupFees(ctx sdk.Context, outputStore store.OutputStore, inputs ...Output) { + for _, output := range inputs { + outputStore.StoreFee(ctx, output.Position, output.Output) + } +} + func setupDeposits(ctx sdk.Context, outputStore store.OutputStore, inputs ...Deposit) { for _, i := range inputs { deposit := plasma.Deposit{ diff --git a/plasma/transaction.go b/plasma/transaction.go index a83fc6d..941b84c 100644 --- a/plasma/transaction.go +++ b/plasma/transaction.go @@ -132,6 +132,14 @@ func (tx Transaction) Sigs() (sigs [2][65]byte) { return sigs } +// InputPositions returns the input positions for this transaction +func (tx Transaction) InputPositions() (positions []Position) { + for _, input := range tx.Inputs { + positions = append(positions, input.Position) + } + return positions +} + func (tx Transaction) String() (str string) { for i, input := range tx.Inputs { str += fmt.Sprintf("Input %d: %s\n", i, input) diff --git a/store/outputStore.go b/store/outputStore.go index 88289f4..a43e2d7 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -81,11 +81,19 @@ func (store OutputStore) GetFee(ctx sdk.Context, pos plasma.Position) (Output, b // GetOutput returns the output at the given position func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output, bool) { - // allow deposits to returned as an output + // allow deposits/fees to returned as an output if pos.IsDeposit() { return store.depositToOutput(ctx, pos.DepositNonce) } + if pos.IsFee() { + fee, ok := store.GetFee(ctx, pos) + if !ok { + return Output{}, ok + } + return fee, ok + } + key := prefixKey(positionKey, pos.Bytes()) hash := store.Get(ctx, key) @@ -308,11 +316,17 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend /* Helpers */ // GetUnspentForAccount returns the unspent outputs that belong to the given account -func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []Output) { +// Returns using struct QueryOutput so user has access to transaction that created the output +func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []QueryOutput) { for _, p := range acc.Unspent { - utxo, ok := store.GetOutput(ctx, p) + output, ok := store.GetOutput(ctx, p) + var utxo QueryOutput + if ok { + utxo.Output = output + } + tx, ok := store.GetTxWithPosition(ctx, p) if ok { - utxos = append(utxos, utxo) + utxo.Tx = tx } } return utxos diff --git a/store/querier.go b/store/querier.go index 5722fbb..7a71845 100644 --- a/store/querier.go +++ b/store/querier.go @@ -62,7 +62,7 @@ func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) return acc.Balance, nil } -func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]Output, sdk.Error) { +func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]QueryOutput, sdk.Error) { acc, ok := outputStore.GetAccount(ctx, addr) if !ok { return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) diff --git a/store/types.go b/store/types.go index 34c0b3c..75cc345 100644 --- a/store/types.go +++ b/store/types.go @@ -34,3 +34,10 @@ type Transaction struct { Spenders [][]byte Position plasma.Position } + +/* Wraps Output with transaction is was created with + this allows for input addresses to be retireved */ +type QueryOutput struct { + Output Output + Tx Transaction +} From 0cbbb1a3174723337e35d802c84d1691378d1417 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Mon, 3 Jun 2019 19:57:13 -0700 Subject: [PATCH 24/49] query metadata about 10 blocks --- store/plasmaStore.go | 12 +++++++ store/querier.go | 80 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/store/plasmaStore.go b/store/plasmaStore.go index 764c869..2c82461 100644 --- a/store/plasmaStore.go +++ b/store/plasmaStore.go @@ -97,6 +97,18 @@ func (store PlasmaStore) StoreConfirmSignatures(ctx sdk.Context, position plasma store.Set(ctx, key, sigs) } +func (store PlasmaStore) PlasmaBlockHeight(ctx sdk.Context) *big.Int { + var plasmaBlockNum *big.Int + data := store.Get(ctx, []byte(plasmaBlockNumKey)) + if data == nil { + plasmaBlockNum = big.NewInt(1) + } else { + plasmaBlockNum = new(big.Int).SetBytes(data) + } + + return plasmaBlockNum +} + func (store PlasmaStore) NextPlasmaBlockNum(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) diff --git a/store/querier.go b/store/querier.go index 55fb452..0beda8f 100644 --- a/store/querier.go +++ b/store/querier.go @@ -2,6 +2,8 @@ package store import ( "encoding/json" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" abci "github.com/tendermint/tendermint/abci/types" @@ -9,8 +11,13 @@ import ( ) const ( + // QueryBalance retrieves the aggregate value of + // the set of owned by the specified address QueryBalance = "balance" - QueryInfo = "info" + + // QueryInfo retrieves the entire utxo set owned + // by the specified address + QueryInfo = "info" ) func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { @@ -22,7 +29,7 @@ func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { switch path[0] { case QueryBalance: if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("balance query follows balance/
") + return nil, sdk.ErrUnknownRequest("exprected balance/
") } addr := common.HexToAddress(path[1]) total, err := queryBalance(ctx, utxoStore, addr) @@ -33,7 +40,7 @@ func NewUtxoQuerier(utxoStore UTXOStore) sdk.Querier { case QueryInfo: if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("info query follows /info/
") + return nil, sdk.ErrUnknownRequest("expected info/
") } addr := common.HexToAddress(path[1]) utxos, err := queryInfo(ctx, utxoStore, addr) @@ -85,9 +92,21 @@ func queryInfo(ctx sdk.Context, utxoStore UTXOStore, addr common.Address) ([]UTX } const ( + // QueryBlocks retrieves full information about a + // speficied block QueryBlock = "block" + + // QueryBlocs retrieves metadata about 10 blocks from + // a specified start point or the last 10 from the latest + // block + QueryBlocks = "blocks" ) +type BlocksResp struct { + StartingBlockHeight *big.Int + Blocks []plasma.Block +} + func NewPlasmaQuerier(plasmaStore PlasmaStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { @@ -97,11 +116,11 @@ func NewPlasmaQuerier(plasmaStore PlasmaStore) sdk.Querier { switch path[0] { case QueryBlock: if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("block query follows /plasma/block/") + return nil, sdk.ErrUnknownRequest("expected block/") } blockNum, ok := new(big.Int).SetString(path[1], 10) if !ok { - return nil, sdk.ErrUnknownRequest("block number must be provided in deicmal format") + return nil, sdk.ErrUnknownRequest("block number must be provided in decimal format") } block, ok := plasmaStore.GetBlock(ctx, blockNum) if !ok { @@ -112,8 +131,59 @@ func NewPlasmaQuerier(plasmaStore PlasmaStore) sdk.Querier { return nil, sdk.ErrInternal("serialization error") } return data, nil + case QueryBlocks: + if len(path) > 2 { + return nil, sdk.ErrUnknownRequest("expected /blocks or /blocks/") + } + + var startingBlockNum *big.Int + if len(path) == 1 { + // latest 10 blocks + startingBlockNum = plasmaStore.PlasmaBlockHeight(ctx) + bigNine := big.NewInt(9) + if startingBlockNum.Cmp(bigNine) <= 0 { + startingBlockNum = big.NewInt(1) + } else { + startingBlockNum = startingBlockNum.Sub(startingBlockNum, bigNine) + } + } else { + // predefined starting point + var ok bool + startingBlockNum, ok = new(big.Int).SetString(path[1], 10) + if !ok { + return nil, sdk.ErrUnknownRequest("block number must be in decimal format") + } + } + + blocks, sdkErr := queryBlocks(ctx, plasmaStore, startingBlockNum) + if sdkErr != nil { + return nil, sdkErr + } + data, err := json.Marshal(blocks) + if err != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil default: return nil, sdk.ErrUnknownRequest("unregistered endpoint") } } } + +func queryBlocks(ctx sdk.Context, plasmaStore PlasmaStore, startPoint *big.Int) (BlocksResp, sdk.Error) { + resp := BlocksResp{startPoint, []plasma.Block{}} + + // want `startPoint` to remain the same + blockHeight := new(big.Int).Add(startPoint, utils.Big0) + for i := 0; i < 10; i++ { + block, ok := plasmaStore.GetBlock(ctx, blockHeight) + if !ok { + return resp, nil + } + + resp.Blocks = append(resp.Blocks, block.Block) + blockHeight = blockHeight.Add(blockHeight, utils.Big1) + } + + return resp, nil +} From 7c600510edd419fed2d320966a697f85c6867173 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Mon, 3 Jun 2019 19:57:48 -0700 Subject: [PATCH 25/49] blocks query commond and likewise rest interface --- client/plasmacli/main.go | 5 +- client/plasmacli/query/block.go | 81 ++++++++++++++++++++++++++++++++- client/plasmacli/rest.go | 41 +++++++++++++++-- 3 files changed, 119 insertions(+), 8 deletions(-) diff --git a/client/plasmacli/main.go b/client/plasmacli/main.go index 532c505..a741d51 100644 --- a/client/plasmacli/main.go +++ b/client/plasmacli/main.go @@ -32,8 +32,9 @@ const ( ) var rootCmd = &cobra.Command{ - Use: "plasmacli", - Short: "Plasma Client", + Use: "plasmacli", + Short: "Plasma Client", + SilenceErrors: true, } func init() { diff --git a/client/plasmacli/query/block.go b/client/plasmacli/query/block.go index 1883639..ac7f46a 100644 --- a/client/plasmacli/query/block.go +++ b/client/plasmacli/query/block.go @@ -4,13 +4,16 @@ import ( "encoding/json" "fmt" "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/FourthState/plasma-mvp-sidechain/utils" "github.com/cosmos/cosmos-sdk/client/context" "github.com/spf13/cobra" + "math/big" "strings" ) func init() { queryCmd.AddCommand(blockCmd) + queryCmd.AddCommand(blocksCmd) } var blockCmd = &cobra.Command{ @@ -18,7 +21,7 @@ var blockCmd = &cobra.Command{ Short: "Query information about a plasma block", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCLIContext().WithTrustNode(true) + ctx := context.NewCLIContext() num := strings.TrimSpace(args[0]) block, err := Block(ctx, num) @@ -34,6 +37,61 @@ var blockCmd = &cobra.Command{ }, } +var blocksCmd = &cobra.Command{ + Use: "blocks ", + Short: "Query Metadata about blocks", + Long: "Query Metadata about blocks. If no height is provided, the latest 10 blocks will be queried. Otherwise 10 starting from the specified height", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.NewCLIContext() + if len(args) > 1 { + return fmt.Errorf("only maximum one argument") + } + + startingBlockNum := "" + if len(args) == 1 { + startingBlockNum = strings.TrimSpace(args[0]) + if startingBlockNum == "0" { + return fmt.Errorf("plasma blocks start at height 1") + } + + _, ok := new(big.Int).SetString(startingBlockNum, 10) + if !ok { + return fmt.Errorf("provided block height must be in decimal format") + } + + fmt.Printf("Querying blocks from height %s...\n", startingBlockNum) + } else { + fmt.Printf("No height specified. Querying the latest 10 blocks..\n") + } + + // finished argument checking + cmd.SilenceUsage = true + + blocksResp, err := BlocksMetadata(ctx, startingBlockNum) + if err != nil { + return err + } + + if len(blocksResp.Blocks) == 0 { + fmt.Printf("no block starting at height %s\n", startingBlockNum) + return nil + } + + blockHeight := blocksResp.StartingBlockHeight + for _, block := range blocksResp.Blocks { + fmt.Println("") + fmt.Printf("Block Height: %s\n", blockHeight) + fmt.Printf("Header: 0x%x\n", block.Header) + fmt.Printf("Transaction Count: %d\n", block.TxnCount) + fmt.Printf("Fee Amount: %s\n", block.FeeAmount) + + blockHeight = blockHeight.Add(blockHeight, utils.Big1) + } + + return nil + }, +} + func Block(ctx context.CLIContext, num string) (store.Block, error) { queryPath := fmt.Sprintf("custom/plasma/block/%s", num) data, err := ctx.Query(queryPath, nil) @@ -48,3 +106,24 @@ func Block(ctx context.CLIContext, num string) (store.Block, error) { return block, nil } + +func BlocksMetadata(ctx context.CLIContext, startingBlockNum string) (store.BlocksResp, error) { + var queryPath string + if startingBlockNum == "" { + queryPath = "custom/plasma/blocks" + } else { + queryPath = fmt.Sprintf("custom/plasma/blocks/%s", startingBlockNum) + } + + data, err := ctx.Query(queryPath, nil) + if err != nil { + return store.BlocksResp{}, err + } + + var resp store.BlocksResp + if err := json.Unmarshal(data, &resp); err != nil { + return store.BlocksResp{}, err + } + + return resp, nil +} diff --git a/client/plasmacli/rest.go b/client/plasmacli/rest.go index 1f0ce70..349c4f9 100644 --- a/client/plasmacli/rest.go +++ b/client/plasmacli/rest.go @@ -27,11 +27,12 @@ func registerRoutes(rs *lcd.RestServer) { r := rs.Mux r.HandleFunc("/balance/{address}", balanceHandler(ctx)).Methods("GET") - r.HandleFunc("/block/{num}", blockHandler(ctx)).Methods("GET") + r.HandleFunc("/block/{num:[0-9]+}", blockHandler(ctx)).Methods("GET") + r.HandleFunc("/blocks", blocksHandler(ctx)).Methods("GET") + r.HandleFunc("/blocks/{num:[0-9]+}", blocksHandler(ctx)).Methods("GET") r.HandleFunc("/info/{address}", infoHandler(ctx)).Methods("GET") - r.HandleFunc("/submit", submitHandler(ctx)).Methods("POST") - r.HandleFunc("/blocks", blocksHandler(ctx)).Methods("GET") + r.HandleFunc("/submit", submitHandler(ctx)).Methods("POST") } func balanceHandler(ctx context.CLIContext) http.HandlerFunc { @@ -127,7 +128,7 @@ func blockHandler(ctx context.CLIContext) http.HandlerFunc { if num == "0" || num == "" { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("plasma block start at 1")) + w.Write([]byte("plasma blocks start at 1")) return } @@ -150,13 +151,14 @@ func blockHandler(ctx context.CLIContext) http.HandlerFunc { tmBlock, err := ctx.Client.Block(&height) if err != nil { w.WriteHeader(http.StatusInternalServerError) - // log the error + w.Write([]byte(err.Error())) return } data, err := json.Marshal(resp{block.Block, tmBlock.Block.Data.Txs}) if err != nil { w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) return } @@ -168,5 +170,34 @@ func blockHandler(ctx context.CLIContext) http.HandlerFunc { // retrieve metadata about the last 10 blocks func blocksHandler(ctx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + num, ok := mux.Vars(r)["num"] + if !ok { + num = "" + } + + if num == "0" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("plasma blocks start at 1")) + return + } + + blocksResp, err := query.BlocksMetadata(ctx, num) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + // TODO: check the against the codespace type + // maybe the block does not exist? + return + } + + data, err := json.Marshal(blocksResp) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(data) } } From 9dd6071310b61b430b3e1dd99699dc04cab83c1d Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Tue, 4 Jun 2019 00:51:39 -0700 Subject: [PATCH 26/49] SubmitBlock test no longer applicable --- eth/plasma_test.go | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/eth/plasma_test.go b/eth/plasma_test.go index 2b5a3e8..661b312 100644 --- a/eth/plasma_test.go +++ b/eth/plasma_test.go @@ -54,35 +54,6 @@ func TestPlasmaInit(t *testing.T) { require.NoError(t, err, "error binding to contract") } -/* Test needs to be changed to simulate the sdk context and plasma store. -func TestSubmitBlock(t *testing.T) { - logger := log.NewNopLogger() - client, _ := InitEthConn(clientAddr, logger) - - privKey, _ := crypto.HexToECDSA(operatorPrivKey) - plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), client, 1, commitmentRate, logger, true, privKey) - - var header [32]byte - copy(header[:], crypto.Keccak256([]byte("blah"))) - block := plasmaTypes.Block{ - Header: header, - TxnCount: 1, - FeeAmount: utils.Big0, - } - err := plasma.SubmitBlock(block) - require.NoError(t, err, "block submission error") - - blockNum, err := plasma.contract.LastCommittedBlock(nil) - require.NoError(t, err, "failed to query for the last committed block") - - result, err := plasma.contract.PlasmaChain(nil, blockNum) - require.NoError(t, err, "failed contract plasma chain query") - - require.Truef(t, bytes.Compare(result.Header[:], header[:]) == 0, - "Mismatch in block headers. Got: %x. Expected: %x", result, header) -} -*/ - func TestDepositFinalityBound(t *testing.T) { logger := log.NewNopLogger() client, _ := InitEthConn(clientAddr, logger) From 28d9f2a2875a66d6a5d93b9d02455fe9e8e12e76 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 4 Jun 2019 17:25:37 -0700 Subject: [PATCH 27/49] reflect some pr comments --- handlers/anteHandler_test.go | 10 ++++---- handlers/spendMsgHandler.go | 5 +--- store/blockStore.go | 6 ++--- store/errors.go | 12 ++++----- store/outputStore.go | 48 ++++++++++++++++++------------------ store/outputStore_test.go | 10 ++++---- store/querier.go | 4 +-- store/types.go | 14 +++++------ store/types_test.go | 8 +++--- store/utils.go | 17 ++----------- 10 files changed, 59 insertions(+), 75 deletions(-) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index 1ec16d9..bc33d37 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -26,7 +26,7 @@ type Tx struct { Transaction plasma.Transaction ConfirmationHash []byte Spent []bool - Spenders [][]byte + SpenderTxs [][]byte Position plasma.Position } @@ -212,7 +212,7 @@ func TestAnteExitedInputs(t *testing.T) { Transaction: plasma.Transaction{[]plasma.Input{plasma.NewInput(getPosition("10.0.0.0"), [65]byte{}, nil)}, []plasma.Output{plasma.NewOutput(addr, big.NewInt(10))}, big.NewInt(0)}, ConfirmationHash: []byte("confirmation hash"), Spent: []bool{false}, - Spenders: [][]byte{}, + SpenderTxs: [][]byte{}, Position: getPosition("(1.0.0.0)"), } setupTxs(ctx, outputStore, inputs) @@ -270,7 +270,7 @@ func TestAnteInvalidConfirmSig(t *testing.T) { Transaction: parentTx.Transaction, ConfirmationHash: confHash[:], Spent: []bool{false}, - Spenders: make([][]byte, len(parentTx.Transaction.Outputs)), + SpenderTxs: make([][]byte, len(parentTx.Transaction.Outputs)), Position: getPosition("(1.0.0.0)"), } outputStore.StoreTx(ctx, tx) @@ -332,7 +332,7 @@ func TestAnteValidTx(t *testing.T) { Transaction: parentTx.Transaction, ConfirmationHash: confBytes[:], Spent: []bool{false}, - Spenders: make([][]byte, len(parentTx.Transaction.Outputs)), + SpenderTxs: make([][]byte, len(parentTx.Transaction.Outputs)), Position: getPosition("(1.0.0.0)"), } outputStore.StoreTx(ctx, tx) @@ -494,7 +494,7 @@ func setupTxs(ctx sdk.Context, outputStore store.OutputStore, inputs ...Tx) { Transaction: i.Transaction, ConfirmationHash: i.ConfirmationHash, Spent: i.Spent, - Spenders: i.Spenders, + SpenderTxs: i.SpenderTxs, Position: i.Position, } outputStore.StoreTx(ctx, tx) diff --git a/handlers/spendMsgHandler.go b/handlers/spendMsgHandler.go index cba668e..d796007 100644 --- a/handlers/spendMsgHandler.go +++ b/handlers/spendMsgHandler.go @@ -34,7 +34,7 @@ func NewSpendHandler(outputStore store.OutputStore, blockStore store.BlockStore, tx := store.Transaction{ Transaction: spendMsg.Transaction, Spent: make([]bool, len(spendMsg.Outputs)), - Spenders: make([][]byte, len(spendMsg.Outputs)), + SpenderTxs: make([][]byte, len(spendMsg.Outputs)), ConfirmationHash: confirmationHash[:], Position: plasma.NewPosition(blockHeight, txIndex, 0, big.NewInt(0)), } @@ -59,9 +59,6 @@ func NewSpendHandler(outputStore store.OutputStore, blockStore store.BlockStore, return sdk.ErrInternal("error updating the aggregate fee").Result() } - /* Create Outputs */ - outputStore.StoreTx(ctx, tx) - return sdk.Result{} } } diff --git a/store/blockStore.go b/store/blockStore.go index 1f2ec7d..130b130 100644 --- a/store/blockStore.go +++ b/store/blockStore.go @@ -39,9 +39,9 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { return nil } -const ( - blockKey = "block" - plasmaBlockNumKey = "plasmaBlockNum" +var ( + blockKey = []byte{0x0} + plasmaBlockNumKey = []byte{0x1} ) func NewBlockStore(ctxKey sdk.StoreKey) BlockStore { diff --git a/store/errors.go b/store/errors.go index 10a7e56..6780294 100644 --- a/store/errors.go +++ b/store/errors.go @@ -12,14 +12,14 @@ const ( CodeAccountDNE sdk.CodeType = 3 ) -func ErrOutputDNE(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeOutputDNE, msg, args) +func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) } -func ErrOutputSpent(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeOutputSpent, msg, args) +func ErrOutputSpent(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeOutputSpent, msg, args) } -func ErrAccountDNE(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeAccountDNE, msg, args) +func ErrAccountDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeAccountDNE, msg, args) } diff --git a/store/outputStore.go b/store/outputStore.go index a43e2d7..30e4cb0 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -9,12 +9,12 @@ import ( "math/big" ) -const ( - accountKey = "acc" - depositKey = "deposit" - feeKey = "fee" - hashKey = "hash" - positionKey = "pos" +var ( + accountKey = []byte{0x0} + depositKey = []byte{0x1} + feeKey = []byte{0x2} + hashKey = []byte{0x3} + positionKey = []byte{0x4} ) /* Output Store */ @@ -103,9 +103,9 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output } output := Output{ - Output: tx.Transaction.Outputs[pos.OutputIndex], - Spent: tx.Spent[pos.OutputIndex], - Spender: tx.Spenders[pos.OutputIndex], + Output: tx.Transaction.Outputs[pos.OutputIndex], + Spent: tx.Spent[pos.OutputIndex], + SpenderTx: tx.SpenderTxs[pos.OutputIndex], } return output, ok @@ -254,16 +254,16 @@ func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { // SpendDeposit changes the deposit to be spent // Updates the account of the deposit owner -func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender []byte) sdk.Result { +func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spenderTx []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { - return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() + return ErrOutputDNE(fmt.Sprintf("deposit with nonce %s does not exist", nonce)).Result() } else if deposit.Spent { - return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() + return ErrOutputSpent(fmt.Sprintf("deposit with nonce %s is already spent", nonce)).Result() } deposit.Spent = true - deposit.Spender = spender + deposit.SpenderTx = spenderTx store.setDeposit(ctx, nonce, deposit) store.subtractFromAccount(ctx, deposit.Deposit.Owner, deposit.Deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) @@ -273,16 +273,16 @@ func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spender [ // SpendFee changes the fee to be spent // Updates the account of the fee owner -func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { +func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { fee, ok := store.GetFee(ctx, pos) if !ok { - return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("fee with position %s does not exist", pos)).Result() + return ErrOutputDNE(fmt.Sprintf("fee with position %s does not exist", pos)).Result() } else if fee.Spent { - return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("fee with position %s is already spent", pos)).Result() + return ErrOutputSpent(fmt.Sprintf("fee with position %s is already spent", pos)).Result() } fee.Spent = true - fee.Spender = spender + fee.SpenderTx = spenderTx store.setFee(ctx, pos, fee) store.subtractFromAccount(ctx, fee.Output.Owner, fee.Output.Amount, pos) @@ -292,19 +292,19 @@ func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spender // SpendOutput changes the output to be spent // Updates the account of the output owner -func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spender []byte) sdk.Result { +func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { key := prefixKey(positionKey, pos.Bytes()) hash := store.Get(ctx, key) tx, ok := store.GetTx(ctx, hash) if !ok { - return ErrOutputDNE(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() + return ErrOutputDNE(fmt.Sprintf("output with index %x and transaction hash 0x%x does not exist", pos.OutputIndex, hash)).Result() } else if tx.Spent[pos.OutputIndex] { - return ErrOutputSpent(DefaultCodespace, fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() + return ErrOutputSpent(fmt.Sprintf("output with index %x and transaction hash 0x%x is already spent", pos.OutputIndex, hash)).Result() } tx.Spent[pos.OutputIndex] = true - tx.Spenders[pos.OutputIndex] = spender + tx.SpenderTxs[pos.OutputIndex] = spenderTx store.setTx(ctx, tx) store.subtractFromAccount(ctx, tx.Transaction.Outputs[pos.OutputIndex].Owner, tx.Transaction.Outputs[pos.OutputIndex].Amount, pos) @@ -339,9 +339,9 @@ func (store OutputStore) depositToOutput(ctx sdk.Context, nonce *big.Int) (Outpu return Output{}, ok } output := Output{ - Output: plasma.NewOutput(deposit.Deposit.Owner, deposit.Deposit.Amount), - Spent: deposit.Spent, - Spender: deposit.Spender, + Output: plasma.NewOutput(deposit.Deposit.Owner, deposit.Deposit.Amount), + Spent: deposit.Spent, + SpenderTx: deposit.SpenderTx, } return output, ok } diff --git a/store/outputStore_test.go b/store/outputStore_test.go index 5807b78..ea157af 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -49,7 +49,7 @@ func TestDeposits(t *testing.T) { require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") deposit.Spent = true - deposit.Spender = hash + deposit.SpenderTx = hash recoveredDeposit, ok = outputStore.GetDeposit(ctx, nonce) require.True(t, ok, "error when retrieving deposit") require.True(t, reflect.DeepEqual(deposit, recoveredDeposit), "mismatch in stored and retrieved deposit") @@ -93,7 +93,7 @@ func TestFees(t *testing.T) { require.Equal(t, res.Code, CodeOutputSpent, "allowed output to be spent twice") fee.Spent = true - fee.Spender = hash + fee.SpenderTx = hash recoveredFee, ok = outputStore.GetFee(ctx, pos) require.True(t, ok, "error when retrieving fee") require.True(t, reflect.DeepEqual(fee, recoveredFee), "mismatch in stored and retrieved fee") @@ -179,8 +179,8 @@ func TestTransactions(t *testing.T) { // Create and store new transaction tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Position} - for i, _ := range tx.Spenders { - tx.Spenders[i] = []byte{} + for i, _ := range tx.SpenderTxs { + tx.SpenderTxs[i] = []byte{} } outputStore.StoreTx(ctx, tx) @@ -211,7 +211,7 @@ func TestTransactions(t *testing.T) { require.Equal(t, res.Code, CodeOutputSpent, fmt.Sprintf("allowed output with index %d to be spent twice on case %d", j, i)) tx.Spent[j] = true - tx.Spenders[j] = plasmaTx.Transaction.MerkleHash() + tx.SpenderTxs[j] = plasmaTx.Transaction.MerkleHash() recoveredTx, ok = outputStore.GetTxWithPosition(ctx, p) require.True(t, ok, "error when retrieving transaction") require.True(t, reflect.DeepEqual(tx, recoveredTx), fmt.Sprintf("mismatch in stored and retrieved transaction on case %d", i)) diff --git a/store/querier.go b/store/querier.go index 7a71845..86d3e3d 100644 --- a/store/querier.go +++ b/store/querier.go @@ -56,7 +56,7 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) (*big.Int, sdk.Error) { acc, ok := outputStore.GetAccount(ctx, addr) if !ok { - return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) + return nil, ErrAccountDNE(fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) } return acc.Balance, nil @@ -65,7 +65,7 @@ func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]QueryOutput, sdk.Error) { acc, ok := outputStore.GetAccount(ctx, addr) if !ok { - return nil, ErrAccountDNE(DefaultCodespace, fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) + return nil, ErrAccountDNE(fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) } return outputStore.GetUnspentForAccount(ctx, acc), nil } diff --git a/store/types.go b/store/types.go index 75cc345..80e0e31 100644 --- a/store/types.go +++ b/store/types.go @@ -14,16 +14,16 @@ type Account struct { /* Wrap plasma deposit with spend information */ type Deposit struct { - Deposit plasma.Deposit - Spent bool - Spender []byte + Deposit plasma.Deposit + Spent bool + SpenderTx []byte // transaction hash that spends this deposit } /* Wrap plasma output with spend information */ type Output struct { - Output plasma.Output - Spent bool - Spender []byte + Output plasma.Output + Spent bool + SpenderTx []byte // transaction that spends this output } /* Wrap plasma transaction with spend information */ @@ -31,7 +31,7 @@ type Transaction struct { Transaction plasma.Transaction ConfirmationHash []byte Spent []bool - Spenders [][]byte + SpenderTxs [][]byte // transaction hashes that spend the outputs of this transaction Position plasma.Position } diff --git a/store/types_test.go b/store/types_test.go index e98c29f..72b7e5a 100644 --- a/store/types_test.go +++ b/store/types_test.go @@ -40,9 +40,9 @@ func TestDepositSerialization(t *testing.T) { } deposit := Deposit{ - Deposit: plasmaDeposit, - Spent: true, - Spender: []byte{}, + Deposit: plasmaDeposit, + Spent: true, + SpenderTx: []byte{}, } bytes, err := rlp.EncodeToBytes(&deposit) @@ -70,7 +70,7 @@ func TestTxSerialization(t *testing.T) { tx := Transaction{ Transaction: transaction, Spent: []bool{false, false}, - Spenders: [][]byte{}, + SpenderTxs: [][]byte{}, ConfirmationHash: hashes, } diff --git a/store/utils.go b/store/utils.go index 10652bc..88b359d 100644 --- a/store/utils.go +++ b/store/utils.go @@ -1,18 +1,5 @@ package store -import ( - "bytes" -) - -const ( - separator = "::" -) - -func prefixKey(prefix string, key []byte) []byte { - buffer := new(bytes.Buffer) - buffer.Write([]byte(prefix)) - buffer.Write([]byte(separator)) - buffer.Write(key) - - return buffer.Bytes() +func prefixKey(prefix, key []byte) []byte { + return append(prefix, key...) } From a2352d2370ac0eeba04872010b0e9e8ce5d253aa Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Wed, 5 Jun 2019 11:59:08 -0700 Subject: [PATCH 28/49] update client query, handler err fixes --- client/plasmacli/query/info.go | 6 ++--- client/plasmacli/query/main.go | 21 ++++++++++++++++ client/plasmacli/sign.go | 42 ++++++++++++------------------- docs/architecure/store.md | 15 +++++++++++ handlers/anteHandler.go | 46 +++++++++++++++++----------------- handlers/depositHandler.go | 2 +- handlers/errors.go | 24 +++++++++--------- store/outputStore.go | 6 ++--- store/querier.go | 32 +++++++++++++++++++++-- store/types.go | 2 +- 10 files changed, 125 insertions(+), 71 deletions(-) create mode 100644 docs/architecure/store.md diff --git a/client/plasmacli/query/info.go b/client/plasmacli/query/info.go index a84a410..48ec7e2 100644 --- a/client/plasmacli/query/info.go +++ b/client/plasmacli/query/info.go @@ -34,7 +34,7 @@ var infoCmd = &cobra.Command{ for i, utxo := range utxos { fmt.Printf("UTXO %d\n", i) - fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Tx.Position, utxo.Output.Output.Amount.String(), utxo.Output.Spent, utxo.Output.Spender) + fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Tx.Position, utxo.Output.Output.Amount.String(), utxo.Output.Spent, utxo.Output.SpenderTx) fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.Tx.Transaction.TxHash(), utxo.Tx.ConfirmationHash) // print inputs if applicable positions := utxo.Tx.Transaction.InputPositions() @@ -53,7 +53,7 @@ var infoCmd = &cobra.Command{ }, } -func Info(ctx context.CLIContext, addr common.Address) ([]store.QueryOutput, error) { +func Info(ctx context.CLIContext, addr common.Address) ([]store.OutputInfo, error) { // query for all utxos owned by this address queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) data, err := ctx.Query(queryRoute, nil) @@ -61,7 +61,7 @@ func Info(ctx context.CLIContext, addr common.Address) ([]store.QueryOutput, err return nil, err } - var utxos []store.QueryOutput + var utxos []store.OutputInfo if err := json.Unmarshal(data, &utxos); err != nil { return nil, err } diff --git a/client/plasmacli/query/main.go b/client/plasmacli/query/main.go index 425d039..9b37635 100644 --- a/client/plasmacli/query/main.go +++ b/client/plasmacli/query/main.go @@ -1,6 +1,11 @@ package query import ( + "encoding/json" + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/spf13/cobra" ) @@ -12,3 +17,19 @@ var queryCmd = &cobra.Command{ func QueryCmd() *cobra.Command { return queryCmd } + +func Output(ctx context.CLIContext, pos plasma.Position) (store.Output, error) { + // query for an output for the given position + queryRoute := fmt.Sprintf("custom/utxo/output/%s", pos) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return store.Output{}, err + } + + var output store.Output + if err := json.Unmarshal(data, &output); err != nil { + return store.Output{}, err + } + + return output, nil +} diff --git a/client/plasmacli/sign.go b/client/plasmacli/sign.go index 9877386..7907dd9 100644 --- a/client/plasmacli/sign.go +++ b/client/plasmacli/sign.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/FourthState/plasma-mvp-sidechain/client/plasmacli/query" clistore "github.com/FourthState/plasma-mvp-sidechain/client/store" "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" @@ -10,7 +11,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" "github.com/spf13/cobra" "github.com/spf13/viper" "strings" @@ -64,20 +64,16 @@ Usage: return err } - res, err := ctx.QuerySubspace(signerAddr.Bytes(), "utxo") + utxos, err := query.Info(ctx, signerAddr) if err != nil { return err } - utxo := store.UTXO{} - for _, pair := range res { - if err := rlp.DecodeBytes(pair.Value, &utxo); err != nil { - return err - } + for _, output := range utxos { - if utxo.Spent { - spenderPositions := utxo.SpenderPositions() - spenderAddresses := utxo.SpenderAddresses() + if output.Output.Spent { + spenderPositions := output.Tx.SpenderPositions() + spenderAddresses := output.Tx.SpenderAddresses() for i, pos := range spenderPositions { err = signSingleConfirmSig(ctx, pos, signerAddr, spenderAddresses[i], name) if err != nil { @@ -93,19 +89,13 @@ Usage: // generate confirmation signature for specified owner and position func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, signerAddr, owner ethcmn.Address, name string) error { - // retrieve the new output - utxo := store.UTXO{} - key := append(owner.Bytes(), position.Bytes()...) - res, err := ctx.QueryStore(key, "utxo") + // query for output for the specified position + output, err := query.Output(ctx, position) if err != nil { return err } - if err := rlp.DecodeBytes(res, &utxo); err != nil { - return err - } - - if err := verifyAndSign(utxo, signerAddr, name); err != nil { + if err := verifyAndSign(output, signerAddr, name); err != nil { return err } return nil @@ -114,9 +104,9 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign // verify that the inputs provided are correct // signing address should match one of the input addresses // generate confirmation signature for given utxo -func verifyAndSign(utxo store.UTXO, signerAddr ethcmn.Address, name string) error { - sig, _ := clistore.GetSig(utxo.Position) - inputAddrs := utxo.InputAddresses() +func verifyAndSign(output store.Output, signerAddr ethcmn.Address, name string) error { + sig, _ := clistore.GetSig(output.Position) + inputAddrs := output.InputAddresses() if len(sig) == 130 || (len(sig) == 65 && len(inputAddrs) == 1) { return nil @@ -128,7 +118,7 @@ func verifyAndSign(utxo store.UTXO, signerAddr ethcmn.Address, name string) erro } // get confirmation to generate signature - fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", utxo.Position, utxo.Output.Owner, utxo.Output.Amount) + fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.OutputPosition, output.Output.Owner, output.Output.Amount) buf := cosmoscli.BufferStdin() auth, err := cosmoscli.GetString(signPrompt, buf) if err != nil { @@ -138,18 +128,18 @@ func verifyAndSign(utxo store.UTXO, signerAddr ethcmn.Address, name string) erro return nil } - hash := utils.ToEthSignedMessageHash(utxo.ConfirmationHash) + hash := utils.ToEthSignedMessageHash(output.Tx.ConfirmationHash) sig, err := clistore.SignHashWithPassphrase(name, hash) if err != nil { return fmt.Errorf("failed to generate confirmation signature: { %s }", err) } - if err := clistore.SaveSig(utxo.Position, sig, i == 0); err != nil { + if err := clistore.SaveSig(output.Output.Position, sig, i == 0); err != nil { return err } // print the results - fmt.Printf("Confirmation Signature for output with position: %s\n", utxo.Position) + fmt.Printf("Confirmation Signature for output with position: %s\n", output.Output.Position) fmt.Printf("0x%x\n", sig) } return nil diff --git a/docs/architecure/store.md b/docs/architecure/store.md new file mode 100644 index 0000000..e7c5b37 --- /dev/null +++ b/docs/architecure/store.md @@ -0,0 +1,15 @@ +# Store Design # + +The store package provides the backend to storage of all information necessary for the sidechain to function. +There are 2 stores, the block store and output store. + +## Block Store ## +The block store maintains all necessary information related to each plasma block produced. +The Block type within the block store wraps the tendermint block it was committed at which is necessary for querying. +The Block store keeps a counter for the current and next plasma block number to be used. + +## Output Store ## +All deposits, fees, and regular outputs can be stored and queried from the output store. + + + diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index a11a84c..a3f36f0 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -31,7 +31,7 @@ func NewAnteHandler(outputStore store.OutputStore, blockStore store.BlockStore, spendMsg := msg.(msgs.SpendMsg) return spendMsgAnteHandler(ctx, spendMsg, outputStore, blockStore, client) default: - return ctx, ErrInvalidTransaction(DefaultCodespace, "msg is not of type SpendMsg or IncludeDepositMsg").Result(), true + return ctx, ErrInvalidTransaction("msg is not of type SpendMsg or IncludeDepositMsg").Result(), true } } } @@ -44,11 +44,11 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, outputStore st // attempt to recover signers signers := spendMsg.GetSigners() if len(signers) == 0 { - return ctx, ErrInvalidTransaction(DefaultCodespace, "failed recovering signers").Result(), true + return ctx, ErrInvalidTransaction("failed recovering signers").Result(), true } if len(signers) != len(spendMsg.Inputs) { - return ctx, ErrInvalidSignature(DefaultCodespace, "number of signers does not equal number of signatures").Result(), true + return ctx, ErrInvalidSignature("number of signers does not equal number of signatures").Result(), true } /* validate inputs */ @@ -60,7 +60,7 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, outputStore st // first input must cover the fee if i == 0 && amt.Cmp(spendMsg.Fee) < 0 { - return ctx, ErrInsufficientFee(DefaultCodespace, "first input has an insufficient amount to pay the fee").Result(), true + return ctx, ErrInsufficientFee("first input has an insufficient amount to pay the fee").Result(), true } totalInputAmt = totalInputAmt.Add(totalInputAmt, amt) @@ -73,7 +73,7 @@ func spendMsgAnteHandler(ctx sdk.Context, spendMsg msgs.SpendMsg, outputStore st totalOutputAmt = new(big.Int).Add(totalOutputAmt, spendMsg.Fee) if totalInputAmt.Cmp(totalOutputAmt) != 0 { - return ctx, ErrInvalidTransaction(DefaultCodespace, "inputs do not equal Outputs (+ fee)").Result(), true + return ctx, ErrInvalidTransaction("inputs do not equal Outputs (+ fee)").Result(), true } return ctx, sdk.Result{}, false @@ -87,16 +87,16 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o // check the owner of the position inputUTXO, ok := outputStore.GetOutput(ctx, input.Position) if !ok { - return nil, ErrInvalidInput(DefaultCodespace, "input, %v, does not exist", input.Position).Result() + return nil, ErrInvalidInput("input, %v, does not exist", input.Position).Result() } if !bytes.Equal(inputUTXO.Output.Owner[:], signer[:]) { - return nil, ErrSignatureVerificationFailure(DefaultCodespace, fmt.Sprintf("transaction was not signed by correct address. Got: 0x%x. Expected: 0x%x", signer, inputUTXO.Output.Owner)).Result() + return nil, ErrSignatureVerificationFailure(fmt.Sprintf("transaction was not signed by correct address. Got: 0x%x. Expected: 0x%x", signer, inputUTXO.Output.Owner)).Result() } if inputUTXO.Spent { - return nil, ErrInvalidInput(DefaultCodespace, "input, %v, already spent", input.Position).Result() + return nil, ErrInvalidInput("input, %v, already spent", input.Position).Result() } - if client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), input.Position) { - return nil, ErrExitedInput(DefaultCodespace, "input, %v, utxo has exitted", input.Position).Result() + if client.HasTxBeenExited(blockStore.PlasmaBlockHeight(ctx), input.Position) { + return nil, ErrExitedInput("input, %v, utxo has exitted", input.Position).Result() } // validate inputs/confirmation signatures if not a fee utxo or deposit utxo @@ -113,8 +113,8 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o // check if the parent utxo has exited for _, in := range tx.Transaction.Inputs { - if client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), in.Position) { - return nil, ErrExitedInput(DefaultCodespace, fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() + if client.HasTxBeenExited(blockStore.PlasmaBlockHeight(ctx), in.Position) { + return nil, ErrExitedInput(fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() } } } @@ -127,19 +127,19 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o // validates the input's confirm signatures func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx store.Transaction, outputStore store.OutputStore) sdk.Result { if len(input.ConfirmSignatures) > 0 && len(inputTx.Transaction.Inputs) != len(input.ConfirmSignatures) { - return ErrInvalidTransaction(DefaultCodespace, "incorrect number of confirm signatures").Result() + return ErrInvalidTransaction("incorrect number of confirm signatures").Result() } confirmationHash := utils.ToEthSignedMessageHash(inputTx.ConfirmationHash[:])[:] for i, in := range inputTx.Transaction.Inputs { utxo, ok := outputStore.GetOutput(ctx, in.Position) if !ok { - return ErrInvalidInput(DefaultCodespace, fmt.Sprintf("failed to retrieve input utxo with position %s", in.Position)).Result() + return ErrInvalidInput(fmt.Sprintf("failed to retrieve input utxo with position %s", in.Position)).Result() } pubKey, _ := crypto.SigToPub(confirmationHash, input.ConfirmSignatures[i][:]) signer := crypto.PubkeyToAddress(*pubKey) if !bytes.Equal(signer[:], utxo.Output.Owner[:]) { - return ErrSignatureVerificationFailure(DefaultCodespace, fmt.Sprintf("confirm signature not signed by the correct address. Got: %x. Expected: %x", signer, utxo.Output.Owner)).Result() + return ErrSignatureVerificationFailure(fmt.Sprintf("confirm signature not signed by the correct address. Got: %x. Expected: %x", signer, utxo.Output.Owner)).Result() } } @@ -148,26 +148,26 @@ func validateConfirmSignatures(ctx sdk.Context, input plasma.Input, inputTx stor func includeDepositAnteHandler(ctx sdk.Context, outputStore store.OutputStore, blockStore store.BlockStore, msg msgs.IncludeDepositMsg, client plasmaConn) (newCtx sdk.Context, res sdk.Result, abort bool) { if outputStore.HasDeposit(ctx, msg.DepositNonce) { - return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, already exists in store", msg.DepositNonce.String()).Result(), true + return ctx, ErrInvalidTransaction("deposit, %s, already exists in store", msg.DepositNonce.String()).Result(), true } - deposit, threshold, ok := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), msg.DepositNonce) + deposit, threshold, ok := client.GetDeposit(blockStore.PlasmaBlockHeight(ctx), msg.DepositNonce) if !ok && threshold == nil { - return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, does not exist.", msg.DepositNonce.String()).Result(), true + return ctx, ErrInvalidTransaction("deposit, %s, does not exist.", msg.DepositNonce.String()).Result(), true } if !ok { - return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has not finalized yet. Please wait at least %d blocks before resubmitting", msg.DepositNonce.String(), threshold.Int64()).Result(), true + return ctx, ErrInvalidTransaction("deposit, %s, has not finalized yet. Please wait at least %d blocks before resubmitting", msg.DepositNonce.String(), threshold.Int64()).Result(), true } if !bytes.Equal(deposit.Owner[:], msg.Owner[:]) { - return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, does not equal the owner specified in the include-deposit Msg", msg.DepositNonce).Result(), true + return ctx, ErrInvalidTransaction("deposit, %s, does not equal the owner specified in the include-deposit Msg", msg.DepositNonce).Result(), true } depositPosition := plasma.NewPosition(big.NewInt(0), 0, 0, msg.DepositNonce) - exited := client.HasTxBeenExited(blockStore.CurrentPlasmaBlockNum(ctx), depositPosition) + exited := client.HasTxBeenExited(blockStore.PlasmaBlockHeight(ctx), depositPosition) if exited { - return ctx, ErrInvalidTransaction(DefaultCodespace, "deposit, %s, has already exitted from rootchain", msg.DepositNonce.String()).Result(), true + return ctx, ErrInvalidTransaction("deposit, %s, has already exitted from rootchain", msg.DepositNonce.String()).Result(), true } if !bytes.Equal(msg.Owner.Bytes(), deposit.Owner.Bytes()) { - return ctx, msgs.ErrInvalidTransaction(DefaultCodespace, fmt.Sprintf("msg has the wrong owner field for given deposit. Resubmit with correct deposit owner: %s", deposit.Owner.String())).Result(), true + return ctx, ErrInvalidTransaction(fmt.Sprintf("msg has the wrong owner field for given deposit. Resubmit with correct deposit owner: %s", deposit.Owner.String())).Result(), true } return ctx, sdk.Result{}, false } diff --git a/handlers/depositHandler.go b/handlers/depositHandler.go index da35a4c..ef464b3 100644 --- a/handlers/depositHandler.go +++ b/handlers/depositHandler.go @@ -16,7 +16,7 @@ func NewDepositHandler(outputStore store.OutputStore, blockStore store.BlockStor // Increment txIndex so that it doesn't collide with SpendMsg nextTxIndex() - deposit, _, _ := client.GetDeposit(blockStore.CurrentPlasmaBlockNum(ctx), depositMsg.DepositNonce) + deposit, _, _ := client.GetDeposit(blockStore.PlasmaBlockHeight(ctx), depositMsg.DepositNonce) outputStore.StoreDeposit(ctx, depositMsg.DepositNonce, deposit) diff --git a/handlers/errors.go b/handlers/errors.go index fdd0b68..f129984 100644 --- a/handlers/errors.go +++ b/handlers/errors.go @@ -15,26 +15,26 @@ const ( CodeInvalidInput sdk.CodeType = 6 ) -func ErrInsufficientFee(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeInsufficientFee, msg, args) +func ErrInsufficientFee(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInsufficientFee, msg, args) } -func ErrExitedInput(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeExitedInput, msg, args) +func ErrExitedInput(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeExitedInput, msg, args) } -func ErrSignatureVerificationFailure(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeSignatureVerificationFailure, msg, args) +func ErrSignatureVerificationFailure(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeSignatureVerificationFailure, msg, args) } -func ErrInvalidTransaction(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeInvalidTransaction, msg, args) +func ErrInvalidTransaction(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidTransaction, msg, args) } -func ErrInvalidSignature(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeInvalidSignature, msg, args) +func ErrInvalidSignature(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidSignature, msg, args) } -func ErrInvalidInput(codespace sdk.CodespaceType, msg string, args ...interface{}) sdk.Error { - return sdk.NewError(codespace, CodeInvalidInput, msg, args) +func ErrInvalidInput(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, msg, args) } diff --git a/store/outputStore.go b/store/outputStore.go index 30e4cb0..af3a19e 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -316,11 +316,11 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend /* Helpers */ // GetUnspentForAccount returns the unspent outputs that belong to the given account -// Returns using struct QueryOutput so user has access to transaction that created the output -func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []QueryOutput) { +// Returns using struct OutputInfo so user has access to transaction that created the output +func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []OutputInfo) { for _, p := range acc.Unspent { output, ok := store.GetOutput(ctx, p) - var utxo QueryOutput + var utxo OutputInfo if ok { utxo.Output = output } diff --git a/store/querier.go b/store/querier.go index 2f74381..3297ef5 100644 --- a/store/querier.go +++ b/store/querier.go @@ -19,6 +19,10 @@ const ( // QueryInfo retrieves the entire utxo set owned // by the specified address QueryInfo = "info" + + // QueryOutput retrieves a single output at + // the given position + QueryOutput = "output" ) func NewOutputQuerier(outputStore OutputStore) sdk.Querier { @@ -53,7 +57,23 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { return nil, sdk.ErrInternal("serialization error") } return data, nil - + case QueryOutput: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("expected output/") + } + pos, e := plasma.FromPositionString(path[1]) + if e != nil { + return nil, sdk.ErrInternal("position decoding error") + } + output, err := queryOutput(ctx, outputStore, pos) + if err != nil { + return nil, err + } + data, e := json.Marshal(output) + if e != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil default: return nil, sdk.ErrUnknownRequest("unregistered endpoint") } @@ -69,7 +89,7 @@ func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) return acc.Balance, nil } -func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]QueryOutput, sdk.Error) { +func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]OutputInfo, sdk.Error) { acc, ok := outputStore.GetAccount(ctx, addr) if !ok { return nil, ErrAccountDNE(fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) @@ -77,6 +97,14 @@ func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([ return outputStore.GetUnspentForAccount(ctx, acc), nil } +func queryOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (Output, sdk.Error) { + output, ok := outputStore.GetOutput(ctx, pos) + if !ok { + return Output{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) + } + return output, nil +} + const ( // QueryBlocks retrieves full information about a // speficied block diff --git a/store/types.go b/store/types.go index 80e0e31..43b4083 100644 --- a/store/types.go +++ b/store/types.go @@ -37,7 +37,7 @@ type Transaction struct { /* Wraps Output with transaction is was created with this allows for input addresses to be retireved */ -type QueryOutput struct { +type OutputInfo struct { Output Output Tx Transaction } From c5a2ef6ffb3ef9339eef65e576e6cfd2d3009bbf Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 6 Jun 2019 16:56:33 -0700 Subject: [PATCH 29/49] some more refactor on cli --- client/plasmacli/query/main.go | 24 +++++++++++++++---- client/plasmacli/sign.go | 43 +++++++++++++++------------------- go.mod | 7 +++++- go.sum | 15 ++++++++++++ store/errors.go | 5 ++++ store/querier.go | 19 ++++++++++++--- 6 files changed, 81 insertions(+), 32 deletions(-) diff --git a/client/plasmacli/query/main.go b/client/plasmacli/query/main.go index 9b37635..7014dc5 100644 --- a/client/plasmacli/query/main.go +++ b/client/plasmacli/query/main.go @@ -18,18 +18,34 @@ func QueryCmd() *cobra.Command { return queryCmd } -func Output(ctx context.CLIContext, pos plasma.Position) (store.Output, error) { +func OutputInfo(ctx context.CLIContext, pos plasma.Position) (store.OutputInfo, error) { // query for an output for the given position queryRoute := fmt.Sprintf("custom/utxo/output/%s", pos) data, err := ctx.Query(queryRoute, nil) if err != nil { - return store.Output{}, err + return store.OutputInfo{}, err } - var output store.Output + var output store.OutputInfo if err := json.Unmarshal(data, &output); err != nil { - return store.Output{}, err + return store.OutputInfo{}, err } return output, nil } + +func Tx(ctx context.CLIContext, hash []byte) (store.Transaction, error) { + // query for a transaction using the provided hash + queryRoute := fmt.Sprintf("custom/utxo/tx/%s", hash) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return store.Transaction{}, err + } + + var tx store.Transaction + if err := json.Unmarshal(data, &tx); err != nil { + return store.Transaction{}, err + } + + return tx, nil +} diff --git a/client/plasmacli/sign.go b/client/plasmacli/sign.go index 7907dd9..d38c516 100644 --- a/client/plasmacli/sign.go +++ b/client/plasmacli/sign.go @@ -21,7 +21,6 @@ const ( ) func init() { - signCmd.Flags().String(ownerF, "", "Owner of the output (required with position flag)") signCmd.Flags().String(positionF, "", "Position of transaction to finalize (required with owner flag)") } @@ -33,7 +32,7 @@ Prompt the user for confirmation to finailze the pending transactions. Owner and Usage: plasmacli sign - plasmacli sign --owner
--position "(blknum.txindex.oindex.depositnonce)"`, + plasmacli sign --position "(blknum.txindex.oindex.depositnonce)"`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) @@ -46,21 +45,14 @@ Usage: return err } - ownerS := viper.GetString(ownerF) positionS := viper.GetString(positionF) - if ownerS != "" || positionS != "" { + if positionS != "" { position, err := plasma.FromPositionString(strings.TrimSpace(viper.GetString(positionF))) if err != nil { return err } - ownerStr := utils.RemoveHexPrefix(strings.TrimSpace(viper.GetString(ownerF))) - if ownerStr == "" || !ethcmn.IsHexAddress(ownerStr) { - return fmt.Errorf("must provide the address that owns position %s using the --owner flag in hex format", position) - } - ownerAddr := ethcmn.HexToAddress(ownerStr) - - err = signSingleConfirmSig(ctx, position, signerAddr, ownerAddr, name) + err = signSingleConfirmSig(ctx, position, signerAddr, name) return err } @@ -72,10 +64,13 @@ Usage: for _, output := range utxos { if output.Output.Spent { - spenderPositions := output.Tx.SpenderPositions() - spenderAddresses := output.Tx.SpenderAddresses() - for i, pos := range spenderPositions { - err = signSingleConfirmSig(ctx, pos, signerAddr, spenderAddresses[i], name) + tx, err := query.Tx(ctx, output.Output.SpenderTx) + if err != nil { + return err + } + + for i, pos := range tx.InputPositions() { + err = signSingleConfirmSig(ctx, pos, signerAddr, name) if err != nil { fmt.Println(err) } @@ -88,9 +83,9 @@ Usage: } // generate confirmation signature for specified owner and position -func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, signerAddr, owner ethcmn.Address, name string) error { +func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, signerAddr ethcmn.Address, name string) error { // query for output for the specified position - output, err := query.Output(ctx, position) + output, err := query.OutputInfo(ctx, position) if err != nil { return err } @@ -104,9 +99,9 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign // verify that the inputs provided are correct // signing address should match one of the input addresses // generate confirmation signature for given utxo -func verifyAndSign(output store.Output, signerAddr ethcmn.Address, name string) error { - sig, _ := clistore.GetSig(output.Position) - inputAddrs := output.InputAddresses() +func verifyAndSign(output store.OutputInfo, signerAddr ethcmn.Address, name string) error { + sig, _ := clistore.GetSig(output.Tx.Position) + inputAddrs := output.Tx.InputAddresses() if len(sig) == 130 || (len(sig) == 65 && len(inputAddrs) == 1) { return nil @@ -116,9 +111,9 @@ func verifyAndSign(output store.Output, signerAddr ethcmn.Address, name string) if input != signerAddr { continue } - + // TODO: fix to use correct position // get confirmation to generate signature - fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.OutputPosition, output.Output.Owner, output.Output.Amount) + fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.Tx.Position, output.Output.Owner, output.Output.Amount) buf := cosmoscli.BufferStdin() auth, err := cosmoscli.GetString(signPrompt, buf) if err != nil { @@ -134,12 +129,12 @@ func verifyAndSign(output store.Output, signerAddr ethcmn.Address, name string) return fmt.Errorf("failed to generate confirmation signature: { %s }", err) } - if err := clistore.SaveSig(output.Output.Position, sig, i == 0); err != nil { + if err := clistore.SaveSig(output.Tx.Position, sig, i == 0); err != nil { return err } // print the results - fmt.Printf("Confirmation Signature for output with position: %s\n", output.Output.Position) + fmt.Printf("Confirmation Signature for output with position: %s\n", output.Tx.Position) fmt.Printf("0x%x\n", sig) } return nil diff --git a/go.mod b/go.mod index b61d38d..2ce18bf 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,12 @@ require ( github.com/zondax/hid v0.9.0 // indirect github.com/zondax/ledger-cosmos-go v0.9.7 // indirect github.com/zondax/ledger-go v0.8.0 // indirect - golang.org/x/sys v0.0.0-20190302025703-b6889370fb10 // indirect + golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 // indirect + golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect + golang.org/x/net v0.0.0-20190603091049-60506f45cf65 // indirect + golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect + golang.org/x/text v0.3.2 // indirect + golang.org/x/tools v0.0.0-20190603231351-8aaa1484dc10 // indirect google.golang.org/genproto v0.0.0-20190227213309-4f5b463f9597 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect diff --git a/go.sum b/go.sum index 51c2d8e..22c22d4 100644 --- a/go.sum +++ b/go.sum @@ -182,6 +182,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -190,24 +192,37 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190302025703-b6889370fb10 h1:xQJI9OEiErEQ++DoXOHqEpzsGMrAv2Q2jyCpi7DmfpQ= golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190603231351-8aaa1484dc10 h1:9LyHDLeGJwq8p/x0xbF8aG63cLC6EDjlNbGYc7dacDc= +golang.org/x/tools v0.0.0-20190603231351-8aaa1484dc10/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/store/errors.go b/store/errors.go index 6780294..9e052e5 100644 --- a/store/errors.go +++ b/store/errors.go @@ -10,6 +10,7 @@ const ( CodeOutputDNE sdk.CodeType = 1 CodeOutputSpent sdk.CodeType = 2 CodeAccountDNE sdk.CodeType = 3 + CodeTxDNE sdk.CodeType = 4 ) func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { @@ -23,3 +24,7 @@ func ErrOutputSpent(msg string, args ...interface{}) sdk.Error { func ErrAccountDNE(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeAccountDNE, msg, args) } + +func ErrTxDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeTxDNE, msg, args) +} diff --git a/store/querier.go b/store/querier.go index 3297ef5..5f864e0 100644 --- a/store/querier.go +++ b/store/querier.go @@ -22,7 +22,7 @@ const ( // QueryOutput retrieves a single output at // the given position - QueryOutput = "output" + QueryOutputInfo = "output" ) func NewOutputQuerier(outputStore OutputStore) sdk.Querier { @@ -57,7 +57,7 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { return nil, sdk.ErrInternal("serialization error") } return data, nil - case QueryOutput: + case QueryOutputInfo: if len(path) != 2 { return nil, sdk.ErrUnknownRequest("expected output/") } @@ -69,7 +69,12 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { if err != nil { return nil, err } - data, e := json.Marshal(output) + tx, err := queryTxWithPosition(ctx, outputStore, pos) + if err != nil { + return nil, err + } + outputInfo := OutputInfo{output, tx} + data, e := json.Marshal(outputInfo) if e != nil { return nil, sdk.ErrInternal("serialization error") } @@ -105,6 +110,14 @@ func queryOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) return output, nil } +func queryTxWithPosition(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (Transaction, sdk.Error) { + tx, ok := outputStore.GetTxWithPosition(ctx, pos) + if !ok { + return Transaction{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) + } + return tx, nil +} + const ( // QueryBlocks retrieves full information about a // speficied block From 62ac3122f2db66fd14c3625022c0f070d5985c2d Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sat, 8 Jun 2019 09:22:10 -0700 Subject: [PATCH 30/49] change account to wallet --- client/plasmacli/sign.go | 2 +- docs/architecure/store.md | 12 ++++- store/blockStore.go | 7 +++ store/blockStore_test.go | 4 +- store/errors.go | 3 +- store/outputStore.go | 97 ++++++++++++++++++++------------------- store/querier.go | 59 ++++++++++++++---------- store/types.go | 38 ++++++++++++--- store/types_test.go | 12 ++--- 9 files changed, 145 insertions(+), 89 deletions(-) diff --git a/client/plasmacli/sign.go b/client/plasmacli/sign.go index d38c516..a022918 100644 --- a/client/plasmacli/sign.go +++ b/client/plasmacli/sign.go @@ -113,7 +113,7 @@ func verifyAndSign(output store.OutputInfo, signerAddr ethcmn.Address, name stri } // TODO: fix to use correct position // get confirmation to generate signature - fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.Tx.Position, output.Output.Owner, output.Output.Amount) + fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.Tx.Position, output.Output.Output.Owner, output.Output.Amount) buf := cosmoscli.BufferStdin() auth, err := cosmoscli.GetString(signPrompt, buf) if err != nil { diff --git a/docs/architecure/store.md b/docs/architecure/store.md index e7c5b37..71a8797 100644 --- a/docs/architecure/store.md +++ b/docs/architecure/store.md @@ -5,11 +5,21 @@ There are 2 stores, the block store and output store. ## Block Store ## The block store maintains all necessary information related to each plasma block produced. -The Block type within the block store wraps the tendermint block it was committed at which is necessary for querying. +The Block type within the block store wraps the tendermint block it was committed at with a plasma block. The Block store keeps a counter for the current and next plasma block number to be used. ## Output Store ## All deposits, fees, and regular outputs can be stored and queried from the output store. +There are several mappings in the output store. +There exists the following mappings: +- transaction hash to transaction +- position to transaction hash +- deposit nonce to deposit +- fee position to fee +- address to wallet + +## Wallet ## +Wallets are a convience struct to maintain track of address balances, unspent outputs and spent outputs. diff --git a/store/blockStore.go b/store/blockStore.go index e383706..4282749 100644 --- a/store/blockStore.go +++ b/store/blockStore.go @@ -24,10 +24,12 @@ type block struct { TMBlockHeight uint64 } +// EncodeRLP RLP encodes a Block struct func (b *Block) EncodeRLP(w io.Writer) error { return rlp.Encode(w, &block{b.Block, b.TMBlockHeight}) } +// DecodeRLP decodes the byte stream into a Block func (b *Block) DecodeRLP(s *rlp.Stream) error { var block block if err := s.Decode(&block); err != nil { @@ -39,17 +41,20 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { return nil } +// keys var ( blockKey = []byte{0x0} plasmaBlockNumKey = []byte{0x1} ) +// NewBlockStore is a constructor function for BlockStore func NewBlockStore(ctxKey sdk.StoreKey) BlockStore { return BlockStore{ kvStore: NewKVStore(ctxKey), } } +// GetBlock returns the plasma block at the provided height func (store BlockStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, bool) { key := prefixKey(blockKey, blockHeight.Bytes()) data := store.Get(ctx, key) @@ -84,6 +89,7 @@ func (store BlockStore) StoreBlock(ctx sdk.Context, tmBlockHeight uint64, block return plasmaBlockNum } +// PlasmaBlockHeight returns the current plasma block height func (store BlockStore) PlasmaBlockHeight(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) @@ -96,6 +102,7 @@ func (store BlockStore) PlasmaBlockHeight(ctx sdk.Context) *big.Int { return plasmaBlockNum } +// NextPlasmaBlockNum returns the next plasma block number to be used func (store BlockStore) NextPlasmaBlockNum(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) diff --git a/store/blockStore_test.go b/store/blockStore_test.go index 3d450ed..0d344d8 100644 --- a/store/blockStore_test.go +++ b/store/blockStore_test.go @@ -12,7 +12,7 @@ import ( "testing" ) -// Test that a block can be serialized and deserialized +// test that a block can be serialized and deserialized func TestBlockSerialization(t *testing.T) { // Construct Block plasmaBlock := plasma.Block{} @@ -37,7 +37,7 @@ func TestBlockSerialization(t *testing.T) { require.True(t, reflect.DeepEqual(block, recoveredBlock), "mismatch in serialized and deserialized block") } -// Test that the plasma block number increments correctly +// test that the plasma block number increments correctly func TestPlasmaBlockStorage(t *testing.T) { ctx, key := setup() blockStore := NewBlockStore(key) diff --git a/store/errors.go b/store/errors.go index 9e052e5..fd6d508 100644 --- a/store/errors.go +++ b/store/errors.go @@ -13,6 +13,7 @@ const ( CodeTxDNE sdk.CodeType = 4 ) +// TODO: refactor DNE errors into one func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) } @@ -21,7 +22,7 @@ func ErrOutputSpent(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeOutputSpent, msg, args) } -func ErrAccountDNE(msg string, args ...interface{}) sdk.Error { +func ErrWalletDNE(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeAccountDNE, msg, args) } diff --git a/store/outputStore.go b/store/outputStore.go index af3a19e..61f6bb4 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -9,8 +9,9 @@ import ( "math/big" ) +// keys var ( - accountKey = []byte{0x0} + walletKey = []byte{0x0} depositKey = []byte{0x1} feeKey = []byte{0x2} hashKey = []byte{0x3} @@ -31,20 +32,20 @@ func NewOutputStore(ctxKey sdk.StoreKey) OutputStore { // ----------------------------------------------------------------------------- /* Getters */ -// GetAccount returns the account at the associated address -func (store OutputStore) GetAccount(ctx sdk.Context, addr common.Address) (Account, bool) { - key := prefixKey(accountKey, addr.Bytes()) +// GetWallet returns the wallet at the associated address +func (store OutputStore) GetWallet(ctx sdk.Context, addr common.Address) (Wallet, bool) { + key := prefixKey(walletKey, addr.Bytes()) data := store.Get(ctx, key) if data == nil { - return Account{}, false + return Wallet{}, false } - var acc Account - if err := rlp.DecodeBytes(data, &acc); err != nil { + var wallet Wallet + if err := rlp.DecodeBytes(data, &wallet); err != nil { panic(fmt.Sprintf("transaction store corrupted: %s", err)) } - return acc, true + return wallet, true } // GetDeposit returns the deposit at the given nonce @@ -137,9 +138,9 @@ func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) // ----------------------------------------------------------------------------- /* Has */ -// HasAccount returns whether an account at the given address exists -func (store OutputStore) HasAccount(ctx sdk.Context, addr common.Address) bool { - key := prefixKey(accountKey, addr.Bytes()) +// HasWallet returns whether an wallet at the given address exists +func (store OutputStore) HasWallet(ctx sdk.Context, addr common.Address) bool { + key := prefixKey(walletKey, addr.Bytes()) return store.Has(ctx, key) } @@ -172,10 +173,10 @@ func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { // ----------------------------------------------------------------------------- /* Set */ -// SetAccount overwrites the account stored at the given address -func (store OutputStore) setAccount(ctx sdk.Context, addr common.Address, acc Account) { - key := prefixKey(accountKey, addr.Bytes()) - data, err := rlp.EncodeToBytes(&acc) +// SetWallet overwrites the wallet stored at the given address +func (store OutputStore) setWallet(ctx sdk.Context, addr common.Address, wallet Wallet) { + key := prefixKey(walletKey, addr.Bytes()) + data, err := rlp.EncodeToBytes(&wallet) if err != nil { panic(fmt.Sprintf("error marshaling transaction: %s", err)) } @@ -226,25 +227,25 @@ func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { /* Store */ // StoreDeposit adds an unspent deposit -// Updates the deposit owner's account +// Updates the deposit owner's wallet func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { store.setDeposit(ctx, nonce, Deposit{deposit, false, make([]byte, 0)}) - store.addToAccount(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) + store.addToWallet(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) } // StoreFee adds an unspent fee -// Updates the fee owner's account +// Updates the fee owner's wallet func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output plasma.Output) { store.setFee(ctx, pos, Output{output, false, make([]byte, 0)}) - store.addToAccount(ctx, output.Owner, output.Amount, pos) + store.addToWallet(ctx, output.Owner, output.Amount, pos) } // StoreTx adds the transaction -// Updates the output owner's accounts +// Updates the output owner's wallets func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { store.setTx(ctx, tx) for i, output := range tx.Transaction.Outputs { - store.addToAccount(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) + store.addToWallet(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) store.setOutput(ctx, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0)), tx.Transaction.TxHash()) } } @@ -253,7 +254,7 @@ func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { /* Spend */ // SpendDeposit changes the deposit to be spent -// Updates the account of the deposit owner +// Updates the wallet of the deposit owner func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spenderTx []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { @@ -266,13 +267,13 @@ func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spenderTx deposit.SpenderTx = spenderTx store.setDeposit(ctx, nonce, deposit) - store.subtractFromAccount(ctx, deposit.Deposit.Owner, deposit.Deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) + store.subtractFromWallet(ctx, deposit.Deposit.Owner, deposit.Deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) return sdk.Result{} } // SpendFee changes the fee to be spent -// Updates the account of the fee owner +// Updates the wallet of the fee owner func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { fee, ok := store.GetFee(ctx, pos) if !ok { @@ -285,13 +286,13 @@ func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spenderT fee.SpenderTx = spenderTx store.setFee(ctx, pos, fee) - store.subtractFromAccount(ctx, fee.Output.Owner, fee.Output.Amount, pos) + store.subtractFromWallet(ctx, fee.Output.Owner, fee.Output.Amount, pos) return sdk.Result{} } // SpendOutput changes the output to be spent -// Updates the account of the output owner +// Updates the wallet of the output owner func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { key := prefixKey(positionKey, pos.Bytes()) hash := store.Get(ctx, key) @@ -307,7 +308,7 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend tx.SpenderTxs[pos.OutputIndex] = spenderTx store.setTx(ctx, tx) - store.subtractFromAccount(ctx, tx.Transaction.Outputs[pos.OutputIndex].Owner, tx.Transaction.Outputs[pos.OutputIndex].Amount, pos) + store.subtractFromWallet(ctx, tx.Transaction.Outputs[pos.OutputIndex].Owner, tx.Transaction.Outputs[pos.OutputIndex].Amount, pos) return sdk.Result{} } @@ -315,10 +316,10 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend // ----------------------------------------------------------------------------- /* Helpers */ -// GetUnspentForAccount returns the unspent outputs that belong to the given account +// GetUnspentForWallett returns the unspent outputs that belong to the given wallet // Returns using struct OutputInfo so user has access to transaction that created the output -func (store OutputStore) GetUnspentForAccount(ctx sdk.Context, acc Account) (utxos []OutputInfo) { - for _, p := range acc.Unspent { +func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (utxos []OutputInfo) { + for _, p := range wallet.Unspent { output, ok := store.GetOutput(ctx, p) var utxo OutputInfo if ok { @@ -346,36 +347,36 @@ func (store OutputStore) depositToOutput(ctx sdk.Context, nonce *big.Int) (Outpu return output, ok } -// addToAccount adds the passed in amount to the account with the given address -// adds the position provided to the list of unspent positions within the account -func (store OutputStore) addToAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { - acc, ok := store.GetAccount(ctx, addr) +// addToWallet adds the passed in amount to the wallet with the given address +// adds the position provided to the list of unspent positions within the wallet +func (store OutputStore) addToWallet(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { + wallet, ok := store.GetWallet(ctx, addr) if !ok { - acc = Account{big.NewInt(0), make([]plasma.Position, 0), make([]plasma.Position, 0)} + wallet = Wallet{big.NewInt(0), make([]plasma.Position, 0), make([]plasma.Position, 0)} } - acc.Balance = new(big.Int).Add(acc.Balance, amount) - acc.Unspent = append(acc.Unspent, pos) - store.setAccount(ctx, addr, acc) + wallet.Balance = new(big.Int).Add(wallet.Balance, amount) + wallet.Unspent = append(wallet.Unspent, pos) + store.setWallet(ctx, addr, wallet) } -// subtractFromAccount subtracts the passed in amount from the account with the given address +// subtractFromWallet subtracts the passed in amount from the wallet with the given address // moves the provided position from the unspent list to the spent list -func (store OutputStore) subtractFromAccount(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { - acc, ok := store.GetAccount(ctx, addr) +func (store OutputStore) subtractFromWallet(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { + wallet, ok := store.GetWallet(ctx, addr) if !ok { panic(fmt.Sprintf("transaction store has been corrupted")) } - // Update Account - acc.Balance = new(big.Int).Sub(acc.Balance, amount) - if acc.Balance.Sign() == -1 { - panic(fmt.Sprintf("account with address 0x%x has a negative balance", addr)) + // Update Wallet + wallet.Balance = new(big.Int).Sub(wallet.Balance, amount) + if wallet.Balance.Sign() == -1 { + panic(fmt.Sprintf("wallet with address 0x%x has a negative balance", addr)) } - acc.Unspent = removePosition(acc.Unspent, pos) - acc.Spent = append(acc.Spent, pos) - store.setAccount(ctx, addr, acc) + wallet.Unspent = removePosition(wallet.Unspent, pos) + wallet.Spent = append(wallet.Spent, pos) + store.setWallet(ctx, addr, wallet) } // helper function to remove a position from the unspent list diff --git a/store/querier.go b/store/querier.go index 5f864e0..73d46b6 100644 --- a/store/querier.go +++ b/store/querier.go @@ -20,9 +20,13 @@ const ( // by the specified address QueryInfo = "info" - // QueryOutput retrieves a single output at - // the given position - QueryOutputInfo = "output" + // QueryTransactionOutput retrieves a single output at + // the given position and returns it with transactional + // information + QueryTransactionOutput = "output" + + // QueryTx retrieves a transaction at the given hash + QueryTx = "tx" ) func NewOutputQuerier(outputStore OutputStore) sdk.Querier { @@ -57,24 +61,33 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { return nil, sdk.ErrInternal("serialization error") } return data, nil - case QueryOutputInfo: + case QueryTransactionOutput: if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("expected output/") + return nil, sdk.ErrUnknownRequest("expected txo/") } pos, e := plasma.FromPositionString(path[1]) if e != nil { return nil, sdk.ErrInternal("position decoding error") } - output, err := queryOutput(ctx, outputStore, pos) + output, err := queryTransactionOutput(ctx, outputStore, pos) if err != nil { return nil, err } - tx, err := queryTxWithPosition(ctx, outputStore, pos) - if err != nil { - return nil, err + txo := TransactionOutput{} + data, e := json.Marshal(txo) + if e != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil + case QueryTx: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("expected tx/") } - outputInfo := OutputInfo{output, tx} - data, e := json.Marshal(outputInfo) + tx, ok := outputStore.GetTx(ctx, []byte(path[1])) + if !ok { + return nil, ErrTxDNE(fmt.Sprintf("no transaction exists for the hash provided: %x", []byte(path[1]))) + } + data, e := json.Marshal(tx) if e != nil { return nil, sdk.ErrInternal("serialization error") } @@ -86,36 +99,36 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { } func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) (*big.Int, sdk.Error) { - acc, ok := outputStore.GetAccount(ctx, addr) + acc, ok := outputStore.GetWallet(ctx, addr) if !ok { - return nil, ErrAccountDNE(fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) + return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) } return acc.Balance, nil } func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]OutputInfo, sdk.Error) { - acc, ok := outputStore.GetAccount(ctx, addr) + acc, ok := outputStore.GetWallet(ctx, addr) if !ok { - return nil, ErrAccountDNE(fmt.Sprintf("no account exists for the address provided: 0x%x", addr)) + return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) } - return outputStore.GetUnspentForAccount(ctx, acc), nil + return outputStore.GetUnspentForWallet(ctx, acc), nil } -func queryOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (Output, sdk.Error) { +func queryTransactionOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (TransactionOutput, sdk.Error) { output, ok := outputStore.GetOutput(ctx, pos) if !ok { - return Output{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) + return TransactionOutput{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) } - return output, nil -} -func queryTxWithPosition(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (Transaction, sdk.Error) { tx, ok := outputStore.GetTxWithPosition(ctx, pos) if !ok { - return Transaction{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) + return TransactionOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) } - return tx, nil + + txo := NewTransactionOutput(output.Output, pos, output.Spent, output.SpenderTx, tx.InputAddress(), tx.InputPositions()) + + return output, nil } const ( diff --git a/store/types.go b/store/types.go index 43b4083..6d1b489 100644 --- a/store/types.go +++ b/store/types.go @@ -2,11 +2,13 @@ package store import ( "github.com/FourthState/plasma-mvp-sidechain/plasma" + ethcmn "github.com/ethereum/go-ethereum/common" "math/big" ) -/* Account */ -type Account struct { +// Wallet holds reference to the total balance, unspent, and spent outputs +// at a given address +type Wallet struct { Balance *big.Int // total amount avaliable to be spent Unspent []plasma.Position // position of unspent transaction outputs Spent []plasma.Position // position of spent transaction outputs @@ -26,6 +28,12 @@ type Output struct { SpenderTx []byte // transaction that spends this output } +// TODO: remove +type OutputInfo struct { + Output Output + Tx Transaction +} + /* Wrap plasma transaction with spend information */ type Transaction struct { Transaction plasma.Transaction @@ -35,9 +43,25 @@ type Transaction struct { Position plasma.Position } -/* Wraps Output with transaction is was created with - this allows for input addresses to be retireved */ -type OutputInfo struct { - Output Output - Tx Transaction +// TransactionOutput holds all transactional information related to an output +// It is used to return output information from the store +type TransactionOutput struct { + plasma.Output + Position plasma.Position + Spent bool + SpenderTx []byte + InputAddresses []ethcmn.Address + InputPositions []plasma.Position +} + +// NewTransactionOutput is a constructor function for TransactionOutput +func NewTransactionOutput(output plasma.Output, pos plasma.Position, spent bool, spenderTx []byte, inputAddresses []ethcmn.Address, inputPosition []plasma.Position) TransactionOutput { + return TransactionOutput{ + Output: output, + Position: pos, + Spent: spent, + SpenderTx: spenderTx, + InputAddresses: inputAddresses, + InputPositions: inputPositions, + } } diff --git a/store/types_test.go b/store/types_test.go index 72b7e5a..5cb8904 100644 --- a/store/types_test.go +++ b/store/types_test.go @@ -11,10 +11,10 @@ import ( "testing" ) -// Test that an account can be serialized and deserialized -func TestAccountSerialization(t *testing.T) { - // Construct Account - acc := Account{ +// Test that an wallet can be serialized and deserialized +func TestWalletSerialization(t *testing.T) { + // Construct Wallet + acc := Wallet{ Balance: big.NewInt(234578), Unspent: []plasma.Position{getPosition("(8745.1239.1.0)"), getPosition("(23409.12456.0.0)"), getPosition("(894301.1.1.0)"), getPosition("(0.0.0.540124)")}, Spent: []plasma.Position{getPosition("0.0.0.3"), getPosition("7.734.1.3")}, @@ -23,11 +23,11 @@ func TestAccountSerialization(t *testing.T) { bytes, err := rlp.EncodeToBytes(&acc) require.NoError(t, err) - recoveredAcc := Account{} + recoveredAcc := Wallet{} err = rlp.DecodeBytes(bytes, &recoveredAcc) require.NoError(t, err) - require.True(t, reflect.DeepEqual(acc, recoveredAcc), "mismatch in serialized and deserialized account") + require.True(t, reflect.DeepEqual(acc, recoveredAcc), "mismatch in serialized and deserialized wallet") } // Test that the Deposit can be serialized and deserialized without loss of information From 8094c0e71865e24e209847c4a0a632974a7d0dcd Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sat, 8 Jun 2019 09:24:18 -0700 Subject: [PATCH 31/49] consolidate verifyandsign func --- client/plasmacli/sign.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/client/plasmacli/sign.go b/client/plasmacli/sign.go index a022918..fd39285 100644 --- a/client/plasmacli/sign.go +++ b/client/plasmacli/sign.go @@ -83,6 +83,9 @@ Usage: } // generate confirmation signature for specified owner and position +// verify that the inputs provided are correct +// signing address should match one of the input addresses +// generate confirmation signature for given utxo func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, signerAddr ethcmn.Address, name string) error { // query for output for the specified position output, err := query.OutputInfo(ctx, position) @@ -90,16 +93,6 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign return err } - if err := verifyAndSign(output, signerAddr, name); err != nil { - return err - } - return nil -} - -// verify that the inputs provided are correct -// signing address should match one of the input addresses -// generate confirmation signature for given utxo -func verifyAndSign(output store.OutputInfo, signerAddr ethcmn.Address, name string) error { sig, _ := clistore.GetSig(output.Tx.Position) inputAddrs := output.Tx.InputAddresses() From 0c5a538a0c40a201c9da09ebed8dabd7cf4f6c00 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 19 Jun 2019 14:05:36 -0700 Subject: [PATCH 32/49] improves output store and prove --- CHANGELOG.md | 2 +- client/plasmacli/eth/prove.go | 52 ++++++++++++++++++----------------- handlers/spendMsgHandler.go | 21 +++++++------- store/keys.go | 48 ++++++++++++++++++++++++++++++++ store/outputStore.go | 51 ++++++++++++++++------------------ store/utils.go | 5 ---- 6 files changed, 110 insertions(+), 69 deletions(-) create mode 100644 store/keys.go delete mode 100644 store/utils.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c4e838a..fa5cf47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - [\#147](https://github.com/FourthState/plasma-mvp-sidechain/pull/147) Fix Syncing bug where syncing nodes would panic after processing exitted inputs/deposits. Bug is explained in detail here: [\#143](https://github.com/FourthState/plasma-mvp-sidechain/issues/143) - [\#154](https://github.com/FourthState/plasma-mvp-sidechain/pull/154) Fixes issue where include-Deposit msg.Owner == deposit.Owner not enforced. This is necessary to prevent malicious users from rewriting an already included UTXO in store. ### Deprecated -- Dep is no longer be supported +- Dep is no longer supported ## PreHistory diff --git a/client/plasmacli/eth/prove.go b/client/plasmacli/eth/prove.go index fea0ba0..4b8da43 100644 --- a/client/plasmacli/eth/prove.go +++ b/client/plasmacli/eth/prove.go @@ -4,7 +4,7 @@ import ( "fmt" ks "github.com/FourthState/plasma-mvp-sidechain/client/store" "github.com/FourthState/plasma-mvp-sidechain/plasma" - // "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/FourthState/plasma-mvp-sidechain/store" // "github.com/cosmos/cosmos-sdk/client/context" ethcmn "github.com/ethereum/go-ethereum/common" // "github.com/ethereum/go-ethereum/rlp" @@ -75,30 +75,32 @@ var proveCmd = &cobra.Command{ // Trusts connected full node //TODO: REDO -func getProof(addr ethcmn.Address, position plasma.Position) (*tm.ResultTx, []byte, error) { - /* ctx := context.NewCLIContext().WithTrustNode(true) +func getProof(position plasma.Position) (*tm.ResultTx, []byte, error) { + ctx := context.NewCLIContext().WithTrustNode(true) + + key := store.GetOutputKey(pos) + hash, err := ctx.QueryStore(key, "outputs") + if err != nil { + return &tm.ResultTx{}, nil, err + } + + txKey := store.GetTxKey(hash) + txBytes, err := ctx.QueryStore(txKey, "outputs") + + var tx Transaction + if err := rlp.DecodeBytes(txBytes, &tx); err != nil { + return &tm.ResultTx{}, nil, fmt.Errorf("Transaction decoding failed: %s", err.String()) + } + + // query tm node for information about this tx + result, err := ctx.Client.Tx(tx.MerkleHash(), true) + if err != nil { + return &tm.ResultTx{}, nil, err + } + + // Look for confirmation signatures + key = append([]byte("confirmSignature"), utxo.Position.Bytes()...) + sigs, err := ctx.QueryStore(key, "plasma") - // query for the output - key := append(store.Key, position.Bytes()...) - res, err := ctx.QueryStore(key, "outputs") - if err != nil { - return &tm.ResultTx{}, nil, err - } - - utxo := store.Output{} - if err := rlp.DecodeBytes(res, &utxo); err != nil { - return &tm.ResultTx{}, nil, err - } - - // query tm node for information about this tx - result, err := ctx.Client.Tx(utxo.MerkleHash, true) - if err != nil { - return &tm.ResultTx{}, nil, err - } - - // Look for confirmation signatures - key = append([]byte("confirmSignature"), utxo.Position.Bytes()...) - sigs, err := ctx.QueryStore(key, "plasma") - */ return &tm.ResultTx{}, []byte{}, nil } diff --git a/handlers/spendMsgHandler.go b/handlers/spendMsgHandler.go index d796007..0c4abdd 100644 --- a/handlers/spendMsgHandler.go +++ b/handlers/spendMsgHandler.go @@ -30,16 +30,6 @@ func NewSpendHandler(outputStore store.OutputStore, blockStore store.BlockStore, header := ctx.BlockHeader().DataHash confirmationHash := sha256.Sum256(append(merkleHash, header...)) - /* Store Transaction */ - tx := store.Transaction{ - Transaction: spendMsg.Transaction, - Spent: make([]bool, len(spendMsg.Outputs)), - SpenderTxs: make([][]byte, len(spendMsg.Outputs)), - ConfirmationHash: confirmationHash[:], - Position: plasma.NewPosition(blockHeight, txIndex, 0, big.NewInt(0)), - } - outputStore.StoreTx(ctx, tx) - /* Spend Inputs */ for _, input := range spendMsg.Inputs { var res sdk.Result @@ -54,6 +44,17 @@ func NewSpendHandler(outputStore store.OutputStore, blockStore store.BlockStore, } } + /* Store Transaction and create new outputs */ + tx := store.Transaction{ + Transaction: spendMsg.Transaction, + Spent: make([]bool, len(spendMsg.Outputs)), + SpenderTxs: make([][]byte, len(spendMsg.Outputs)), + ConfirmationHash: confirmationHash[:], + Position: plasma.NewPosition(blockHeight, txIndex, 0, big.NewInt(0)), + } + outputStore.StoreTx(ctx, tx) + outputStore.StoreOutputs(ctx, tx) + // update the aggregate fee amount for the block if err := feeUpdater(spendMsg.Fee); err != nil { return sdk.ErrInternal("error updating the aggregate fee").Result() diff --git a/store/keys.go b/store/keys.go new file mode 100644 index 0000000..a5cd6bb --- /dev/null +++ b/store/keys.go @@ -0,0 +1,48 @@ +package store + +import ( + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "math/big" +) + +// keys +var ( + walletKey = []byte{0x0} + depositKey = []byte{0x1} + feeKey = []byte{0x2} + txKey = []byte{0x3} + outputKey = []byte{0x4} +) + +/* Returns key to retrieve wallet for given address */ +func GetWalletKey(addr common.Address) []byte { + return prefixKey(walletKey, addr.Bytes()) +} + +/* Returns key to retrieve deposit for given nonce */ +func GetDepositKey(nonce *big.Int) []byte { + return prefixKey(depositKey, nonce.Bytes()) +} + +/* Returns key to retrieve fee for given position */ +func GetFeeKey(pos plasma.Position) []byte { + return prefixKey(feeKey, pos.Bytes()) +} + +/* Returns key to retrieve UTXO for given position */ +func GetOutputKey(pos plasma.Position) []byte { + return prefixKey(outputKey, pos.Bytes()) +} + +/* Returns Transaction for given hash */ +func GetTxKey(hash []byte) []byte { + return prefixKey(txKey, hash) +} + +func prefixKey(prefix, key []byte) []byte { + return append(prefix, key...) +} diff --git a/store/outputStore.go b/store/outputStore.go index 61f6bb4..bda39e8 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -9,15 +9,6 @@ import ( "math/big" ) -// keys -var ( - walletKey = []byte{0x0} - depositKey = []byte{0x1} - feeKey = []byte{0x2} - hashKey = []byte{0x3} - positionKey = []byte{0x4} -) - /* Output Store */ type OutputStore struct { kvStore @@ -34,7 +25,7 @@ func NewOutputStore(ctxKey sdk.StoreKey) OutputStore { // GetWallet returns the wallet at the associated address func (store OutputStore) GetWallet(ctx sdk.Context, addr common.Address) (Wallet, bool) { - key := prefixKey(walletKey, addr.Bytes()) + key := GetWalletKey(addr) data := store.Get(ctx, key) if data == nil { return Wallet{}, false @@ -50,7 +41,7 @@ func (store OutputStore) GetWallet(ctx sdk.Context, addr common.Address) (Wallet // GetDeposit returns the deposit at the given nonce func (store OutputStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, bool) { - key := prefixKey(depositKey, nonce.Bytes()) + key := GetDepositKey(nonce) data := store.Get(ctx, key) if data == nil { return Deposit{}, false @@ -66,7 +57,7 @@ func (store OutputStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, b // GetFee returns the fee at the given position func (store OutputStore) GetFee(ctx sdk.Context, pos plasma.Position) (Output, bool) { - key := prefixKey(feeKey, pos.Bytes()) + key := GetFeeKey(pos) data := store.Get(ctx, key) if data == nil { return Output{}, false @@ -95,7 +86,7 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output return fee, ok } - key := prefixKey(positionKey, pos.Bytes()) + key := GetOutputKey(pos) hash := store.Get(ctx, key) tx, ok := store.GetTx(ctx, hash) @@ -114,7 +105,7 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output // GetTx returns the transaction with the provided transaction hash func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { - key := prefixKey(hashKey, hash) + key := GetTxKey(hash) data := store.Get(ctx, key) if data == nil { return Transaction{}, false @@ -130,9 +121,10 @@ func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) // GetTxWithPosition returns the transaction that contains the provided position as an output func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { - key := prefixKey(positionKey, pos.Bytes()) + key := GetOutputKey(pos) hash := store.Get(ctx, key) - return store.GetTx(ctx, hash) + txKey := GetTxKey(hash) + return store.GetTx(ctx, txKey) } // ----------------------------------------------------------------------------- @@ -140,25 +132,25 @@ func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) // HasWallet returns whether an wallet at the given address exists func (store OutputStore) HasWallet(ctx sdk.Context, addr common.Address) bool { - key := prefixKey(walletKey, addr.Bytes()) + key := GetWalletKey(addr) return store.Has(ctx, key) } // HasDeposit returns whether a deposit with the given nonce exists func (store OutputStore) HasDeposit(ctx sdk.Context, nonce *big.Int) bool { - key := prefixKey(depositKey, nonce.Bytes()) + key := GetDepositKey(nonce) return store.Has(ctx, key) } // HasFee returns whether a fee with the given position exists func (store OutputStore) HasFee(ctx sdk.Context, pos plasma.Position) bool { - key := prefixKey(feeKey, pos.Bytes()) + key := GetFeeKey(pos) return store.Has(ctx, key) } // HasOutput returns whether an output with the given position exists func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { - key := prefixKey(positionKey, pos.Bytes()) + key := GetOutputKey(pos) hash := store.Get(ctx, key) return store.HasTx(ctx, hash) @@ -166,7 +158,7 @@ func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { // HasTx returns whether a transaction with the given transaction hash exists func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { - key := prefixKey(hashKey, hash) + key := GetTxKey(hash) return store.Has(ctx, key) } @@ -175,7 +167,7 @@ func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { // SetWallet overwrites the wallet stored at the given address func (store OutputStore) setWallet(ctx sdk.Context, addr common.Address, wallet Wallet) { - key := prefixKey(walletKey, addr.Bytes()) + key := GetWalletKey(addr) data, err := rlp.EncodeToBytes(&wallet) if err != nil { panic(fmt.Sprintf("error marshaling transaction: %s", err)) @@ -191,7 +183,7 @@ func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Dep panic(fmt.Sprintf("error marshaling deposit with nonce %s: %s", nonce, err)) } - key := prefixKey(depositKey, nonce.Bytes()) + key := GetDepositKey(nonce) store.Set(ctx, key, data) } @@ -202,13 +194,13 @@ func (store OutputStore) setFee(ctx sdk.Context, pos plasma.Position, fee Output panic(fmt.Sprintf("error marshaling fee with position %s: %s", pos, err)) } - key := prefixKey(feeKey, pos.Bytes()) + key := GetFeeKey(pos) store.Set(ctx, key, data) } // SetOutput adds a mapping from position to transaction hash func (store OutputStore) setOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { - key := prefixKey(positionKey, pos.Bytes()) + key := GetOutputKey(pos) store.Set(ctx, key, hash) } @@ -219,7 +211,7 @@ func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { panic(fmt.Sprintf("error marshaling transaction: %s", err)) } - key := prefixKey(hashKey, tx.Transaction.TxHash()) + key := GetTxKey(tx.Transaction.TxHash()) store.Set(ctx, key, data) } @@ -241,9 +233,12 @@ func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output p } // StoreTx adds the transaction -// Updates the output owner's wallets func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { store.setTx(ctx, tx) +} + +// StoreOutputs adds new Output UTXO's to respective owner wallets +func (store OutputStore) StoreOutputs(ctx sdk.Context, tx Transaction) { for i, output := range tx.Transaction.Outputs { store.addToWallet(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) store.setOutput(ctx, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0)), tx.Transaction.TxHash()) @@ -294,7 +289,7 @@ func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spenderT // SpendOutput changes the output to be spent // Updates the wallet of the output owner func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { - key := prefixKey(positionKey, pos.Bytes()) + key := GetOutputKey(pos) hash := store.Get(ctx, key) tx, ok := store.GetTx(ctx, hash) diff --git a/store/utils.go b/store/utils.go deleted file mode 100644 index 88b359d..0000000 --- a/store/utils.go +++ /dev/null @@ -1,5 +0,0 @@ -package store - -func prefixKey(prefix, key []byte) []byte { - return append(prefix, key...) -} From 5fdb575a7fd561f0d0fd4d15d1a1f7fdd36d833a Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 19 Jun 2019 14:09:20 -0700 Subject: [PATCH 33/49] finish prove and fmt --- client/plasmacli/eth/prove.go | 7 ++++--- store/keys.go | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/client/plasmacli/eth/prove.go b/client/plasmacli/eth/prove.go index 4b8da43..d74f6fb 100644 --- a/client/plasmacli/eth/prove.go +++ b/client/plasmacli/eth/prove.go @@ -91,7 +91,7 @@ func getProof(position plasma.Position) (*tm.ResultTx, []byte, error) { if err := rlp.DecodeBytes(txBytes, &tx); err != nil { return &tm.ResultTx{}, nil, fmt.Errorf("Transaction decoding failed: %s", err.String()) } - + // query tm node for information about this tx result, err := ctx.Client.Tx(tx.MerkleHash(), true) if err != nil { @@ -99,8 +99,9 @@ func getProof(position plasma.Position) (*tm.ResultTx, []byte, error) { } // Look for confirmation signatures + // Ignore error if no confirm sig currently exists in store key = append([]byte("confirmSignature"), utxo.Position.Bytes()...) - sigs, err := ctx.QueryStore(key, "plasma") + sigs, _ := ctx.QueryStore(key, "plasma") - return &tm.ResultTx{}, []byte{}, nil + return result, sigs, nil } diff --git a/store/keys.go b/store/keys.go index a5cd6bb..4b98f2c 100644 --- a/store/keys.go +++ b/store/keys.go @@ -11,11 +11,11 @@ import ( // keys var ( - walletKey = []byte{0x0} - depositKey = []byte{0x1} - feeKey = []byte{0x2} - txKey = []byte{0x3} - outputKey = []byte{0x4} + walletKey = []byte{0x0} + depositKey = []byte{0x1} + feeKey = []byte{0x2} + txKey = []byte{0x3} + outputKey = []byte{0x4} ) /* Returns key to retrieve wallet for given address */ From 2714487820e4bff35c840a84a20bb246449aeef4 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 19 Jun 2019 15:51:39 -0700 Subject: [PATCH 34/49] revert mistake --- store/outputStore.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/store/outputStore.go b/store/outputStore.go index bda39e8..9d011c0 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -123,8 +123,7 @@ func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { key := GetOutputKey(pos) hash := store.Get(ctx, key) - txKey := GetTxKey(hash) - return store.GetTx(ctx, txKey) + return store.GetTx(ctx, hash) } // ----------------------------------------------------------------------------- From 5e6198ec4bc055e29ddf9eb0f3a9df088e654972 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 25 Jun 2019 17:46:42 -0700 Subject: [PATCH 35/49] some refactors on comments and pr comments --- eth/{test_helper.go => common_test.go} | 0 handlers/anteHandler.go | 2 + handlers/{handler_test.go => common_test.go} | 0 msgs/depositMsg.go | 14 +++- msgs/spendMsg.go | 9 ++- plasma/block.go | 8 +- plasma/deposit.go | 4 + plasma/input.go | 3 +- server/app/app.go | 9 +-- store/blockStore.go | 48 +++-------- store/{store_test.go => common_test.go} | 0 store/keys.go | 22 +++-- store/outputStore.go | 85 +++++++++++--------- store/querier.go | 19 ++--- store/types.go | 50 +++++++++--- 15 files changed, 150 insertions(+), 123 deletions(-) rename eth/{test_helper.go => common_test.go} (100%) rename handlers/{handler_test.go => common_test.go} (100%) rename store/{store_test.go => common_test.go} (100%) diff --git a/eth/test_helper.go b/eth/common_test.go similarity index 100% rename from eth/test_helper.go rename to eth/common_test.go diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index a3f36f0..347cc40 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -20,6 +20,8 @@ type plasmaConn interface { HasTxBeenExited(*big.Int, plasma.Position) bool } +// NewAnteHandler returns an ante handler capable of handling include_deposit +// and spend_utxo Msgs. func NewAnteHandler(outputStore store.OutputStore, blockStore store.BlockStore, client plasmaConn) sdk.AnteHandler { return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { msg := tx.GetMsgs()[0] // tx should only have one msg diff --git a/handlers/handler_test.go b/handlers/common_test.go similarity index 100% rename from handlers/handler_test.go rename to handlers/common_test.go diff --git a/msgs/depositMsg.go b/msgs/depositMsg.go index cac2705..85d5220 100644 --- a/msgs/depositMsg.go +++ b/msgs/depositMsg.go @@ -13,27 +13,33 @@ const ( var _ sdk.Tx = IncludeDepositMsg{} -// Implements sdk.Msg and sdk.Tx interfaces -// since no authentication is happening +// IncludeDepositMsg implements sdk.Msg and sdk.Tx interfaces since +// no authentication is happening. type IncludeDepositMsg struct { DepositNonce *big.Int Owner common.Address ReplayNonce uint64 // to get around tx cache issues when resubmitting } +// Type returns the message type. func (msg IncludeDepositMsg) Type() string { return "include_deposit" } +// Route returns the route for this message. func (msg IncludeDepositMsg) Route() string { return IncludeDepositMsgRoute } -// No signers necessary on IncludeDepositMsg +// GetSigners returns nil since no signers necessary on IncludeDepositMsg. func (msg IncludeDepositMsg) GetSigners() []sdk.AccAddress { return nil } +// GetSignBytes returns nil since no signature validation required for +// IncludeDepositMsg. func (msg IncludeDepositMsg) GetSignBytes() []byte { return nil } +// ValidateBasic asserts that the DepositNonce is positive and that the +// Owner field is not the zero address. func (msg IncludeDepositMsg) ValidateBasic() sdk.Error { if msg.DepositNonce.Sign() != 1 { return ErrInvalidIncludeDepositMsg(DefaultCodespace, "DepositNonce must be greater than 0") @@ -44,7 +50,7 @@ func (msg IncludeDepositMsg) ValidateBasic() sdk.Error { return nil } -// Also satisfy the sdk.Tx interface +// GetMsgs implements the sdk.Tx interface func (msg IncludeDepositMsg) GetMsgs() []sdk.Msg { return []sdk.Msg{msg} } diff --git a/msgs/spendMsg.go b/msgs/spendMsg.go index 2cfe0f0..0a10a3e 100644 --- a/msgs/spendMsg.go +++ b/msgs/spendMsg.go @@ -8,6 +8,7 @@ import ( ) const ( + // SpendMsgRoute is used for routing this message. SpendMsgRoute = "spend" ) @@ -16,10 +17,10 @@ type SpendMsg struct { plasma.Transaction } -// Implement the sdk.Msg interface - +// Type implements the sdk.Msg interface. func (msg SpendMsg) Type() string { return "spend_utxo" } +// Route implements the sdk.Msg interface. func (msg SpendMsg) Route() string { return SpendMsgRoute } // GetSigners will attempt to retrieve the signers of the message. @@ -47,10 +48,12 @@ func (msg SpendMsg) GetSigners() []sdk.AccAddress { return addrs } +// GetSignBytes returns the sha256 hash of the transaction. func (msg SpendMsg) GetSignBytes() []byte { return msg.TxHash() } +// ValidateBasic verifies that the transaction is valid. func (msg SpendMsg) ValidateBasic() sdk.Error { if err := msg.Transaction.ValidateBasic(); err != nil { return ErrInvalidSpendMsg(DefaultCodespace, err.Error()) @@ -59,7 +62,7 @@ func (msg SpendMsg) ValidateBasic() sdk.Error { return nil } -// Also satisfy the sdk.Tx interface +// GetMsgs implements the sdk.Tx interface func (msg SpendMsg) GetMsgs() []sdk.Msg { return []sdk.Msg{msg} } diff --git a/plasma/block.go b/plasma/block.go index 648b90e..425b101 100644 --- a/plasma/block.go +++ b/plasma/block.go @@ -6,7 +6,7 @@ import ( "math/big" ) -// Block represent one plasma block +// Block represents a plasma block. type Block struct { Header [32]byte TxnCount uint16 @@ -19,19 +19,19 @@ type block struct { FeeAmount []byte } -// NewBlock is a constructor for Block +// NewBlock creates a Block object. func NewBlock(header [32]byte, txnCount uint16, feeAmount *big.Int) Block { return Block{header, txnCount, feeAmount} } -// EncodeRLP satisfies the rlp interface for Block +// EncodeRLP satisfies the rlp interface for Block. func (b *Block) EncodeRLP(w io.Writer) error { blk := &block{b.Header, b.TxnCount, b.FeeAmount.Bytes()} return rlp.Encode(w, blk) } -// DecodeRLP satisfies the rlp interface for Block +// DecodeRLP satisfies the rlp interface for Block. func (b *Block) DecodeRLP(s *rlp.Stream) error { var blk block if err := s.Decode(&blk); err != nil { diff --git a/plasma/deposit.go b/plasma/deposit.go index fc24f6f..d25f47f 100644 --- a/plasma/deposit.go +++ b/plasma/deposit.go @@ -7,6 +7,7 @@ import ( "math/big" ) +// Deposit represents a deposit that occured on the ethereum plasma smart contract. type Deposit struct { Owner common.Address `json:"Owner"` Amount *big.Int `json:"Amount"` @@ -19,6 +20,7 @@ type deposit struct { EthBlockNum []byte } +// NewDeposit creates a Deposit object. func NewDeposit(owner common.Address, amount *big.Int, ethBlockNum *big.Int) Deposit { return Deposit{ Owner: owner, @@ -27,12 +29,14 @@ func NewDeposit(owner common.Address, amount *big.Int, ethBlockNum *big.Int) Dep } } +// EncodeRLP satisfies the rlp interface for Deposit. func (d *Deposit) EncodeRLP(w io.Writer) error { deposit := &deposit{d.Owner, d.Amount.Bytes(), d.EthBlockNum.Bytes()} return rlp.Encode(w, deposit) } +// DecodeRLP satisfies the rlp interface for Deposit. func (d *Deposit) DecodeRLP(s *rlp.Stream) error { var dep deposit if err := s.Decode(&dep); err != nil { diff --git a/plasma/input.go b/plasma/input.go index 1672c36..b2bd908 100644 --- a/plasma/input.go +++ b/plasma/input.go @@ -5,13 +5,14 @@ import ( "fmt" ) -// Input represents the input to a spend +// Input represents the input to a spend transaction. type Input struct { Position Signature [65]byte ConfirmSignatures [][65]byte } +// NewInput creates a Input object. func NewInput(position Position, sig [65]byte, confirmSigs [][65]byte) Input { // nil != empty slice. avoid deserialization issues by forcing empty slices if confirmSigs == nil { diff --git a/server/app/app.go b/server/app/app.go index d92a589..3b3c946 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -114,13 +114,8 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio // custom queriers app.QueryRouter(). - AddRoute("tx", store.NewOutputQuerier(outputStore)). - AddRoute("block", store.NewBlockQuerier(blockStore)) - - // custom queriers - app.QueryRouter(). - AddRoute("utxo", query.NewUtxoQuerier(utxoStore)). - AddRoute("plasma", query.NewPlasmaQuerier(plasmaStore)) + AddRoute(store.QueryOutputStore, store.NewOutputQuerier(outputStore)). + AddRoute(store.QueryBlockStore, store.NewBlockQuerier(blockStore)) // Set the AnteHandler app.SetAnteHandler(handlers.NewAnteHandler(app.outputStore, app.blockStore, plasmaClient)) diff --git a/store/blockStore.go b/store/blockStore.go index 4282749..c8d9a06 100644 --- a/store/blockStore.go +++ b/store/blockStore.go @@ -6,55 +6,28 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/utils" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/rlp" - "io" "math/big" ) -type BlockStore struct { - kvStore -} - -type Block struct { - plasma.Block - TMBlockHeight uint64 -} - -type block struct { - PlasmaBlock plasma.Block - TMBlockHeight uint64 -} - -// EncodeRLP RLP encodes a Block struct -func (b *Block) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &block{b.Block, b.TMBlockHeight}) -} - -// DecodeRLP decodes the byte stream into a Block -func (b *Block) DecodeRLP(s *rlp.Stream) error { - var block block - if err := s.Decode(&block); err != nil { - return err - } - - b.Block = block.PlasmaBlock - b.TMBlockHeight = block.TMBlockHeight - return nil -} - // keys var ( blockKey = []byte{0x0} plasmaBlockNumKey = []byte{0x1} ) -// NewBlockStore is a constructor function for BlockStore +// BlockStore holds plamsa blocks. +type BlockStore struct { + kvStore +} + +// NewBlockStore is a constructor function for BlockStore. func NewBlockStore(ctxKey sdk.StoreKey) BlockStore { return BlockStore{ kvStore: NewKVStore(ctxKey), } } -// GetBlock returns the plasma block at the provided height +// GetBlock returns the plasma block at the provided height. func (store BlockStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, bool) { key := prefixKey(blockKey, blockHeight.Bytes()) data := store.Get(ctx, key) @@ -70,7 +43,8 @@ func (store BlockStore) GetBlock(ctx sdk.Context, blockHeight *big.Int) (Block, return block, true } -// StoreBlock will store the plasma block and return the plasma block number in which it was stored under +// StoreBlock will store the plasma block and return the plasma block number +// in which it was stored at. func (store BlockStore) StoreBlock(ctx sdk.Context, tmBlockHeight uint64, block plasma.Block) *big.Int { plasmaBlockNum := store.NextPlasmaBlockNum(ctx) @@ -89,7 +63,7 @@ func (store BlockStore) StoreBlock(ctx sdk.Context, tmBlockHeight uint64, block return plasmaBlockNum } -// PlasmaBlockHeight returns the current plasma block height +// PlasmaBlockHeight returns the current plasma block height. func (store BlockStore) PlasmaBlockHeight(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) @@ -102,7 +76,7 @@ func (store BlockStore) PlasmaBlockHeight(ctx sdk.Context) *big.Int { return plasmaBlockNum } -// NextPlasmaBlockNum returns the next plasma block number to be used +// NextPlasmaBlockNum returns the next plasma block number to be used. func (store BlockStore) NextPlasmaBlockNum(ctx sdk.Context) *big.Int { var plasmaBlockNum *big.Int data := store.Get(ctx, []byte(plasmaBlockNumKey)) diff --git a/store/store_test.go b/store/common_test.go similarity index 100% rename from store/store_test.go rename to store/common_test.go diff --git a/store/keys.go b/store/keys.go index 4b98f2c..e11bd76 100644 --- a/store/keys.go +++ b/store/keys.go @@ -1,14 +1,19 @@ package store import ( - "fmt" "github.com/FourthState/plasma-mvp-sidechain/plasma" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" "math/big" ) +const ( + // QueryOutputStore is the route to the OutputStore + QueryOutputStore = "outputs" // TODO: + + // QueryBlockStore is the route to the BlockStore + QueryBlockStore = "block" +) + // keys var ( walletKey = []byte{0x0} @@ -18,31 +23,32 @@ var ( outputKey = []byte{0x4} ) -/* Returns key to retrieve wallet for given address */ +// GetWalletKey returns the key to retrieve wallet for given address. func GetWalletKey(addr common.Address) []byte { return prefixKey(walletKey, addr.Bytes()) } -/* Returns key to retrieve deposit for given nonce */ +// GetDepositKey returns the key to retrieve deposit for given nonce. func GetDepositKey(nonce *big.Int) []byte { return prefixKey(depositKey, nonce.Bytes()) } -/* Returns key to retrieve fee for given position */ +// GetFeeKey returns the key to retrieve fee for given position. func GetFeeKey(pos plasma.Position) []byte { return prefixKey(feeKey, pos.Bytes()) } -/* Returns key to retrieve UTXO for given position */ +// GetOutputKey returns key to retrieve UTXO for given position. func GetOutputKey(pos plasma.Position) []byte { return prefixKey(outputKey, pos.Bytes()) } -/* Returns Transaction for given hash */ +// GetTxKey returns Transaction for given hash. func GetTxKey(hash []byte) []byte { return prefixKey(txKey, hash) } +// prefixes the key func prefixKey(prefix, key []byte) []byte { return append(prefix, key...) } diff --git a/store/outputStore.go b/store/outputStore.go index 9d011c0..293b935 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -9,11 +9,17 @@ import ( "math/big" ) -/* Output Store */ +// Output Store stores deposits, fee, and regular ouputs. It contains the +// following mappings: +// - position to transaction hash +// - transaction hash to transaction +// - deposit nonce to deposit +// - fee position to fee type OutputStore struct { kvStore } +// NewOutputStore creates a new OutputStore object. func NewOutputStore(ctxKey sdk.StoreKey) OutputStore { return OutputStore{ kvStore: NewKVStore(ctxKey), @@ -23,7 +29,7 @@ func NewOutputStore(ctxKey sdk.StoreKey) OutputStore { // ----------------------------------------------------------------------------- /* Getters */ -// GetWallet returns the wallet at the associated address +// GetWallet returns the wallet at the associated address. func (store OutputStore) GetWallet(ctx sdk.Context, addr common.Address) (Wallet, bool) { key := GetWalletKey(addr) data := store.Get(ctx, key) @@ -39,7 +45,7 @@ func (store OutputStore) GetWallet(ctx sdk.Context, addr common.Address) (Wallet return wallet, true } -// GetDeposit returns the deposit at the given nonce +// GetDeposit returns the deposit at the given nonce. func (store OutputStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, bool) { key := GetDepositKey(nonce) data := store.Get(ctx, key) @@ -55,7 +61,7 @@ func (store OutputStore) GetDeposit(ctx sdk.Context, nonce *big.Int) (Deposit, b return deposit, true } -// GetFee returns the fee at the given position +// GetFee returns the fee at the given position. func (store OutputStore) GetFee(ctx sdk.Context, pos plasma.Position) (Output, bool) { key := GetFeeKey(pos) data := store.Get(ctx, key) @@ -71,7 +77,7 @@ func (store OutputStore) GetFee(ctx sdk.Context, pos plasma.Position) (Output, b return fee, true } -// GetOutput returns the output at the given position +// GetOutput returns the output at the given position. func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output, bool) { // allow deposits/fees to returned as an output if pos.IsDeposit() { @@ -103,7 +109,7 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output return output, ok } -// GetTx returns the transaction with the provided transaction hash +// GetTx returns the transaction with the provided transaction hash. func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) { key := GetTxKey(hash) data := store.Get(ctx, key) @@ -119,7 +125,8 @@ func (store OutputStore) GetTx(ctx sdk.Context, hash []byte) (Transaction, bool) return tx, true } -// GetTxWithPosition returns the transaction that contains the provided position as an output +// GetTxWithPosition returns the transaction that contains the provided +// position as an output. func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) (Transaction, bool) { key := GetOutputKey(pos) hash := store.Get(ctx, key) @@ -129,25 +136,25 @@ func (store OutputStore) GetTxWithPosition(ctx sdk.Context, pos plasma.Position) // ----------------------------------------------------------------------------- /* Has */ -// HasWallet returns whether an wallet at the given address exists +// HasWallet returns whether an wallet at the given address exists. func (store OutputStore) HasWallet(ctx sdk.Context, addr common.Address) bool { key := GetWalletKey(addr) return store.Has(ctx, key) } -// HasDeposit returns whether a deposit with the given nonce exists +// HasDeposit returns whether a deposit with the given nonce exists. func (store OutputStore) HasDeposit(ctx sdk.Context, nonce *big.Int) bool { key := GetDepositKey(nonce) return store.Has(ctx, key) } -// HasFee returns whether a fee with the given position exists +// HasFee returns whether a fee with the given position exists. func (store OutputStore) HasFee(ctx sdk.Context, pos plasma.Position) bool { key := GetFeeKey(pos) return store.Has(ctx, key) } -// HasOutput returns whether an output with the given position exists +// HasOutput returns whether an output with the given position exists. func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { key := GetOutputKey(pos) hash := store.Get(ctx, key) @@ -155,7 +162,8 @@ func (store OutputStore) HasOutput(ctx sdk.Context, pos plasma.Position) bool { return store.HasTx(ctx, hash) } -// HasTx returns whether a transaction with the given transaction hash exists +// HasTx returns whether a transaction with the given transaction hash +// exists. func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { key := GetTxKey(hash) return store.Has(ctx, key) @@ -164,7 +172,7 @@ func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { // ----------------------------------------------------------------------------- /* Set */ -// SetWallet overwrites the wallet stored at the given address +// SetWallet overwrites the wallet stored at the given address. func (store OutputStore) setWallet(ctx sdk.Context, addr common.Address, wallet Wallet) { key := GetWalletKey(addr) data, err := rlp.EncodeToBytes(&wallet) @@ -175,7 +183,7 @@ func (store OutputStore) setWallet(ctx sdk.Context, addr common.Address, wallet store.Set(ctx, key, data) } -// SetDeposit overwrites the deposit stored with the given nonce +// SetDeposit overwrites the deposit stored at the given nonce. func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { data, err := rlp.EncodeToBytes(&deposit) if err != nil { @@ -186,7 +194,7 @@ func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Dep store.Set(ctx, key, data) } -// setFee overwrites the fee stored with the given position +// setFee overwrites the fee stored at the given position. func (store OutputStore) setFee(ctx sdk.Context, pos plasma.Position, fee Output) { data, err := rlp.EncodeToBytes(&fee) if err != nil { @@ -197,13 +205,13 @@ func (store OutputStore) setFee(ctx sdk.Context, pos plasma.Position, fee Output store.Set(ctx, key, data) } -// SetOutput adds a mapping from position to transaction hash +// SetOutput adds a mapping from position to transaction hash. func (store OutputStore) setOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { key := GetOutputKey(pos) store.Set(ctx, key, hash) } -// SetTx overwrites the mapping from transaction hash to transaction +// SetTx overwrites the mapping from transaction hash to transaction. func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { data, err := rlp.EncodeToBytes(&tx) if err != nil { @@ -217,26 +225,25 @@ func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { // ----------------------------------------------------------------------------- /* Store */ -// StoreDeposit adds an unspent deposit -// Updates the deposit owner's wallet +// StoreDeposit adds an unspent deposit and updates the deposit owner's +// wallet. func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit plasma.Deposit) { store.setDeposit(ctx, nonce, Deposit{deposit, false, make([]byte, 0)}) store.addToWallet(ctx, deposit.Owner, deposit.Amount, plasma.NewPosition(big.NewInt(0), 0, 0, nonce)) } -// StoreFee adds an unspent fee -// Updates the fee owner's wallet +// StoreFee adds an unspent fee and updates the fee owner's wallet. func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output plasma.Output) { store.setFee(ctx, pos, Output{output, false, make([]byte, 0)}) store.addToWallet(ctx, output.Owner, output.Amount, pos) } -// StoreTx adds the transaction +// StoreTx adds the transaction. func (store OutputStore) StoreTx(ctx sdk.Context, tx Transaction) { store.setTx(ctx, tx) } -// StoreOutputs adds new Output UTXO's to respective owner wallets +// StoreOutputs adds new Output UTXO's to respective owner wallets. func (store OutputStore) StoreOutputs(ctx sdk.Context, tx Transaction) { for i, output := range tx.Transaction.Outputs { store.addToWallet(ctx, output.Owner, output.Amount, plasma.NewPosition(tx.Position.BlockNum, tx.Position.TxIndex, uint8(i), big.NewInt(0))) @@ -247,8 +254,8 @@ func (store OutputStore) StoreOutputs(ctx sdk.Context, tx Transaction) { // ----------------------------------------------------------------------------- /* Spend */ -// SpendDeposit changes the deposit to be spent -// Updates the wallet of the deposit owner +// SpendDeposit changes the deposit to be spent and updates the wallet of +// the deposit owner. func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spenderTx []byte) sdk.Result { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { @@ -266,8 +273,8 @@ func (store OutputStore) SpendDeposit(ctx sdk.Context, nonce *big.Int, spenderTx return sdk.Result{} } -// SpendFee changes the fee to be spent -// Updates the wallet of the fee owner +// SpendFee changes the fee to be spent and updates the wallet of the fee +// owner. func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { fee, ok := store.GetFee(ctx, pos) if !ok { @@ -285,8 +292,8 @@ func (store OutputStore) SpendFee(ctx sdk.Context, pos plasma.Position, spenderT return sdk.Result{} } -// SpendOutput changes the output to be spent -// Updates the wallet of the output owner +// SpendOutput changes the output to be spent and updates the wallet of the +// output owner. func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spenderTx []byte) sdk.Result { key := GetOutputKey(pos) hash := store.Get(ctx, key) @@ -310,8 +317,9 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend // ----------------------------------------------------------------------------- /* Helpers */ -// GetUnspentForWallett returns the unspent outputs that belong to the given wallet -// Returns using struct OutputInfo so user has access to transaction that created the output +// GetUnspentForWallet returns the unspent outputs that belong to the given +// wallet. Returns the struct OutputInfo so user has access to the +// transaction that created the output. func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (utxos []OutputInfo) { for _, p := range wallet.Unspent { output, ok := store.GetOutput(ctx, p) @@ -327,7 +335,8 @@ func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (ut return utxos } -// depositToOutput retrieves the deposit with the given nonce, and returns it as an output +// depositToOutput retrieves the deposit with the given nonce, and returns +// it as an output. func (store OutputStore) depositToOutput(ctx sdk.Context, nonce *big.Int) (Output, bool) { deposit, ok := store.GetDeposit(ctx, nonce) if !ok { @@ -341,8 +350,9 @@ func (store OutputStore) depositToOutput(ctx sdk.Context, nonce *big.Int) (Outpu return output, ok } -// addToWallet adds the passed in amount to the wallet with the given address -// adds the position provided to the list of unspent positions within the wallet +// addToWallet adds the passed in amount to the wallet with the given +// address and adds the position provided to the list of unspent positions +// within the wallet. func (store OutputStore) addToWallet(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { wallet, ok := store.GetWallet(ctx, addr) if !ok { @@ -354,8 +364,9 @@ func (store OutputStore) addToWallet(ctx sdk.Context, addr common.Address, amoun store.setWallet(ctx, addr, wallet) } -// subtractFromWallet subtracts the passed in amount from the wallet with the given address -// moves the provided position from the unspent list to the spent list +// subtractFromWallet subtracts the passed in amount from the wallet with +// the given address and moves the provided position from the unspent list +// to the spent list. func (store OutputStore) subtractFromWallet(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { wallet, ok := store.GetWallet(ctx, addr) if !ok { @@ -373,7 +384,7 @@ func (store OutputStore) subtractFromWallet(ctx sdk.Context, addr common.Address store.setWallet(ctx, addr, wallet) } -// helper function to remove a position from the unspent list +// helper function to remove a position from the unspent list. func removePosition(positions []plasma.Position, pos plasma.Position) []plasma.Position { for i, p := range positions { if p.String() == pos.String() { diff --git a/store/querier.go b/store/querier.go index 73d46b6..088a8b4 100644 --- a/store/querier.go +++ b/store/querier.go @@ -20,15 +20,16 @@ const ( // by the specified address QueryInfo = "info" - // QueryTransactionOutput retrieves a single output at + // QueryTxnOutput retrieves a single output at // the given position and returns it with transactional // information - QueryTransactionOutput = "output" + QueryTxOutput = "output" // QueryTx retrieves a transaction at the given hash QueryTx = "tx" ) +// NewOutputQuerier creates a OutputQuerier object. func NewOutputQuerier(outputStore OutputStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { @@ -61,7 +62,7 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { return nil, sdk.ErrInternal("serialization error") } return data, nil - case QueryTransactionOutput: + case QueryTxOutput: if len(path) != 2 { return nil, sdk.ErrUnknownRequest("expected txo/") } @@ -69,11 +70,11 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { if e != nil { return nil, sdk.ErrInternal("position decoding error") } - output, err := queryTransactionOutput(ctx, outputStore, pos) + output, err := queryTxOutput(ctx, outputStore, pos) if err != nil { return nil, err } - txo := TransactionOutput{} + txo := TxOutput{} data, e := json.Marshal(txo) if e != nil { return nil, sdk.ErrInternal("serialization error") @@ -115,18 +116,18 @@ func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([ return outputStore.GetUnspentForWallet(ctx, acc), nil } -func queryTransactionOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (TransactionOutput, sdk.Error) { +func queryTxOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (TxOutput, sdk.Error) { output, ok := outputStore.GetOutput(ctx, pos) if !ok { - return TransactionOutput{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) + return TxOutput{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) } tx, ok := outputStore.GetTxWithPosition(ctx, pos) if !ok { - return TransactionOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) + return TxOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) } - txo := NewTransactionOutput(output.Output, pos, output.Spent, output.SpenderTx, tx.InputAddress(), tx.InputPositions()) + txo := NewTxOutput(output.Output, pos, output.Spent, output.SpenderTx, tx.InputAddress(), tx.InputPositions()) return output, nil } diff --git a/store/types.go b/store/types.go index 6d1b489..340523b 100644 --- a/store/types.go +++ b/store/types.go @@ -3,9 +3,39 @@ package store import ( "github.com/FourthState/plasma-mvp-sidechain/plasma" ethcmn "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "io" "math/big" ) +// Block wraps a plasma block with the tendermint block height. +type Block struct { + plasma.Block + TMBlockHeight uint64 +} + +type block struct { + PlasmaBlock plasma.Block + TMBlockHeight uint64 +} + +// EncodeRLP RLP encodes a Block struct. +func (b *Block) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &block{b.Block, b.TMBlockHeight}) +} + +// DecodeRLP decodes the byte stream into a Block. +func (b *Block) DecodeRLP(s *rlp.Stream) error { + var block block + if err := s.Decode(&block); err != nil { + return err + } + + b.Block = block.PlasmaBlock + b.TMBlockHeight = block.TMBlockHeight + return nil +} + // Wallet holds reference to the total balance, unspent, and spent outputs // at a given address type Wallet struct { @@ -14,27 +44,21 @@ type Wallet struct { Spent []plasma.Position // position of spent transaction outputs } -/* Wrap plasma deposit with spend information */ +// Deposit wraps a plasma deposit with spend information. type Deposit struct { Deposit plasma.Deposit Spent bool SpenderTx []byte // transaction hash that spends this deposit } -/* Wrap plasma output with spend information */ +// Output wraps a plasma output with spend information. type Output struct { Output plasma.Output Spent bool SpenderTx []byte // transaction that spends this output } -// TODO: remove -type OutputInfo struct { - Output Output - Tx Transaction -} - -/* Wrap plasma transaction with spend information */ +// Transaction wraps a plasma transaction with spend information. type Transaction struct { Transaction plasma.Transaction ConfirmationHash []byte @@ -43,9 +67,9 @@ type Transaction struct { Position plasma.Position } -// TransactionOutput holds all transactional information related to an output +// TxOutput holds all transactional information related to an output // It is used to return output information from the store -type TransactionOutput struct { +type TxOutput struct { plasma.Output Position plasma.Position Spent bool @@ -54,8 +78,8 @@ type TransactionOutput struct { InputPositions []plasma.Position } -// NewTransactionOutput is a constructor function for TransactionOutput -func NewTransactionOutput(output plasma.Output, pos plasma.Position, spent bool, spenderTx []byte, inputAddresses []ethcmn.Address, inputPosition []plasma.Position) TransactionOutput { +// NewTxOutput creates a TxOutput object. +func NewTxOutput(output plasma.Output, pos plasma.Position, spent bool, spenderTx []byte, inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TransactionOutput { return TransactionOutput{ Output: output, Position: pos, From 7224c29def888f987aad2cd28699e63fc6835d31 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sun, 30 Jun 2019 11:45:53 -0700 Subject: [PATCH 36/49] updated store querying --- plasma/transaction.go | 9 +++++- store/outputStore.go | 24 ++++++++------ store/outputStore_test.go | 6 ++-- store/querier.go | 14 +++++--- store/types.go | 67 ++++++++++++++++++++------------------- store/types_test.go | 1 + 6 files changed, 71 insertions(+), 50 deletions(-) diff --git a/plasma/transaction.go b/plasma/transaction.go index 941b84c..2e96146 100644 --- a/plasma/transaction.go +++ b/plasma/transaction.go @@ -137,9 +137,16 @@ func (tx Transaction) InputPositions() (positions []Position) { for _, input := range tx.Inputs { positions = append(positions, input.Position) } - return positions + return } +// OutputAddresses returns the output addresses for this transaction. +func (tx Transaction) OutputAddresses() (addrs []common.Address) { + for _, output := range tx.Outputs { + addrs = append(addrs, output.Owner) + } + return +} func (tx Transaction) String() (str string) { for i, input := range tx.Inputs { str += fmt.Sprintf("Input %d: %s\n", i, input) diff --git a/store/outputStore.go b/store/outputStore.go index 293b935..6647bc9 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -103,6 +103,7 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output output := Output{ Output: tx.Transaction.Outputs[pos.OutputIndex], Spent: tx.Spent[pos.OutputIndex], + InputTx: tx.InputTxs[pos.OutputIndex], SpenderTx: tx.SpenderTxs[pos.OutputIndex], } @@ -234,7 +235,7 @@ func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit p // StoreFee adds an unspent fee and updates the fee owner's wallet. func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output plasma.Output) { - store.setFee(ctx, pos, Output{output, false, make([]byte, 0)}) + store.setFee(ctx, pos, Output{output, false, make([]byte, 0), make([]byte, 0)}) store.addToWallet(ctx, output.Owner, output.Amount, pos) } @@ -318,19 +319,24 @@ func (store OutputStore) SpendOutput(ctx sdk.Context, pos plasma.Position, spend /* Helpers */ // GetUnspentForWallet returns the unspent outputs that belong to the given -// wallet. Returns the struct OutputInfo so user has access to the -// transaction that created the output. -func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (utxos []OutputInfo) { +// wallet. Returns the struct TxOutput so user has access to the +// transactional information related to the output. +func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (utxos []TxOutput) { for _, p := range wallet.Unspent { output, ok := store.GetOutput(ctx, p) - var utxo OutputInfo - if ok { - utxo.Output = output + if !ok { + panic("") // TODO } tx, ok := store.GetTxWithPosition(ctx, p) - if ok { - utxo.Tx = tx + if !ok { + panic("") // TODO + } + inputTx, ok := store.GetTx(ctx, output.InputTx) + if !ok { + panic("") // TODO } + txo := NewTxOutput(output.Output, p, output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) + utxos = append(utxos, txo) } return utxos } diff --git a/store/outputStore_test.go b/store/outputStore_test.go index ea157af..e63df5c 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -77,7 +77,7 @@ func TestFees(t *testing.T) { // Create and store new fee output := plasma.NewOutput(addr, big.NewInt(int64(1000*i))) - fee := Output{output, false, make([]byte, 0)} + fee := Output{output, false, make([]byte, 0), make([]byte, 0)} outputStore.StoreFee(ctx, pos, output) exists = outputStore.HasFee(ctx, pos) @@ -178,11 +178,13 @@ func TestTransactions(t *testing.T) { } // Create and store new transaction - tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Position} + tx := Transaction{make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Position} for i, _ := range tx.SpenderTxs { tx.SpenderTxs[i] = []byte{} + tx.InputTxs[i] = []byte{} } outputStore.StoreTx(ctx, tx) + outputStore.StoreOutputs(ctx, tx) // Check for outputs for j, _ := range plasmaTx.Transaction.Outputs { diff --git a/store/querier.go b/store/querier.go index 088a8b4..3b7ef34 100644 --- a/store/querier.go +++ b/store/querier.go @@ -70,11 +70,10 @@ func NewOutputQuerier(outputStore OutputStore) sdk.Querier { if e != nil { return nil, sdk.ErrInternal("position decoding error") } - output, err := queryTxOutput(ctx, outputStore, pos) + txo, err := queryTxOutput(ctx, outputStore, pos) if err != nil { return nil, err } - txo := TxOutput{} data, e := json.Marshal(txo) if e != nil { return nil, sdk.ErrInternal("serialization error") @@ -108,7 +107,7 @@ func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) return acc.Balance, nil } -func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]OutputInfo, sdk.Error) { +func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]TxOutput, sdk.Error) { acc, ok := outputStore.GetWallet(ctx, addr) if !ok { return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) @@ -127,9 +126,14 @@ func queryTxOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position return TxOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) } - txo := NewTxOutput(output.Output, pos, output.Spent, output.SpenderTx, tx.InputAddress(), tx.InputPositions()) + inputTx, ok := outputStore.GetTx(ctx, output.InputTx) + if !ok { + return TxOutput{}, ErrTxDNE(fmt.Sprintf("no input transaction exists for the output with position: %s", pos)) + } + + txo := NewTxOutput(output.Output, pos, output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) - return output, nil + return txo, nil } const ( diff --git a/store/types.go b/store/types.go index 340523b..a703197 100644 --- a/store/types.go +++ b/store/types.go @@ -8,34 +8,6 @@ import ( "math/big" ) -// Block wraps a plasma block with the tendermint block height. -type Block struct { - plasma.Block - TMBlockHeight uint64 -} - -type block struct { - PlasmaBlock plasma.Block - TMBlockHeight uint64 -} - -// EncodeRLP RLP encodes a Block struct. -func (b *Block) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &block{b.Block, b.TMBlockHeight}) -} - -// DecodeRLP decodes the byte stream into a Block. -func (b *Block) DecodeRLP(s *rlp.Stream) error { - var block block - if err := s.Decode(&block); err != nil { - return err - } - - b.Block = block.PlasmaBlock - b.TMBlockHeight = block.TMBlockHeight - return nil -} - // Wallet holds reference to the total balance, unspent, and spent outputs // at a given address type Wallet struct { @@ -55,11 +27,13 @@ type Deposit struct { type Output struct { Output plasma.Output Spent bool - SpenderTx []byte // transaction that spends this output + InputTx []byte // transaction hash that created this output + SpenderTx []byte // transaction hash that spent this output } // Transaction wraps a plasma transaction with spend information. type Transaction struct { + InputTxs [][]byte // transaction hashes the created the inputs of this transaction Transaction plasma.Transaction ConfirmationHash []byte Spent []bool @@ -67,8 +41,7 @@ type Transaction struct { Position plasma.Position } -// TxOutput holds all transactional information related to an output -// It is used to return output information from the store +// TxOutput holds all transactional information related to an output. type TxOutput struct { plasma.Output Position plasma.Position @@ -79,8 +52,8 @@ type TxOutput struct { } // NewTxOutput creates a TxOutput object. -func NewTxOutput(output plasma.Output, pos plasma.Position, spent bool, spenderTx []byte, inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TransactionOutput { - return TransactionOutput{ +func NewTxOutput(output plasma.Output, pos plasma.Position, spent bool, spenderTx []byte, inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TxOutput { + return TxOutput{ Output: output, Position: pos, Spent: spent, @@ -89,3 +62,31 @@ func NewTxOutput(output plasma.Output, pos plasma.Position, spent bool, spenderT InputPositions: inputPositions, } } + +// Block wraps a plasma block with the tendermint block height. +type Block struct { + plasma.Block + TMBlockHeight uint64 +} + +type block struct { + PlasmaBlock plasma.Block + TMBlockHeight uint64 +} + +// EncodeRLP RLP encodes a Block struct. +func (b *Block) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &block{b.Block, b.TMBlockHeight}) +} + +// DecodeRLP decodes the byte stream into a Block. +func (b *Block) DecodeRLP(s *rlp.Stream) error { + var block block + if err := s.Decode(&block); err != nil { + return err + } + + b.Block = block.PlasmaBlock + b.TMBlockHeight = block.TMBlockHeight + return nil +} diff --git a/store/types_test.go b/store/types_test.go index 5cb8904..06b31c1 100644 --- a/store/types_test.go +++ b/store/types_test.go @@ -68,6 +68,7 @@ func TestTxSerialization(t *testing.T) { } tx := Transaction{ + InputTxs: [][]byte{}, Transaction: transaction, Spent: []bool{false, false}, SpenderTxs: [][]byte{}, From ba636489009843bffb994929a29f1c3675b1c64c Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sun, 30 Jun 2019 12:29:19 -0700 Subject: [PATCH 37/49] fix various build/merge issue --- app/app.go | 4 +- cmd/plasmacli/subcmd/query/info.go | 12 +- cmd/plasmacli/subcmd/query/root.go | 17 +- handlers/anteHandler.go | 18 ++- store/errors.go | 16 +- store/keys.go | 2 +- store/querier.go | 234 ---------------------------- store/query/{plasma.go => block.go} | 12 +- store/query/errors.go | 16 ++ store/query/utxo.go | 103 ++++++++++-- store/types.go | 1 + 11 files changed, 148 insertions(+), 287 deletions(-) delete mode 100644 store/querier.go rename store/query/{plasma.go => block.go} (87%) diff --git a/app/app.go b/app/app.go index 9742102..f65678f 100644 --- a/app/app.go +++ b/app/app.go @@ -122,8 +122,8 @@ func NewPlasmaMVPChain(logger log.Logger, db dbm.DB, traceStore io.Writer, optio // custom queriers app.QueryRouter(). - AddRoute(store.QueryOutputStore, store.NewOutputQuerier(outputStore)). - AddRoute(store.QueryBlockStore, store.NewBlockQuerier(blockStore)) + AddRoute(store.QueryOutputStore, query.NewOutputQuerier(outputStore)). + AddRoute(store.QueryBlockStore, query.NewBlockQuerier(blockStore)) // Set the AnteHandler app.SetAnteHandler(handlers.NewAnteHandler(app.outputStore, app.blockStore, plasmaClient)) diff --git a/cmd/plasmacli/subcmd/query/info.go b/cmd/plasmacli/subcmd/query/info.go index d707d06..bf5972f 100644 --- a/cmd/plasmacli/subcmd/query/info.go +++ b/cmd/plasmacli/subcmd/query/info.go @@ -34,21 +34,21 @@ var infoCmd = &cobra.Command{ addr = ethcmn.HexToAddress(args[0]) } - queryPath := fmt.Sprintf("custom/tx/info/%s", addr) + queryPath := fmt.Sprintf("custom/utxo/info/%s", addr) data, err := ctx.Query(queryPath, nil) if err != nil { return err } - var utxos []store.OuptputInfo + var utxos []store.TxOutput if err := json.Unmarshal(data, &utxos); err != nil { return fmt.Errorf("unmarshaling json query response: %s", err) } for i, utxo := range utxos { fmt.Printf("UTXO %d\n", i) - fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Tx.Position, utxo.Output.Output.Amount.String(), utxo.Output.Spent, utxo.Output.SpenderTx) - fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.Tx.Transaction.TxHash(), utxo.Tx.ConfirmationHash) + fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent, utxo.SpenderTx) + fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.Transaction.TxHash(), utxo.Transaction.ConfirmationHash) // print inputs if applicable positions := utxo.Tx.Transaction.InputPositions() for i, p := range positions { @@ -66,7 +66,7 @@ var infoCmd = &cobra.Command{ }, } -func Info(ctx context.CLIContext, addr common.Address) ([]store.OutputInfo, error) { +func Info(ctx context.CLIContext, addr ethcmn.Address) ([]store.TxOutput, error) { // query for all utxos owned by this address queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) data, err := ctx.Query(queryRoute, nil) @@ -74,7 +74,7 @@ func Info(ctx context.CLIContext, addr common.Address) ([]store.OutputInfo, erro return nil, err } - var utxos []store.OutputInfo + var utxos []store.TxOutput if err := json.Unmarshal(data, &utxos); err != nil { return nil, err } diff --git a/cmd/plasmacli/subcmd/query/root.go b/cmd/plasmacli/subcmd/query/root.go index 1cf42a4..7e08429 100644 --- a/cmd/plasmacli/subcmd/query/root.go +++ b/cmd/plasmacli/subcmd/query/root.go @@ -1,7 +1,12 @@ package query import ( + "encoding/json" + "fmt" "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/config" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/spf13/cobra" ) @@ -22,21 +27,17 @@ var queryCmd = &cobra.Command{ Short: "Query information related to the sidechain", } -func QueryCmd() *cobra.Command { - return queryCmd -} - -func OutputInfo(ctx context.CLIContext, pos plasma.Position) (store.OutputInfo, error) { +func TxOutput(ctx context.CLIContext, pos plasma.Position) (store.TxOutput, error) { // query for an output for the given position queryRoute := fmt.Sprintf("custom/utxo/output/%s", pos) data, err := ctx.Query(queryRoute, nil) if err != nil { - return store.OutputInfo{}, err + return store.TxOutput{}, err } - var output store.OutputInfo + var output store.TxOutput if err := json.Unmarshal(data, &output); err != nil { - return store.OutputInfo{}, err + return store.TxOutput{}, err } return output, nil diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index a270094..bf40f5d 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -97,7 +97,11 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o if inputUTXO.Spent { return nil, ErrInvalidInput("input, %v, already spent", input.Position).Result() } - if client.HasTxBeenExited(blockStore.PlasmaBlockHeight(ctx), input.Position) { + exited, err := client.HasTxExited(blockStore.PlasmaBlockHeight(ctx), input.Position) + if err != nil { + // TODO: add updated error + return nil, ErrExitedInput("failed to retrieve exit information on input, %v", input.Position).Result() + } else if exited { return nil, ErrExitedInput("input, %v, utxo has exitted", input.Position).Result() } @@ -115,7 +119,11 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o // check if the parent utxo has exited for _, in := range tx.Transaction.Inputs { - if client.HasTxBeenExited(blockStore.PlasmaBlockHeight(ctx), in.Position) { + exited, err = client.HasTxExited(blockStore.PlasmaBlockHeight(ctx), in.Position) + if err != nil { + // TODO: update err + return nil, ErrExitedInput(fmt.Sprintf("failed to retrieve exit information on input, %v", in.Position)).Result() + } else if exited { return nil, ErrExitedInput(fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() } } @@ -164,8 +172,10 @@ func includeDepositAnteHandler(ctx sdk.Context, outputStore store.OutputStore, b } depositPosition := plasma.NewPosition(big.NewInt(0), 0, 0, msg.DepositNonce) - exited := client.HasTxBeenExited(blockStore.PlasmaBlockHeight(ctx), depositPosition) - if exited { + exited, err := client.HasTxExited(blockStore.PlasmaBlockHeight(ctx), depositPosition) + if err != nil { + return ctx, ErrInvalidTransaction("failed to retrieve deposit information for deposit, %s", msg.DepositNonce.String()).Result(), true + } else if exited { return ctx, ErrInvalidTransaction("deposit, %s, has already exitted from rootchain", msg.DepositNonce.String()).Result(), true } if !bytes.Equal(msg.Owner.Bytes(), deposit.Owner.Bytes()) { diff --git a/store/errors.go b/store/errors.go index fd6d508..3350448 100644 --- a/store/errors.go +++ b/store/errors.go @@ -9,23 +9,13 @@ const ( CodeOutputDNE sdk.CodeType = 1 CodeOutputSpent sdk.CodeType = 2 - CodeAccountDNE sdk.CodeType = 3 - CodeTxDNE sdk.CodeType = 4 ) -// TODO: refactor DNE errors into one -func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { - return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) -} - func ErrOutputSpent(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeOutputSpent, msg, args) } -func ErrWalletDNE(msg string, args ...interface{}) sdk.Error { - return sdk.NewError(DefaultCodespace, CodeAccountDNE, msg, args) -} - -func ErrTxDNE(msg string, args ...interface{}) sdk.Error { - return sdk.NewError(DefaultCodespace, CodeTxDNE, msg, args) +// TODO: refactor DNE errors into one +func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) } diff --git a/store/keys.go b/store/keys.go index e11bd76..363efff 100644 --- a/store/keys.go +++ b/store/keys.go @@ -8,7 +8,7 @@ import ( const ( // QueryOutputStore is the route to the OutputStore - QueryOutputStore = "outputs" // TODO: + QueryOutputStore = "utxo" // TODO: // QueryBlockStore is the route to the BlockStore QueryBlockStore = "block" diff --git a/store/querier.go b/store/querier.go deleted file mode 100644 index 3b7ef34..0000000 --- a/store/querier.go +++ /dev/null @@ -1,234 +0,0 @@ -package store - -import ( - "encoding/json" - "fmt" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/utils" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - abci "github.com/tendermint/tendermint/abci/types" - "math/big" -) - -const ( - // QueryBalance retrieves the aggregate value of - // the set of owned by the specified address - QueryBalance = "balance" - - // QueryInfo retrieves the entire utxo set owned - // by the specified address - QueryInfo = "info" - - // QueryTxnOutput retrieves a single output at - // the given position and returns it with transactional - // information - QueryTxOutput = "output" - - // QueryTx retrieves a transaction at the given hash - QueryTx = "tx" -) - -// NewOutputQuerier creates a OutputQuerier object. -func NewOutputQuerier(outputStore OutputStore) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { - if len(path) == 0 { - return nil, sdk.ErrUnknownRequest("path not specified") - } - - switch path[0] { - case QueryBalance: - if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("exprected balance/
") - } - addr := common.HexToAddress(path[1]) - total, err := queryBalance(ctx, outputStore, addr) - if err != nil { - return nil, sdk.ErrInternal("failed query balance") - } - return []byte(total.String()), nil - - case QueryInfo: - if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("expected info/
") - } - addr := common.HexToAddress(path[1]) - utxos, err := queryInfo(ctx, outputStore, addr) - if err != nil { - return nil, err - } - data, e := json.Marshal(utxos) - if e != nil { - return nil, sdk.ErrInternal("serialization error") - } - return data, nil - case QueryTxOutput: - if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("expected txo/") - } - pos, e := plasma.FromPositionString(path[1]) - if e != nil { - return nil, sdk.ErrInternal("position decoding error") - } - txo, err := queryTxOutput(ctx, outputStore, pos) - if err != nil { - return nil, err - } - data, e := json.Marshal(txo) - if e != nil { - return nil, sdk.ErrInternal("serialization error") - } - return data, nil - case QueryTx: - if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("expected tx/") - } - tx, ok := outputStore.GetTx(ctx, []byte(path[1])) - if !ok { - return nil, ErrTxDNE(fmt.Sprintf("no transaction exists for the hash provided: %x", []byte(path[1]))) - } - data, e := json.Marshal(tx) - if e != nil { - return nil, sdk.ErrInternal("serialization error") - } - return data, nil - default: - return nil, sdk.ErrUnknownRequest("unregistered endpoint") - } - } -} - -func queryBalance(ctx sdk.Context, outputStore OutputStore, addr common.Address) (*big.Int, sdk.Error) { - acc, ok := outputStore.GetWallet(ctx, addr) - if !ok { - return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) - } - - return acc.Balance, nil -} - -func queryInfo(ctx sdk.Context, outputStore OutputStore, addr common.Address) ([]TxOutput, sdk.Error) { - acc, ok := outputStore.GetWallet(ctx, addr) - if !ok { - return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) - } - return outputStore.GetUnspentForWallet(ctx, acc), nil -} - -func queryTxOutput(ctx sdk.Context, outputStore OutputStore, pos plasma.Position) (TxOutput, sdk.Error) { - output, ok := outputStore.GetOutput(ctx, pos) - if !ok { - return TxOutput{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) - } - - tx, ok := outputStore.GetTxWithPosition(ctx, pos) - if !ok { - return TxOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) - } - - inputTx, ok := outputStore.GetTx(ctx, output.InputTx) - if !ok { - return TxOutput{}, ErrTxDNE(fmt.Sprintf("no input transaction exists for the output with position: %s", pos)) - } - - txo := NewTxOutput(output.Output, pos, output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) - - return txo, nil -} - -const ( - // QueryBlocks retrieves full information about a - // speficied block - QueryBlock = "block" - - // QueryBlocs retrieves metadata about 10 blocks from - // a specified start point or the last 10 from the latest - // block - QueryBlocks = "blocks" -) - -type BlocksResp struct { - StartingBlockHeight *big.Int - Blocks []plasma.Block -} - -func NewBlockQuerier(blockStore BlockStore) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { - if len(path) == 0 { - return nil, sdk.ErrUnknownRequest("path not specified") - } - - switch path[0] { - case QueryBlock: - if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("expected block/") - } - blockNum, ok := new(big.Int).SetString(path[1], 10) - if !ok { - return nil, sdk.ErrUnknownRequest("block number must be provided in decimal format") - } - block, ok := blockStore.GetBlock(ctx, blockNum) - if !ok { - return nil, sdk.ErrUnknownRequest("nonexistent plasma block") - } - data, err := json.Marshal(block) - if err != nil { - return nil, sdk.ErrInternal("serialization error") - } - return data, nil - case QueryBlocks: - if len(path) > 2 { - return nil, sdk.ErrUnknownRequest("expected /blocks or /blocks/") - } - - var startingBlockNum *big.Int - if len(path) == 1 { - // latest 10 blocks - startingBlockNum = blockStore.PlasmaBlockHeight(ctx) - bigNine := big.NewInt(9) - if startingBlockNum.Cmp(bigNine) <= 0 { - startingBlockNum = big.NewInt(1) - } else { - startingBlockNum = startingBlockNum.Sub(startingBlockNum, bigNine) - } - } else { - // predefined starting point - var ok bool - startingBlockNum, ok = new(big.Int).SetString(path[1], 10) - if !ok { - return nil, sdk.ErrUnknownRequest("block number must be in decimal format") - } - } - - blocks, sdkErr := queryBlocks(ctx, blockStore, startingBlockNum) - if sdkErr != nil { - return nil, sdkErr - } - data, err := json.Marshal(blocks) - if err != nil { - return nil, sdk.ErrInternal("serialization error") - } - return data, nil - default: - return nil, sdk.ErrUnknownRequest("unregistered endpoint") - } - } -} - -func queryBlocks(ctx sdk.Context, blockStore BlockStore, startPoint *big.Int) (BlocksResp, sdk.Error) { - resp := BlocksResp{startPoint, []plasma.Block{}} - - // want `startPoint` to remain the same - blockHeight := new(big.Int).Add(startPoint, utils.Big0) - for i := 0; i < 10; i++ { - block, ok := blockStore.GetBlock(ctx, blockHeight) - if !ok { - return resp, nil - } - - resp.Blocks = append(resp.Blocks, block.Block) - blockHeight = blockHeight.Add(blockHeight, utils.Big1) - } - - return resp, nil -} diff --git a/store/query/plasma.go b/store/query/block.go similarity index 87% rename from store/query/plasma.go rename to store/query/block.go index 38b38c5..1a5cfc3 100644 --- a/store/query/plasma.go +++ b/store/query/block.go @@ -26,7 +26,7 @@ type BlocksResp struct { Blocks []plasma.Block } -func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { +func NewBlockQuerier(blockStore store.BlockStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, ErrInvalidPath("path not specified") @@ -43,7 +43,7 @@ func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { } else if blockNum.Sign() < 0 { return nil, ErrInvalidPath("block number must be positive") } - block, ok := plasmaStore.GetBlock(ctx, blockNum) + block, ok := blockStore.GetBlock(ctx, blockNum) if !ok { return nil, ErrInvalidPath("nonexistent plasma block") } @@ -60,7 +60,7 @@ func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { var blockNum *big.Int if len(path) == 1 { // latest 10 blocks - blockNum = plasmaStore.PlasmaBlockHeight(ctx) + blockNum = blockStore.PlasmaBlockHeight(ctx) bigNine := big.NewInt(9) if blockNum.Cmp(bigNine) <= 0 { blockNum = big.NewInt(1) @@ -76,7 +76,7 @@ func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { } } - blocks := queryBlocks(ctx, plasmaStore, blockNum) + blocks := queryBlocks(ctx, blockStore, blockNum) data, err := json.Marshal(blocks) if err != nil { return nil, ErrSerialization("json: %s", err) @@ -89,13 +89,13 @@ func NewPlasmaQuerier(plasmaStore store.PlasmaStore) sdk.Querier { } // queryBlocks will return an empty list of blocks if none are present -func queryBlocks(ctx sdk.Context, plasmaStore store.PlasmaStore, startPoint *big.Int) BlocksResp { +func queryBlocks(ctx sdk.Context, blockStore store.BlockStore, startPoint *big.Int) BlocksResp { resp := BlocksResp{startPoint, []plasma.Block{}} // want `startPoint` to remain the same blockHeight := new(big.Int).Add(startPoint, utils.Big0) for i := 0; i < 10; i++ { - block, ok := plasmaStore.GetBlock(ctx, blockHeight) + block, ok := blockStore.GetBlock(ctx, blockHeight) if !ok { return resp } diff --git a/store/query/errors.go b/store/query/errors.go index 8282e97..5458c8e 100644 --- a/store/query/errors.go +++ b/store/query/errors.go @@ -9,6 +9,9 @@ const ( CodeSerialization sdk.CodeType = 4 CodeInvalidPath sdk.CodeType = 5 + CodeTxDNE sdk.CodeType = 6 + CodeWalletDNE sdk.CodeType = 7 + CodeOutputDNE sdk.CodeType = 8 ) func ErrSerialization(msg string, args ...interface{}) sdk.Error { @@ -18,3 +21,16 @@ func ErrSerialization(msg string, args ...interface{}) sdk.Error { func ErrInvalidPath(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeInvalidPath, msg, args) } + +// TODO: refactor DNE errors into one +func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) +} + +func ErrTxDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeTxDNE, msg, args) +} + +func ErrWalletDNE(msg string, args ...interface{}) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeWalletDNE, msg, args) +} diff --git a/store/query/utxo.go b/store/query/utxo.go index f548a04..a44e6af 100644 --- a/store/query/utxo.go +++ b/store/query/utxo.go @@ -2,6 +2,8 @@ package query import ( "encoding/json" + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -17,9 +19,17 @@ const ( // QueryInfo retrieves the entire utxo set owned // by the specified address QueryInfo = "info" + + // QueryTxnOutput retrieves a single output at + // the given position and returns it with transactional + // information + QueryTxOutput = "output" + + // QueryTx retrieves a transaction at the given hash + QueryTx = "tx" ) -func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { +func NewOutputQuerier(outputStore store.OutputStore) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { if len(path) == 0 { return nil, ErrInvalidPath("path not specified") @@ -28,28 +38,57 @@ func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { switch path[0] { case QueryBalance: if len(path) != 2 { - return nil, ErrInvalidPath("expected balance/
") + return nil, sdk.ErrUnknownRequest("exprected balance/
") } addr := common.HexToAddress(path[1]) - utxos := utxoStore.GetUTXOSet(ctx, addr) - - total := big.NewInt(0) - for _, utxo := range utxos { - if !utxo.Spent { - total = total.Add(total, utxo.Output.Amount) - } + total, err := queryBalance(ctx, outputStore, addr) + if err != nil { + return nil, sdk.ErrInternal("failed query balance") } return []byte(total.String()), nil case QueryInfo: if len(path) != 2 { - return nil, ErrInvalidPath("expected info/
") + return nil, sdk.ErrUnknownRequest("expected info/
") } addr := common.HexToAddress(path[1]) - utxos := utxoStore.GetUTXOSet(ctx, addr) - data, err := json.Marshal(utxos) + utxos, err := queryInfo(ctx, outputStore, addr) if err != nil { - return nil, ErrSerialization("json: %s", err) + return nil, err + } + data, e := json.Marshal(utxos) + if e != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil + case QueryTxOutput: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("expected txo/") + } + pos, e := plasma.FromPositionString(path[1]) + if e != nil { + return nil, sdk.ErrInternal("position decoding error") + } + txo, err := queryTxOutput(ctx, outputStore, pos) + if err != nil { + return nil, err + } + data, e := json.Marshal(txo) + if e != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil + case QueryTx: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("expected tx/") + } + tx, ok := outputStore.GetTx(ctx, []byte(path[1])) + if !ok { + return nil, ErrTxDNE(fmt.Sprintf("no transaction exists for the hash provided: %x", []byte(path[1]))) + } + data, e := json.Marshal(tx) + if e != nil { + return nil, sdk.ErrInternal("serialization error") } return data, nil @@ -58,3 +97,41 @@ func NewUtxoQuerier(utxoStore store.UTXOStore) sdk.Querier { } } } + +func queryBalance(ctx sdk.Context, outputStore store.OutputStore, addr common.Address) (*big.Int, sdk.Error) { + acc, ok := outputStore.GetWallet(ctx, addr) + if !ok { + return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) + } + + return acc.Balance, nil +} + +func queryInfo(ctx sdk.Context, outputStore store.OutputStore, addr common.Address) ([]store.TxOutput, sdk.Error) { + acc, ok := outputStore.GetWallet(ctx, addr) + if !ok { + return nil, ErrWalletDNE(fmt.Sprintf("no wallet exists for the address provided: 0x%x", addr)) + } + return outputStore.GetUnspentForWallet(ctx, acc), nil +} + +func queryTxOutput(ctx sdk.Context, outputStore store.OutputStore, pos plasma.Position) (store.TxOutput, sdk.Error) { + output, ok := outputStore.GetOutput(ctx, pos) + if !ok { + return store.TxOutput{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) + } + + tx, ok := outputStore.GetTxWithPosition(ctx, pos) + if !ok { + return store.TxOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) + } + + inputTx, ok := outputStore.GetTx(ctx, output.InputTx) + if !ok { + return store.TxOutput{}, ErrTxDNE(fmt.Sprintf("no input transaction exists for the output with position: %s", pos)) + } + + txo := store.NewTxOutput(output.Output, pos, output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) + + return txo, nil +} diff --git a/store/types.go b/store/types.go index a703197..578f321 100644 --- a/store/types.go +++ b/store/types.go @@ -42,6 +42,7 @@ type Transaction struct { } // TxOutput holds all transactional information related to an output. +// TODO: add txhash and conf hash type TxOutput struct { plasma.Output Position plasma.Position From 52f5487b34cc2ddfe3474e1ac565afd7b4669e58 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 1 Jul 2019 16:45:05 -0700 Subject: [PATCH 38/49] cli fixes --- cmd/plasmacli/subcmd/eth/challenge.go | 14 +------- cmd/plasmacli/subcmd/eth/exit.go | 6 +--- cmd/plasmacli/subcmd/eth/prove.go | 47 +++++++++++++++++---------- cmd/plasmacli/subcmd/query/info.go | 4 +-- cmd/plasmacli/subcmd/rest.go | 4 +-- cmd/plasmacli/subcmd/tx/sign.go | 22 ++++++------- cmd/plasmacli/subcmd/tx/spend.go | 41 +++++++++++------------ store/keys.go | 2 +- store/outputStore.go | 2 +- store/query/utxo.go | 2 +- store/types.go | 29 ++++++++++------- 11 files changed, 85 insertions(+), 88 deletions(-) diff --git a/cmd/plasmacli/subcmd/eth/challenge.go b/cmd/plasmacli/subcmd/eth/challenge.go index e778160..be7a432 100644 --- a/cmd/plasmacli/subcmd/eth/challenge.go +++ b/cmd/plasmacli/subcmd/eth/challenge.go @@ -5,10 +5,7 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/config" ks "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/ethereum/go-ethereum/accounts/abi/bind" - ethcmn "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/spf13/cobra" "github.com/spf13/viper" tm "github.com/tendermint/tendermint/rpc/core/types" @@ -19,7 +16,6 @@ import ( func ChallengeCmd() *cobra.Command { config.AddPersistentTMFlags(challengeCmd) challengeCmd.Flags().StringP(gasLimitF, "g", "300000", "gas limit for ethereum transaction") - challengeCmd.Flags().String(ownerF, "", "owner of the challenging transaction, required if different from the specified account") challengeCmd.Flags().String(proofF, "", "merkle proof of inclusion") challengeCmd.Flags().String(sigsF, "", "confirmation signatures for the challenging transaction") challengeCmd.Flags().Bool(useNodeF, false, "trust connected full node") @@ -41,7 +37,6 @@ Usage: Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - ctx := context.NewCLIContext() // parse positions exitingPos, err := plasma.FromPositionString(args[0]) @@ -64,13 +59,6 @@ Usage: return fmt.Errorf("failed to retrieve account key: { %s }", err) } - var owner ethcmn.Address - if viper.GetString(ownerF) != "" { - owner = ethcmn.HexToAddress(viper.GetString(ownerF)) - } else { - owner = crypto.PubkeyToAddress(key.PublicKey) - } - // bind key auth := bind.NewKeyedTransactor(key) transactOpts := &bind.TransactOpts{ @@ -82,7 +70,7 @@ Usage: var txBytes, proof, confirmSignatures []byte if viper.GetBool(useNodeF) { var result *tm.ResultTx - result, confirmSignatures, err = getProof(ctx, owner, challengingPos) + result, confirmSignatures, err = getProof(challengingPos) if err != nil { return fmt.Errorf("failed to retrieve exit information: { %s }", err) } diff --git a/cmd/plasmacli/subcmd/eth/exit.go b/cmd/plasmacli/subcmd/eth/exit.go index 81abdcf..ef9bed8 100644 --- a/cmd/plasmacli/subcmd/eth/exit.go +++ b/cmd/plasmacli/subcmd/eth/exit.go @@ -5,11 +5,9 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/config" ks "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcmn "github.com/ethereum/go-ethereum/common" eth "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/spf13/cobra" "github.com/spf13/viper" tm "github.com/tendermint/tendermint/rpc/core/types" @@ -46,7 +44,6 @@ Transaction Exit Usage: Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) (err error) { viper.BindPFlags(cmd.Flags()) - ctx := context.NewCLIContext() var tx *eth.Transaction // parse position @@ -70,7 +67,6 @@ Transaction Exit Usage: if err != nil { return fmt.Errorf("failed to retrieve account key: { %s }", err) } - addr := crypto.PubkeyToAddress(key.PublicKey) // bind key, generate transact opts auth := bind.NewKeyedTransactor(key) @@ -105,7 +101,7 @@ Transaction Exit Usage: var txBytes, proof, confirmSignatures []byte if viper.GetBool(useNodeF) { // query full node var result *tm.ResultTx - result, confirmSignatures, err = getProof(ctx, addr, position) + result, confirmSignatures, err = getProof(position) if err != nil { return fmt.Errorf("failed to retrieve exit information: { %s }", err) } diff --git a/cmd/plasmacli/subcmd/eth/prove.go b/cmd/plasmacli/subcmd/eth/prove.go index 7e2d4a1..f236985 100644 --- a/cmd/plasmacli/subcmd/eth/prove.go +++ b/cmd/plasmacli/subcmd/eth/prove.go @@ -1,14 +1,13 @@ package eth import ( + "encoding/json" "fmt" "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/config" - ks "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" "github.com/FourthState/plasma-mvp-sidechain/plasma" "github.com/FourthState/plasma-mvp-sidechain/store" - // "github.com/cosmos/cosmos-sdk/client/context" - ethcmn "github.com/ethereum/go-ethereum/common" - // "github.com/ethereum/go-ethereum/rlp" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/ethereum/go-ethereum/rlp" "github.com/spf13/cobra" tm "github.com/tendermint/tendermint/rpc/core/types" ) @@ -19,24 +18,18 @@ func ProveCmd() *cobra.Command { } var proveCmd = &cobra.Command{ - Use: "prove ", + Use: "prove ", Short: "Prove transaction inclusion: prove ", Args: cobra.ExactArgs(2), Long: "Returns proof for transaction inclusion. Use to exit transactions in the smart contract", RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.NewCLIContext() - addr, err := ks.GetAccount(args[0]) - if err != nil { - return fmt.Errorf("failed to retrieve account: { %s }", err) - } - // parse position position, err := plasma.FromPositionString(args[1]) if err != nil { return err } - result, sigs, err := getProof(ctx, addr, position) + result, sigs, err := getProof(position) if err != nil { return err } @@ -81,7 +74,7 @@ var proveCmd = &cobra.Command{ func getProof(position plasma.Position) (*tm.ResultTx, []byte, error) { ctx := context.NewCLIContext().WithTrustNode(true) - key := store.GetOutputKey(pos) + key := store.GetOutputKey(position) hash, err := ctx.QueryStore(key, "outputs") if err != nil { return &tm.ResultTx{}, nil, err @@ -90,21 +83,39 @@ func getProof(position plasma.Position) (*tm.ResultTx, []byte, error) { txKey := store.GetTxKey(hash) txBytes, err := ctx.QueryStore(txKey, "outputs") - var tx Transaction + var tx store.Transaction if err := rlp.DecodeBytes(txBytes, &tx); err != nil { - return &tm.ResultTx{}, nil, fmt.Errorf("Transaction decoding failed: %s", err.String()) + return &tm.ResultTx{}, nil, fmt.Errorf("Transaction decoding failed: %s", err.Error()) } // query tm node for information about this tx - result, err := ctx.Client.Tx(tx.MerkleHash(), true) + result, err := ctx.Client.Tx(tx.Transaction.MerkleHash(), true) if err != nil { return &tm.ResultTx{}, nil, err } // Look for confirmation signatures // Ignore error if no confirm sig currently exists in store - key = append([]byte("confirmSignature"), utxo.Position.Bytes()...) - sigs, _ := ctx.QueryStore(key, "plasma") + var sigs []byte + if len(tx.SpenderTxs[position.OutputIndex]) > 0 { + queryPath := fmt.Sprintf("custom/utxo/tx/%s", tx.SpenderTxs[position.OutputIndex]) + data, err := ctx.Query(queryPath, nil) + if err != nil { + return &tm.ResultTx{}, nil, err + } + + var spenderTx store.Transaction + if err := json.Unmarshal(data, &spenderTx); err != nil { + return &tm.ResultTx{}, nil, fmt.Errorf("unmarshaling json query response: %s", err) + } + for _, input := range spenderTx.Transaction.Inputs { + if input.Position.String() == position.String() { + for _, sig := range input.ConfirmSignatures { + sigs = append(sigs, sig[:]...) + } + } + } + } return result, sigs, nil } diff --git a/cmd/plasmacli/subcmd/query/info.go b/cmd/plasmacli/subcmd/query/info.go index bf5972f..cec9d3c 100644 --- a/cmd/plasmacli/subcmd/query/info.go +++ b/cmd/plasmacli/subcmd/query/info.go @@ -48,9 +48,9 @@ var infoCmd = &cobra.Command{ for i, utxo := range utxos { fmt.Printf("UTXO %d\n", i) fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent, utxo.SpenderTx) - fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.Transaction.TxHash(), utxo.Transaction.ConfirmationHash) + fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.TxHash, utxo.ConfirmationHash) // print inputs if applicable - positions := utxo.Tx.Transaction.InputPositions() + positions := utxo.InputPositions for i, p := range positions { fmt.Printf("Input %d Position: %s\n", i, p) } diff --git a/cmd/plasmacli/subcmd/rest.go b/cmd/plasmacli/subcmd/rest.go index 01f23e0..edd3238 100644 --- a/cmd/plasmacli/subcmd/rest.go +++ b/cmd/plasmacli/subcmd/rest.go @@ -76,7 +76,7 @@ func balanceHandler(ctx context.CLIContext) http.HandlerFunc { return } - queryPath := fmt.Sprintf("custom/tx/balance/%s", addr) + queryPath := fmt.Sprintf("custom/utxo/balance/%s", addr) total, err := ctx.Query(queryPath, nil) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -99,7 +99,7 @@ func infoHandler(ctx context.CLIContext) http.HandlerFunc { return } - queryPath := fmt.Sprintf("custom/tx/info/%s", addr) + queryPath := fmt.Sprintf("custom/utxo/info/%s", addr) data, err := ctx.Query(queryPath, nil) if err != nil { w.WriteHeader(http.StatusBadRequest) diff --git a/cmd/plasmacli/subcmd/tx/sign.go b/cmd/plasmacli/subcmd/tx/sign.go index cf864a2..4b8924e 100644 --- a/cmd/plasmacli/subcmd/tx/sign.go +++ b/cmd/plasmacli/subcmd/tx/sign.go @@ -3,8 +3,8 @@ package tx import ( "fmt" clistore "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" + "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/subcmd/query" "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/store" "github.com/FourthState/plasma-mvp-sidechain/utils" cosmoscli "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" @@ -63,13 +63,13 @@ Usage: for _, output := range utxos { - if output.Output.Spent { - tx, err := query.Tx(ctx, output.Output.SpenderTx) + if output.Spent { + tx, err := query.Tx(ctx, output.SpenderTx) if err != nil { return err } - for i, pos := range tx.InputPositions() { + for _, pos := range tx.Transaction.InputPositions() { err = signSingleConfirmSig(ctx, pos, signerAddr, name) if err != nil { fmt.Println(err) @@ -88,13 +88,13 @@ Usage: // generate confirmation signature for given utxo func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, signerAddr ethcmn.Address, name string) error { // query for output for the specified position - output, err := query.OutputInfo(ctx, position) + output, err := query.TxOutput(ctx, position) if err != nil { return err } - sig, _ := clistore.GetSig(output.Tx.Position) - inputAddrs := output.Tx.InputAddresses() + sig, _ := clistore.GetSig(output.Position) + inputAddrs := output.InputAddresses if len(sig) == 130 || (len(sig) == 65 && len(inputAddrs) == 1) { return nil @@ -106,7 +106,7 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign } // TODO: fix to use correct position // get confirmation to generate signature - fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.Tx.Position, output.Output.Output.Owner, output.Output.Amount) + fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.Position, output.Output.Owner, output.Output.Amount) buf := cosmoscli.BufferStdin() auth, err := cosmoscli.GetString(signPrompt, buf) if err != nil { @@ -116,18 +116,18 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign return nil } - hash := utils.ToEthSignedMessageHash(output.Tx.ConfirmationHash) + hash := utils.ToEthSignedMessageHash(output.ConfirmationHash) sig, err := clistore.SignHashWithPassphrase(name, hash) if err != nil { return fmt.Errorf("failed to generate confirmation signature: { %s }", err) } - if err := clistore.SaveSig(output.Tx.Position, sig, i == 0); err != nil { + if err := clistore.SaveSig(output.Position, sig, i == 0); err != nil { return err } // print the results - fmt.Printf("Confirmation Signature for output with position: %s\n", output.Tx.Position) + fmt.Printf("Confirmation Signature for output with position: %s\n", output.Position) fmt.Printf("0x%x\n", sig) } return nil diff --git a/cmd/plasmacli/subcmd/tx/spend.go b/cmd/plasmacli/subcmd/tx/spend.go index 75392bf..e5ba365 100644 --- a/cmd/plasmacli/subcmd/tx/spend.go +++ b/cmd/plasmacli/subcmd/tx/spend.go @@ -2,6 +2,7 @@ package tx import ( "encoding/hex" + "encoding/json" "fmt" clistore "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/store" "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/subcmd/eth" @@ -95,19 +96,19 @@ Usage: // build transaction // create the inputs without signatures tx := plasma.Transaction{} - tx.Input0 = plasma.NewInput(inputs[0], [65]byte{}, confirmSignatures[0]) + tx.Inputs = append(tx.Inputs, plasma.NewInput(inputs[0], [65]byte{}, confirmSignatures[0])) if len(inputs) > 1 { - tx.Input1 = plasma.NewInput(inputs[1], [65]byte{}, confirmSignatures[1]) + tx.Inputs = append(tx.Inputs, plasma.NewInput(inputs[1], [65]byte{}, confirmSignatures[1])) } else { - tx.Input1 = plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil) + tx.Inputs = append(tx.Inputs, plasma.NewInput(plasma.NewPosition(nil, 0, 0, nil), [65]byte{}, nil)) } // generate outputs // use change to determine outcome of second output - tx.Outputs[0] = plasma.NewOutput(toAddrs[0], amounts[0]) + tx.Outputs = append(tx.Outputs, plasma.NewOutput(toAddrs[0], amounts[0])) if len(toAddrs) > 1 { if change.Sign() == 0 { - tx.Output1 = plasma.NewOutput(toAddrs[1], amounts[1]) + tx.Outputs = append(tx.Outputs, plasma.NewOutput(toAddrs[1], amounts[1])) } else { return fmt.Errorf("cannot spend to two addresses since exact utxo inputs could not be found") } @@ -116,9 +117,9 @@ Usage: if err != nil { return err } - tx.Output1 = plasma.NewOutput(addr, change) + tx.Outputs = append(tx.Outputs, plasma.NewOutput(addr, change)) } else { - tx.Output1 = plasma.NewOutput(ethcmn.Address{}, nil) + tx.Outputs = append(tx.Outputs, plasma.NewOutput(ethcmn.Address{}, nil)) } tx.Fee = fee @@ -131,7 +132,7 @@ Usage: return err } copy(signature[:], sig) - tx.Input0.Signature = signature + tx.Inputs[0].Signature = signature if len(inputs) > 1 { if len(accs) > 2 { signer = accs[1] @@ -141,7 +142,7 @@ Usage: return err } copy(signature[:], sig) - tx.Input1.Signature = signature + tx.Inputs[1].Signature = signature } // create SpendMsg and txBytes @@ -331,22 +332,22 @@ func retrieveInputs(ctx context.CLIContext, accs []string, total *big.Int) (inpu return inputs, change, err } - res, err := ctx.QuerySubspace(addr.Bytes(), "utxo") + queryPath := fmt.Sprintf("custom/utxo/info/%s", addr) + res, err := ctx.Query(queryPath, nil) if err != nil { return inputs, change, err } + var utxos []store.TxOutput + if err := json.Unmarshal(res, &utxos); err != nil { + return inputs, change, err + } + var optimalChange = total var position0, position1 plasma.Position // iterate through utxo's looking for optimal input pairing // return input pairing if input + input == total - utxo0 := store.UTXO{} - utxo1 := store.UTXO{} - for i, outer := range res { - if err := rlp.DecodeBytes(outer.Value, &utxo0); err != nil { - return nil, nil, err - } - + for i, utxo0 := range utxos { exitted, err := eth.HasTxExited(utxo0.Position) if err != nil { return nil, nil, fmt.Errorf("Must connect full eth node or specify inputs using flags. Error encountered: %s", err) @@ -359,16 +360,12 @@ func retrieveInputs(ctx context.CLIContext, accs []string, total *big.Int) (inpu inputs = append(inputs, utxo0.Position) return inputs, big.NewInt(0), nil } - for k, inner := range res { + for k, utxo1 := range utxos { // do not pair an input with itself if i == k { continue } - if err := rlp.DecodeBytes(inner.Value, &utxo1); err != nil { - return nil, nil, err - } - exitted, err := eth.HasTxExited(utxo1.Position) if err != nil { return nil, nil, fmt.Errorf("Must connect full eth node or specify inputs using flags. Error encountered: %s", err) diff --git a/store/keys.go b/store/keys.go index 363efff..1af720b 100644 --- a/store/keys.go +++ b/store/keys.go @@ -11,7 +11,7 @@ const ( QueryOutputStore = "utxo" // TODO: // QueryBlockStore is the route to the BlockStore - QueryBlockStore = "block" + QueryBlockStore = "plasma" ) // keys diff --git a/store/outputStore.go b/store/outputStore.go index 6647bc9..83e34e8 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -335,7 +335,7 @@ func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (ut if !ok { panic("") // TODO } - txo := NewTxOutput(output.Output, p, output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) + txo := NewTxOutput(output.Output, p, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) utxos = append(utxos, txo) } return utxos diff --git a/store/query/utxo.go b/store/query/utxo.go index a44e6af..8336c28 100644 --- a/store/query/utxo.go +++ b/store/query/utxo.go @@ -131,7 +131,7 @@ func queryTxOutput(ctx sdk.Context, outputStore store.OutputStore, pos plasma.Po return store.TxOutput{}, ErrTxDNE(fmt.Sprintf("no input transaction exists for the output with position: %s", pos)) } - txo := store.NewTxOutput(output.Output, pos, output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) + txo := store.NewTxOutput(output.Output, pos, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) return txo, nil } diff --git a/store/types.go b/store/types.go index 578f321..82d812d 100644 --- a/store/types.go +++ b/store/types.go @@ -45,22 +45,27 @@ type Transaction struct { // TODO: add txhash and conf hash type TxOutput struct { plasma.Output - Position plasma.Position - Spent bool - SpenderTx []byte - InputAddresses []ethcmn.Address - InputPositions []plasma.Position + Position plasma.Position + ConfirmationHash []byte + TxHash []byte + Spent bool + SpenderTx []byte + InputAddresses []ethcmn.Address + InputPositions []plasma.Position } // NewTxOutput creates a TxOutput object. -func NewTxOutput(output plasma.Output, pos plasma.Position, spent bool, spenderTx []byte, inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TxOutput { +func NewTxOutput(output plasma.Output, pos plasma.Position, confirmationHash, txHash []byte, spent bool, spenderTx []byte, + inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TxOutput { return TxOutput{ - Output: output, - Position: pos, - Spent: spent, - SpenderTx: spenderTx, - InputAddresses: inputAddresses, - InputPositions: inputPositions, + Output: output, + Position: pos, + ConfirmationHash: confirmationHash, + TxHash: txHash, + Spent: spent, + SpenderTx: spenderTx, + InputAddresses: inputAddresses, + InputPositions: inputPositions, } } From 3010eb9f8db2fde91fa29bdc5e14813a8fa9822a Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 1 Jul 2019 16:47:48 -0700 Subject: [PATCH 39/49] rm todos --- cmd/plasmacli/subcmd/eth/prove.go | 2 -- cmd/plasmacli/subcmd/tx/sign.go | 1 - 2 files changed, 3 deletions(-) diff --git a/cmd/plasmacli/subcmd/eth/prove.go b/cmd/plasmacli/subcmd/eth/prove.go index f236985..13ae1d5 100644 --- a/cmd/plasmacli/subcmd/eth/prove.go +++ b/cmd/plasmacli/subcmd/eth/prove.go @@ -69,8 +69,6 @@ var proveCmd = &cobra.Command{ // Returns transaction results for given position // Trusts connected full node - -//TODO: REDO func getProof(position plasma.Position) (*tm.ResultTx, []byte, error) { ctx := context.NewCLIContext().WithTrustNode(true) diff --git a/cmd/plasmacli/subcmd/tx/sign.go b/cmd/plasmacli/subcmd/tx/sign.go index 4b8924e..ef51f82 100644 --- a/cmd/plasmacli/subcmd/tx/sign.go +++ b/cmd/plasmacli/subcmd/tx/sign.go @@ -104,7 +104,6 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign if input != signerAddr { continue } - // TODO: fix to use correct position // get confirmation to generate signature fmt.Printf("\nUTXO\nPosition: %s\nOwner: 0x%x\nValue: %d\n", output.Position, output.Output.Owner, output.Output.Amount) buf := cosmoscli.BufferStdin() From f4cce1398a9ef5bf2e65ee94af8b233abb45cb78 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 1 Jul 2019 16:55:45 -0700 Subject: [PATCH 40/49] store fee --- app/app.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/app.go b/app/app.go index f65678f..be7e056 100644 --- a/app/app.go +++ b/app/app.go @@ -174,6 +174,8 @@ func (app *PlasmaMVPChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) copy(header[:], ctx.BlockHeader().DataHash) block := plasma.NewBlock(header, app.txIndex, app.feeAmount) app.blockStore.StoreBlock(ctx, tmBlockHeight, block) + // TODO verify + app.outputStore.StoreFee(ctx, plasma.NewPosition(app.blockStore.PlasmaBlockHeight(ctx), 1<<16-1, 0, big.NewInt(0)), plasma.NewOutput(app.operatorAddress, app.feeAmount)) app.ethConnection.CommitPlasmaHeaders(ctx, app.blockStore) app.txIndex = 0 From c4436e2aeb5a662ffca9df0c21266b59e9340eb2 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 1 Jul 2019 20:28:45 -0700 Subject: [PATCH 41/49] validateBasic change --- plasma/transaction.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plasma/transaction.go b/plasma/transaction.go index 2e96146..bc9c0fa 100644 --- a/plasma/transaction.go +++ b/plasma/transaction.go @@ -76,6 +76,11 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { } func (tx Transaction) ValidateBasic() error { + // Error if more than 2 inputs or outputs. Will be allowed in future versions + if len(tx.Inputs) > 2 || len(tx.Outputs) > 2 { + return fmt.Errorf("invalid tx, maximum of 2 input/output allowed") + } + // validate inputs for i, input := range tx.Inputs { if err := input.ValidateBasic(); err != nil { From d9926bd5e7e1e96fd70f00c39ca9712c3abaaf14 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 1 Jul 2019 23:28:52 -0700 Subject: [PATCH 42/49] fix bugs --- cmd/plasmacli/subcmd/query/info.go | 3 ++ cmd/plasmacli/subcmd/query/root.go | 16 ++++++++++ cmd/plasmacli/subcmd/tx/sign.go | 6 +++- eth/plasma_test.go | 8 ++--- handlers/anteHandler_test.go | 7 +++++ store/outputStore.go | 14 +++------ store/outputStore_test.go | 5 ++- store/query/utxo.go | 50 +++++++++++++++++++++++++++--- store/types.go | 30 +++++++++++++----- store/types_test.go | 1 - 10 files changed, 109 insertions(+), 31 deletions(-) diff --git a/cmd/plasmacli/subcmd/query/info.go b/cmd/plasmacli/subcmd/query/info.go index cec9d3c..02d425c 100644 --- a/cmd/plasmacli/subcmd/query/info.go +++ b/cmd/plasmacli/subcmd/query/info.go @@ -49,11 +49,14 @@ var infoCmd = &cobra.Command{ fmt.Printf("UTXO %d\n", i) fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent, utxo.SpenderTx) fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.TxHash, utxo.ConfirmationHash) + /* + // TODO: Add --verbose flag that if set will query TxInput and print InputAddresses and InputPositions as well // print inputs if applicable positions := utxo.InputPositions for i, p := range positions { fmt.Printf("Input %d Position: %s\n", i, p) } + */ fmt.Printf("End UTXO %d info\n\n", i) } diff --git a/cmd/plasmacli/subcmd/query/root.go b/cmd/plasmacli/subcmd/query/root.go index 7e08429..1566066 100644 --- a/cmd/plasmacli/subcmd/query/root.go +++ b/cmd/plasmacli/subcmd/query/root.go @@ -43,6 +43,22 @@ func TxOutput(ctx context.CLIContext, pos plasma.Position) (store.TxOutput, erro return output, nil } +func TxInput(ctx context.CLIContext, pos plasma.Position) (store.TxInput, error) { + // query for input info on the given position + queryRoute := fmt.Sprintf("custom/utxo/input/%s", pos) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return store.TxInput{}, err + } + + var input store.TxInput + if err := json.Unmarshal(data, &input); err != nil { + return store.TxInput{}, err + } + + return input, nil +} + func Tx(ctx context.CLIContext, hash []byte) (store.Transaction, error) { // query for a transaction using the provided hash queryRoute := fmt.Sprintf("custom/utxo/tx/%s", hash) diff --git a/cmd/plasmacli/subcmd/tx/sign.go b/cmd/plasmacli/subcmd/tx/sign.go index ef51f82..eebd770 100644 --- a/cmd/plasmacli/subcmd/tx/sign.go +++ b/cmd/plasmacli/subcmd/tx/sign.go @@ -92,9 +92,13 @@ func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, sign if err != nil { return err } + inputInfo, err := query.TxInput(ctx, position) + if err != nil { + return err + } sig, _ := clistore.GetSig(output.Position) - inputAddrs := output.InputAddresses + inputAddrs := inputInfo.InputAddresses if len(sig) == 130 || (len(sig) == 65 && len(inputAddrs) == 1) { return nil diff --git a/eth/plasma_test.go b/eth/plasma_test.go index 35f98c0..308f0bd 100644 --- a/eth/plasma_test.go +++ b/eth/plasma_test.go @@ -76,7 +76,7 @@ func TestSubmitBlock(t *testing.T) { blockStore.StoreBlock(ctx, uint64(i), block) } - err := plasma.CommitPlasmaHeaders(ctx, blockStore) + err := plasmaContract.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") @@ -136,11 +136,11 @@ func TestDepositFinalityBound(t *testing.T) { blockStore.StoreBlock(ctx, uint64(i), block) } - err = plasma.CommitPlasmaHeaders(ctx, blockStore) + err = plasmaContract.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") - err = plasma.CommitPlasmaHeaders(ctx, blockStore) + err = plasmaContract.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") // Try to retrieve deposit from before peg @@ -164,7 +164,7 @@ func TestDepositFinalityBound(t *testing.T) { } blockStore.StoreBlock(ctx, uint64(4), block) - err = plasma.CommitPlasmaHeaders(ctx, blockStore) + err = plasmaContract.CommitPlasmaHeaders(ctx, blockStore) require.NoError(t, err, "block submission error") // Try to retrieve deposit once peg has advanced AND finality bound reached. diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index f778396..5d70d40 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -349,8 +349,15 @@ func TestAnteValidTx(t *testing.T) { Position: getPosition("(1.0.0.0)"), } outputStore.StoreTx(ctx, tx) + outputStore.StoreOutputs(ctx, tx) outputStore.SpendDeposit(ctx, big.NewInt(2), tx.Transaction.TxHash()) + output, check := outputStore.GetOutput(ctx, getPosition("(1.0.0.0)")) + if !check { + fmt.Println("WHy?") + } + fmt.Printf("Output: %v\n", output) + // store confirm sig into correct format var confirmSig [65]byte copy(confirmSig[:], confSig) diff --git a/store/outputStore.go b/store/outputStore.go index 83e34e8..b3ee889 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -103,7 +103,6 @@ func (store OutputStore) GetOutput(ctx sdk.Context, pos plasma.Position) (Output output := Output{ Output: tx.Transaction.Outputs[pos.OutputIndex], Spent: tx.Spent[pos.OutputIndex], - InputTx: tx.InputTxs[pos.OutputIndex], SpenderTx: tx.SpenderTxs[pos.OutputIndex], } @@ -235,7 +234,7 @@ func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit p // StoreFee adds an unspent fee and updates the fee owner's wallet. func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output plasma.Output) { - store.setFee(ctx, pos, Output{output, false, make([]byte, 0), make([]byte, 0)}) + store.setFee(ctx, pos, Output{output, false, make([]byte, 0)}) store.addToWallet(ctx, output.Owner, output.Amount, pos) } @@ -325,17 +324,14 @@ func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (ut for _, p := range wallet.Unspent { output, ok := store.GetOutput(ctx, p) if !ok { - panic("") // TODO + panic(fmt.Sprintf("Corrupted store: Wallet contains unspent position (%v) that doesn't exist in store", p)) } tx, ok := store.GetTxWithPosition(ctx, p) if !ok { - panic("") // TODO + panic(fmt.Sprintf("Corrupted store: Wallet contains unspent position (%v) that doesn't have corresponding tx", p)) } - inputTx, ok := store.GetTx(ctx, output.InputTx) - if !ok { - panic("") // TODO - } - txo := NewTxOutput(output.Output, p, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) + + txo := NewTxOutput(output.Output, p, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx) utxos = append(utxos, txo) } return utxos diff --git a/store/outputStore_test.go b/store/outputStore_test.go index e63df5c..489ed59 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -77,7 +77,7 @@ func TestFees(t *testing.T) { // Create and store new fee output := plasma.NewOutput(addr, big.NewInt(int64(1000*i))) - fee := Output{output, false, make([]byte, 0), make([]byte, 0)} + fee := Output{output, false, make([]byte, 0)} outputStore.StoreFee(ctx, pos, output) exists = outputStore.HasFee(ctx, pos) @@ -178,10 +178,9 @@ func TestTransactions(t *testing.T) { } // Create and store new transaction - tx := Transaction{make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Position} + tx := Transaction{plasmaTx.Transaction, confirmationHash, make([]bool, len(plasmaTx.Transaction.Outputs)), make([][]byte, len(plasmaTx.Transaction.Outputs)), plasmaTx.Position} for i, _ := range tx.SpenderTxs { tx.SpenderTxs[i] = []byte{} - tx.InputTxs[i] = []byte{} } outputStore.StoreTx(ctx, tx) outputStore.StoreOutputs(ctx, tx) diff --git a/store/query/utxo.go b/store/query/utxo.go index 8336c28..9137d05 100644 --- a/store/query/utxo.go +++ b/store/query/utxo.go @@ -20,11 +20,15 @@ const ( // by the specified address QueryInfo = "info" - // QueryTxnOutput retrieves a single output at + // QueryTxOutput retrieves a single output at // the given position and returns it with transactional // information QueryTxOutput = "output" + // QueryTxInput retrieves basic transaction data at + // given position along with input information + QueryTxInput = "input" + // QueryTx retrieves a transaction at the given hash QueryTx = "tx" ) @@ -78,6 +82,23 @@ func NewOutputQuerier(outputStore store.OutputStore) sdk.Querier { return nil, sdk.ErrInternal("serialization error") } return data, nil + case QueryTxInput: + if len(path) != 2 { + return nil, sdk.ErrUnknownRequest("expected input/") + } + pos, e := plasma.FromPositionString(path[1]) + if e != nil { + return nil, sdk.ErrInternal("position decoding error") + } + txInput, err := queryTxInput(ctx, outputStore, pos) + if err != nil { + return nil, err + } + data, e := json.Marshal(txInput) + if e != nil { + return nil, sdk.ErrInternal("serialization error") + } + return data, nil case QueryTx: if len(path) != 2 { return nil, sdk.ErrUnknownRequest("expected tx/") @@ -126,12 +147,31 @@ func queryTxOutput(ctx sdk.Context, outputStore store.OutputStore, pos plasma.Po return store.TxOutput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) } - inputTx, ok := outputStore.GetTx(ctx, output.InputTx) + txo := store.NewTxOutput(output.Output, pos, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx) + + return txo, nil +} + +func queryTxInput(ctx sdk.Context, outputStore store.OutputStore, pos plasma.Position) (store.TxInput, sdk.Error) { + output, ok := outputStore.GetOutput(ctx, pos) if !ok { - return store.TxOutput{}, ErrTxDNE(fmt.Sprintf("no input transaction exists for the output with position: %s", pos)) + return store.TxInput{}, ErrOutputDNE(fmt.Sprintf("no output exists for the position provided: %s", pos)) } - txo := store.NewTxOutput(output.Output, pos, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx, inputTx.Transaction.OutputAddresses(), tx.Transaction.InputPositions()) + tx, ok := outputStore.GetTxWithPosition(ctx, pos) + if !ok { + return store.TxInput{}, ErrTxDNE(fmt.Sprintf("no transaction exists for the position provided: %s", pos)) + } - return txo, nil + inputPositions := tx.Transaction.InputPositions() + var inputAddresses []common.Address + for _, inPos := range inputPositions { + input, ok := outputStore.GetOutput(ctx, inPos) + if !ok { + panic(fmt.Sprintf("Corrupted store: input position for given transaction does not exist: %s", pos)) + } + inputAddresses = append(inputAddresses, input.Output.Owner) + } + + return store.NewTxInput(output.Output, pos, tx.Transaction.TxHash(), inputAddresses, inputPositions), nil } diff --git a/store/types.go b/store/types.go index 82d812d..210ffeb 100644 --- a/store/types.go +++ b/store/types.go @@ -27,13 +27,11 @@ type Deposit struct { type Output struct { Output plasma.Output Spent bool - InputTx []byte // transaction hash that created this output - SpenderTx []byte // transaction hash that spent this output + SpenderTx []byte // transaction hash that spent this output } // Transaction wraps a plasma transaction with spend information. type Transaction struct { - InputTxs [][]byte // transaction hashes the created the inputs of this transaction Transaction plasma.Transaction ConfirmationHash []byte Spent []bool @@ -42,7 +40,6 @@ type Transaction struct { } // TxOutput holds all transactional information related to an output. -// TODO: add txhash and conf hash type TxOutput struct { plasma.Output Position plasma.Position @@ -50,13 +47,11 @@ type TxOutput struct { TxHash []byte Spent bool SpenderTx []byte - InputAddresses []ethcmn.Address - InputPositions []plasma.Position } // NewTxOutput creates a TxOutput object. -func NewTxOutput(output plasma.Output, pos plasma.Position, confirmationHash, txHash []byte, spent bool, spenderTx []byte, - inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TxOutput { +func NewTxOutput(output plasma.Output, pos plasma.Position, confirmationHash, txHash []byte, + spent bool, spenderTx []byte) TxOutput { return TxOutput{ Output: output, Position: pos, @@ -64,6 +59,25 @@ func NewTxOutput(output plasma.Output, pos plasma.Position, confirmationHash, tx TxHash: txHash, Spent: spent, SpenderTx: spenderTx, + } +} + +// TxInput holds basic transactional data along with input information +type TxInput struct { + plasma.Output + Position plasma.Position + TxHash []byte + InputAddresses []ethcmn.Address + InputPositions []plasma.Position +} + +// NewTxInput creates a TxInput object. +func NewTxInput(output plasma.Output, pos plasma.Position, txHash []byte, + inputAddresses []ethcmn.Address, inputPositions []plasma.Position) TxInput { + return TxInput{ + Output: output, + Position: pos, + TxHash: txHash, InputAddresses: inputAddresses, InputPositions: inputPositions, } diff --git a/store/types_test.go b/store/types_test.go index 06b31c1..5cb8904 100644 --- a/store/types_test.go +++ b/store/types_test.go @@ -68,7 +68,6 @@ func TestTxSerialization(t *testing.T) { } tx := Transaction{ - InputTxs: [][]byte{}, Transaction: transaction, Spent: []bool{false, false}, SpenderTxs: [][]byte{}, From 527d879d49850222ab9c5effde1ea5839db1dad7 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 2 Jul 2019 06:45:27 +0000 Subject: [PATCH 43/49] Apply suggestions from code review: Doc fixes --- docs/architecure/store.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecure/store.md b/docs/architecure/store.md index 71a8797..94c9459 100644 --- a/docs/architecure/store.md +++ b/docs/architecure/store.md @@ -15,11 +15,11 @@ There exists the following mappings: - transaction hash to transaction - position to transaction hash - deposit nonce to deposit -- fee position to fee +- fee position to total fees collected in block - address to wallet ## Wallet ## -Wallets are a convience struct to maintain track of address balances, unspent outputs and spent outputs. +Wallets are a convenience struct to maintain track of address balances, unspent outputs and spent outputs. From b1eec748807e574501f79137a68110e80c7561bd Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 2 Jul 2019 16:28:18 -0700 Subject: [PATCH 44/49] address some pr comments --- app/app.go | 7 ++- cmd/plasmacli/subcmd/query/info.go | 28 +++-------- cmd/plasmacli/subcmd/query/query.go | 74 +++++++++++++++++++++++++++++ cmd/plasmacli/subcmd/query/root.go | 53 --------------------- msgs/spendMsg.go | 2 +- store/errors.go | 1 - store/keys.go | 4 +- store/outputStore.go | 5 +- store/outputStore_test.go | 2 +- store/query/errors.go | 1 - 10 files changed, 92 insertions(+), 85 deletions(-) create mode 100644 cmd/plasmacli/subcmd/query/query.go diff --git a/app/app.go b/app/app.go index be7e056..c258dd2 100644 --- a/app/app.go +++ b/app/app.go @@ -174,8 +174,11 @@ func (app *PlasmaMVPChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) copy(header[:], ctx.BlockHeader().DataHash) block := plasma.NewBlock(header, app.txIndex, app.feeAmount) app.blockStore.StoreBlock(ctx, tmBlockHeight, block) - // TODO verify - app.outputStore.StoreFee(ctx, plasma.NewPosition(app.blockStore.PlasmaBlockHeight(ctx), 1<<16-1, 0, big.NewInt(0)), plasma.NewOutput(app.operatorAddress, app.feeAmount)) + + if app.feeAmount.Sign() == 1 { + app.outputStore.StoreFee(ctx, app.blockStore.PlasmaBlockHeight(ctx), plasma.NewOutput(app.operatorAddress, app.feeAmount)) + } + app.ethConnection.CommitPlasmaHeaders(ctx, app.blockStore) app.txIndex = 0 diff --git a/cmd/plasmacli/subcmd/query/info.go b/cmd/plasmacli/subcmd/query/info.go index 02d425c..acc7967 100644 --- a/cmd/plasmacli/subcmd/query/info.go +++ b/cmd/plasmacli/subcmd/query/info.go @@ -50,12 +50,12 @@ var infoCmd = &cobra.Command{ fmt.Printf("Position: %s, Amount: %s, Spent: %t\nSpender Hash: %s\n", utxo.Position, utxo.Output.Amount.String(), utxo.Spent, utxo.SpenderTx) fmt.Printf("Transaction Hash: 0x%x\nConfirmationHash: 0x%x\n", utxo.TxHash, utxo.ConfirmationHash) /* - // TODO: Add --verbose flag that if set will query TxInput and print InputAddresses and InputPositions as well - // print inputs if applicable - positions := utxo.InputPositions - for i, p := range positions { - fmt.Printf("Input %d Position: %s\n", i, p) - } + // TODO: Add --verbose flag that if set will query TxInput and print InputAddresses and InputPositions as well + // print inputs if applicable + positions := utxo.InputPositions + for i, p := range positions { + fmt.Printf("Input %d Position: %s\n", i, p) + } */ fmt.Printf("End UTXO %d info\n\n", i) @@ -68,19 +68,3 @@ var infoCmd = &cobra.Command{ return nil }, } - -func Info(ctx context.CLIContext, addr ethcmn.Address) ([]store.TxOutput, error) { - // query for all utxos owned by this address - queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) - data, err := ctx.Query(queryRoute, nil) - if err != nil { - return nil, err - } - - var utxos []store.TxOutput - if err := json.Unmarshal(data, &utxos); err != nil { - return nil, err - } - - return utxos, nil -} diff --git a/cmd/plasmacli/subcmd/query/query.go b/cmd/plasmacli/subcmd/query/query.go new file mode 100644 index 0000000..2055889 --- /dev/null +++ b/cmd/plasmacli/subcmd/query/query.go @@ -0,0 +1,74 @@ +package query + +import ( + "encoding/json" + "fmt" + "github.com/FourthState/plasma-mvp-sidechain/plasma" + "github.com/FourthState/plasma-mvp-sidechain/store" + "github.com/cosmos/cosmos-sdk/client/context" + ethcmn "github.com/ethereum/go-ethereum/common" +) + +func TxOutput(ctx context.CLIContext, pos plasma.Position) (store.TxOutput, error) { + // query for an output for the given position + queryRoute := fmt.Sprintf("custom/utxo/output/%s", pos) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return store.TxOutput{}, err + } + + var output store.TxOutput + if err := json.Unmarshal(data, &output); err != nil { + return store.TxOutput{}, err + } + + return output, nil +} + +func TxInput(ctx context.CLIContext, pos plasma.Position) (store.TxInput, error) { + // query for input info on the given position + queryRoute := fmt.Sprintf("custom/utxo/input/%s", pos) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return store.TxInput{}, err + } + + var input store.TxInput + if err := json.Unmarshal(data, &input); err != nil { + return store.TxInput{}, err + } + + return input, nil +} + +func Tx(ctx context.CLIContext, hash []byte) (store.Transaction, error) { + // query for a transaction using the provided hash + queryRoute := fmt.Sprintf("custom/utxo/tx/%s", hash) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return store.Transaction{}, err + } + + var tx store.Transaction + if err := json.Unmarshal(data, &tx); err != nil { + return store.Transaction{}, err + } + + return tx, nil +} + +func Info(ctx context.CLIContext, addr ethcmn.Address) ([]store.TxOutput, error) { + // query for all utxos owned by this address + queryRoute := fmt.Sprintf("custom/utxo/info/%s", addr.Hex()) + data, err := ctx.Query(queryRoute, nil) + if err != nil { + return nil, err + } + + var utxos []store.TxOutput + if err := json.Unmarshal(data, &utxos); err != nil { + return nil, err + } + + return utxos, nil +} diff --git a/cmd/plasmacli/subcmd/query/root.go b/cmd/plasmacli/subcmd/query/root.go index 1566066..746f4ac 100644 --- a/cmd/plasmacli/subcmd/query/root.go +++ b/cmd/plasmacli/subcmd/query/root.go @@ -1,12 +1,7 @@ package query import ( - "encoding/json" - "fmt" "github.com/FourthState/plasma-mvp-sidechain/cmd/plasmacli/config" - "github.com/FourthState/plasma-mvp-sidechain/plasma" - "github.com/FourthState/plasma-mvp-sidechain/store" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/spf13/cobra" ) @@ -26,51 +21,3 @@ var queryCmd = &cobra.Command{ Use: "query", Short: "Query information related to the sidechain", } - -func TxOutput(ctx context.CLIContext, pos plasma.Position) (store.TxOutput, error) { - // query for an output for the given position - queryRoute := fmt.Sprintf("custom/utxo/output/%s", pos) - data, err := ctx.Query(queryRoute, nil) - if err != nil { - return store.TxOutput{}, err - } - - var output store.TxOutput - if err := json.Unmarshal(data, &output); err != nil { - return store.TxOutput{}, err - } - - return output, nil -} - -func TxInput(ctx context.CLIContext, pos plasma.Position) (store.TxInput, error) { - // query for input info on the given position - queryRoute := fmt.Sprintf("custom/utxo/input/%s", pos) - data, err := ctx.Query(queryRoute, nil) - if err != nil { - return store.TxInput{}, err - } - - var input store.TxInput - if err := json.Unmarshal(data, &input); err != nil { - return store.TxInput{}, err - } - - return input, nil -} - -func Tx(ctx context.CLIContext, hash []byte) (store.Transaction, error) { - // query for a transaction using the provided hash - queryRoute := fmt.Sprintf("custom/utxo/tx/%s", hash) - data, err := ctx.Query(queryRoute, nil) - if err != nil { - return store.Transaction{}, err - } - - var tx store.Transaction - if err := json.Unmarshal(data, &tx); err != nil { - return store.Transaction{}, err - } - - return tx, nil -} diff --git a/msgs/spendMsg.go b/msgs/spendMsg.go index 0a10a3e..84a48e3 100644 --- a/msgs/spendMsg.go +++ b/msgs/spendMsg.go @@ -48,7 +48,7 @@ func (msg SpendMsg) GetSigners() []sdk.AccAddress { return addrs } -// GetSignBytes returns the sha256 hash of the transaction. +// GetSignBytes returns the Keccak256 hash of the transaction. func (msg SpendMsg) GetSignBytes() []byte { return msg.TxHash() } diff --git a/store/errors.go b/store/errors.go index 3350448..cfe56ef 100644 --- a/store/errors.go +++ b/store/errors.go @@ -15,7 +15,6 @@ func ErrOutputSpent(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeOutputSpent, msg, args) } -// TODO: refactor DNE errors into one func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) } diff --git a/store/keys.go b/store/keys.go index 1af720b..fcc0571 100644 --- a/store/keys.go +++ b/store/keys.go @@ -38,12 +38,12 @@ func GetFeeKey(pos plasma.Position) []byte { return prefixKey(feeKey, pos.Bytes()) } -// GetOutputKey returns key to retrieve UTXO for given position. +// GetOutputKey returns key to retrieve Output for given position. func GetOutputKey(pos plasma.Position) []byte { return prefixKey(outputKey, pos.Bytes()) } -// GetTxKey returns Transaction for given hash. +// GetTxKey returns key to retrieve Transaction for given hash. func GetTxKey(hash []byte) []byte { return prefixKey(txKey, hash) } diff --git a/store/outputStore.go b/store/outputStore.go index b3ee889..96ab938 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -233,7 +233,8 @@ func (store OutputStore) StoreDeposit(ctx sdk.Context, nonce *big.Int, deposit p } // StoreFee adds an unspent fee and updates the fee owner's wallet. -func (store OutputStore) StoreFee(ctx sdk.Context, pos plasma.Position, output plasma.Output) { +func (store OutputStore) StoreFee(ctx sdk.Context, blockNum *big.Int, output plasma.Output) { + pos := plasma.NewPosition(blockNum, 1<<16-1, 0, big.NewInt(0)) store.setFee(ctx, pos, Output{output, false, make([]byte, 0)}) store.addToWallet(ctx, output.Owner, output.Amount, pos) } @@ -330,7 +331,7 @@ func (store OutputStore) GetUnspentForWallet(ctx sdk.Context, wallet Wallet) (ut if !ok { panic(fmt.Sprintf("Corrupted store: Wallet contains unspent position (%v) that doesn't have corresponding tx", p)) } - + txo := NewTxOutput(output.Output, p, tx.ConfirmationHash, tx.Transaction.TxHash(), output.Spent, output.SpenderTx) utxos = append(utxos, txo) } diff --git a/store/outputStore_test.go b/store/outputStore_test.go index 489ed59..aa393fd 100644 --- a/store/outputStore_test.go +++ b/store/outputStore_test.go @@ -78,7 +78,7 @@ func TestFees(t *testing.T) { // Create and store new fee output := plasma.NewOutput(addr, big.NewInt(int64(1000*i))) fee := Output{output, false, make([]byte, 0)} - outputStore.StoreFee(ctx, pos, output) + outputStore.StoreFee(ctx, pos.BlockNum, output) exists = outputStore.HasFee(ctx, pos) require.True(t, exists, "returned false for fee that was stored") diff --git a/store/query/errors.go b/store/query/errors.go index 5458c8e..b773f2e 100644 --- a/store/query/errors.go +++ b/store/query/errors.go @@ -22,7 +22,6 @@ func ErrInvalidPath(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeInvalidPath, msg, args) } -// TODO: refactor DNE errors into one func ErrOutputDNE(msg string, args ...interface{}) sdk.Error { return sdk.NewError(DefaultCodespace, CodeOutputDNE, msg, args) } From 214ae3502e3e325ccd4e4687bcf81453fdbf91f4 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 2 Jul 2019 17:01:15 -0700 Subject: [PATCH 45/49] build fix --- handlers/anteHandler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/anteHandler_test.go b/handlers/anteHandler_test.go index 5d70d40..3b55496 100644 --- a/handlers/anteHandler_test.go +++ b/handlers/anteHandler_test.go @@ -497,7 +497,7 @@ func TestAnteDepositDNE(t *testing.T) { func setupFees(ctx sdk.Context, outputStore store.OutputStore, inputs ...Output) { for _, output := range inputs { - outputStore.StoreFee(ctx, output.Position, output.Output) + outputStore.StoreFee(ctx, output.Position.BlockNum, output.Output) } } From 4af2bf2388a6f84918bb5212a8e464073594ed04 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 5 Jul 2019 15:51:27 -0700 Subject: [PATCH 46/49] reflect my own pr comments --- cmd/plasmacli/subcmd/tx/sign.go | 7 +++---- docs/architecure/store.md | 2 +- store/keys.go | 2 +- store/outputStore.go | 10 +++++----- store/query/utxo.go | 4 ++-- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cmd/plasmacli/subcmd/tx/sign.go b/cmd/plasmacli/subcmd/tx/sign.go index eebd770..2e4b48b 100644 --- a/cmd/plasmacli/subcmd/tx/sign.go +++ b/cmd/plasmacli/subcmd/tx/sign.go @@ -82,10 +82,9 @@ Usage: }, } -// generate confirmation signature for specified owner and position -// verify that the inputs provided are correct -// signing address should match one of the input addresses -// generate confirmation signature for given utxo +// generate confirmation signature for specified position and verify that +// the inputs provided are correct. Signing address should match one of +// the input addresses. Generate confirmation signature for given output. func signSingleConfirmSig(ctx context.CLIContext, position plasma.Position, signerAddr ethcmn.Address, name string) error { // query for output for the specified position output, err := query.TxOutput(ctx, position) diff --git a/docs/architecure/store.md b/docs/architecure/store.md index 94c9459..053b523 100644 --- a/docs/architecure/store.md +++ b/docs/architecure/store.md @@ -1,6 +1,6 @@ # Store Design # -The store package provides the backend to storage of all information necessary for the sidechain to function. +The store package provides the backend storage for all information necessary for the sidechain to function. There are 2 stores, the block store and output store. ## Block Store ## diff --git a/store/keys.go b/store/keys.go index fcc0571..ed36542 100644 --- a/store/keys.go +++ b/store/keys.go @@ -8,7 +8,7 @@ import ( const ( // QueryOutputStore is the route to the OutputStore - QueryOutputStore = "utxo" // TODO: + QueryOutputStore = "utxo" // QueryBlockStore is the route to the BlockStore QueryBlockStore = "plasma" diff --git a/store/outputStore.go b/store/outputStore.go index 96ab938..0012b52 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -172,18 +172,18 @@ func (store OutputStore) HasTx(ctx sdk.Context, hash []byte) bool { // ----------------------------------------------------------------------------- /* Set */ -// SetWallet overwrites the wallet stored at the given address. +// setWallet overwrites the wallet stored at the given address. func (store OutputStore) setWallet(ctx sdk.Context, addr common.Address, wallet Wallet) { key := GetWalletKey(addr) data, err := rlp.EncodeToBytes(&wallet) if err != nil { - panic(fmt.Sprintf("error marshaling transaction: %s", err)) + panic(fmt.Sprintf("error marshaling wallet with address %s: %s", addr, err)) } store.Set(ctx, key, data) } -// SetDeposit overwrites the deposit stored at the given nonce. +// setDeposit overwrites the deposit stored at the given nonce. func (store OutputStore) setDeposit(ctx sdk.Context, nonce *big.Int, deposit Deposit) { data, err := rlp.EncodeToBytes(&deposit) if err != nil { @@ -205,13 +205,13 @@ func (store OutputStore) setFee(ctx sdk.Context, pos plasma.Position, fee Output store.Set(ctx, key, data) } -// SetOutput adds a mapping from position to transaction hash. +// setOutput adds a mapping from position to transaction hash. func (store OutputStore) setOutput(ctx sdk.Context, pos plasma.Position, hash []byte) { key := GetOutputKey(pos) store.Set(ctx, key, hash) } -// SetTx overwrites the mapping from transaction hash to transaction. +// setTx overwrites the mapping from transaction hash to transaction. func (store OutputStore) setTx(ctx sdk.Context, tx Transaction) { data, err := rlp.EncodeToBytes(&tx) if err != nil { diff --git a/store/query/utxo.go b/store/query/utxo.go index 9137d05..178ccf4 100644 --- a/store/query/utxo.go +++ b/store/query/utxo.go @@ -42,12 +42,12 @@ func NewOutputQuerier(outputStore store.OutputStore) sdk.Querier { switch path[0] { case QueryBalance: if len(path) != 2 { - return nil, sdk.ErrUnknownRequest("exprected balance/
") + return nil, sdk.ErrUnknownRequest("expected balance/
") } addr := common.HexToAddress(path[1]) total, err := queryBalance(ctx, outputStore, addr) if err != nil { - return nil, sdk.ErrInternal("failed query balance") + return nil, sdk.ErrInternal(fmt.Sprintf("failed query balance for 0x%x", addr)) } return []byte(total.String()), nil From 41a5024699b7bed28a3e5b187c4b92cd2a6225fa Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 5 Jul 2019 15:59:53 -0700 Subject: [PATCH 47/49] error update --- store/outputStore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/outputStore.go b/store/outputStore.go index 0012b52..7c78743 100644 --- a/store/outputStore.go +++ b/store/outputStore.go @@ -373,7 +373,7 @@ func (store OutputStore) addToWallet(ctx sdk.Context, addr common.Address, amoun func (store OutputStore) subtractFromWallet(ctx sdk.Context, addr common.Address, amount *big.Int, pos plasma.Position) { wallet, ok := store.GetWallet(ctx, addr) if !ok { - panic(fmt.Sprintf("transaction store has been corrupted")) + panic(fmt.Sprintf("output store has been corrupted")) } // Update Wallet From 780f3ea568ad5b966243ef919d086f7fa48ddce5 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Sat, 6 Jul 2019 10:34:28 -0700 Subject: [PATCH 48/49] changelog and updated err in ante --- CHANGELOG.md | 1 + handlers/anteHandler.go | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5cf47..34734a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Plasma configuration file - Added IncludeDepositMsg with handling to allow explicit deposit inclusion into sidechain ### Changed +- [\#153](https://github.com/FourthState/plasma-mvp-sidechain/pull/153) Major refactor of store/, can query with position, transaction hash, or address. REST Supported. - [\#141](https://github.com/FourthState/plasma-mvp-sidechain/pull/141) Dependency management is now handled by go modules instead of Dep - [\#129](https://github.com/FourthState/plasma-mvp-sidechain/pull/129) Updated sign command to iterate over an account to finalize transactions - [\#129](https://github.com/FourthState/plasma-mvp-sidechain/pull/129) Updated spend to auto generate transaction for users based on the utxos they own diff --git a/handlers/anteHandler.go b/handlers/anteHandler.go index bf40f5d..522cc8f 100644 --- a/handlers/anteHandler.go +++ b/handlers/anteHandler.go @@ -99,8 +99,7 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o } exited, err := client.HasTxExited(blockStore.PlasmaBlockHeight(ctx), input.Position) if err != nil { - // TODO: add updated error - return nil, ErrExitedInput("failed to retrieve exit information on input, %v", input.Position).Result() + return nil, ErrInvalidInput("failed to retrieve exit information on input, %v", input.Position).Result() } else if exited { return nil, ErrExitedInput("input, %v, utxo has exitted", input.Position).Result() } @@ -121,8 +120,7 @@ func validateInput(ctx sdk.Context, input plasma.Input, signer common.Address, o for _, in := range tx.Transaction.Inputs { exited, err = client.HasTxExited(blockStore.PlasmaBlockHeight(ctx), in.Position) if err != nil { - // TODO: update err - return nil, ErrExitedInput(fmt.Sprintf("failed to retrieve exit information on input, %v", in.Position)).Result() + return nil, ErrInvalidInput(fmt.Sprintf("failed to retrieve exit information on input, %v", in.Position)).Result() } else if exited { return nil, ErrExitedInput(fmt.Sprintf("a parent of the input has exited. Position: %v", in.Position)).Result() } From b9ecdab8ea3e51d8a43cc53cfa1f7ef22e9f4e70 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 8 Jul 2019 16:43:46 -0700 Subject: [PATCH 49/49] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34734a5..0bf919e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Plasma configuration file - Added IncludeDepositMsg with handling to allow explicit deposit inclusion into sidechain ### Changed -- [\#153](https://github.com/FourthState/plasma-mvp-sidechain/pull/153) Major refactor of store/, can query with position, transaction hash, or address. REST Supported. +- [\#153](https://github.com/FourthState/plasma-mvp-sidechain/pull/153) Major refactor of store/, [Store architecture details](https://github.com/FourthState/plasma-mvp-sidechain/tree/develop/docs/architecure/store.md). REST Supported. - [\#141](https://github.com/FourthState/plasma-mvp-sidechain/pull/141) Dependency management is now handled by go modules instead of Dep - [\#129](https://github.com/FourthState/plasma-mvp-sidechain/pull/129) Updated sign command to iterate over an account to finalize transactions - [\#129](https://github.com/FourthState/plasma-mvp-sidechain/pull/129) Updated spend to auto generate transaction for users based on the utxos they own