Skip to content

Commit 040a620

Browse files
authored
feat: invoke host call (#217)
1 parent 93cfee7 commit 040a620

File tree

3 files changed

+198
-2
lines changed

3 files changed

+198
-2
lines changed

internal/polkavm/host_call/common.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ const (
7979
OK Code = 0
8080
)
8181

82+
// Inner pvm invocations have their own set of result codes
83+
const (
84+
HALT = 0 // The invocation completed and halted normally.
85+
PANIC = 1 // The invocation completed with a panic.
86+
FAULT = 2 // The invocation completed with a page fault.
87+
HOST = 3 // The invocation completed with a host-call fault.
88+
OOG = 4 // The invocation completed by running out of gas.
89+
)
90+
8291
func (r Code) String() string {
8392
switch r {
8493
case NONE:
@@ -107,7 +116,7 @@ func (r Code) String() string {
107116
return "unknown"
108117
}
109118

110-
func readNumber[U interface{ ~uint32 | ~uint64 }](mem Memory, addr uint32, length int) (u U, err error) {
119+
func readNumber[U interface{ ~uint32 | ~uint64 | ~int64 }](mem Memory, addr uint32, length int) (u U, err error) {
111120
b := make([]byte, length)
112121
if err = mem.Read(addr, b); err != nil {
113122
return

internal/polkavm/host_call/refine_functions.go

+90-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package host_call
22

33
import (
4+
"bytes"
5+
"errors"
6+
"log"
47
"math"
58

69
"github.com/eigerco/strawberry/internal/block"
710
"github.com/eigerco/strawberry/internal/common"
811
"github.com/eigerco/strawberry/internal/crypto"
912
"github.com/eigerco/strawberry/internal/jamtime"
1013
. "github.com/eigerco/strawberry/internal/polkavm"
14+
"github.com/eigerco/strawberry/internal/polkavm/interpreter"
1115
"github.com/eigerco/strawberry/internal/service"
1216
"github.com/eigerco/strawberry/internal/work"
17+
"github.com/eigerco/strawberry/pkg/serialization/codec/jam"
1318
)
1419

1520
// HistoricalLookup ΩH(ϱ, ω, µ, (m, e), s, d, t)
@@ -366,7 +371,91 @@ func Invoke(
366371
mem Memory,
367372
ctxPair RefineContextPair,
368373
) (Gas, Registers, Memory, RefineContextPair, error) {
369-
return gas, regs, mem, ctxPair, nil
374+
if gas < InvokeCost {
375+
return gas, regs, mem, ctxPair, ErrOutOfGas
376+
}
377+
gas -= InvokeCost
378+
// let [n, o] = ω7,8
379+
pvmKey, addr := regs[A0], regs[A1]
380+
381+
// let (g, w) = (g, w) ∶ E8(g) ⌢ E#8(w) = μo⋅⋅⋅+112 if No⋅⋅⋅+112 ⊂ V∗μ
382+
invokeGas, err := readNumber[Gas](mem, uint32(addr), 8)
383+
if err != nil {
384+
return gas, withCode(regs, OOB), mem, ctxPair, nil
385+
}
386+
var invokeRegs Registers // w
387+
for i := range 13 {
388+
invokeReg, err := readNumber[uint64](mem, uint32(addr+(uint64(i+1)*8)), 8)
389+
if err != nil {
390+
return gas, withCode(regs, OOB), mem, ctxPair, nil
391+
}
392+
invokeRegs[i] = invokeReg
393+
}
394+
395+
// let (c, i′, g′, w′, u′) = Ψ(m[n]p, m[n]i, g, w, m[n]u)
396+
pvm, ok := ctxPair.IntegratedPVMMap[pvmKey]
397+
if !ok { // if n ∉ m
398+
return gas, withCode(regs, WHO), mem, ctxPair, nil // (WHO, ω8, μ, m)
399+
}
400+
updateIntegratedPVM := func(isHostCall bool, resultInstr uint32, resultMem Memory) {
401+
pvm.Ram = resultMem
402+
if isHostCall {
403+
// m*[n]i = i′ + 1 if c ∈ {̵h} × NR
404+
pvm.InstructionCounter = resultInstr + 1
405+
} else {
406+
// m*[n]i = i′
407+
pvm.InstructionCounter = resultInstr
408+
}
409+
ctxPair.IntegratedPVMMap[pvmKey] = pvm
410+
}
411+
412+
// we only parse the code and jump table as we are not expected to invoke a full program
413+
program := &Program{}
414+
if err := ParseCodeAndJumpTable(uint32(len(pvm.Code)), NewReader(bytes.NewReader(pvm.Code)), program); err != nil {
415+
return gas, withCode(regs, PANIC), mem, ctxPair, nil
416+
}
417+
418+
log.Println("invokeGas", invokeGas)
419+
log.Println("invokeRegs", invokeRegs)
420+
resultInstr, resultGas, resultRegs, resultMem, hostCall, invokeErr := interpreter.Invoke(program, nil, pvm.InstructionCounter, invokeGas, invokeRegs, pvm.Ram)
421+
422+
if bb, err := jam.Marshal([14]uint64(append([]uint64{uint64(resultGas)}, resultRegs[:]...))); err != nil {
423+
return gas, withCode(regs, OOB), mem, ctxPair, nil // (OOB, ω8, μ, m)
424+
} else if err := mem.Write(uint32(addr), bb); err != nil {
425+
return gas, withCode(regs, OOB), mem, ctxPair, nil // (OOB, ω8, μ, m)
426+
}
427+
if invokeErr != nil {
428+
if errors.Is(invokeErr, ErrOutOfGas) {
429+
updateIntegratedPVM(false, resultInstr, resultMem)
430+
return gas, withCode(regs, OOG), mem, ctxPair, nil // (OOG, ω8, μ*, m*)
431+
}
432+
if errors.Is(invokeErr, ErrHalt) {
433+
updateIntegratedPVM(false, resultInstr, resultMem)
434+
return gas, withCode(regs, HALT), mem, ctxPair, nil // (HALT, ω8, μ*, m*)
435+
}
436+
if errors.Is(invokeErr, ErrHostCall) {
437+
updateIntegratedPVM(true, resultInstr, resultMem)
438+
regs[A1] = uint64(hostCall)
439+
return gas, withCode(regs, HOST), mem, ctxPair, nil // (HOST, h, μ*, m*)
440+
}
441+
pageFault := &ErrPageFault{}
442+
if errors.As(invokeErr, &pageFault) {
443+
updateIntegratedPVM(false, resultInstr, resultMem)
444+
regs[A1] = uint64(pageFault.Address)
445+
return gas, withCode(regs, FAULT), mem, ctxPair, nil
446+
}
447+
panicErr := &ErrPanic{}
448+
if errors.As(invokeErr, &panicErr) {
449+
updateIntegratedPVM(false, resultInstr, resultMem)
450+
return gas, withCode(regs, PANIC), mem, ctxPair, nil
451+
}
452+
453+
// must never occur
454+
panic(invokeErr)
455+
}
456+
457+
updateIntegratedPVM(false, resultInstr, resultMem)
458+
return gas, withCode(regs, HALT), mem, ctxPair, nil // (HALT, ω8, μ*, m*)
370459
}
371460

372461
// Expunge ΩX(ϱ, ω, µ, (m, e))

internal/polkavm/host_call/refine_functions_test.go

+98
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/eigerco/strawberry/internal/polkavm/host_call"
1616
"github.com/eigerco/strawberry/internal/polkavm/interpreter"
1717
"github.com/eigerco/strawberry/internal/service"
18+
"github.com/eigerco/strawberry/pkg/serialization/codec/jam"
1819
)
1920

2021
var initialGas = uint64(100)
@@ -603,6 +604,103 @@ func TestVoid(t *testing.T) {
603604
assert.Equal(t, expectedGasRemaining, gasRemaining)
604605
}
605606

607+
func TestInvoke(t *testing.T) {
608+
pp := &polkavm.Program{
609+
Instructions: []polkavm.Instruction{
610+
{Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1},
611+
{Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2},
612+
},
613+
}
614+
615+
memoryMap, err := polkavm.NewMemoryMap(0, 128*1024, 0, 0)
616+
require.NoError(t, err)
617+
618+
mem := memoryMap.NewMemory(nil, nil, nil)
619+
620+
bb, err := jam.Marshal([14]uint64{
621+
10000, // gas
622+
0, // regs
623+
0,
624+
0,
625+
0,
626+
0,
627+
0,
628+
0,
629+
1,
630+
2,
631+
0,
632+
0,
633+
0,
634+
0,
635+
})
636+
require.NoError(t, err)
637+
638+
addr := memoryMap.RWDataAddress
639+
if err := mem.Write(addr, bb); err != nil {
640+
t.Fatal(err)
641+
}
642+
643+
pvmKey := uint64(0)
644+
645+
ctxPair := polkavm.RefineContextPair{
646+
IntegratedPVMMap: map[uint64]polkavm.IntegratedPVM{pvmKey: {
647+
Code: addInstrProgram,
648+
Ram: polkavm.Memory{}, // we don't use memory in tests yet
649+
InstructionCounter: 0,
650+
}},
651+
}
652+
653+
initialRegs := polkavm.Registers{
654+
polkavm.RA: polkavm.VmAddressReturnToHost,
655+
polkavm.SP: uint64(memoryMap.StackAddressHigh),
656+
polkavm.A0: pvmKey,
657+
polkavm.A1: uint64(addr),
658+
}
659+
660+
hostCall := func(hc uint32, gasCounter polkavm.Gas, regs polkavm.Registers,
661+
mm polkavm.Memory, x struct{},
662+
) (polkavm.Gas, polkavm.Registers, polkavm.Memory, struct{}, error) {
663+
gasOut, regsOut, memOut, ctxOut, err := host_call.Invoke(gasCounter, regs, mm, ctxPair)
664+
require.NoError(t, err)
665+
ctxPair = ctxOut
666+
return gasOut, regsOut, memOut, x, err
667+
}
668+
669+
gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(
670+
pp,
671+
memoryMap,
672+
0,
673+
initialGas,
674+
initialRegs,
675+
mem,
676+
hostCall,
677+
struct{}{},
678+
)
679+
require.ErrorIs(t, err, polkavm.ErrHalt)
680+
681+
assert.Equal(t, uint64(host_call.PANIC), regsOut[polkavm.A0])
682+
683+
expectedGasRemaining := polkavm.Gas(initialGas) -
684+
host_call.InvokeCost -
685+
polkavm.GasCosts[polkavm.Ecalli] -
686+
polkavm.GasCosts[polkavm.JumpIndirect]
687+
assert.Equal(t, expectedGasRemaining, gasRemaining)
688+
689+
invokeResult := make([]byte, 112)
690+
err = mem.Read(addr, invokeResult)
691+
require.NoError(t, err)
692+
693+
invokeGasAndRegs := [14]uint64{}
694+
err = jam.Unmarshal(invokeResult, &invokeGasAndRegs)
695+
require.NoError(t, err)
696+
697+
assert.Equal(t, uint32(3), ctxPair.IntegratedPVMMap[pvmKey].InstructionCounter)
698+
assert.Equal(t, uint64(9998), invokeGasAndRegs[0])
699+
assert.Equal(t, []uint64{0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0}, invokeGasAndRegs[1:])
700+
}
701+
702+
var addInstrProgram = []byte{0, 0, 3, 8, 135, 9, 1} // copied from testvectors
703+
606704
func TestExpunge(t *testing.T) {
607705
pp := &polkavm.Program{
608706
Instructions: []polkavm.Instruction{

0 commit comments

Comments
 (0)