From 47f0ef0eaeeaaa199c1606672e117ceb742167cf Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Fri, 7 Aug 2020 16:45:52 +0100 Subject: [PATCH] test: port chain validation multisig test suite (#216) --- tvx/suite_messages_create_actor.go | 1 + tvx/suite_messages_multisig.go | 362 +++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 tvx/suite_messages_multisig.go diff --git a/tvx/suite_messages_create_actor.go b/tvx/suite_messages_create_actor.go index bf727a81d..3f41c2868 100644 --- a/tvx/suite_messages_create_actor.go +++ b/tvx/suite_messages_create_actor.go @@ -28,6 +28,7 @@ func suiteMessages(c *cli.Context) error { err = multierror.Append(MessageTest_AccountActorCreation()) err = multierror.Append(MessageTest_InitActorSequentialIDAddressCreate()) err = multierror.Append(MessageTest_MessageApplicationEdgecases()) + err = multierror.Append(MessageTest_MultiSigActor()) err = multierror.Append(MessageTest_Paych()) err = multierror.Append(MessageTest_ValueTransferSimple()) err = multierror.Append(MessageTest_ValueTransferAdvance()) diff --git a/tvx/suite_messages_multisig.go b/tvx/suite_messages_multisig.go new file mode 100644 index 000000000..03b41f9f5 --- /dev/null +++ b/tvx/suite_messages_multisig.go @@ -0,0 +1,362 @@ +package main + +import ( + "fmt" + "os" + + address "github.com/filecoin-project/go-address" + "github.com/filecoin-project/oni/tvx/chain" + "github.com/filecoin-project/oni/tvx/drivers" + abi_spec "github.com/filecoin-project/specs-actors/actors/abi" + big_spec "github.com/filecoin-project/specs-actors/actors/abi/big" + builtin_spec "github.com/filecoin-project/specs-actors/actors/builtin" + multisig_spec "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + exitcode_spec "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" + "github.com/minio/blake2b-simd" +) + +func MessageTest_MultiSigActor() error { + err := func(testname string) error { + const numApprovals = 1 + const unlockDuration = 10 + var valueSend = abi_spec.NewTokenAmount(10) + var initialBal = abi_spec.NewTokenAmount(200000000000) + + td := drivers.NewTestDriver() + td.Vector.Meta.Desc = testname + + // creator of the multisig actor + alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal) + + // expected address of the actor + multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(aliceID)) + + preroot := td.GetStateRoot() + + createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr) + td.MustCreateAndVerifyMultisigActor(0, valueSend, multisigAddr, alice, + &multisig_spec.ConstructorParams{ + Signers: []address.Address{aliceID}, + NumApprovalsThreshold: numApprovals, + UnlockDuration: unlockDuration, + }, + exitcode_spec.Ok, chain.MustSerialize(&createRet)) + + postroot := td.GetStateRoot() + + td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot) + td.Vector.Pre.StateTree.RootCID = preroot + td.Vector.Post.StateTree.RootCID = postroot + + // encode and output + fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON())) + + return nil + }("constructor test") + if err != nil { + return err + } + + err = func(testname string) error { + const numApprovals = 2 + const unlockDuration = 10 + var valueSend = abi_spec.NewTokenAmount(10) + var initialBal = abi_spec.NewTokenAmount(200000000000) + + td := drivers.NewTestDriver() + td.Vector.Meta.Desc = testname + + alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal) + + bob, bobID := td.NewAccountActor(drivers.SECP, initialBal) + outsider, outsiderID := td.NewAccountActor(drivers.SECP, initialBal) + + multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(outsiderID)) + + preroot := td.GetStateRoot() + + createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr) + // create the multisig actor + td.MustCreateAndVerifyMultisigActor(0, valueSend, multisigAddr, alice, + &multisig_spec.ConstructorParams{ + Signers: []address.Address{aliceID, bobID}, + NumApprovalsThreshold: numApprovals, + UnlockDuration: unlockDuration, + }, + exitcode_spec.Ok, chain.MustSerialize(&createRet)) + td.AssertBalance(multisigAddr, valueSend) + + // alice proposes that outsider should receive 'valueSend' FIL. + pparams := multisig_spec.ProposeParams{ + To: outsider, + Value: valueSend, + Method: builtin_spec.MethodSend, + Params: nil, + } + + // propose the transaction and assert it exists in the actor state + txID0 := multisig_spec.TxnID(0) + expected := multisig_spec.ProposeReturn{ + TxnID: 0, + Applied: false, + Code: 0, + Ret: nil, + } + td.ApplyExpect( + td.MessageProducer.MultisigPropose(alice, multisigAddr, &pparams, chain.Nonce(1)), + chain.MustSerialize(&expected)) + + txn0 := multisig_spec.Transaction{ + To: pparams.To, + Value: pparams.Value, + Method: pparams.Method, + Params: pparams.Params, + Approved: []address.Address{aliceID}, + } + ph := mustMakeProposalHash(&txn0) + td.AssertMultisigTransaction(multisigAddr, txID0, txn0) + + // bob cancels alice's transaction. This fails as bob did not create alice's transaction. + td.ApplyFailure( + td.MessageProducer.MultisigCancel(bob, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(0)), + exitcode_spec.ErrForbidden) + + // alice cancels their transaction. The outsider doesn't receive any FIL, the multisig actor's balance is empty, and the + // transaction is canceled. + td.ApplyOk( + td.MessageProducer.MultisigCancel(alice, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(2)), + ) + td.AssertMultisigState(multisigAddr, multisig_spec.State{ + Signers: []address.Address{aliceID, bobID}, + NumApprovalsThreshold: numApprovals, + NextTxnID: 1, + InitialBalance: valueSend, + StartEpoch: 1, + UnlockDuration: unlockDuration, + }) + td.AssertBalance(multisigAddr, valueSend) + + postroot := td.GetStateRoot() + + td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot) + td.Vector.Pre.StateTree.RootCID = preroot + td.Vector.Post.StateTree.RootCID = postroot + + // encode and output + fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON())) + + return nil + }("propose and cancel") + if err != nil { + return err + } + + err = func(testname string) error { + td := drivers.NewTestDriver() + td.Vector.Meta.Desc = testname + + var initialBal = abi_spec.NewTokenAmount(200000000000) + const numApprovals = 2 + const unlockDuration = 1 + var valueSend = abi_spec.NewTokenAmount(10) + + // Signers + alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal) + bob, bobID := td.NewAccountActor(drivers.SECP, initialBal) + + // Not Signer + outsider, outsiderID := td.NewAccountActor(drivers.SECP, initialBal) + + preroot := td.GetStateRoot() + + // Multisig actor address + multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(outsiderID)) + createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr) + + // create the multisig actor + td.MustCreateAndVerifyMultisigActor(0, valueSend, multisigAddr, alice, + &multisig_spec.ConstructorParams{ + Signers: []address.Address{aliceID, bobID}, + NumApprovalsThreshold: numApprovals, + UnlockDuration: unlockDuration, + }, + exitcode_spec.Ok, chain.MustSerialize(&createRet)) + + // setup propose expected values and params + pparams := multisig_spec.ProposeParams{ + To: outsider, + Value: valueSend, + Method: builtin_spec.MethodSend, + Params: nil, + } + + // propose the transaction and assert it exists in the actor state + txID0 := multisig_spec.TxnID(0) + expectedPropose := multisig_spec.ProposeReturn{ + TxnID: 0, + Applied: false, + Code: 0, + Ret: nil, + } + td.ApplyExpect( + td.MessageProducer.MultisigPropose(alice, multisigAddr, &pparams, chain.Nonce(1)), + chain.MustSerialize(&expectedPropose)) + + txn0 := multisig_spec.Transaction{ + To: pparams.To, + Value: pparams.Value, + Method: pparams.Method, + Params: pparams.Params, + Approved: []address.Address{aliceID}, + } + ph := mustMakeProposalHash(&txn0) + td.AssertMultisigTransaction(multisigAddr, txID0, txn0) + + // outsider proposes themselves to receive 'valueSend' FIL. This fails as they are not a signer. + td.ApplyFailure( + td.MessageProducer.MultisigPropose(outsider, multisigAddr, &pparams, chain.Nonce(0)), + exitcode_spec.ErrForbidden) + + // outsider approves the value transfer alice sent. This fails as they are not a signer. + td.ApplyFailure( + td.MessageProducer.MultisigApprove(outsider, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(1)), + exitcode_spec.ErrForbidden) + + // increment the epoch to unlock the funds + td.ExeCtx.Epoch += unlockDuration + balanceBefore := td.GetBalance(outsider) + + // bob approves transfer of 'valueSend' FIL to outsider. + expectedApprove := multisig_spec.ApproveReturn{ + Applied: true, + Code: 0, + Ret: nil, + } + td.ApplyExpect( + td.MessageProducer.MultisigApprove(bob, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(0)), + chain.MustSerialize(&expectedApprove)) + + txID1 := multisig_spec.TxnID(1) + td.AssertMultisigState(multisigAddr, multisig_spec.State{ + Signers: []address.Address{aliceID, bobID}, + NumApprovalsThreshold: numApprovals, + NextTxnID: txID1, + InitialBalance: valueSend, + StartEpoch: 1, + UnlockDuration: unlockDuration, + }) + td.AssertMultisigContainsTransaction(multisigAddr, txID0, false) + // Multisig balance has been transferred to outsider. + td.AssertBalance(multisigAddr, big_spec.Zero()) + td.AssertBalance(outsider, big_spec.Add(balanceBefore, valueSend)) + + postroot := td.GetStateRoot() + + td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot) + td.Vector.Pre.StateTree.RootCID = preroot + td.Vector.Post.StateTree.RootCID = postroot + + // encode and output + fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON())) + + return nil + }("propose and approve") + if err != nil { + return err + } + + err = func(testname string) error { + const initialNumApprovals = 1 + var msValue = abi_spec.NewTokenAmount(100000000000) + var initialBal = abi_spec.NewTokenAmount(200000000000) + + td := drivers.NewTestDriver() + td.Vector.Meta.Desc = testname + + alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal) // 101 + _, bobID := td.NewAccountActor(drivers.SECP, initialBal) // 102 + var initialSigners = []address.Address{aliceID} + + multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(bobID)) + + preroot := td.GetStateRoot() + + createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr) + + td.MustCreateAndVerifyMultisigActor(0, msValue, multisigAddr, alice, + &multisig_spec.ConstructorParams{ + Signers: initialSigners, + NumApprovalsThreshold: initialNumApprovals, + UnlockDuration: 0, + }, + exitcode_spec.Ok, + chain.MustSerialize(&createRet), + ) + + addSignerParams := multisig_spec.AddSignerParams{ + Signer: bobID, + Increase: false, + } + + // alice fails to call directly since AddSigner + td.ApplyFailure( + td.MessageProducer.MultisigAddSigner(alice, multisigAddr, &addSignerParams, chain.Nonce(1)), + exitcode_spec.SysErrForbidden, + ) + + // AddSigner must be staged through the multisig itself + // Alice proposes the AddSigner. + // Since approvals = 1 this auto-approves the transaction. + expected := multisig_spec.ProposeReturn{ + TxnID: 0, + Applied: true, + Code: 0, + Ret: nil, + } + td.ApplyExpect( + td.MessageProducer.MultisigPropose(alice, multisigAddr, &multisig_spec.ProposeParams{ + To: multisigAddr, + Value: big_spec.Zero(), + Method: builtin_spec.MethodsMultisig.AddSigner, + Params: chain.MustSerialize(&addSignerParams), + }, chain.Nonce(2)), + chain.MustSerialize(&expected), + ) + + // TODO also exercise the approvals = 2 case with explicit approval. + + // Check that bob is now a signer + td.AssertMultisigState(multisigAddr, multisig_spec.State{ + Signers: append(initialSigners, bobID), + NumApprovalsThreshold: initialNumApprovals, + NextTxnID: multisig_spec.TxnID(1), + InitialBalance: big_spec.Zero(), + StartEpoch: 0, + UnlockDuration: 0, + }) + + postroot := td.GetStateRoot() + + td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot) + td.Vector.Pre.StateTree.RootCID = preroot + td.Vector.Post.StateTree.RootCID = postroot + + // encode and output + fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON())) + + return nil + }("add signer") + if err != nil { + return err + } + + return nil +} + +func mustMakeProposalHash(txn *multisig_spec.Transaction) []byte { + txnHash, err := multisig_spec.ComputeProposalHash(txn, blake2b.Sum256) + if err != nil { + panic(err) + } + return txnHash +}