From 131204275efc219606d29127c01339c17a27cae9 Mon Sep 17 00:00:00 2001 From: boomyl <563807243@qq.com> Date: Mon, 17 May 2021 15:51:02 +0800 Subject: [PATCH] vm state data (#1930) * vm state data * fix comments:2d bytes compare * fix altstack copying data * fix comments * fix comments --- protocol/validation/tx.go | 4 ++-- protocol/validation/vmcontext.go | 32 ++++++++++++++++++++++++++------ protocol/validation/vmcontext_test.go | 30 +++++++++++++++++++++--------- protocol/vm/context.go | 3 ++- protocol/vm/introspection.go | 2 +- protocol/vm/introspection_test.go | 19 +++++++++++++------ protocol/vm/vm.go | 21 +++++++++++++++++++++ 7 files changed, 86 insertions(+), 25 deletions(-) diff --git a/protocol/validation/tx.go b/protocol/validation/tx.go index fda9007b..aa0414c9 100644 --- a/protocol/validation/tx.go +++ b/protocol/validation/tx.go @@ -234,7 +234,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { return errors.WithDetailf(ErrMismatchedAssetID, "asset ID is %x, issuance wants %x", computedAssetID.Bytes(), e.Value.AssetId.Bytes()) } - gasLeft, err := vm.Verify(NewTxVMContext(vs, e, e.WitnessAssetDefinition.IssuanceProgram, e.WitnessArguments), vs.gasStatus.GasLeft) + gasLeft, err := vm.Verify(NewTxVMContext(vs, e, e.WitnessAssetDefinition.IssuanceProgram, &bc.StateData{}, e.WitnessArguments), vs.gasStatus.GasLeft) if err != nil { return errors.Wrap(err, "checking issuance program") } @@ -257,7 +257,7 @@ func checkValid(vs *validationState, e bc.Entry) (err error) { return errors.Wrap(err, "getting spend prevout") } - gasLeft, err := vm.Verify(NewTxVMContext(vs, e, spentOutput.ControlProgram, e.WitnessArguments), vs.gasStatus.GasLeft) + gasLeft, err := vm.Verify(NewTxVMContext(vs, e, spentOutput.ControlProgram, spentOutput.StateData, e.WitnessArguments), vs.gasStatus.GasLeft) if err != nil { return errors.Wrap(err, "checking control program") } diff --git a/protocol/validation/vmcontext.go b/protocol/validation/vmcontext.go index 4dd1b8a7..e2fefe8d 100644 --- a/protocol/validation/vmcontext.go +++ b/protocol/validation/vmcontext.go @@ -12,7 +12,7 @@ import ( ) // NewTxVMContext generates the vm.Context for BVM -func NewTxVMContext(vs *validationState, entry bc.Entry, prog *bc.Program, args [][]byte) *vm.Context { +func NewTxVMContext(vs *validationState, entry bc.Entry, prog *bc.Program, stateData *bc.StateData, args [][]byte) *vm.Context { var ( tx = vs.tx blockHeight = vs.block.BlockHeader.GetHeight() @@ -67,6 +67,7 @@ func NewTxVMContext(vs *validationState, entry bc.Entry, prog *bc.Program, args result := &vm.Context{ VMVersion: prog.VmVersion, Code: convertProgram(prog.Code, vs.converter), + StateData: stateData.StateData, Arguments: args, EntryID: entryID.Bytes(), @@ -108,18 +109,19 @@ type entryContext struct { entries map[bc.Hash]bc.Entry } -func (ec *entryContext) checkOutput(index uint64, amount uint64, assetID []byte, vmVersion uint64, code []byte, expansion bool) (bool, error) { +func (ec *entryContext) checkOutput(index uint64, amount uint64, assetID []byte, vmVersion uint64, code []byte, state [][]byte, expansion bool) (bool, error) { checkEntry := func(e bc.Entry) (bool, error) { - check := func(prog *bc.Program, value *bc.AssetAmount) bool { + check := func(prog *bc.Program, value *bc.AssetAmount, stateData *bc.StateData) bool { return (prog.VmVersion == vmVersion && bytes.Equal(prog.Code, code) && bytes.Equal(value.AssetId.Bytes(), assetID) && - value.Amount == amount) + value.Amount == amount && + bytesEqual(stateData.StateData, state)) } switch e := e.(type) { case *bc.Output: - return check(e.ControlProgram, e.Source.Value), nil + return check(e.ControlProgram, e.Source.Value, e.StateData), nil case *bc.Retirement: var prog bc.Program @@ -131,7 +133,7 @@ func (ec *entryContext) checkOutput(index uint64, amount uint64, assetID []byte, // (The spec always requires prog.VmVersion to be zero.) prog.Code = code } - return check(&prog, e.Source.Value), nil + return check(&prog, e.Source.Value, &bc.StateData{}), nil } return false, vm.ErrContext @@ -182,3 +184,21 @@ func (ec *entryContext) checkOutput(index uint64, amount uint64, assetID []byte, return false, vm.ErrContext } + +func bytesEqual(a, b [][]byte) bool { + if (a == nil) != (b == nil) { + return false + } + + if len(a) != len(b) { + return false + } + + for i, v := range a { + if !bytes.Equal(v, b[i]) { + return false + } + } + + return true +} diff --git a/protocol/validation/vmcontext_test.go b/protocol/validation/vmcontext_test.go index 59e88a62..c6c1fffc 100644 --- a/protocol/validation/vmcontext_test.go +++ b/protocol/validation/vmcontext_test.go @@ -17,11 +17,11 @@ func TestCheckOutput(t *testing.T) { types.NewIssuanceInput(nil, 6, []byte("issueprog"), nil, nil), }, Outputs: []*types.TxOutput{ - types.NewOriginalTxOutput(bc.NewAssetID([32]byte{3}), 8, []byte("wrongprog"), nil), - types.NewOriginalTxOutput(bc.NewAssetID([32]byte{3}), 8, []byte("controlprog"), nil), - types.NewOriginalTxOutput(bc.NewAssetID([32]byte{2}), 8, []byte("controlprog"), nil), - types.NewOriginalTxOutput(bc.NewAssetID([32]byte{2}), 7, []byte("controlprog"), nil), - types.NewOriginalTxOutput(bc.NewAssetID([32]byte{2}), 7, []byte("controlprog"), nil), + types.NewOriginalTxOutput(bc.NewAssetID([32]byte{3}), 8, []byte("wrongprog"), [][]byte{[]byte("statedata")}), + types.NewOriginalTxOutput(bc.NewAssetID([32]byte{3}), 8, []byte("controlprog"), [][]byte{[]byte("wrongstatedata")}), + types.NewOriginalTxOutput(bc.NewAssetID([32]byte{2}), 8, []byte("controlprog"), [][]byte{[]byte("statedata")}), + types.NewOriginalTxOutput(bc.NewAssetID([32]byte{2}), 7, []byte("controlprog"), [][]byte{[]byte("statedata")}), + types.NewOriginalTxOutput(bc.NewAssetID([32]byte{2}), 7, []byte("controlprog"), [][]byte{[]byte("statedata")}), }, }) @@ -37,6 +37,7 @@ func TestCheckOutput(t *testing.T) { assetID []byte vmVersion uint64 code []byte + state [][]byte wantErr error wantOk bool @@ -47,6 +48,7 @@ func TestCheckOutput(t *testing.T) { assetID: append([]byte{2}, make([]byte, 31)...), vmVersion: 1, code: []byte("controlprog"), + state: [][]byte{[]byte("statedata")}, wantOk: true, }, { @@ -55,6 +57,7 @@ func TestCheckOutput(t *testing.T) { assetID: append([]byte{2}, make([]byte, 31)...), vmVersion: 1, code: []byte("controlprog"), + state: [][]byte{[]byte("statedata")}, wantOk: true, }, { @@ -62,7 +65,16 @@ func TestCheckOutput(t *testing.T) { amount: 1, assetID: append([]byte{9}, make([]byte, 31)...), vmVersion: 1, - code: []byte("missingprog"), + code: []byte("controlprog"), + wantOk: false, + }, + { + index: 1, + amount: 8, + assetID: append([]byte{3}, make([]byte, 31)...), + vmVersion: 1, + code: []byte("controlprog"), + state: [][]byte{[]byte("missingstatedata")}, wantOk: false, }, { @@ -77,13 +89,13 @@ func TestCheckOutput(t *testing.T) { for i, test := range cases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - gotOk, err := txCtx.checkOutput(test.index, test.amount, test.assetID, test.vmVersion, test.code, false) + gotOk, err := txCtx.checkOutput(test.index, test.amount, test.assetID, test.vmVersion, test.code, test.state, false) if g := errors.Root(err); g != test.wantErr { - t.Errorf("checkOutput(%v, %v, %x, %v, %x) err = %v, want %v", test.index, test.amount, test.assetID, test.vmVersion, test.code, g, test.wantErr) + t.Errorf("checkOutput(%v, %v, %x, %v, %x, %v) err = %v, want %v", test.index, test.amount, test.assetID, test.vmVersion, test.code, test.state, g, test.wantErr) return } if gotOk != test.wantOk { - t.Errorf("checkOutput(%v, %v, %x, %v, %x) ok = %t, want %v", test.index, test.amount, test.assetID, test.vmVersion, test.code, gotOk, test.wantOk) + t.Errorf("checkOutput(%v, %v, %x, %v, %x, %v) ok = %t, want %v", test.index, test.amount, test.assetID, test.vmVersion, test.code, test.state, gotOk, test.wantOk) } }) diff --git a/protocol/vm/context.go b/protocol/vm/context.go index 853e91c1..7134a405 100644 --- a/protocol/vm/context.go +++ b/protocol/vm/context.go @@ -12,6 +12,7 @@ package vm type Context struct { VMVersion uint64 Code []byte + StateData [][]byte Arguments [][]byte EntryID []byte @@ -31,5 +32,5 @@ type Context struct { SpentOutputID *[]byte TxSigHash func() []byte - CheckOutput func(index uint64, amount uint64, assetID []byte, vmVersion uint64, code []byte, expansion bool) (bool, error) + CheckOutput func(index uint64, amount uint64, assetID []byte, vmVersion uint64, code []byte, state [][]byte, expansion bool) (bool, error) } diff --git a/protocol/vm/introspection.go b/protocol/vm/introspection.go index 36b71c78..1809578d 100644 --- a/protocol/vm/introspection.go +++ b/protocol/vm/introspection.go @@ -47,7 +47,7 @@ func opCheckOutput(vm *virtualMachine) error { return ErrContext } - ok, err := vm.context.CheckOutput(uint64(index), amount, assetID, uint64(vmVersion), code, vm.expansionReserved) + ok, err := vm.context.CheckOutput(uint64(index), amount, assetID, uint64(vmVersion), code, vm.altStack, vm.expansionReserved) if err != nil { return err } diff --git a/protocol/vm/introspection_test.go b/protocol/vm/introspection_test.go index 5ed369a0..2189908a 100644 --- a/protocol/vm/introspection_test.go +++ b/protocol/vm/introspection_test.go @@ -106,8 +106,9 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("missingprog"), }, + altStack: [][]byte{[]byte("statedata")}, context: &Context{ - CheckOutput: func(uint64, uint64, []byte, uint64, []byte, bool) (bool, error) { + CheckOutput: func(uint64, uint64, []byte, uint64, []byte, [][]byte, bool) (bool, error) { return false, nil }, }, @@ -116,6 +117,7 @@ func TestIntrospectionOps(t *testing.T) { runLimit: 50062, deferredCost: -78, dataStack: [][]byte{{}}, + altStack: [][]byte{[]byte("statedata")}, }, }, { op: OP_CHECKOUTPUT, @@ -128,7 +130,8 @@ func TestIntrospectionOps(t *testing.T) { Int64Bytes(-1), []byte("controlprog"), }, - context: &Context{}, + altStack: [][]byte{[]byte("statedata")}, + context: &Context{}, }, wantErr: ErrBadValue, }, { @@ -142,7 +145,8 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, - context: &Context{}, + altStack: [][]byte{[]byte("statedata")}, + context: &Context{}, }, wantErr: ErrBadValue, }, { @@ -156,7 +160,8 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, - context: &Context{}, + altStack: [][]byte{[]byte("statedata")}, + context: &Context{}, }, wantErr: ErrBadValue, }, { @@ -170,8 +175,9 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + altStack: [][]byte{[]byte("statedata")}, context: &Context{ - CheckOutput: func(uint64, uint64, []byte, uint64, []byte, bool) (bool, error) { + CheckOutput: func(uint64, uint64, []byte, uint64, []byte, [][]byte, bool) (bool, error) { return false, ErrBadValue }, }, @@ -189,7 +195,8 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, - context: &Context{}, + altStack: [][]byte{[]byte("statedata")}, + context: &Context{}, }, wantErr: ErrRunLimitExceeded, }, { diff --git a/protocol/vm/vm.go b/protocol/vm/vm.go index 839d17dc..898e0f0f 100644 --- a/protocol/vm/vm.go +++ b/protocol/vm/vm.go @@ -59,6 +59,12 @@ func Verify(context *Context, gasLimit int64) (gasLeft int64, err error) { runLimit: gasLimit, context: context, } + stateData := context.StateData + for i, state := range stateData { + if err = vm.pushAlt(state, false); err != nil { + return vm.runLimit, errors.Wrapf(err, "pushing initial statedata %d", i) + } + } args := context.Arguments for i, arg := range args { @@ -152,6 +158,21 @@ func (vm *virtualMachine) push(data []byte, deferred bool) error { return nil } +func (vm *virtualMachine) pushAlt(data []byte, deferred bool) error { + cost := 8 + int64(len(data)) + if deferred { + vm.deferCost(cost) + } else { + err := vm.applyCost(cost) + if err != nil { + return err + } + } + vm.altStack = append(vm.altStack, data) + + return nil +} + func (vm *virtualMachine) pushBool(b bool, deferred bool) error { return vm.push(BoolBytes(b), deferred) } -- 2.11.0