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")
}
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")
}
)
// 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()
result := &vm.Context{
VMVersion: prog.VmVersion,
Code: convertProgram(prog.Code, vs.converter),
+ StateData: stateData.StateData,
Arguments: args,
EntryID: entryID.Bytes(),
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
// (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
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
+}
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")}),
},
})
assetID []byte
vmVersion uint64
code []byte
+ state [][]byte
wantErr error
wantOk bool
assetID: append([]byte{2}, make([]byte, 31)...),
vmVersion: 1,
code: []byte("controlprog"),
+ state: [][]byte{[]byte("statedata")},
wantOk: true,
},
{
assetID: append([]byte{2}, make([]byte, 31)...),
vmVersion: 1,
code: []byte("controlprog"),
+ state: [][]byte{[]byte("statedata")},
wantOk: true,
},
{
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,
},
{
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)
}
})
type Context struct {
VMVersion uint64
Code []byte
+ StateData [][]byte
Arguments [][]byte
EntryID []byte
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)
}
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
}
{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
},
},
runLimit: 50062,
deferredCost: -78,
dataStack: [][]byte{{}},
+ altStack: [][]byte{[]byte("statedata")},
},
}, {
op: OP_CHECKOUTPUT,
Int64Bytes(-1),
[]byte("controlprog"),
},
- context: &Context{},
+ altStack: [][]byte{[]byte("statedata")},
+ context: &Context{},
},
wantErr: ErrBadValue,
}, {
{1},
[]byte("controlprog"),
},
- context: &Context{},
+ altStack: [][]byte{[]byte("statedata")},
+ context: &Context{},
},
wantErr: ErrBadValue,
}, {
{1},
[]byte("controlprog"),
},
- context: &Context{},
+ altStack: [][]byte{[]byte("statedata")},
+ context: &Context{},
},
wantErr: ErrBadValue,
}, {
{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
},
},
{1},
[]byte("controlprog"),
},
- context: &Context{},
+ altStack: [][]byte{[]byte("statedata")},
+ context: &Context{},
},
wantErr: ErrRunLimitExceeded,
}, {
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 {
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)
}