From 623fa0cbddc89c2d637a9814bfc1849c0f271f1e Mon Sep 17 00:00:00 2001 From: colin axner Date: Wed, 19 Dec 2018 22:33:55 -0800 Subject: [PATCH] save confirmations sigs into app state (#102) * starter * testing client with ganache * Squashed 'contracts/' content from commit ab4fd0e git-subtree-dir: contracts git-subtree-split: ab4fd0ed780c5828ca815946922ba3b05c1e9de4 * contract wrappers and bindings * subscribed to deposits/exists. Some tests * added wrappers to the contracts folder * Squashed 'contracts/' changes from ab4fd0e..dc28e6a dc28e6a [Audit Milestone] No rootchain finality checks (#79) b7ddf5c Simple Merkle Tree (#77) 62a9462 position in the exit struct. removed helpers (#78) git-subtree-dir: contracts git-subtree-split: dc28e6a17dc3e4b7eb9574464dd97d5a16861095 * MOAR PROGRESS * papusa * gopkg fix * added clean to npm script. temp removed wrappers * Squashed 'contracts/' changes from dc28e6a..4fcc49e 4fcc49e doc typo (#83) 088adc7 [Audit milestone] Fee withdrawal for validators (#80) bc1b116 Finalize Revert (#82) git-subtree-dir: contracts git-subtree-split: 4fcc49e72a86674bfb7ebb731bef59834ce7bd79 * updated wrappers * updated config for block finality and eth priv key default directory * Squashed 'contracts/' changes from 4fcc49e..1b58d54 1b58d54 added eth blocknum to the deposit struct (#85) git-subtree-dir: contracts git-subtree-split: 1b58d54a6ba8d54bd3e802189e07f68b83afc1be * deposits with finality checks * merge conflicts * removed err from HasTxBeenExited * refactors and added tests. Fixed deposit encoding/decoding * typo * debugging * changed rlp to json * save progress * save confirmations sigs into app state * added check for fee amount * added confirmation signatures to prove command * ante * adjusted names, load latest version * remove prints * merge conflicts * merge conflicts for contracts * more merge conflicts * conflict * undo merges that didnt delete * last of conflicts * fixed build and test issues * refactored app tests, works around genesis deposit issue --- app/app.go | 43 +++--- app/app_test.go | 209 +++++++++++++-------------- app/genesis_test.go | 7 +- app/options.go | 19 ++- app/options_test.go | 17 +-- auth/ante.go | 94 +++++++----- auth/ante_test.go | 49 +++++-- client/context/types.go | 7 +- client/context/viper.go | 2 +- client/plasmacli/cmd/confirmSig.go | 2 +- client/plasmacli/cmd/prove.go | 27 ++++ contracts/test/rootchain/deposits.js | 191 ------------------------ eth/main.go | 2 +- eth/plasma.go | 13 -- types/utxo.go | 7 + utils/utils.go | 3 + x/kvstore/mapper.go | 30 ++++ x/metadata/mapper.go | 30 ---- 18 files changed, 314 insertions(+), 438 deletions(-) delete mode 100644 contracts/test/rootchain/deposits.js create mode 100644 x/kvstore/mapper.go delete mode 100644 x/metadata/mapper.go diff --git a/app/app.go b/app/app.go index 518ee8c..ae8e2a8 100644 --- a/app/app.go +++ b/app/app.go @@ -1,13 +1,14 @@ package app import ( - "fmt" "crypto/ecdsa" "encoding/binary" "encoding/json" auth "github.com/FourthState/plasma-mvp-sidechain/auth" + "github.com/FourthState/plasma-mvp-sidechain/eth" "github.com/FourthState/plasma-mvp-sidechain/types" - "github.com/FourthState/plasma-mvp-sidechain/x/metadata" + "github.com/FourthState/plasma-mvp-sidechain/utils" + "github.com/FourthState/plasma-mvp-sidechain/x/kvstore" "github.com/FourthState/plasma-mvp-sidechain/x/utxo" abci "github.com/tendermint/tendermint/abci/types" "io" @@ -42,12 +43,12 @@ type ChildChain struct { // keys to access the substores capKeyMainStore *sdk.KVStoreKey - capKeyMetadataStore *sdk.KVStoreKey + capKeyPlasmaStore *sdk.KVStoreKey // Manage addition and deletion of utxo's utxoMapper utxo.Mapper - metadataMapper metadata.MetadataMapper + plasmaStore kvstore.KVStore /* Validator Information */ isValidator bool @@ -65,10 +66,10 @@ type ChildChain struct { nodeURL string // Minimum Fee a validator is willing to accept - min_fees uint64 + minFees uint64 // Number of blocks required for a submitted block to be considered final - block_finality uint64 + blockFinality uint64 ethConnection *eth.Plasma } @@ -80,12 +81,12 @@ func NewChildChain(logger log.Logger, db dbm.DB, traceStore io.Writer, options . bapp.SetCommitMultiStoreTracer(traceStore) var app = &ChildChain{ - BaseApp: bapp, - cdc: cdc, - txIndex: 0, - feeAmount: 0, - capKeyMainStore: sdk.NewKVStoreKey("main"), - capKeyMetadataStore: sdk.NewKVStoreKey("metadata"), + BaseApp: bapp, + cdc: cdc, + txIndex: 0, + feeAmount: 0, + capKeyMainStore: sdk.NewKVStoreKey("main"), + capKeyPlasmaStore: sdk.NewKVStoreKey("plasma"), } for _, option := range options { @@ -98,27 +99,26 @@ func NewChildChain(logger log.Logger, db dbm.DB, traceStore io.Writer, options . cdc, ) - app.metadataMapper = metadata.NewMetadataMapper( - app.capKeyMetadataStore, + app.plasmaStore = kvstore.NewKVStore( + app.capKeyPlasmaStore, ) app.Router(). AddRoute("spend", utxo.NewSpendHandler(app.utxoMapper, app.nextPosition)) app.MountStoresIAVL(app.capKeyMainStore) - app.MountStoresIAVL(app.capKeyMetadataStore) + app.MountStoresIAVL(app.capKeyPlasmaStore) app.SetInitChainer(app.initChainer) app.SetEndBlocker(app.endBlocker) // Set Ethereum connection - fmt.Println(app.nodeURL) client, err := eth.InitEthConn(app.nodeURL, bapp.Logger) if err != nil { panic(err) } - plasmaClient, err := eth.InitPlasma(app.rootchain.Hex(), app.validatorPrivKey, client, app.BaseApp.Logger, app.block_finality, app.isValidator) + plasmaClient, err := eth.InitPlasma(app.rootchain, app.validatorPrivKey, client, app.BaseApp.Logger, app.blockFinality) if err != nil { panic(err) } @@ -126,12 +126,16 @@ func NewChildChain(logger log.Logger, db dbm.DB, traceStore io.Writer, options . app.ethConnection = plasmaClient // NOTE: type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool) - app.SetAnteHandler(auth.NewAnteHandler(app.utxoMapper, app.metadataMapper, app.feeUpdater, app.ethConnection)) + app.SetAnteHandler(auth.NewAnteHandler(app.utxoMapper, app.plasmaStore, app.feeUpdater, app.ethConnection)) err = app.LoadLatestVersion(app.capKeyMainStore) if err != nil { cmn.Exit(err.Error()) } + err = app.LoadLatestVersion(app.capKeyPlasmaStore) + if err != nil { + cmn.Exit(err.Error()) + } return app } @@ -180,9 +184,10 @@ func (app *ChildChain) endBlocker(ctx sdk.Context, req abci.RequestEndBlock) abc blknumKey := make([]byte, binary.MaxVarintLen64) binary.PutUvarint(blknumKey, uint64(ctx.BlockHeight())) + key := append(utils.RootHashPrefix, blknumKey...) if ctx.BlockHeader().DataHash != nil { - app.metadataMapper.StoreMetadata(ctx, blknumKey, ctx.BlockHeader().DataHash) + app.plasmaStore.Set(ctx, key, ctx.BlockHeader().DataHash) } return abci.ResponseEndBlock{} } diff --git a/app/app_test.go b/app/app_test.go index c1236c5..f754fdc 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -3,6 +3,7 @@ package app import ( "encoding/binary" "fmt" + "io/ioutil" "os" "reflect" "testing" @@ -24,16 +25,43 @@ import ( rlp "github.com/ethereum/go-ethereum/rlp" ) -/* - Note: Check() has been taken out from testing - at the moment because it increments txIndex - -*/ +const ( + privkey = "9cd69f009ac86203e54ec50e3686de95ff6126d3b30a19f926a0fe9323c17181" + nodeURL = "ws://127.0.0.1:8545" + plasmaContractAddr = "5cae340fb2c2bb0a2f194a95cda8a1ffdc9d2f85" +) func newChildChain() *ChildChain { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") db := dbm.NewMemDB() - return NewChildChain(logger, db, nil) + privkeyFile, _ := ioutil.TempFile("", "privateKey") + privkeyFile.Write([]byte(privkey)) + defer os.Remove(privkeyFile.Name()) + return NewChildChain(logger, db, nil, SetEthConfig(true, privkeyFile.Name(), plasmaContractAddr, nodeURL, "0", "5")) +} + +// Adds a initial utxo at the specified position +// Note: The input keys, txHash, and blockHash are not accurate +// Use only for spending +func storeInitUTXO(cc *ChildChain, position types.PlasmaPosition, addr common.Address) { + // Add blockhash to plasmaStore + blknumKey := make([]byte, binary.MaxVarintLen64) + binary.PutUvarint(blknumKey, uint64(position.Blknum)) + key := append(utils.RootHashPrefix, blknumKey...) + blockHash := []byte("merkle root") + + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: int64(position.Blknum) + 1}}) + ctx := cc.NewContext(false, abci.Header{}) + cc.plasmaStore.Set(ctx, key, blockHash) + + // Creates an input key that maps to itself + var inputKeys [][]byte + inKey := cc.utxoMapper.ConstructKey(addr.Bytes(), position) + inputKeys = append(inputKeys, inKey) + + txhash := []byte("txhash") + input := utxo.NewUTXOwithInputs(addr.Bytes(), 100, "Ether", position, txhash, inputKeys) + cc.utxoMapper.ReceiveUTXO(ctx, input) } // Creates a deposit of value 100 for each address in input @@ -171,51 +199,6 @@ func TestBadSpendMsg(t *testing.T) { } -func TestSpendDeposit(t *testing.T) { - cc := newChildChain() - - privKeyA, _ := ethcrypto.GenerateKey() - privKeyB, _ := ethcrypto.GenerateKey() - addrA := utils.PrivKeyToAddress(privKeyA) - addrB := utils.PrivKeyToAddress(privKeyB) - - InitTestChain(cc, utils.GenerateAddress(), addrA) - - msg := GenerateSimpleMsg(addrA, addrB, [4]uint64{0, 0, 0, 1}, 100, 0) - - // no confirmation signatures needed for spending a deposit - - // Signs the hash of the transaction - tx := GetTx(msg, privKeyA, nil, false) - txBytes, _ := rlp.EncodeToBytes(tx) - - // Must commit for checkState to be set correctly. Should be fixed in next version of SDK - cc.BeginBlock(abci.RequestBeginBlock{}) - cc.EndBlock(abci.RequestEndBlock{}) - cc.Commit() - - // Simulate a block - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 1}}) - - // Deliver tx, updates states - dres := cc.DeliverTx(txBytes) - - require.Equal(t, sdk.CodeType(0), sdk.CodeType(dres.Code), dres.Log) - - // Create context - ctx := cc.NewContext(false, abci.Header{}) - - // Retrieve UTXO from context - position := types.NewPlasmaPosition(1, 0, 0, 0) - res := cc.utxoMapper.GetUTXO(ctx, addrB.Bytes(), position) - - inputKey := cc.utxoMapper.ConstructKey(addrA.Bytes(), types.NewPlasmaPosition(0, 0, 0, 1)) - txHash := tmhash.Sum(txBytes) - expected := utxo.NewUTXOwithInputs(addrB.Bytes(), 100, "Ether", position, txHash, [][]byte{inputKey}) - - require.Equal(t, expected, res, "UTXO did not get added to store correctly") -} - func TestSpendTx(t *testing.T) { cc := newChildChain() @@ -227,44 +210,33 @@ func TestSpendTx(t *testing.T) { InitTestChain(cc, utils.GenerateAddress(), addrA) cc.Commit() - msg := GenerateSimpleMsg(addrA, addrB, [4]uint64{0, 0, 0, 1}, 100, 0) - - // Signs the hash of the transaction - tx := GetTx(msg, privKeyA, nil, false) - txBytes, _ := rlp.EncodeToBytes(tx) - txHash := tmhash.Sum(txBytes) - - // Simulate a block - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 1}}) - - // Deliver tx, updates states - res := cc.DeliverTx(txBytes) - - require.True(t, res.IsOK(), res.Log) - - cc.EndBlock(abci.RequestEndBlock{}) + // Add a UTXO into the utxoMapper + position := types.NewPlasmaPosition(1, 0, 0, 0) + storeInitUTXO(cc, position, addrB) cc.Commit() + txHash := []byte("txhash") cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 5}}) // Create context ctx := cc.NewContext(false, abci.Header{}) blknumKey := make([]byte, binary.MaxVarintLen64) binary.PutUvarint(blknumKey, uint64(1)) - blockhash := cc.metadataMapper.GetMetadata(ctx, blknumKey) + key := append(utils.RootHashPrefix, blknumKey...) + blockHash := cc.plasmaStore.Get(ctx, key) // Test that spending from a non-deposit/non-genesis UTXO works // generate simple msg - msg = GenerateSimpleMsg(addrB, addrA, [4]uint64{1, 0, 0, 0}, 100, 0) + msg := GenerateSimpleMsg(addrB, addrA, [4]uint64{1, 0, 0, 0}, 100, 0) - hash := tmhash.Sum(append(txHash, blockhash...)) + hash := tmhash.Sum(append(txHash, blockHash...)) // Set confirm signatures - msg.Input0ConfirmSigs = CreateConfirmSig(hash, privKeyA, &ecdsa.PrivateKey{}, false) + msg.Input0ConfirmSigs = CreateConfirmSig(hash, privKeyB, &ecdsa.PrivateKey{}, false) // Signs the hash of the transaction - tx = GetTx(msg, privKeyB, nil, false) - txBytes, _ = rlp.EncodeToBytes(tx) + tx := GetTx(msg, privKeyB, nil, false) + txBytes, _ := rlp.EncodeToBytes(tx) txHash = tmhash.Sum(txBytes) cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 5}}) @@ -274,7 +246,7 @@ func TestSpendTx(t *testing.T) { require.Equal(t, sdk.CodeType(0), sdk.CodeType(dres.Code), dres.Log) // Retrieve UTXO from context - position := types.NewPlasmaPosition(5, 0, 0, 0) + position = types.NewPlasmaPosition(5, 0, 0, 0) actual := cc.utxoMapper.GetUTXO(ctx, addrA.Bytes(), position) inputKey := cc.utxoMapper.ConstructKey(addrB.Bytes(), types.NewPlasmaPosition(1, 0, 0, 0)) @@ -307,6 +279,10 @@ func TestDifferentTxForms(t *testing.T) { } InitTestChain(cc, utils.GenerateAddress(), addrs...) + + // Add inital utxo + position := types.NewPlasmaPosition(6, 0, 0, 0) + storeInitUTXO(cc, position, addrs[0]) cc.Commit() cases := []struct { @@ -320,7 +296,7 @@ func TestDifferentTxForms(t *testing.T) { // Test Case 0: 1 input 2 output // Tx spends the genesis deposit and creates 2 new ouputs for addr[1] and addr[2] { - Input{0, addrs[0], types.NewPlasmaPosition(0, 0, 0, 1), 0, -1}, + Input{0, addrs[0], types.NewPlasmaPosition(6, 0, 0, 0), 0, -1}, Input{0, common.Address{}, types.PlasmaPosition{}, -1, -1}, addrs[1], 20, addrs[2], 80, @@ -396,8 +372,9 @@ func TestDifferentTxForms(t *testing.T) { // and therefore we only need to grab the first txhash from the inptus input_utxo := cc.utxoMapper.GetUTXO(ctx, tc.input0.addr.Bytes(), tc.input0.position) blknumKey := make([]byte, binary.MaxVarintLen64) - binary.PutUvarint(blknumKey, uint64(7+uint64(index-1))) - blockhash := cc.metadataMapper.GetMetadata(ctx, blknumKey) + binary.PutUvarint(blknumKey, uint64(7+int64(index-1))) + key := append(utils.RootHashPrefix, blknumKey...) + blockhash := cc.plasmaStore.Get(ctx, key) hash := tmhash.Sum(append(input_utxo.TxHash, blockhash...)) msg.Input0ConfirmSigs = CreateConfirmSig(hash, keys[tc.input0.input_index0], keys[input0_index1], tc.input0.input_index1 != -1) @@ -462,10 +439,18 @@ func TestMultiTxBlocks(t *testing.T) { InitTestChain(cc, utils.GenerateAddress(), addrs...) cc.Commit() - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 1}}) for i := uint64(0); i < N; i++ { - msgs[i] = GenerateSimpleMsg(addrs[i], addrs[i], [4]uint64{0, 0, 0, i + 1}, 100, 0) + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: int64(i + 1)}}) + position := types.NewPlasmaPosition(i+1, 0, 0, 0) + storeInitUTXO(cc, position, addrs[i]) + cc.Commit() + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: N + 1}}) + + msgs[i] = GenerateSimpleMsg(addrs[i], addrs[i], [4]uint64{i + 1, 0, 0, 0}, 100, 0) + hash := tmhash.Sum(append([]byte("txhash"), []byte("merkle root")...)) + msgs[i].Input0ConfirmSigs = CreateConfirmSig(hash, keys[i], &ecdsa.PrivateKey{}, false) + txs[i] = GetTx(msgs[i], keys[i], &ecdsa.PrivateKey{}, false) txBytes, _ := rlp.EncodeToBytes(txs[i]) @@ -473,38 +458,38 @@ func TestMultiTxBlocks(t *testing.T) { require.Equal(t, sdk.CodeType(0), sdk.CodeType(dres.Code), dres.Log) } - ctx := cc.NewContext(false, abci.Header{}) + cc.EndBlock(abci.RequestEndBlock{}) + cc.Commit() + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: N + 2}}) + ctx := cc.NewContext(false, abci.Header{Height: N + 1}) // Retrieve and check UTXO from context for i := uint16(0); i < N; i++ { txBytes, _ := rlp.EncodeToBytes(txs[i]) - position := types.NewPlasmaPosition(1, i, 0, 0) + position := types.NewPlasmaPosition(N+1, i, 0, 0) actual := cc.utxoMapper.GetUTXO(ctx, addrs[i].Bytes(), position) - inputKey := cc.utxoMapper.ConstructKey(addrs[i].Bytes(), types.NewPlasmaPosition(0, 0, 0, uint64(i+1))) + inputKey := cc.utxoMapper.ConstructKey(addrs[i].Bytes(), types.NewPlasmaPosition(uint64(i+1), 0, 0, 0)) expected := utxo.NewUTXOwithInputs(addrs[i].Bytes(), 100, "Ether", position, tmhash.Sum(txBytes), [][]byte{inputKey}) expected.TxHash = tmhash.Sum(txBytes) require.Equal(t, expected, actual, fmt.Sprintf("UTXO %d did not get added to store correctly", i+1)) - position = types.NewPlasmaPosition(0, 0, 0, uint64(i)+1) + position = types.NewPlasmaPosition(uint64(i)+1, 0, 0, 0) deposit := cc.utxoMapper.GetUTXO(ctx, addrs[i].Bytes(), position) - require.False(t, deposit.Valid, fmt.Sprintf("deposit %d did not get removed correctly from the utxo store", i+1)) + require.False(t, deposit.Valid, fmt.Sprintf("utxo %d did not get removed correctly from the utxo store", i+1)) } - cc.EndBlock(abci.RequestEndBlock{}) - cc.Commit() - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 2}}) - // send to different address for i := uint16(0); i < N; i++ { - msgs[i].Blknum0 = 1 + msgs[i].Blknum0 = N + 1 msgs[i].Txindex0 = i msgs[i].DepositNum0 = 0 blknumKey := make([]byte, binary.MaxVarintLen64) - binary.PutUvarint(blknumKey, uint64(1)) - blockhash := cc.metadataMapper.GetMetadata(ctx, blknumKey) + binary.PutUvarint(blknumKey, uint64(N+1)) + key := append(utils.RootHashPrefix, blknumKey...) + blockhash := cc.plasmaStore.Get(ctx, key) txBytes, _ := rlp.EncodeToBytes(txs[i]) txHash := tmhash.Sum(txBytes) @@ -525,14 +510,14 @@ func TestMultiTxBlocks(t *testing.T) { // Retrieve and check UTXO from context for i := uint16(0); i < N; i++ { txBytes, _ := rlp.EncodeToBytes(txs[i]) - actual := cc.utxoMapper.GetUTXO(ctx, addrs[(i+1)%N].Bytes(), types.NewPlasmaPosition(2, i, 0, 0)) + actual := cc.utxoMapper.GetUTXO(ctx, addrs[(i+1)%N].Bytes(), types.NewPlasmaPosition(N+2, i, 0, 0)) - inputKey := cc.utxoMapper.ConstructKey(addrs[i].Bytes(), types.NewPlasmaPosition(1, i, 0, 0)) - expected := utxo.NewUTXOwithInputs(addrs[(i+1)%N].Bytes(), 100, "Ether", types.NewPlasmaPosition(2, i, 0, 0), tmhash.Sum(txBytes), [][]byte{inputKey}) + inputKey := cc.utxoMapper.ConstructKey(addrs[i].Bytes(), types.NewPlasmaPosition(N+1, i, 0, 0)) + expected := utxo.NewUTXOwithInputs(addrs[(i+1)%N].Bytes(), 100, "Ether", types.NewPlasmaPosition(N+2, i, 0, 0), tmhash.Sum(txBytes), [][]byte{inputKey}) require.Equal(t, expected, actual, fmt.Sprintf("UTXO %d did not get added to store correctly", i+1)) - input := cc.utxoMapper.GetUTXO(ctx, addrs[i].Bytes(), types.NewPlasmaPosition(1, i, 0, 0)) + input := cc.utxoMapper.GetUTXO(ctx, addrs[i].Bytes(), types.NewPlasmaPosition(N+1, i, 0, 0)) require.False(t, input.Valid, fmt.Sprintf("UTXO %d did not get removed from the utxo store correctly", i)) } @@ -552,13 +537,23 @@ func TestFee(t *testing.T) { } InitTestChain(cc, valAddr, addrs...) + position := types.NewPlasmaPosition(1, 0, 0, 0) + storeInitUTXO(cc, position, addrs[0]) + position = types.NewPlasmaPosition(2, 0, 0, 0) + storeInitUTXO(cc, position, addrs[1]) + cc.Commit() - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 1}}) + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 3}}) + // Create tx's with fees and deliver them in block 3 + msg1 := GenerateSimpleMsg(addrs[0], addrs[1], [4]uint64{1, 0, 0, 0}, 90, 10) + msg2 := GenerateSimpleMsg(addrs[1], addrs[0], [4]uint64{2, 0, 0, 0}, 90, 10) - // Create tx's with fees and deliver them in block 1 - msg1 := GenerateSimpleMsg(addrs[0], addrs[1], [4]uint64{0, 0, 0, 1}, 90, 10) - msg2 := GenerateSimpleMsg(addrs[1], addrs[0], [4]uint64{0, 0, 0, 2}, 90, 10) + blockHash := []byte("merkle root") + txHash := []byte("txhash") + hash := tmhash.Sum(append(txHash, blockHash...)) + msg1.Input0ConfirmSigs = CreateConfirmSig(hash, privKeys[0], &ecdsa.PrivateKey{}, false) + msg2.Input0ConfirmSigs = CreateConfirmSig(hash, privKeys[1], &ecdsa.PrivateKey{}, false) tx1 := GetTx(msg1, privKeys[0], nil, false) tx2 := GetTx(msg2, privKeys[1], nil, false) @@ -580,14 +575,14 @@ func TestFee(t *testing.T) { cc.EndBlock(abci.RequestEndBlock{}) cc.Commit() - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 2}}) + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 4}}) - expectedPosition1 := types.NewPlasmaPosition(1, uint16(0), uint8(0), 0) - expectedPosition2 := types.NewPlasmaPosition(1, uint16(1), uint8(0), 0) + expectedPosition1 := types.NewPlasmaPosition(3, uint16(0), uint8(0), 0) + expectedPosition2 := types.NewPlasmaPosition(3, uint16(1), uint8(0), 0) - expectedValPosition := types.NewPlasmaPosition(1, uint16(1<<16-1), uint8(0), 0) + expectedValPosition := types.NewPlasmaPosition(3, uint16(1<<16-1), uint8(0), 0) - ctx := cc.NewContext(false, abci.Header{Height: 2}) + ctx := cc.NewContext(false, abci.Header{Height: 4}) utxo1 := cc.utxoMapper.GetUTXO(ctx, addrs[1].Bytes(), expectedPosition1) utxo2 := cc.utxoMapper.GetUTXO(ctx, addrs[0].Bytes(), expectedPosition2) @@ -600,7 +595,7 @@ func TestFee(t *testing.T) { require.Equal(t, uint64(20), valUTXO.Amount, "Validator fees did not get collected into UTXO correctly") // Check that validator can spend his fees as if they were a regular UTXO on sidechain - valMsg := GenerateSimpleMsg(valAddr, addrs[0], [4]uint64{1, 1<<16 - 1, 0, 0}, 10, 10) + valMsg := GenerateSimpleMsg(valAddr, addrs[0], [4]uint64{3, 1<<16 - 1, 0, 0}, 10, 10) valTx := GetTx(valMsg, valPrivKey, nil, false) @@ -611,11 +606,11 @@ func TestFee(t *testing.T) { cc.EndBlock(abci.RequestEndBlock{}) cc.Commit() - cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 3}}) + cc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: 5}}) ctx = cc.NewContext(false, abci.Header{Height: 3}) // Check that fee Amount gets reset between blocks. feeAmount for block 2 is 10 not 30. - feeUTXO2 := cc.utxoMapper.GetUTXO(ctx, valAddr.Bytes(), types.NewPlasmaPosition(2, 1<<16-1, 0, 0)) + feeUTXO2 := cc.utxoMapper.GetUTXO(ctx, valAddr.Bytes(), types.NewPlasmaPosition(4, 1<<16-1, 0, 0)) require.Equal(t, uint64(10), feeUTXO2.Amount, "Fee Amount on second block is incorrect") } diff --git a/app/genesis_test.go b/app/genesis_test.go index 4509813..d612dc3 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -9,6 +9,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" + "io/ioutil" "os" "testing" @@ -18,7 +19,11 @@ import ( func TestGenesisState(t *testing.T) { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") db := dbm.NewMemDB() - app := NewChildChain(logger, db, nil) + privkeyFile, _ := ioutil.TempFile("", "privateKey") + privkeyFile.Write([]byte(privkey)) + defer os.Remove(privkeyFile.Name()) + + app := NewChildChain(logger, db, nil, SetEthConfig(true, privkeyFile.Name(), plasmaContractAddr, nodeURL, "0", "5")) addrs := []common.Address{utils.GenerateAddress(), utils.GenerateAddress()} diff --git a/app/options.go b/app/options.go index 2fa3eba..5473a67 100644 --- a/app/options.go +++ b/app/options.go @@ -4,19 +4,20 @@ import ( "crypto/ecdsa" "fmt" "path/filepath" + "strconv" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) -func SetEthConfig(isValidator bool, privkey_file, rootchain_addr, nodeURL, minFees, finality string) func(*ChildChain) { +func SetEthConfig(isValidator bool, privkeyFile, rootchainAddr, nodeURL, minFeesStr, finality string) func(*ChildChain) { var privkey *ecdsa.PrivateKey var rootchain ethcmn.Address - var min_fees uint64 - var block_finality uint64 + var minFees uint64 + var blockFinality uint64 if isValidator { - path, err := filepath.Abs(privkey_file) + path, err := filepath.Abs(privkeyFile) if err != nil { errMsg := fmt.Sprintf("Could not resolve provided private key file path: %v", err) panic(errMsg) @@ -28,21 +29,23 @@ func SetEthConfig(isValidator bool, privkey_file, rootchain_addr, nodeURL, minFe panic(errMsg) } - min_fees, err = strconv.ParseUint(minFees, 10, 64) + minFees, err = strconv.ParseUint(minFeesStr, 10, 64) if err != nil { panic(err) } } - block_finality, err := strconv.ParseUint(finality, 10, 64) + blockFinality, err := strconv.ParseUint(finality, 10, 64) if err != nil { panic(err) } + rootchain = ethcmn.HexToAddress(rootchainAddr) + return func(cc *ChildChain) { cc.validatorPrivKey = privkey cc.isValidator = isValidator cc.rootchain = rootchain cc.nodeURL = nodeURL - cc.min_fees = min_fees - cc.block_finality = block_finality + cc.minFees = minFees + cc.blockFinality = blockFinality } } diff --git a/app/options_test.go b/app/options_test.go index 9675e90..7679618 100644 --- a/app/options_test.go +++ b/app/options_test.go @@ -6,20 +6,13 @@ import ( "os" "testing" - "github.com/FourthState/plasma-mvp-sidechain/utils" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" ) -const ( - privkey = "713bd18559e878e0fa3ee32c8ff3ef4393b82ff9f272a3d7de707882f9a3f7d7" -) - -func TestSetPrivKey(t *testing.T) { - rootchain := utils.GenerateAddress() - +func TestSetEthConfig(t *testing.T) { // create a private key file privkey_file, err := ioutil.TempFile("", "private_key") require.NoError(t, err) @@ -33,7 +26,7 @@ func TestSetPrivKey(t *testing.T) { db := dbm.NewMemDB() logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main") cc := NewChildChain(logger, db, nil, - SetEthConfig(true, privkey_file.Name(), rootchain.String(), nodeURL, "200", "16"), + SetEthConfig(true, privkey_file.Name(), plasmaContractAddr, nodeURL, "200", "16"), ) private_key, _ := crypto.LoadECDSA(privkey_file.Name()) @@ -43,9 +36,9 @@ func TestSetPrivKey(t *testing.T) { var empty ethcmn.Address require.NotEqual(t, empty, cc.rootchain) - require.Equal(t, rootchain, cc.rootchain) + require.Equal(t, ethcmn.HexToAddress(plasmaContractAddr), cc.rootchain) - require.Equal(t, uint64(200), cc.min_fees) + require.Equal(t, uint64(200), cc.minFees) - require.Equal(t, uint64(16), cc.block_finality) + require.Equal(t, uint64(16), cc.blockFinality) } diff --git a/auth/ante.go b/auth/ante.go index d6fa92a..9f4a588 100644 --- a/auth/ante.go +++ b/auth/ante.go @@ -6,12 +6,13 @@ import ( "github.com/FourthState/plasma-mvp-sidechain/eth" types "github.com/FourthState/plasma-mvp-sidechain/types" utils "github.com/FourthState/plasma-mvp-sidechain/utils" - "github.com/FourthState/plasma-mvp-sidechain/x/metadata" + "github.com/FourthState/plasma-mvp-sidechain/x/kvstore" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ethcrypto "github.com/ethereum/go-ethereum/crypto" amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/crypto/tmhash" + "math/big" "reflect" "github.com/FourthState/plasma-mvp-sidechain/x/utxo" @@ -19,7 +20,7 @@ import ( // NewAnteHandler returns an AnteHandler that checks signatures, // confirm signatures, and increments the feeAmount -func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapper, feeUpdater utxo.FeeUpdater, plasmaClient *eth.Plasma) sdk.AnteHandler { +func NewAnteHandler(utxoMapper utxo.Mapper, plasmaStore kvstore.KVStore, feeUpdater utxo.FeeUpdater, plasmaClient *eth.Plasma) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, simulate bool, ) (_ sdk.Context, _ sdk.Result, abort bool) { @@ -29,13 +30,7 @@ func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapp return ctx, sdk.ErrInternal("tx must be in form of BaseTx").Result(), true } - // Assert that there are signatures sigs := baseTx.GetSignatures() - if len(sigs) == 0 { - return ctx, - sdk.ErrUnauthorized("no signers").Result(), - true - } // Base Tx must have only one msg msg := baseTx.GetMsgs()[0] @@ -53,7 +48,7 @@ func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapp addr0 := common.BytesToAddress(signerAddrs[0].Bytes()) position0 := types.PlasmaPosition{spendMsg.Blknum0, spendMsg.Txindex0, spendMsg.Oindex0, spendMsg.DepositNum0} - res := checkUTXO(ctx, plasmaClient, utxoMapper, position0, addr0) + res := checkUTXO(ctx, plasmaClient, utxoMapper, position0, addr0, spendMsg.FeeAmount) if !res.IsOK() { return ctx, res, true } @@ -61,26 +56,25 @@ func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapp if exitErr != nil { return ctx, exitErr.Result(), true } - fmt.Println("I got past Tx Exited") if position0.IsDeposit() { deposit, _ := DepositExists(position0.DepositNum, plasmaClient) inputUTXO := utxo.NewUTXO(deposit.Owner.Bytes(), deposit.Amount.Uint64(), types.Denom, position0) utxoMapper.ReceiveUTXO(ctx, inputUTXO) } - fmt.Println("I got past second Deposit check") res = processSig(addr0, sigs[0], signBytes) - if !res.IsOK() { return ctx, res, true } // verify the confirmation signature if the input is not a deposit if position0.DepositNum == 0 && position0.TxIndex != 1<<16-1 { - res = processConfirmSig(ctx, utxoMapper, metadataMapper, position0, addr0, spendMsg.Input0ConfirmSigs) + res = processConfirmSig(ctx, utxoMapper, plasmaStore, position0, addr0, spendMsg.Input0ConfirmSigs) if !res.IsOK() { return ctx, res, true } + + setConfirmSigs(ctx, plasmaStore, spendMsg.Blknum0, uint64(spendMsg.Txindex0), spendMsg.Input0ConfirmSigs) } // Verify the second input @@ -92,7 +86,9 @@ func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapp if exitErr != nil { return ctx, exitErr.Result(), true } - res := checkUTXO(ctx, plasmaClient, utxoMapper, position1, addr1) + + // second input can be less than fee amount + res := checkUTXO(ctx, plasmaClient, utxoMapper, position1, addr1, 0) if !res.IsOK() { return ctx, res, true } @@ -109,10 +105,14 @@ func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapp } if position1.DepositNum == 0 && position1.TxIndex != 1<<16-1 { - res = processConfirmSig(ctx, utxoMapper, metadataMapper, position1, addr1, spendMsg.Input1ConfirmSigs) + res = processConfirmSig(ctx, utxoMapper, plasmaStore, position1, addr1, spendMsg.Input1ConfirmSigs) if !res.IsOK() { return ctx, res, true } + + if spendMsg.Blknum0 != spendMsg.Blknum1 && spendMsg.Txindex0 != spendMsg.Txindex1 { + setConfirmSigs(ctx, plasmaStore, spendMsg.Blknum1, uint64(spendMsg.Txindex1), spendMsg.Input1ConfirmSigs) + } } } @@ -120,7 +120,6 @@ func NewAnteHandler(utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapp if balanceErr != nil { return ctx, balanceErr.Result(), true } - // TODO: tx tags (?) return ctx, sdk.Result{}, false // continue... } @@ -142,7 +141,7 @@ func processSig( } func processConfirmSig( - ctx sdk.Context, utxoMapper utxo.Mapper, metadataMapper metadata.MetadataMapper, + ctx sdk.Context, utxoMapper utxo.Mapper, plasmaStore kvstore.KVStore, position types.PlasmaPosition, addr common.Address, sigs [][65]byte) ( res sdk.Result) { @@ -160,7 +159,8 @@ func processConfirmSig( // Get the block hash that input was created in blknumKey := make([]byte, binary.MaxVarintLen64) binary.PutUvarint(blknumKey, input.Position.Get()[0].Uint64()) - blockHash := metadataMapper.GetMetadata(ctx, blknumKey) + key := append(utils.RootHashPrefix, blknumKey...) + blockHash := plasmaStore.Get(ctx, key) // Create confirm signature hash hash := append(input.TxHash, blockHash...) @@ -177,30 +177,50 @@ func processConfirmSig( return sdk.Result{} } +// Helper function for setting confirmation signatures into app state +// Key is formed as : prefix + {Blknum, TxIndex} +// This prevents double storage of confirmation signatures +// Returns true if successful +func setConfirmSigs(ctx sdk.Context, plasmaStore kvstore.KVStore, blknum, txindex uint64, sigs [][65]byte) bool { + cdc := amino.NewCodec() + pos := [2]uint64{blknum, txindex} + + bz, err := cdc.MarshalBinaryBare(pos) + if err != nil { + return false + } + + plasmaKey := append(utils.ConfirmSigPrefix, bz...) + + var confirmSigs []byte + confirmSigs = append(confirmSigs, sigs[0][:]...) + if len(sigs) == 2 { + confirmSigs = append(confirmSigs, sigs[1][:]...) + } + plasmaStore.Set(ctx, plasmaKey, confirmSigs) + return true +} + // Checks that utxo at the position specified exists, matches the address in the SpendMsg // and returns the denomination associated with the utxo -func checkUTXO(ctx sdk.Context, plasmaClient *eth.Plasma, mapper utxo.Mapper, position types.PlasmaPosition, addr common.Address) sdk.Result { +func checkUTXO(ctx sdk.Context, plasmaClient *eth.Plasma, mapper utxo.Mapper, position types.PlasmaPosition, addr common.Address, feeAmount uint64) sdk.Result { var inputAddress []byte - if position.IsDeposit() { + input := mapper.GetUTXO(ctx, addr.Bytes(), &position) + if position.IsDeposit() && reflect.DeepEqual(input, utxo.UTXO{}) { deposit, ok := DepositExists(position.DepositNum, plasmaClient) if !ok { return utxo.ErrInvalidUTXO(2, "Deposit UTXO does not exist yet").Result() } inputAddress = deposit.Owner.Bytes() } else { - input := mapper.GetUTXO(ctx, addr.Bytes(), &position) if !input.Valid { return sdk.ErrUnknownRequest(fmt.Sprintf("UTXO trying to be spent, is not valid: %v.", position)).Result() } - cdc := amino.NewCodec() - types.RegisterAmino(cdc) - parentPositions := input.InputPositions(cdc, types.ProtoPosition) - for _, pos := range parentPositions { - exited := hasTXExited(plasmaClient, pos) - if exited != nil { - return types.ErrInvalidTransaction(types.DefaultCodespace, "Parent UTXO has already exited").Result() - } - } + inputAddress = input.Address + } + + if input.Amount < feeAmount { + return types.ErrInvalidTransaction(types.DefaultCodespace, "Fee cannot be worth more than first input amount").Result() } // Verify that utxo owner equals input address in the transaction @@ -211,8 +231,7 @@ func checkUTXO(ctx sdk.Context, plasmaClient *eth.Plasma, mapper utxo.Mapper, po } func DepositExists(nonce uint64, plasmaClient *eth.Plasma) (types.Deposit, bool) { - fmt.Println("Called Deposit Exists") - deposit, err := plasmaClient.GetDeposit(sdk.NewUint(nonce)) + deposit, err := plasmaClient.GetDeposit(big.NewInt(int64(nonce))) if err != nil { return types.Deposit{}, false @@ -220,11 +239,14 @@ func DepositExists(nonce uint64, plasmaClient *eth.Plasma) (types.Deposit, bool) return *deposit, true } -func hasTXExited(plasmaClient *eth.Plasma, pos utxo.Position) sdk.Error { - // has parents tx been exited? - var positions [4]sdk.Uint +func hasTXExited(plasmaClient *eth.Plasma, pos types.PlasmaPosition) sdk.Error { + if plasmaClient == nil { + return nil + } + + var positions [4]*big.Int for i, num := range pos.Get() { - positions[i] = num + positions[i] = big.NewInt(int64(num.Uint64())) } exited := plasmaClient.HasTXBeenExited(positions) if exited { diff --git a/auth/ante_test.go b/auth/ante_test.go index 6ab165c..9f5b7df 100644 --- a/auth/ante_test.go +++ b/auth/ante_test.go @@ -6,7 +6,7 @@ import ( "fmt" types "github.com/FourthState/plasma-mvp-sidechain/types" utils "github.com/FourthState/plasma-mvp-sidechain/utils" - "github.com/FourthState/plasma-mvp-sidechain/x/metadata" + "github.com/FourthState/plasma-mvp-sidechain/x/kvstore" "github.com/FourthState/plasma-mvp-sidechain/x/utxo" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -19,17 +19,17 @@ import ( "testing" ) -func setup() (sdk.Context, utxo.Mapper, metadata.MetadataMapper, utxo.FeeUpdater) { - ms, capKey, metadataCapKey := utxo.SetupMultiStore() +func setup() (sdk.Context, utxo.Mapper, kvstore.KVStore, utxo.FeeUpdater) { + ms, capKey, plasmaCapKey := utxo.SetupMultiStore() ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) cdc := utxo.MakeCodec() types.RegisterAmino(cdc) mapper := utxo.NewBaseMapper(capKey, cdc) - metadataMapper := metadata.NewMetadataMapper(metadataCapKey) + plasmaStore := kvstore.NewKVStore(plasmaCapKey) - return ctx, mapper, metadataMapper, feeUpdater + return ctx, mapper, plasmaStore, feeUpdater } // should be modified when fees are implemented @@ -111,7 +111,7 @@ func getInputAddr(addr0, addr1 common.Address, two bool) [][]byte { // No signatures are provided func TestNoSigs(t *testing.T) { - ctx, mapper, metadataMapper, feeUpdater := setup() + ctx, mapper, plasmaStore, feeUpdater := setup() var msg = GenSpendMsg() var emptysigs [2][65]byte @@ -123,7 +123,7 @@ func TestNoSigs(t *testing.T) { mapper.ReceiveUTXO(ctx, utxo1) mapper.ReceiveUTXO(ctx, utxo2) - handler := NewAnteHandler(mapper, metadataMapper, feeUpdater) + handler := NewAnteHandler(mapper, plasmaStore, feeUpdater, nil) _, res, abort := handler(ctx, tx, false) assert.Equal(t, true, abort, "did not abort with no signatures") @@ -132,7 +132,7 @@ func TestNoSigs(t *testing.T) { // The wrong amount of signatures are provided func TestNotEnoughSigs(t *testing.T) { - ctx, mapper, metadataMapper, feeUpdater := setup() + ctx, mapper, plasmaStore, feeUpdater := setup() var msg = GenSpendMsg() priv, _ := ethcrypto.GenerateKey() @@ -148,13 +148,37 @@ func TestNotEnoughSigs(t *testing.T) { mapper.ReceiveUTXO(ctx, utxo1) mapper.ReceiveUTXO(ctx, utxo2) - handler := NewAnteHandler(mapper, metadataMapper, feeUpdater) + handler := NewAnteHandler(mapper, plasmaStore, feeUpdater, nil) _, res, abort := handler(ctx, tx, false) assert.Equal(t, true, abort, "did not abort with incorrect number of signatures") require.Equal(t, sdk.ToABCICode(sdk.CodespaceType(1), sdk.CodeType(4)), res.Code, fmt.Sprintf("tx had processed with incorrect number of signatures: %s", res.Log)) } +func TestInvalidFee(t *testing.T) { + ctx, mapper, plasmaStore, feeUpdater := setup() + + var inputAmount uint64 = 1001 + + // sigs not needed since fee amount should be checked with existence check + var msg = GenSpendMsg() + var sigs [2][65]byte + msg.FeeAmount = inputAmount + 1 + tx := types.NewBaseTx(msg, sigs) + + // Add input UTXOs to mapper + utxo1 := utxo.NewUTXO(msg.Owner0.Bytes(), inputAmount, types.Denom, types.NewPlasmaPosition(1, 0, 0, 0)) + utxo2 := utxo.NewUTXO(msg.Owner0.Bytes(), inputAmount, types.Denom, types.NewPlasmaPosition(1, 1, 0, 0)) + mapper.ReceiveUTXO(ctx, utxo1) + mapper.ReceiveUTXO(ctx, utxo2) + + handler := NewAnteHandler(mapper, plasmaStore, feeUpdater, nil) + _, res, abort := handler(ctx, tx, false) + + assert.Equal(t, true, abort, "did not abort with incorrect fee amount") + require.Equal(t, sdk.ToABCICode(sdk.CodespaceType(3), sdk.CodeType(204)), res.Code, "tx had processed with a fee amount greater than first input amount") +} + // helper struct for readability type input struct { owner_index int64 @@ -166,7 +190,7 @@ type input struct { // Tests a different cases. func TestDifferentCases(t *testing.T) { - ctx, mapper, metadataMapper, feeUpdater := setup() + ctx, mapper, plasmaStore, feeUpdater := setup() var keys [6]*ecdsa.PrivateKey var addrs []common.Address @@ -250,7 +274,7 @@ func TestDifferentCases(t *testing.T) { owner_index1 := utils.GetIndex(tc.input1.owner_index) tx := GetTx(msg, keys[tc.input0.owner_index], keys[owner_index1], tc.input1.owner_index != -1) - handler := NewAnteHandler(mapper, metadataMapper, feeUpdater) + handler := NewAnteHandler(mapper, plasmaStore, feeUpdater, nil) _, res, abort := handler(ctx, tx, false) assert.Equal(t, true, abort, fmt.Sprintf("did not abort on utxo that does not exist. Case: %d", index)) @@ -270,7 +294,8 @@ func TestDifferentCases(t *testing.T) { blknumKey := make([]byte, binary.MaxVarintLen64) binary.PutUvarint(blknumKey, tc.input0.position.Get()[0].Uint64()) - metadataMapper.StoreMetadata(ctx, blknumKey, blockHash) + key := append(utils.RootHashPrefix, blknumKey...) + plasmaStore.Set(ctx, key, blockHash) // for ease of testing, txhash is simplified // app_test tests for correct functionality when setting tx_hash diff --git a/client/context/types.go b/client/context/types.go index 31a2bc7..3e6a5ad 100644 --- a/client/context/types.go +++ b/client/context/types.go @@ -24,7 +24,7 @@ type ClientContext struct { Decoder UTXODecoder Verifier tmlite.Verifier UTXOStore string - MetadataStore string + PlasmaStore string } // Returns a copy of the context with an updated height @@ -70,11 +70,6 @@ func (c ClientContext) WithUTXOStore(utxoStore string) ClientContext { return c } -func (c ClientContext) WithMetadataStore(metadataStore string) ClientContext { - c.MetadataStore = metadataStore - return c -} - func (c ClientContext) WithCodec(cdc *codec.Codec) ClientContext { c.Codec = cdc return c diff --git a/client/context/viper.go b/client/context/viper.go index 5cb4899..53d8d21 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -25,6 +25,6 @@ func NewClientContextFromViper() ClientContext { Client: rpc, Decoder: nil, UTXOStore: "main", - MetadataStore: "metadata", + PlasmaStore: "plasma", } } diff --git a/client/plasmacli/cmd/confirmSig.go b/client/plasmacli/cmd/confirmSig.go index 2bcf5a4..1a4c2b9 100644 --- a/client/plasmacli/cmd/confirmSig.go +++ b/client/plasmacli/cmd/confirmSig.go @@ -72,7 +72,7 @@ var signCmd = &cobra.Command{ blknumKey := make([]byte, binary.MaxVarintLen64) binary.PutUvarint(blknumKey, input.Position.Get()[0].Uint64()) - blockhash, err := ctx.QueryStore(blknumKey, ctx.MetadataStore) + blockhash, err := ctx.QueryStore(blknumKey, ctx.PlasmaStore) if err != nil { return err } diff --git a/client/plasmacli/cmd/prove.go b/client/plasmacli/cmd/prove.go index 9296b08..ff150a0 100644 --- a/client/plasmacli/cmd/prove.go +++ b/client/plasmacli/cmd/prove.go @@ -5,9 +5,11 @@ import ( "fmt" "github.com/FourthState/plasma-mvp-sidechain/client" "github.com/FourthState/plasma-mvp-sidechain/client/context" + "github.com/FourthState/plasma-mvp-sidechain/utils" "github.com/FourthState/plasma-mvp-sidechain/x/utxo" "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" + amino "github.com/tendermint/go-amino" ) func init() { @@ -52,11 +54,36 @@ var proveCmd = &cobra.Command{ return err } + // Look for confirmation signatures + cdc := amino.NewCodec() + pos := [2]uint64{position[0].Blknum, uint64(position[0].TxIndex)} + bz, err := cdc.MarshalBinaryBare(pos) + if err != nil { + return err + } + + key = append(utils.ConfirmSigPrefix, bz...) + res, err = ctx.QueryStore(key, ctx.PlasmaStore) + + var sigs [][65]byte + if err == nil { + err = ctx.Codec.UnmarshalBinaryBare(res, &sigs) + if err != nil { + return err + } + } fmt.Printf("Roothash: 0x%s\n", hex.EncodeToString(result.Proof.RootHash)) fmt.Printf("Total: %d\n", result.Proof.Proof.Total) fmt.Printf("LeafHash: 0x%s\n", hex.EncodeToString(result.Proof.Proof.LeafHash)) fmt.Printf("TxBytes: 0x%s\n", hex.EncodeToString(result.Tx)) + switch len(sigs) { + case 1: + fmt.Printf("Confirmation Signatures: %v\n", sigs[0]) + case 2: + fmt.Printf("Confirmation Signatures: %v,%v\n", sigs[0], sigs[1]) + } + // flatten aunts var proof []byte for _, aunt := range result.Proof.Proof.Aunts { diff --git a/contracts/test/rootchain/deposits.js b/contracts/test/rootchain/deposits.js deleted file mode 100644 index abf1230..0000000 --- a/contracts/test/rootchain/deposits.js +++ /dev/null @@ -1,191 +0,0 @@ -let RLP = require('rlp'); -let assert = require('chai').assert; - -let RootChain = artifacts.require("RootChain"); - -let { fastForward, mineNBlocks, proof, zeroHashes } = require('./rootchain_helpers.js'); -let { catchError, toHex } = require('../utilities.js'); - -contract('[RootChain] Deposits', async (accounts) => { - let rootchain; - let one_week = 604800; // in seconds - let minExitBond = 10000; - - let authority = accounts[0]; - beforeEach(async () => { - rootchain = await RootChain.new({from: authority}); - }); - - it("Catches Deposit event", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - let tx = await rootchain.deposit(accounts[1], {from: accounts[1], value: 100}); - // check Deposit event - assert.equal(tx.logs[0].args.depositor, accounts[1], "incorrect deposit owner"); - assert.equal(tx.logs[0].args.amount.toNumber(), 100, "incorrect deposit amount"); - assert.equal(tx.logs[0].args.depositNonce, nonce, "incorrect deposit nonce"); - }); - - /* - it("Allows deposits of funds into a different address", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - let tx = await rootchain.deposit(accounts[2], {from: accounts[1], value: 100}); - - // check Deposit event - assert.equal(tx.logs[0].args.depositor, accounts[2], "incorrect deposit owner"); - assert.equal(tx.logs[0].args.amount.toNumber(), 100, "incorrect deposit amount"); - assert.equal(tx.logs[0].args.depositNonce, nonce, "incorrect deposit nonce"); - - // check rootchain deposit mapping - let deposit = await rootchain.getDeposit.call(nonce); - assert.equal(deposit[0], accounts[2], "incorrect deposit owner"); - assert.equal(deposit[1], 100, "incorrect deposit amount"); - }); - - it("Only allows deposit owner to start a deposit exit", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[1], value: 100}); - let err; - - // accounts[1] cannot start exit because it's not the owner - [err] = await catchError(rootchain.startDepositExit(nonce, {from: accounts[1], value: minExitBond})); - if (!err) - assert.fail("Non deposit owner allowed to start an exit"); - - //accounts[2] should be able to start exit - await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond}); - }); - - it("Rejects exiting a deposit twice", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[2], value: 100}); - await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond}); - - let err; - [err] = await catchError(rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond})); - if (!err) - assert.fail("Started an exit for the same deposit twice."); - }); - - it("Catches StartedDepositExit event", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[2], value: 100}); - let tx = await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond}); - - assert.equal(tx.logs[0].args.nonce.toNumber(), nonce, "StartedDepositExit event emits incorrect nonce"); - assert.equal(tx.logs[0].args.owner, accounts[2], "StartedDepositExit event emits incorrect owner"); - assert.equal(tx.logs[0].args.amount.toNumber(), 100, "StartedDepositExit event emits incorrect amount"); - }); - - it("Requires sufficient bond and refunds excess if overpayed", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[2], value: 100}); - - let err; - [err] = await catchError(rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond-10})); - if (!err) - assert.fail("started exit with insufficient bond"); - - await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond+10}); - - let balance = (await rootchain.balanceOf.call(accounts[2])).toNumber(); - assert.equal(balance, 10, "excess for overpayed bond not refunded to sender"); - }); - - it("Can start and finalize a deposit exit. Child chain balance should reflect accordingly", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[2], value: 100}); - - let childChainBalance = (await rootchain.childChainBalance.call()).toNumber(); - assert.equal(childChainBalance, 100); - - await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond}); - await fastForward(one_week + 100); - await rootchain.finalizeDepositExits(); - - childChainBalance = (await rootchain.childChainBalance.call()).toNumber(); - assert.equal(childChainBalance, 0); - - let balance = (await rootchain.balanceOf.call(accounts[2])).toNumber(); - assert.equal(balance, 100 + minExitBond, "deposit exit not finalized after a week"); - - let exit = await rootchain.getDepositExit.call(nonce); - assert.equal(exit[3], 3, "exit's state not set to finalized"); - }); - - it("Cannot reopen a finalized deposit exit", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[2], value: 100}); - await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond}); - - await fastForward(one_week + 100); - - await rootchain.finalizeDepositExits(); - let err; - [err] = await catchError(rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond})); - if (!err) - assert.fail("reopened a finalized deposit exit"); - }); - - it("Correctly challenge a spent deposit", async () => { - let nonce = (await rootchain.depositNonce.call()).toNumber(); - await rootchain.deposit(accounts[2], {from: accounts[2], value: 100}); - - // construct transcation with first input as the deposit - let msg = Array(17).fill(0); - msg[3] = nonce; msg[12] = accounts[1]; msg[13] = 100; - let encodedMsg = RLP.encode(msg); - let hashedEncodedMsg = web3.sha3(encodedMsg.toString('hex'), {encoding: 'hex'}); - - // create signature by deposit owner. Second signature should be zero - let sigList = Array(2).fill(0); - sigList[0] = (await web3.eth.sign(accounts[2], hashedEncodedMsg)); - - let txBytes = Array(2).fill(0); - txBytes[0] = msg; txBytes[1] = sigList; - txBytes = RLP.encode(txBytes); - - // create signature by deposit owner. Second signature should be zero - let sigs = (await web3.eth.sign(accounts[2], hashedEncodedMsg)); - sigs = sigs + Buffer.alloc(65).toString('hex'); - - let merkleHash = web3.sha3(txBytes.toString('hex'), {encoding: 'hex'}); - - // include this transaction in the next block - let root = merkleHash; - for (let i = 0; i < 16; i++) - root = web3.sha3(root + zeroHashes[i], {encoding: 'hex'}).slice(2) - let blockNum = (await rootchain.currentChildBlock.call()).toNumber(); - mineNBlocks(5); // presumed finality before submitting the block - await rootchain.submitBlock(toHex(root), {from: authority}); - - // create the confirm sig - let confirmHash = web3.sha3(merkleHash.slice(2) + root, {encoding: 'hex'}); - let confirmSig = await web3.eth.sign(accounts[2], confirmHash); - - // start the malicious exit - await rootchain.startDepositExit(nonce, {from: accounts[2], value: minExitBond}); - - // checks matching inputs - let err; - [err] = await catchError(rootchain.challengeDepositExit(nonce-1, [blockNum, 0, 0], - toHex(txBytes), toHex(sigs), toHex(proof), toHex(confirmSig), {from: accounts[3]})); - if (!err) - assert.fail("did not check against matching inputs"); - - // correctly challenge - await rootchain.challengeDepositExit(nonce, [blockNum, 0, 0], - toHex(txBytes), toHex(proof), toHex(confirmSig), {from: accounts[3]}); - - let balance = (await rootchain.balanceOf.call(accounts[3])).toNumber(); - assert.equal(balance, minExitBond, "challenger not awarded exit bond"); - - let exit = await rootchain.getDepositExit.call(nonce); - assert.equal(exit[3], 2, "exit state not changed to challenged"); - - // Cannot challenge twice - [err] = await catchError(rootchain.challengeDepositExit(nonce, [blockNum, 0, 0], - toHex(txBytes), toHex(sigs), toHex(proof), toHex(confirmSig), {from: accounts[3]})); - if (!err) - assert.fail("Allowed a challenge for an exit already challenged"); - });*/ -}); diff --git a/eth/main.go b/eth/main.go index 1df3356..66a1c40 100644 --- a/eth/main.go +++ b/eth/main.go @@ -39,7 +39,7 @@ func InitEthConn(nodeUrl string, logger log.Logger) (*Client, error) { func (client *Client) SubscribeToHeads() (<-chan *types.Header, error) { c := make(chan *types.Header) - _, err := client.ec.SubscribeNewHead(context.Background(), c) + sub, err := client.ec.SubscribeNewHead(context.Background(), c) if err != nil { return nil, err } diff --git a/eth/plasma.go b/eth/plasma.go index 4eb1ec6..beac948 100644 --- a/eth/plasma.go +++ b/eth/plasma.go @@ -114,9 +114,6 @@ func (plasma *Plasma) GetDeposit(nonce *big.Int) (*plasmaTypes.Deposit, error) { key := prefixKey(depositPrefix, nonce.Bytes()) data, err := plasma.memdb.Get(key) - fmt.Println("here is the data retrieved") - fmt.Println(data) - var deposit plasmaTypes.Deposit // check against the contract if the deposit is not in the cache or decoding fails if err != nil || json.Unmarshal(data, &deposit) != nil { @@ -223,11 +220,6 @@ func watchDeposits(plasma *Plasma) { for deposit := range deposits { key := prefixKey(depositPrefix, deposit.DepositNonce.Bytes()) - fmt.Println("Watched a deposit!!!!1") - fmt.Println(deposit) - fmt.Println(deposit.Amount) - fmt.Println(deposit.EthBlockNum) - // remove the nonce, encode, and store data, err := json.Marshal(plasmaTypes.Deposit{ Owner: deposit.Depositor, @@ -258,10 +250,6 @@ func watchExits(plasma *Plasma) { go func() { for depositExit := range startedDepositExits { - fmt.Println("Deposit EXIT") - fmt.Println(depositExit) - fmt.Println("End Exit") - panic("Oh no!") nonce := depositExit.Nonce.Bytes() key := prefixKey(depositExitPrefix, nonce) plasma.memdb.Put(key, nil) @@ -272,7 +260,6 @@ func watchExits(plasma *Plasma) { go func() { for transactionExit := range startedTransactionExits { - panic("Oh no!") priority := calcPriority(transactionExit.Position).Bytes() key := prefixKey(transactionExitPrefix, priority) plasma.memdb.Put(key, nil) diff --git a/types/utxo.go b/types/utxo.go index 6d55585..07a5073 100644 --- a/types/utxo.go +++ b/types/utxo.go @@ -51,6 +51,13 @@ func (position PlasmaPosition) IsValid() bool { } } +func (position PlasmaPosition) IsDeposit() bool { + if !position.IsValid() { + return false + } + return position.DepositNum != 0 +} + type Deposit struct { Owner common.Address Amount sdk.Uint diff --git a/utils/utils.go b/utils/utils.go index 352298c..7ace71c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -9,6 +9,9 @@ import ( "reflect" ) +var RootHashPrefix = []byte("root hash") +var ConfirmSigPrefix = []byte("confirmation signatures") + func ZeroAddress(addr common.Address) bool { return new(big.Int).SetBytes(addr.Bytes()).Sign() == 0 } diff --git a/x/kvstore/mapper.go b/x/kvstore/mapper.go new file mode 100644 index 0000000..43c6d93 --- /dev/null +++ b/x/kvstore/mapper.go @@ -0,0 +1,30 @@ +package kvstore + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type KVStore struct { + contextKey sdk.StoreKey +} + +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) + store.Set(key, value) +} + +func (kvstore KVStore) Get(ctx sdk.Context, key []byte) []byte { + store := ctx.KVStore(kvstore.contextKey) + return store.Get(key) +} + +func (kvstore KVStore) Delete(ctx sdk.Context, key []byte) { + store := ctx.KVStore(kvstore.contextKey) + store.Delete(key) +} diff --git a/x/metadata/mapper.go b/x/metadata/mapper.go deleted file mode 100644 index f5b0b05..0000000 --- a/x/metadata/mapper.go +++ /dev/null @@ -1,30 +0,0 @@ -package metadata - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type MetadataMapper struct { - contextKey sdk.StoreKey -} - -func NewMetadataMapper(contextKey sdk.StoreKey) MetadataMapper { - return MetadataMapper{ - contextKey: contextKey, - } -} - -func (mm MetadataMapper) StoreMetadata(ctx sdk.Context, key []byte, metadata []byte) { - store := ctx.KVStore(mm.contextKey) - store.Set(key, metadata) -} - -func (mm MetadataMapper) GetMetadata(ctx sdk.Context, key []byte) []byte { - store := ctx.KVStore(mm.contextKey) - return store.Get(key) -} - -func (mm MetadataMapper) DeleteMetadata(ctx sdk.Context, key []byte) { - store := ctx.KVStore(mm.contextKey) - store.Delete(key) -}